Request Matching

Overview

API Simulator inspects the requests it receives by applying matching rules. Multi-key matching by URI (route), URI path pattern, any URI component, HTTP Method, HTTP Header fields, and values from the body provide for extensive request matching.

Matchers

Matchers are responsible for applying matching rules. A simlet configuration defines the matchers for a request as a list where each element in the list represents some matching criteria. For example:

request:
- method: POST

- uriPath: "/rest/v1/products/product"

- header: content-type
  equals: "application/json"

- where: body
  element: ".product.upc"
  exists: true

It is to note that the matchers top-level element has been deprecated. Use request instead. The support for the matchers top-level element will be removed in a future release. This doesn’t concern you if you’ve never heard of the matchers field 😉

Matching Operations

Various operations to check for matching text are supported:

equals: "..."
equalsIgnoreCase: "..."
startsWith: "..."
endsWith: "..."
isLike: "...regEx..."
contains: "..."
exists: true
exists: false

To negate an operation (other than exists), use not:

not equals: "..."
not equalsIgnoreCase: "..."
not startsWith: "..."
not endsWith: "..."
not isLike: "..."
not contains: "..."

Method Matching

Matching requests by the HTTP Method (verb) can be specified in several ways. Use the one you like:

- method: POST

- where: method
  equals: POST

- where: method
  equals: "POST"

- where: method
  equals: 'POST'

# A contrived example
- where: method
  not startsWith: "G"

URI Matching

Here are examples of matching elements of the URI:

# Match a value in the whole URI
- where: uri
  contains: "api/places/json?"

# The whole URI is in encoded form just as it comes in the request, whereas
# the parts (path, query string parameters, etc) are decoded.
# Thus, the matching expression for a URI must use encoded values when applicable.
# The expression below matches URI that contains "?type=restaurant&bar" value
- where: uri
  contains: "?type=restaurant%26bar"

# Implies 'equals'. Case sensitive 'uriPath' field!
- uriPath: "/api/places/json"

# Same as above
- where: uriPath
  equals: "/api/places/json"

- where: uriPath
  isLike: "/admin/.*"

# Implies 'matches'. Case sensitive 'uriPathPattern' field!
- uriPathPattern: "/api/{collection}/{format}"

# Same as above
- where: uriPathPattern
  matches: "/api/{collection}/{format}"

# Case-insensitive URI path pattern matching
- where: uriPathPattern
  matchesIgnoreCase: "/Api/{collection}/{format}"

- where: uriScheme
  equals: "https"

- where: uriUserInfo
  startsWith: "admin:"

- where: uriUserInfo
  endsWith: ":passW0rd"

- where: uriHost
  equalsIgnoreCase: "EXAMPLE.com"

- where: uriPort
  equals: "8090"

- where: uriQueryParameter
  named: "radius"
  equals: "5"

# Multi-value query string parameter
- where: uriQueryParameter
  named: "types"
  equals: "food"
- where: uriQueryParameter
  named: "types"
  equals: "cafe"

- where: uriQueryParameter
  named: "checked"
  exists: true

- where: uriQueryParameter
  named: "blah"
  exists: false

- where: uriFragment
  equals: "ref1"

Keep in mind that matchesIgnoreCase and matches apply only to uriPathPattern and not to other URI elements.

Header Matching

- header: Content-Type
  contains: "/json"

- where: header
  named: Content-Type
  equals: "application/xml"

# Header name matching is case insensitive
- header: authorization
  startsWith: "Bearer "

# Matching a custom header is no different
- header: X-Csrf-Token
  exists: true

HTTP requests may contain cookies in an HTTP Header field called Cookie. A single header field can have multiple cookies separated by semi-colon ;.

API Simulator supports matching individual cookies so you don’t have to extract them yourself from the Cookie header field.

request:
- where: cookie
  named: lang
  equals: "en-US"

Body Matching

Currently there are two flavors of body matching methods: one that treats the body as unstructured content, and one that matches elements in structured text (JSON, XML).

Body as Unstructured Content

# Any one of the text matching operations can be applied
# Notice in this example the use of single quotes to surround the value
# that contains double quotes (presumably as part of a JSON payload)
- where: body
  contains: '"salePrice": "22.98"'

