Simlet Parameters

Overview

definition

Parameters are variables a simlet uses to produce dynamic HTTP response from a template and/or to match a request.

They are used in Simula template placeholders and in scriptlets to render dynamic output, and for matching values in requests.

Configuring Parameters

The general YAML DSL for defining a parameter for a simlet is as follows:

SomeParameterName:
  is: parameter

  from: <source>
  #...source-specific configuration fields here...

  # The (text) value from the source is converted to this type
  as: <byte | int16 | int32 | int64 | float | double | boolean | dateTime | \
       byte[] | int16[] | int32[] | int64[] | float[] | double[] | boolean[] | dateTime[]>

  # Only for 'as: dateTime', the format of the datetime string to help with parsing it
  format: "dateTime-format-string"

  # The parameter will default to this value if the value from the source is null.
  # The type of the default value should be the same as in 'as', if 'as' is specified.
  default: <default value>

  # 'isSnapshot' is a boolean flag if the value of this parameter will be the same
  # (snapshot) or will be changing between uses of the parameter in the building
  # of a response (e.g. randomly generated values). Defaults to 'true'.
  isSnapshot: false | true

  eval: "<expression>"

It is to note that the parameters: list field has been deprecated and it is not required anymore to add parameters to that list in order to be able to access and use them in template placeholders. The support for the parameters: field will be removed in a future release.

Here is an example for configuring a parameter and using it in a matcher and the response template:

# This defines a parameter once that is then used in
# a matcher as well as in a template placeholder
ProductID:
  is: parameter
  from: body
  element: ".product.id"


request:
- method: PUT
- uriPath: "/api/v1/products"
  # Parameter as a matcher
- where: parameter
  named: ProductID
  exists: true


response:
  from: template
  template: Simula
  status: 201
  headers:
    # Template placeholder that uses the 'ProductID' parameter
  - "Location: /api/v1/products/${ProductID}/details"

It is to notice that the place in the YAML where a parameter is defined doesn’t matter. For example, ProductID in the simlet definition above could have been in between the request and response definitions or after the response as well.

Parameter values are resolved lazily, on demand, when a parameter is referenced. See also Snapshotting.

Parameter Name

Parameter names must follow these rules:

  • Start with a letter.

  • Contain zero or more letters, digits, and underscore _ characters after the first letter.

  • Case-insensitive.

  • Unique within a simlet.

API Simulator doesn’t error out when it detects a duplicate parameter name - it just logs a warning.

Parameter Type

Parameters can come from a variety of sources where each source type has some required and optional configuration fields. A section below - Parameter Sources - describes them in detail.

With HTTP being a text-based protocol, by default, all parameters from HTTP request elements (path, body, etc.) are strings. Usually there isn’t a need to convert the text to another type unless the parameter value has to be formatted in a template placeholder differently than the original value. Using as instructs API Simulator to convert the string value to one of these types:

  • byte - 8-bit signed integer number in the range from -128 to +127, inclusive.

  • int16 - 16-bit signed integer number in the range from -32,768 to 32,767, inclusive.

  • int32 - 32-bit signed integer number in the range from -231 (-2,147,483,648) to 231 - 1 (+2,147,483,647), inclusive.

  • int64 - 64-bit signed integer number in the range from -263 (-9,223,372,036,854,775,808) to 263 - 1 (+9,223,372,036,854,775,807), inclusive.

  • float - single-precision 32-bit IEEE 754 floating point number.

  • double - double-precision 64-bit IEEE 754 floating point number.

  • boolean - a boolean true/false.

  • dateTime - the internal representation is an int64 number denoting the number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT.

  • byte[] - an array of byte-s values.

  • int16[] - an array of int16-s values.

  • int32[] - an array of int32-s values.

  • int64[] - an array of int64-s values.

  • float[] - an array of float-s values.

  • double[] - an array of double-s values.

  • boolean[] - an array of boolean-s values.

  • dateTime[] - an array of dateTime-s values.

For proper parsing when a parameter’s value comes from a datetime represented as text, API Simulator needs to know the datetime format.

For example, yyyy-MM-dd HH:mm:ss z is the format for a datetime that has:

  • 4 digits for the year (yyyy),

  • 2 digits for the month (MM),

  • 2 digits for the day (dd),

  • a space, followed by

  • two digits for the hour in the day in 24-hour format (HH),

  • two digits for the minutes (mm),

  • two digits for the seconds (ss),

  • one space,

  • then the time zone name (e.g. GMT, CST, PST…​)

…​and all elements will be padded with zero (0) as needed.

Each one of the following case-insensitive value is considered to be boolean true: true, on, yes, and 1. Each one of the following case-insensitive value is considered to be boolean false: false, off, no, and 0.

Failure to convert a parameter value to the desired type will result in the parameter being set to the default value, or to null if default is not configured.

Parameter Evaluation

Sometimes a source value needs additional processing to get to the final parameter value. That is what eval is for.

Using eval allows for great flexibility in configuring parameters. In essence, eval is a simple Groovy script that is executed and whatever it returns becomes value for the parameter. The eval script cannot have custom functions and methods.

