Simlet Parameters
Overview
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
to32,767
, inclusive. -
int32 - 32-bit signed integer number in the range from
-231
(-2,147,483,648
) to231 - 1
(+2,147,483,647
), inclusive. -
int64 - 64-bit signed integer number in the range from
-263
(-9,223,372,036,854,775,808
) to263 - 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:
-
Get the value from the respective source;
-
Convert that value to the type given in
as
, if specified; -
If the value is
null
then it assumes what’s indefault
, if configured. -
Apply additional evaluation to the value by executing the scripting code in
eval
, if present.
Whatever isreturn
-ed fromeval
becomes value for the parameter even if it has nothing to do with the value from the source - the script ineval
has the freedom to return anything and of any type. -
If
isSnapshot
istrue
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 theeval
expression. -
The
ProductID
parameter becomes the first element from theCsvParms
array, if there is such element, ornull
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.
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.
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 with `return` statement
eval: .... return <full-file-specification>
Two global variables are handy when configuring the location of the file: ${simlets.path}
and ${sim.path}
. They resolve to the simlets directory and the simulation directory path, respectively. 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
and _sim.path
in eval
expressions and scripts.
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.
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 leastn
times but not more thanm
times, where0
< =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 theP
,R
, andE
capital letters, and thea
ands
small letters. -
[a-c]
- any small letter betweena
andc
, that’s it -a
,b
, orc
. -
[A-Z]
- any capital letter betweenA
andZ
. -
[0-9]
- any digit between0
and9
. -
PAY-[0-9A-Z]{25}
- the textPAY-
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 betweena
andz
, 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.
CSV File
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 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
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']}
}
}
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:
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. 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
2592228,Men,Hoodies & Sweatshirts,Full-Zip Hooded Fleece Jacket,Dark Gray,XXL,769458426,65.00,52.00
# 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
#...
Download and check out the examples in the Standalone API Simulator distro.
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=?
# 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']}
}
}
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!