request:
- method: POST
- uriPath: "/rest/v1/products/product"
- header: content-type
equals: "application/json"
- where: body
element: ".product.upc"
exists: true
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:
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"
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
Cookie Matching
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 |
|
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 |
|
Dot-notation reference to a child. |
|
Bracket-notation reference to one or more children. Bracket-notation is required for fields that contain spaces. |
|
One or more array elements referenced by their index. |
|
Array slice operator. |
|
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 |
---|---|
|
1234 |
|
The Jumpers |
|
22.98 |
|
22.98 |
|
22.98 |
|
22.98 |
|
white |
|
3 |
|
true |
|
Shoes |
|
Kids |
|
null |
|
["Shoes"] |
|
["10", "10.5"] |
|
null |
|
[null, "The Jumpers", null] |
|
["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! Shoot us an email to [feedback at APISimulator.com] about anything that is on your mind.
Happy API Simulating!