These are the steps API Simulator goes through in determining the final value for a parameter:

  1. Get the value from the respective source;

  2. Convert that value to the type given in as, if specified;

  3. If the value is null then it assumes what’s in default, if configured.

  4. Apply additional evaluation to the value by executing the scripting code in eval, if present.
    Whatever is return-ed from eval becomes value for the parameter even if it has nothing to do with the value from the source - the script in eval has the freedom to return anything and of any type.

  5. If isSnapshot is true then the value at this point will be snapshotted (see Snapshotting).

Let’s look at an example:

request:
#. ...
- header: "Content-Type"
  equals: "text/csv"

- where: parameter
  named: ProductID
  exists: true


UriPath:
  is: parameter
  from: uriPath

# Parameter as result of processing the body content
CsvParms:
  is: parameter
  from: body
  eval: `
      if (null == _ || _.trim().length() == 0 || _.indexOf(',') < 0) return null
      return _.split(',')
`

# Parameter extracted from the processed body content
ProductID:
  is: parameter
  from: parameter
  named: CsvParms
  eval: "return (_ != null && _.size() > 0 ? _[0] : null)"


response:
  from: template
  template: Simula
  status: 201
  headers:
  - "Location: ${ UriPath }/${ ProductID }/details"

The explanation follows:

  • CsvParms is a parameter which value is an array obtained as result of parsing the request body.

  • The underscore _ is a variable for the parameter value at the point of time of starting the execution of the eval expression.

  • The ProductID parameter becomes the first element from the CsvParms array, if there is such element, or null otherwise.

It is to note that only "simple" scripting can be used in eval - no functions/methods/classes/closures/etc.

Snapshotting

The boolean isSnapshot field makes sense for parameters, which values can change from one use of the parameter to the next in the rendering of the same response. Such parameters, for example, are those which values are randomly generated. Setting isSnapshot: false allows a parameter to be defined once and used multiple times in building a response and each time it will return a potentially different value.

Here is an example:

simlet: parameter-snapshot-false

request:
  - method: GET
  - uriPath: /tests/parameter-snapshot-false

UUIDParm:
  is: parameter
  from: uuid
  isSnapshot: false

response:
  from: template
  status: 200
  headers:
   - "content-type: application/json"
  body: `
{
  "uuid-1": "${ UUIDParm }",
  "uuid-2": "${ UUIDParm }"
}
`

uuid-1 and uuid-2 in the body of the response will have different values when the UUIDParm parameter is defined with isSnapshot: false. They will have the same value when isSnapshot: true, which is also the default behavior.

Parameter Sources

Parameters can come from a variety of sources - the HTTP request, a data store, scripts, randomly generated values, current data/time, some computation based on another parameter, and more.

URI

SomeParameterName:
  is: parameter

  # One of the 'from' sources below

  # The HTTP method: GET, POST, PUT, etc.
  from: httpMethod

  # The whole URI
  from: uri

  # The scheme - http/https - if present in the URI
  from: uriScheme

  # The path part of the URI only
  from: uriPath

  from: uriPathPattern
  pattern: "/uri/path/{pattern}/here"

  # The user info (userid and/or password) part of the URI
  from: uriUserInfo

  # The host part of the URI
  from: uriHost

  # The port number part of the URI
  from: uriPort

  # Query String Parameters
  # A map of all Query String Parameters. Each map entry is keyed by the query
  # string parameter name and the value is a list with zero or more elements
  from: uriQueryParameters

  # A Query String Parameter
  # There can be more than one Query String Parameter with the same name.
  # This parameter will have them all in a list of items accessible by index.
  from: uriQueryParameter
  named: "query-string-parameter-name"

  # The fragment (the part after '#') in the URI
  from: uriFragment

  # The HTTP version the request uses (e.g. HTTP/1.1)
  from: httpVersion

URI Path Pattern

Parameters from URI path pattern deserve a bit more details to explain the flexibility in extractingvalues from URI path segments.

In the following example, API clients submit HTTP GET requests like this:

GET /v1/products/2706414/Black+Charcoal/XL

By varying the values after /v1/products, the API returns data for a specific product based on the product’s SKU, color, and size.

Let’s configure simlet for such requests with parameters for the SKU, color, and size from the path:

request:
- method: GET

- where: uriPathPattern
  matches: "/v1/products/{sku}/{color}/{size}"


SkuParm:
  is: parameter
  from: uriPathPattern
  # Two wildcard characters '**' match zero or more directory segments in a path
  # This parameter will have a value for sure because of the matcher defined above
  pattern: "/v1/products/{sku}/**"

ColorParm:
  is: parameter
  from: uriPathPattern
  # A single wildcard character '*' like in this pattern matches a directory segment
  pattern: "/v1/products/*/{color}/*"

SizeParm:
  is: parameter
  from: uriPathPattern
  pattern: "/v1/products/*/*/{size}"

#...

The names of the path segments surrounded by { and } - {sku}, {color}, and {size} - need not to match the name of the parameter in which definition they appear.

HTTP Headers

ParameterName:
  is: parameter
  from: header
  named: "name-of-the-header-field"

Cookies

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 configuring parameters from cookies so you don’t have to extract them yourself. The implementation is compliant with the most recent RFC6265 "HTTP State Management Mechanism". In particular, cookie values are retrieved from a Cookie header regardless of whether they have any $Path or $Version as defined in the older and now-obsolete RFC2965.