JSON

Currently (circa October 2018) there isn’t a standard for JSON similar to what XPath is for XML. There are few proposals and implementations out there for what is dubbed JSONPath, each with some "nuances". API Simulator v1.1+ uses an implementation close to the proposal in Stefan Goessner’s article.

JSONPath provides a way of identifying and navigating to parts of a JSON document. The parts can be individual fields or complex structures.

Below are the syntax elements of JSONPath expressions:

Syntax Element Description

.

A JSONPath expression must start with a dot . character.

@

Denotes the current object/element being processed by a filter (see below about filters).

*

Wildcard.

..

Recursive descent into all children of an object/element. The result is always an array, potentially with some null elements.

.<name>

Dot-notation reference to a child.

['<name>' (, '<name>')]

Bracket-notation reference to one or more children. Bracket-notation is required for fields that contain spaces.

[<number> (, <number>)]

One or more array elements referenced by their index.

[start:end]

Array slice operator.

[?(<filter>)]

Filter expression (see below). Must evaluate to a boolean value.

Filters are expressions used to select array elements. A typical filter would be [?(@.age > 18)] where @ represents the object/element item being processed. More complex filters can be created with logical operators && and ||. String literals must be enclosed by single ' or double quotes " - ([?(@.color == 'blue')] or [?(@.color == "blue")]).

The following table lists the supported filter operators:

Operator Description

==

The left side is equal to the right side (note that the number 1 is not equal to the String '1').

!=

The left side is not equal to the right side.

<

The left side is less than the right side.

<=

The left side is less than or equal to the right side.

>

The left side is greater than the right side.

>=

The left side is greater than or equal to right side.

=~

The left side matches the regular expression on the right.

The following example should help understand how it works. Let’s consider this JSON document:

{
  "product": {
    "id": "1234",
    "name": "The Jumpers",
    "category": [
      "Shoes",
      "Kids"
    ],
    "sizes": {
      "US": [10, 10.5, 12, 13, 13.5]
    },
    "color": "white",
    "sale price": 22.98,
    "count": 3,
    "exclusive": true
  }
}

These are few JSONPath expressions and the result they yield when applied to the JSON document:

JSONPath Result

.product.id

1234

.product.name

The Jumpers

.product['sale price']

22.98

.product.['sale price']

22.98

.['product']['sale price']

22.98

.['product'].['sale price']

22.98

.product.color

white

.product.count

3

.product.exclusive

true

.product.category[0]

Shoes

.product.category[1]

Kids

.product.category[2]

null

.product.category[?(@ =~ /shoes/i)]

["Shoes"]

.product.sizes.US[?(@ =~ /10.*/)]

["10", "10.5"]

.product.blah

null

..name

[null, "The Jumpers", null]

.product..color

["white", null]

Below are examples of matching HTTP body elements if the JSON document was in the HTTP body of a request:

- where: body
  element: ".product.id"
  equals: "1234"

- where: body
  element: ".product.count"
  equals: 3

- where: body
  element: ".product.color"
  exists: true

- where: body
  element: ".product.color"
  equals: "white"

- where: body
  element: ".product.blah"
  exists: false

# The first array element
- where: body
  element: ".product.category[0]"
  equalsIgnoreCase: "shoes"

# The second array element
- where: body
  element: ".product.category[1]"
  equals: "Kids"

# Any of the array elements' value is equal to 'shoes' (case-insensitive regex matching)
- where: body
  element: ".product.category[?(@ =~ /shoes/i)]"
  exists: true

XML without Namespaces

Assuming the following XML body:

<ValidateAddress>
  <ValidateAddressInput>
    <Address>
      <Line1>123 Main Street</Line1>
        <City>Anycity</City>
        <StateProvinceCode>ZZ</StateProvinceCode>
        <PostalCode>12345</PostalCode>
        <CountryCode>USA</CountryCode>
    </Address>
  </ValidateAddressInput>
</ValidateAddress>

…​these are examples of matching elements in the XML body:

- where: body
  element: "ValidateAddress/ValidateAddressInput/Address/Line1"
  equals: "123 Main Street"

- where: body
  element: "//Address/PostalCode"
  equals: "12345"

XML with Namespaces

Often time XML uses namespaces. Assuming the following XML body:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
   xmlns:ws="http://ws.v1_0.avs.example.com/">
<s:Header />
<s:Body>
  <ws:ValidateAddress>
    <ValidateAddressInput>
      <Address>
        <Line1>123 Main Street</Line1>
        <City>Anycity</City>
        <StateProvinceCode>ZZ</StateProvinceCode>
        <PostalCode>12345</PostalCode>
        <CountryCode>USA</CountryCode>
      </Address>
    </ValidateAddressInput>
  </ws:ValidateAddress>
</s:Body>
</s:Envelope>

…​these are examples of matching elements in the XML body when namespaces are in play: :

- where: body
  element: "s:Envelope/s:Body/ws:ValidateAddress/ValidateAddressInput/Address/Line1"
  namespaces:
    s: "http://schemas.xmlsoap.org/soap/envelope/"
    ws: "http://ws.v1_0.avs.example.com/"
  equals: "123 Main Street"

# The namespace prefixes in the path (ns1, ns2) do not have to match the
# prefixes in the request (s, ws) as longs as they refer to the same URI
- where: body
  element: "ns1:Envelope/ns1:Body/ns2:ValidateAddress/ValidateAddressInput/Address/PostalCode"
  namespaces:
    ns1: "http://schemas.xmlsoap.org/soap/envelope/"
    ns2: "http://ws.v1_0.avs.example.com/"
  equals: "123 Main Street"

Parameter Matching

As explained in the Simlet Parameters doc, parameters are used as template placeholders and in scriptlets to render dynamic output, and for matching values in requests.

Without going here into details about simlet parameters, the following examples demonstrate the use of parameters to match values in a request:

request:
- where: parameter
  named: UriPath
  equals: "/v4/products"

- where: parameter
  named: ProductID
  exists: true

- where: parameter
  named: SessionID
  endsWith: "0259"


UriPath:
  is: parameter
  from: uriPath

SessionID:
  is: parameter
  from: cookie
  named: "SessionID"

ProductID:
  is: parameter
  from: body
  element: ".product.id"

Any type of parameter can be used as a matcher.

Matching Rank

Sometimes, simlet matching has to be applied in a certain order. This is where matching rank comes into play. Below is an example.

Simlet 1 matches all GET requests to the admin area:

# The default rank is 0
#rank: 0

request:
- method: GET

- where: uriPath
  isLike: "/admin/.*"

Simlet 2 matches GET requests to the /admin/logout URL in the admin area:

rank: 100

request:
- method: GET

- where: uriPath
  equals: "/admin/logout"

Because Simlet 2’s rank is 100, which is higher than the Simlet 1’s (default) rank of 0, Simlet 2 will be selected for GET requests to the /admin/logout URL.

The rule is that API Simulator tries to match simlets with higher matching ranks before simlets with lower ranks; simlets with the same matching rank are evaluated in no particular order.

Request Sampling

Request sampling in API Simulator is a form of request matching. That is explained in Request Sampling.

Default Simlet

It can happen that an HTTP request doesn’t match any of the matching rules defined in the simlets for a simulation. API Simulator supports default, catch-all simlet when it cannot match a request.

Practically, the default simlet matches any request. To define default simlet for a simulation don’t specify request element at all:

response:
  from: ...

…​or specify explicitly that it matches all and any requests like this:

request: any

response:
  from: ...

What the default simlet returns is configurable like for any other response.

Give the default simlet any valid simlet name - there is no requirement anymore to name it "_default". This doesn’t concern you if you’ve never heard of a requirement to name the default simlet "_default" 😉

Built-in Default Simlet

If a simulation doesn’t have a default simlet configured as explained above, API Simulator will use a built-in simlet that returns response with HTTP status 404 Not Found and "API Simulator couldn’t find a matching simlet for the request." body.


We would love to hear your feedback - send us a quick email to [feedback at APISimulator.com]

Happy API Simulating!