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 element, HTTP Method, HTTP Header fields, and values from the body provide for extensive request matching.

Matching Operations

Various operations are supported to check for matching text:

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: "..."

Matchers

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

matchers:
- method: POST

- where: UriPath
  equals: "/rest/v1/products/product"

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

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

The default simlet should have no matchers. For that, don’t define matchers at all or define them as an empty list:

matchers: []

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 quite 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"

- where: UriPath
  equals: "/api/places/json"

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

- where: UriPathPattern
  matches: "/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

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.

matchers:
  # Matching a cookie value using cookie matcher
  - where: cookie
    named: lang
    equals: "en-US"

  # Matching a cookie value using parameter from a cookie
  - where: parameter
    named: SessionIDParm
    endsWith: "0259"


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

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 JSON payload)
- where: body
  contains: '"salePrice": "22.98"'

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"

JSON Elements

In the absence of a standard for JSON similar to XPath, API Simulator uses an unique extension to the JSON parsing that, internally, safely converts JSON documents to XML documents. Among the things it deals with are field names with spaces, for example. Elements from the original JSON document can then be matched using the same powerful XPath like for XML.

Need not to worry - for the majority of cases there is little to learn beyond how to navigate the structure of the JSON document.

Please see json.org for the complete JSON syntax. Here are some of the definitions to help understand the conversion to XML:

object
    {}
    { members }
members
    pair
    pair , members
pair
    string : value
array
    []
    [ elements ]
elements
    value
    value , elements
value
    string
    number
    object
    array
    true
    false
    null

...see http://json.org/ for the complete JSON syntax.

These are few important conversion rules for converting JSON to XML:

  • We represent { - the start of an unnamed object - with XML tag named <object> and the end - } - with </object>.

  • Simple elements of an array are stored between XML tags <value> and </value>.

  • JSON field names with spaces and other characters invalid in an XML tag name become attribute called 'name' of a <field> tag. For example, <field name="country code">US</field>

The following example should help understand how it works. Assuming this is the JSON document:

{
  "product":
  {
    "id":"1234",
    "name":"The Jumpers",
    "category":
    [
      "Shoes",
      "Kids"
    ],
    "subCategory":"Basketball",
    "color":"white"
  }
}

…​this is the XML representation of the JSON document:

<object>
    <product>
        <id>1234</id>
        <category>
            <value>Shoes</value>
            <value>Kids</value>
        </category>
        <color>white</color>
        <subCategory>Basketball</subCategory>
        <name>The Jumpers</name>
    </product>
</object>

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

- where: body
  element: "object/product/id"
  equals: "1234"

- where: body
  element: "/object/product/id"
  equals: "1234"

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

# The first array element
- where: body
  element: "object/product/category/value"
  equalsIgnoreCase: "shoes"

# Again, the first array element
- where: body
  element: "object/product/category/value[1]"
  equalsIgnoreCase: "shoes"

# The second array element
- where: body
  element: "object/product/category/value[2]"
  equalsIgnoreCase: "kids"

# Any of the array elements' value is equal to 'Kids' (case-sensitive matching)
- where: body
  element: "object/product/category[value='Kids']"
  exists: true

- where: body
  element: "object/product/color"
  exists: true

- where: body
  element: "object/product/size"
  exists: false

Matching Rank

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

Simlet 1 matches all GET requests to the admin area:

# The default rank is 0
#rank: 0

matchers:
- method: GET

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

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

rank: 100

matchers:
- 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.


We would love to hear your feedback! Shoot us a quick email to [feedback at APISimulator.com] to let us know what you think.

Happy API Simulating!