Here are examples for how to define parameters from cookies:

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

LanguageParm:
  is: parameter
  from: cookie
  named: "lang"
  default: "en-US"

HTTP Body

It is possible to treat the body of requests as unstructured content or as structured text (JSON, XML), if applicable.

Parameters from Unstructured Body Content

The following configures a parameter which value is the whole body content from the request:

RequestBody:
  is: parameter
  from: body

API Simulator recognizes JSON and XML body payloads as structured content. Any other format is treated as of it is unstructured. The next example demonstrates how to perform custom extract of pieces of data from a comma-separated values text:

# Parameter as result of processing the body content
CsvParms:
  is: parameter
  from: body
  eval: `
      if (null == _ || _.trim().length() == 0 || _.indexOf(',') < 0) return null
      return _.split(',')
`

# Parameter extracted from the processed body content
ProductID:
  is: parameter
  from: parameter
  named: CsvParms
  eval: "return (_ != null && _.size() > 0 ? _[0] : null)"

Notice that the above isn’t a robust CSV processing that takes into account, for example, commas inside the values, but demonstrates a way to process unstructured body content.

Parameters from Body Elements

To identify an exact element in structured body content (JSON, XML), use path expressions as those used for matching body elements:

ParameterName:
  is: parameter

  # Use the same path expressions as the ones to identify matching body elements
  from: body
  element: "path-to-the-body-element"

  # XML body with namespaces
  from: body
  element: "path-to-the-body-element"
  namespaces:
    prefix1: "<uri1>"
    ...
    prefixN: "<uriN>"

Constant Value

Constant value parameters have a value that doesn’t change and can be used as a placeholder, in computations, etc. Here is how to define one:

ParameterName:
  is: parameter
  from: constant
  value: <some-constant-value>

UUID

Sometimes API calls return UUIDs. There is a special parameter type in API Simulator for UUIDs. Here is how to define one named PaymentID:

PaymentID:
  is: parameter
  from: uuid

…​then use it in a template as any other parameter.

When generated according to standard algorithms, UUID (Universally Unique Identifier), aka GUID (Globally Unique Identifier), is guaranteed to be unique across time and space. Here is an example for a UUID: 707e6337-4298-46b8-8fe7-f7ce396d404f

Current Datetime

A datetime value may contain date and/or time portion. Here’s how to configure a parameter named DateTimeNow for the current datetime:

DateTimeNow:
  is: parameter
  from: dateTime

See also Random Datetime parameters.

Time-based Dependency

Here is an example of a datetime parameter which value is computed with respect to the value of another "base" datetime parameter:

CurrentDateTime:
  is: parameter
  from: dateTime

# Let's read the definition out loud:
# > this 'ExpireDateTime' parameter value comes from/depends on another parameter.
# > the name of the parameter this one depends on is 'CurrentDateTime'.
# > the dependency is time computation - amount of a time unit added or subtracted.
ExpireDateTime:
  is: parameter
  from: parameter
  named: CurrentDateTime
  dependency:
    kind: time
    amount: 86400
    unit: seconds

The "unit" value is case-insensitive and expected to be one of the following: days, day, hours, hour, minutes, minute, seconds, second, milliseconds, and millisecond.

The time-based dependency works also with randomly generated datetime parameters as base.

File

Response body from file configured via response.body.file is a field determined up front when the YAML DSL is processed.

Parameter from file makes it possible the source file to be determined at run-time, for example using values from the request, making it completely dynamic. The content of the file can be used anywhere a parameter value is allowed.

To configure a parameter from file:

# Choose a parameter name
<Parameter-Name>:
  is: parameter
  from: file
  # File type is a text file (the default) or binary (e.g. an image)
  type: text | binary
  # Option to configure the charset when file type is text. Defaults to UTF-8
  charset: UTF-8
  # See below how to configure the file source
  file: ...
  # Whether to cache the file content in memory. Default is true.
  # Notice that when using large number of files and/or big files,
  # you may have to adjust API Simulator's startup memory settings
  # or to disable file caching for some parameters from file
  cacheFile: true | false
  # `isSnapshot` is always false so the parameter gets evaluated every time it is
  # used in a response but the file content can still be retrieved from the cache

Below are the three ways to configure the file source:

<Parameter-Name>:
  is: parameter
  from: file
  ...
  file: <full-file-specification>

# OR
<Parameter-Name>:
  is: parameter
  from: file
  ...
  file:
    path: <path/to/file>
    name: <file-name>

# OR
<Parameter-Name>:
  is: parameter
  from: file
  ...
  file:
    # Groovy script. End it with a `return` statement
    eval: .... return <full-file-specification>

Three global variables are handy when configuring the location of the file: ${simlets.path}, ${sim.path}, and ${simlet.path}. They resolve respectively to the "simlets" directory, the simulation directory path, and the directory of the simlet. When using in a placeholder use them as the only variable - ${sim.path} and ${simlets.path}. Something like ${sim.path/myfile.json} or ${sim.path + '/myfile.json'} is not valid.

Moreover, those global variables can be represented by _simlets.path, _sim.path, and _simlet.path in eval expressions and scripts.

When used in scripts and eval expressions, ${simlet.path} and _simlet.path resolve to the directory of the simlet only after the request is matched and mapped to a simlet. So, don’t use them in Parameter-s that are part of request matching.

Some example snippets follow:

ResponseBody1:
  is: parameter
  from: file
  file: ${sim.path}/product-${ProductID}.json

# Equivalent to ResponseBody1
ResponseBody2:
  is: parameter
  from: file
  file:
    path: ${sim.path}
    name: product-${ProductID}.json

# Equivalent to ResponseBody2
ResponseBody3:
  is: parameter
  from: file
  file:
    eval: return ${sim.path} + '/' + 'product-' + ${ProductID} + '.json'

# Equivalent to ResponseBody3
ResponseBody4:
  is: parameter
  from: file
  file:
    eval: return _sim.path + '/' + 'product-' + ProductID + '.json'

FaviconFileParm:
  is: parameter
  from: file
  type: binary
  file: ${sim.path}/favicon-16x16.png

Here is a complete example:

ProductID:
  is: parameter
  from: uriPathPattern
  pattern: /products/product/{id}

request:
- method: GET
- where: parameter
  named: ProductID
  exists: true

ResponseBody:
  is: parameter
  from: file
  file:
    path: ${simlets.path}
    name: product-${ProductID}.json

response:
  from: template
  headers:
  - "Content-Type: application/json; charset=UTF-8"
  body: ${ResponseBody}

What if the ProductID isn’t recognized or in other words - what if there isn’t a file for it? With a little bit of scripting, let’s configure API Simulator to return dynamic response status code and body when that happens:

ProductID:
  is: parameter
  from: uriPathPattern
  pattern: /products/product/{id}

request:
- method: GET
- where: parameter
  named: ProductID
  exists: true

ProductFileSpec:
  is: parameter
  from: script
  expression: `
fname = 'product-' + ProductID + '.json'
return new File(${sim.path}, fname)
`

ResponseBody:
  is: parameter
  from: file
  file:
    eval: return ProductFileSpec.toString()

IsProductFound:
  is: parameter
  from: script
  expression: return ProductFileSpec.exists()

response:
  from: template
  status: '${ IsProductFound ? 200 : 404 }'
  headers:
  - "Content-Type: application/json; charset=UTF-8"
  # Parameters referenced by name in scriptlet expressions, like
  # ProductID below, or placeholders in the body do get evaluated
  body: `
  <%
     if (IsProductFound) {
       write ResponseBody
     }
     else {
       write '{ "error": "Product with ID=' + ProductID + ' not found" }'
     }
  %>
`

Content loaded using parameter from file is not evaluated for placeholders or scriptlets. That is why the output in the example above is built dynamically in the else branch and it isn’t another parameter from file.

It is to note that using parameter from file for large number of files may not be the best approach. API Simulator supports other ways for generating dynamic responses so you have a choice.

Script

The example above actually defined and used several parameters from script. The general syntax is:

<ParameterName>:
  is: parameter
  from: script
  expression: <Groovy script that must return a value>

Template

A parameter from template resolves dynamically to some value assembled from a mix of placeholders, scriptlets, and static text. That value can be used in other templates and in any place where placeholders can be used. It is another building block at your disposal for dynamically constructing HTTP responses.

Here is the syntax for defining parameters from template:

<ParameterName>:
  is: parameter
  from: template
  template: Simula
  text: <template-text-here>

The template: Simula configuration element is optional and the template is currently always assumed to be a Simula template. The template’s text can be a mix of placeholders, scriptlets, and static text. All other parameter configuration fields - isSnapshot, eval, as, and default - are still applicable.

If using scriptlets in the template text, use write to output values that will become part of the whole result. Assuming TideLevel is a parameter with some integer value (i.e. as: int32) for the tide level:

TodaysTide:
  is: parameter
  from: template
  text: "The tide is <% if (TideLevel < 20) { write 'low' } else { write 'high' } %> today"

response:
  from: template
  headers:
  - "Content-Type: application/text"
  body: ${TodaysTide}

The result from the above parameter from template definition is equivalent to mixing scriptlet code with direct output like this:

TodaysTide:
  is: parameter
  from: template
  text: "The tide is <% if (TideLevel < 20) { %>low<% } else { %>high<% } %> today"

Here is the same result as above but using a placeholder and ternary operator (?:):

TodaysTide:
  is: parameter
  from: template
  text: "The tide is ${ TideLevel < 20 ? 'low' : 'high' } today"

Parameter As Source

Pretty much any dependency between parameters can be defined using eval. Here is a configuration the result of which is equivalent to the Time-based Dependency presented above:

CurrentDateTime:
  is: parameter
  from: dateTime

# Let's read the definition out loud:
# This 'ExpireDateTime' parameter value comes from another parameter named
# 'CurrentDateTime' evaluated by adding 86400 seconds to its value.
ExpireDateTime:
  is: parameter
  from: parameter
  named: CurrentDateTime
  eval: return _ + (86400 * 1000L)

The parenthesis are not required but just to help with reading the expression: "return the current parameter value added to the result from multiplying 86400 by 1000".

Lists

Lists can be a source for parameters. Here is how to define such parameters with the list elements right in the YAML:

# The parameter's value is the whole list
# with the order of elements preserved
LastNameList:
  is: parameter
  from: list
  list: [ "Smith", "Johnson" ]

Given that this is YAML, the list’s elements can also be specified using the one-item-per-line format:

LastNameList:
  is: parameter
  from: list
  list:
  - "Smith"
  - "Johnson"

It is also possible to have the parameter’s list elements come from a file. Here are the options to define one:

<Parameter-Name>:
  is: parameter
  from: list
  file: <file-spec>
    # OR
    path: <file-path>
    name: <file-name>
    # OR
    # Simple Groovy script (e.g. no method or function definitions). End with `return` statement
    eval: ... return <full-file-specification>
  # Optional. The default value is `true`
  cacheFile: true | false
  # Option to configure the charset when file type is text. Defaults to UTF-8
  charset: UTF-8

The file with the list elements is expected to be a text file with each list element on a separate line. The end-of-line can be either a line feed (\n) or carriage return plus line feed (\r\n) characters.

Random Number

The random number parameters contain randomly generated integer (whole) numbers:

ParameterName:
  is: parameter
  from: number
  range:
    min: <minimum-value>
    max: <maximum-value>

To use in a template a randomly generated floating-point number, generate two random integer numbers and concatenate them in the template with a dot . or comma , (as appropriate for the locale).

Random Datetime

Random datetime between dates in a given range can be defined as follows:

OrderDate:
  from: dateTime
  range:
    # At least one of the 'min' or 'max' range boundaries is required.
    # Missing range boundary is assumed to be the current data and time,
    # possibly causing switching the values to assure 'min' < 'max'.
    min:    "2018-01-01 00:00:00 UTC"
    max:    "2018-01-31 11:59:59 UTC"
    # The same format applies to both 'min' and 'max'
    format: "yyyy-MM-dd HH:mm:ss z"

Notice that the same construct can be used to define a date between now and some other day in the past or future.

Sometimes there’s the requirement to use a date that is certain number of days before or after "now". Here it is how to configure such random datetime generation:

OrderDate:
  from: dateTime
  range:
    # Randomly generates a new datetime value that is in the range of
    # [ "now" + min, "now" + max]
    # Negative values for 'min' or max' will be subtracted from "now".
    # At least one of the 'min' or 'max' range boundaries is required.
    # Missing range boundary is assumed to have value of 0 (zero),
    # possibly causing switching the values to assure 'min' < 'max'.
    min: -60
    max: -30
    # The same time unit applies to both 'min' and 'max'
    timeUnit: days

The "timeUnit" can be any of the following case-insensitive values: nanoseconds, microseconds, milliseconds, seconds, minutes, hours, and days.

Random Token

Here is an example for configuring a parameter which value is a random token:

PaymentNumber:
  is: parameter
  from: token
  pattern: "PAY-[0-9A-Z]{25}"

The "pattern" uses the following constructs and rules:

  • Literals = any digits, letters, other characters and symbols to be used verbatim in the resulting token.

  • [] = a group; the token generator will randomly use one of the elements from the group surrounded by square brackets.

  • {n} = exact repetition; placed after a group it determines how many times the token generator will randomly pick an element from the group.

  • {n,m} = repetition range; after a group, it tells the token generator to randomly pick an element from the group at least n times but not more than m times, where 0 < = n < = m.

  • There must not be any whitespace character, like a space, between the end of a group ] and { start of a repetition for that group.

  • [a-d] = range of letters inside a group; a shortcut to avoid listing all elements from the range individually.

  • [0-9] = range of digits inside a group; a shortcut to avoid listing all elements from the range individually.

  • [?a-z!0-9.] = a group can have multiple ranges mixed with individual elements as well.

  • To escape ] and - inside a group, prefix them with /. Notice that [ inside a group doesn’t have to be escaped.

  • To escape a / inside a group, prefix it with / - //.

  • To escape a - inside a group, prefix it with / - /-.

  • Special characters for whitespaces: \n, \r, \t, etc.

Examples:

  • [PaRsE] - one of the P, R, and E capital letters, and the a and s small letters.

  • [a-c] - any small letter between a and c, that’s it - a, b, or c.

  • [A-Z] - any capital letter between A and Z.

  • [0-9] - any digit between 0 and 9.

  • PAY-[0-9A-Z]{25} - the text PAY- followed by 25 randomly picked digits and capital letters.

  • [_$a-z][a-z0-9_]{0,9} - a token that starts with underscore _, dollar sign $, or a small letter between a and z, followed by no more than nine small letters, digits, or underscore _ character.

Random Elements from List

The Lists section introduced lists as parameter values. An extended syntax for list parameter configuration - adding a pick field - is used to define parameters with individual elements or sublists randomly selected from a list of valid values. A list’s elements from which to randomly pick values can be defined in the YAML or come from a file. The following explains the various options for the pick field:

# Parameter for a single element randomly picked from the list
RegionAZ:
  is: parameter
  from: list
  list:
  - "us-central1-a"
  - "us-central1-b"
  - "us-central1-c"
  - "us-central1-f"
  # The same as 'pick: 1'
  pick: any

# The parameter is a list containing all elements randomly reordered!
AvailableZones:
  is: parameter
  from: list
  list:
  - "us-central1-a"
  - "us-central1-b"
  - "us-central1-c"
  - "us-central1-f"
  # The same as 'pick: 4' (e.g. the total number of list items)
  pick: all

# The parameter is a sub-list containing 2 randomly picked elements
FirstNameList:
  is: parameter
  from: list
  list: [ "Anna", "Bill", "Mark", "Sofia" ]
  pick: 2

The examples above use list elements defined in the YAML but it works the same when the list elements come from a file. Here are some examples:

FirstName:
  is: parameter
  from: list
  file: ${sim.path}/first-names.txt
  pick: 1

LastName:
  is: parameter
  from: list
  file:
    path: ${sim.path}
    name: last-names.txt
  pick: 1

As a reminder: the file with the list elements is expected to be a text file with each list element on a separate line; the end-of-line can be either a line feed (\n) or carriage return plus line feed (\r\n) characters.

Requests Counter

The value of this parameter is specific to a simlet. It represents the number of times a request matched the simlet where the parameter is defined. The counter is incremented after a request match is confirmed. Assuming no concurrent requests, the parameter would have a different value when used in request matching compared to when it is used in building a templated response (which is after the request has been matched).

To avoid confusion, use this parameter type in building a templated response and use Request Sequence Number Sampling for matching.

Here is how to define such parameter:

SimletCounter:
  is: parameter
  from: counter
  scope: simlet

The scope for now is always simlet. In the future, there could be a counter parameter with scope: simulation.

A potential use for this parameter is to use its value as a unique number when only one instance of API Simulator is running for a given API simulation.

CSV Data Sources

Another aspect of Test Data Management (TDM) in API Simulator is the capability to use parameters from CSV (comma-separate values) data sources. This powerful feature makes it very easy to model flexible API simulations that return dynamic responses from larger datasets with minimal configuration.

CSV File

CSV data can come from files or be provided in the simlet configuration. The following demonstrates the use of parameters with values that come from a CSV file:

ProductID:
  is: parameter
  from: UriPathPattern
  pattern: "/v1.1/product/{ProductID}/**"

Color:
  is: parameter
  from: UriPathPattern
  pattern: "/v1.1/product/*/{Color}/*"

Size:
  is: parameter
  from: UriPathPattern
  pattern: "/v1.1/product/*/*/{Size}"

ProductData:
  is: parameter
  from: csv

  # The first row in the CSV file is expected to contain unique column names
  # with no spaces or special characters in the names other than underscore.
  # A column value that contains a comma or double quote(s) is to be surrounded
  # by double quotes. Each double quote inside of a column value is to be escaped
  # with another double quote
  file: "${simlets.path}/product-data.csv"

  # List of keys from parameters to use in querying the CSV data.
  # A parameter as key may appear more than once in the list.
  # Parameter names must match the column names in the CSV file
  keys:
  - ProductID
  - Color
  - Size

  # pick: first | all
  # The parameter will contain the "first" matching row or "all" of them
  # for the given key(s) as a single object or a list, respectively.
  # No matches results in null.
  # For backward compatibility, it defaults to "first". Added in v1.12
  pick: first


request:
- method: GET
- uriPathPattern: "/v1.1/product/{id}/{color}/{size}"
- where: parameter
  named: ProductData
  exists: true


response:
  from: template
  template: Simula
  status: 200
  headers:
  - "Content-Type: application/json; charset=UTF-8"
  body: `
{
  "product": {
    "id": "${ProductData['ProductID']}",
    "sku": "${ProductData['SKU']}",
    "name": "${ProductData['Name']}",
    "category": "${ProductData['Category']}",
    "subCategory": "${ProductData['Subcategory']}",
    "color": "${ProductData['Color']}",
    "size": "${ProductData['Size']}",
    "orig_price": ${ProductData['Original_Price']},
    "sale_price": ${ProductData['Sale_Price']}
  }
}
`

The ProductData parameter in the example above will contain a single row (pick: first) regardless of how many rows the keys match. Let’s change the example by removing the Size key, which leads to the keys matching several rows. We’ll also set pick: all so the ProductData parameter will contain a list of those rows:

# Parameter names must match the column names in the CSV file
ProductID:
  is: parameter
  from: uriPathPattern
  pattern: "/v1.1/product/{ProductID}/**"


# Parameter names must match the column names in the CSV file
Color:
  is: parameter
  from: uriPathPattern
  pattern: "/v1.1/product/*/{Color}"


ProductData:
  is: parameter
  from: csv

  # The first row in the CSV file is expected to contain unique column names
  # with no spaces or special characters in the names other than underscore.
  # A column value that contains a comma or double quote(s) is to be surrounded
  # by double quotes. Each double quote inside of a column value is to be escaped
  # with another double quote
  file: "${sim.path}/product-data.csv"

  # List of keys from parameters to use in querying the CSV data.
  # A parameter as key may appear more than once in the list.
  # Parameter names must match the column names in the CSV file
  keys:
  - ProductID
  - Color

  # pick: first | all
  # The parameter will contain the "first" matching row or "all" of them
  # for the given key(s) as a single object or a list, respectively.
  # No matches results in null.
  # For backward compatibility, it defaults to "first". Added in v1.12
  pick: all


request:
- method: GET

- where: uriPathPattern
  matches: "/v1.1/product/{id}/{color}"

  # Matcher that tests if the product data keyed by id,
  # color, and size as they come in the request (!)
  # exists in the CSV data source
- where: parameter
  named: ProductData
  exists: true


response:
  from: template
  template: Simula
  status: 200
  headers:
  - "Content-Type: application/json; charset=UTF-8"
  body: `
{
  "products": [<%
count = 0;
for (Product : ProductData) {
  if (count++ > 0) { write "," } %>
    {
      "id": "${ Product['ProductID'] }",
      "sku": "${ Product['SKU'] }",
      "name": "${ jsonEncode( Product['Name'] ) }",
      "category": "${ Product['Category'] }",
      "subCategory": "${ Product['Subcategory'] }",
      "color": "${ Product['Color'] }",
      "size": "${ Product['Size'] }",
      "orig_price": ${ Product['Original_Price'] },
      "sale_price": ${ Product['Sale_Price'] }
    }<%
} %>
  ]
}
`

Embedded CSV Data

In addition to the ability to read in and use CSV data from a file, API Simulator supports CSV data provided in the simlet’s YAML configuration. For example:

ProductID:
  is: parameter
  from: uriPathPattern
  pattern: "/v1.2/product/{ProductID}/**"

# Parameter names must match the name of the columns in the CSV data
ProductColor:
  is: parameter
  from: uriPathPattern
  pattern: "/v1.2/product/*/{Color}/*"

# Parameter names must match the name of the columns in the CSV data
ProductSize:
  is: parameter
  from: uriPathPattern
  pattern: "/v1.2/product/*/*/{Size}"

ProductData:
  is: parameter
  from: csv

  # The first row in the CSV data is expected to contain unique column names
  # with no spaces or special characters in the names other than underscore.
  # A column value that contains a comma or double quote(s) is to be surrounded
  # by double quotes. Each double quote inside of a column value is to be escaped
  # with another double quote. This is what file export to CSV from MS Excel does
  data: `
ProductID,ProductCategory,ProductSubcategory,ProductName,ProductColor,ProductSize,ProductSKU,Original_Price,Sale_Price
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Black Charcoal,S,769409213,55.00,39.99
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Black Charcoal,M,769413463,55.00,39.99
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Black Charcoal,L,769413609,55.00,39.99
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Black Charcoal,XL,769413531,55.00,39.99
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Black Charcoal,XXL,769413579,55.00,39.99
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Solid Gray,S,769417959,55.00,35.95
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Solid Gray,M,769417966,55.00,36.96
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Solid Gray,L,769413623,55.00,37.97
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Solid Gray,XL,769413692,55.00,38.98
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Solid Gray,XXL,769413678,55.00,39.99
2592228,Men,Hoodies & Sweatshirts,Full-Zip Hooded Fleece Jacket,Dark Gray,S,769458440,65.00,52.00
2592228,Men,Hoodies & Sweatshirts,Full-Zip Hooded Fleece Jacket,Dark Gray,M,769458501,65.00,52.00
2592228,Men,Hoodies & Sweatshirts,Full-Zip Hooded Fleece Jacket,Dark Gray,L,769458327,65.00,52.00
2592228,Men,Hoodies & Sweatshirts,Full-Zip Hooded Fleece Jacket,Dark Gray,XL,769458303,65.00,52.00
2592228,Men,Hoodies & Sweatshirts,Full-Zip Hooded Fleece Jacket,Dark Gray,XXL,769458426,65.00,52.00
6218877,Children,Shoes,The "Rockets",White,4,669032116,35.59,31.99
`

  # List of keys from parameters to use in querying the CSV data.
  # A parameter as key may appear more than once in the list.
  # Parameter names must match the column names in the CSV data
  keys:
  - ProductID
  - ProductColor
  - ProductSize

  # pick: first | all
  # The parameter will contain the "first" matching row or "all" of them
  # for the given key(s) as a single object or a list, respectively.
  # No matches results in null.
  # For backward compatibility, it defaults to "first". Added in v1.12
  pick: first


request:
- method: GET

- where: uriPathPattern
  matches: "/v1.2/product/{id}/{color}/{size}"

- where: parameter
  named: ProductData
  exists: true


response:
  from: template
  template: Simula
  status: 200
  headers:
  - "Content-Type: application/json; charset=UTF-8"
  body: `
{
  "product": {
    "id": "${ ProductData['ProductID'] }",
    "sku": "${ ProductData['ProductSKU'] }",
    "name": "${  jsonEncode( ProductData['ProductName'] ) }",
    "category": "${ ProductData['ProductCategory'] }",
    "subCategory": "${ ProductData['ProductSubcategory'] }",
    "color": "${ ProductData['ProductColor'] }",
    "size": "${ ProductData['ProductSize'] }",
    "orig_price": ${ ProductData['Original_Price'] },
    "sale_price": ${ ProductData['Sale_Price'] }
  }
}
`

The pick: first and pick: all settings work the same as when the CSV data comes from an external file.

Download API Simulator and check out the examples.

There is also working code that uses Excel as parameters source but it requires Apache POI (poi-ooxml in particular) which isn’t currently included in the API Simulator distribution. Please give us a shout if for some reason exporting Excel files to CSV is not an option for your use cases.

SQL Data Store

API Simulator offers the capability to use parameters retrieved from a Relational Database Management System (RDBMS) by means of SELECT SQL statements. Again, this powerful feature allows one to easily model flexible API simulations that return dynamic responses from very large datasets with minimal configuration.

The SQL statements can be parameterized with ? to denote placeholders for query arguments. The argument values themselves come from standalone parameters defined in the simlet.

Below is a complete configuration example - found also in the Standalone API Simulator distro - that demonstrates the use of an external H2 database as the source for parameters in rendering dynamic responses:

ProductID:
  is: parameter
  from: UriPathPattern
  pattern: "/v1.1/product/{productID}/**"

ProductColor:
  is: parameter
  from: UriPathPattern
  pattern: "/v1.1/product/*/{color}/*"

ProductSize:
  is: parameter
  from: UriPathPattern
  pattern: "/v1.1/product/*/*/{size}"

ProductData:
  is: parameter
  from: sql

  connection:
    # Currently, pooled connections are not supported
    uses: driver

    # Driver's fully-qualified class name
    driver: "org.h2.Driver"

    # Using "/./" seems to be required for H2
    url: "jdbc:h2:${simlets.path}/./product-data"

    # Credentials must be provided if the database is password-protected
    username:
    password:

    # Optional other driver-specific name/value properties
    props:
      propName: propValue

  # Parameterized SQL query
  query: `
SELECT p.*, c.*
FROM PRODUCT p, PRICE c
WHERE p.SKU = c.SKU
  AND p.ProductID=?
  AND p.Color=?
  AND p.Size=?
`

  # pick: first | all
  # The parameter will contain the "first" matching row or "all" of them
  # as a single object or a list, respectively. No matches results in null.
  # For backward compatibility, it defaults to "first". Added in v1.12
  pick: first

  # Ordered list of parameters to use in querying the SQL database.
  # A parameter may appear more than once in the list.
  # The parameter names don't have to match the database column names
  parms:
  - ProductID
  - ProductColor
  - ProductSize


request:
- method: GET
- uriPathPattern: "/v1.1/product/{id}/{color}/{size}"
- where: parameter
  named: ProductData
  exists: true


response:
  from: template
  template: Simula
  status: 200
  headers:
  - "Content-Type: application/json; charset=UTF-8"
  body: `
{
  "product": {
    "id": "${ProductData['ProductID']}",
    "sku": "${ProductData['SKU']}",
    "name": "${ProductData['Name']}",
    "category": "${ProductData['Category']}",
    "subCategory": "${ProductData['Subcategory']}",
    "color": "${ProductData['Color']}",
    "size": "${ProductData['Size']}",
    "orig_price": ${ProductData['Original_Price']},
    "sale_price": ${ProductData['Sale_Price']}
  }
}
`

The query in the example above matches and returns a single row for the given dataset. Let’s change the example by removing the condition on product’s size from the WHERE clause, which will then match several rows. We’ll also set pick: all so the parameter will contain a list of those rows:

ProductID:
  is: parameter
  from: uriPathPattern
  pattern: "/v1.1/product/{productID}/*"

ProductColor:
  is: parameter
  from: uriPathPattern
  pattern: "/v1.1/product/*/{color}"


ProductData:
  is: parameter
  from: sql

  connection:
    # Currently, pooled connections are not supported
    uses: driver # pool

    # Driver's fully-qualified class name
    driver: "org.h2.Driver"

    # Using "/./" seems to be required for H2
    url: "jdbc:h2:${simlets.path}/./product-data"

    # Credentials must be provided if the database is password-protected
    username:
    password:

    # Optional other driver-specific name/value properties
    props:
      propName: propValue

  # Parameterized SQL query
  query: `
SELECT p.*, c.*
FROM PRODUCT p, PRICE c
WHERE p.SKU = c.SKU
  AND p.ProductID=?
  AND p.Color=?
ORDER BY p.SKU
`

  # pick: first | all
  # The parameter will contain the "first" matching row or "all" of them
  # as a single object or a list, respectively. No matches results in null.
  # For backward compatibility, it defaults to "first". Added in v1.12
  pick: all

  # Ordered list of parameters to use in querying the SQL database.
  # A parameter may appear more than once in the list.
  # The parameter names need not to match the database column names
  parms:
  - ProductID
  - ProductColor


request:
- method: GET

- where: uriPathPattern
  matches: "/v1.1/product/{id}/{color}"

- where: parameter
  named: ProductData
  exists: true


response:
  from: template
  template: Simula
  status: 200
  headers:
  - "Content-Type: application/json; charset=UTF-8"
  body: `
{
  "products": [<%
count = 0;
for (Product : ProductData) {
  if (count++ > 0) { write "," } %>
    {
      "id": "${ Product['ProductID'] }",
      "sku": "${ Product['SKU'] }",
      "name": "${ jsonEncode( Product['Name'] ) }",
      "category": "${ Product['Category'] }",
      "subCategory": "${ Product['Subcategory'] }",
      "color": "${ Product['Color'] }",
      "size": "${ Product['Size'] }",
      "orig_price": ${ Product['Original_Price'] },
      "sale_price": ${ Product['Sale_Price'] }
    }<%
} %>
  ]
}
`

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

Happy API Simulating!