Simula Template Placeholders
Overview
One type of fragments in a Simula template that can produce variable content is the placeholders.
A placeholder gets resolved to an actual value as part of rendering the template. |
By default, ${
and }
denote the beginning and end of a placeholder.
The following example is for a simlet that simulates the HTTP response to a request to create new product; product data like Product ID, name, etc. are in the body of the request:
request:
- method: PUT
- where: uriPath
equals: "/v3/products"
- header: Content-Type
equals: "application/json"
- where: body
element: ".product.id"
exists: true
# This defines a single parameter named 'ProductID'
# with value from the HTTP request body and extracted
# from the element at the given JSON path.
ProductID:
is: parameter
from: body
element: ".product.id"
# The HTTP response is rendered from a Simula template
response:
from: template
template: Simula
# The HTTP response status will be '201 Created'
status: 201
# A 'Location' header will be returned denoting the endpoint
# to use to get details for the newly created product
headers:
- "Location: /v3/products/${ProductID}/details"
In the example above, ${ProductID}
is a placeholder for the actual Product ID. Its value will be resolved upon rendering the template by trying to look it up in the list of placeholders (see below) first and, if not found there, to find it in the list of parameters next.
In this way, a single simlet simulates the creation of different products without having to "know" all possible Product IDs in advance. The simlet extracts the Product ID from the request and puts it into a parameter that a placeholder then uses to produce dynamic output.
The ${ProductID}
placeholder doesn’t have any formatting - the template engine will replace ${ProductID}
with the "raw" parameter value.
It is possible to apply formatting to a placeholder’s value. Moreover, API Simulator supports encoding of placeholder values so that special characters in them won’t break the output. JSON, XML, HTML, URL, Base64, and Base64Url encoders are currently supported.
API Simulator doesn’t automatically escape all placeholder values. That makes it possible to inject invalid content in a given context (e.g. JSON body) and test how API clients deal with it.
API Simulator will error out if it can’t find the parameter for a placeholder, log an "Unresolvable token=<name-here>" error, and return 500 Internal Server Error
response.
Template Placeholders can also contain simple expressions. In the unlikely case that a ${ }
placeholder expression is to contain {
or }
but not both - {
with corresponding }
- use a scriptlet instead.
It is to note that the placeholders
list field has been deprecated and it is not required anymore to add placeholders to that list in order to format the value and/or to apply special encoding. The support for the placeholders
field will be removed in a future release. This doesn’t concern you if you’ve never heard of the placeholders
field 😉
Template Functions
Template Functions offer great flexibility. They supplement the declarative style parameters and provide an easy way to format values, extract values from JSON using JSONPath or from XML with XPath, generate random values, and more.
To use template functions, import the respective libraries first:
simlet: make-a-payment
import:
random: _random
json: _json
...
...
For the random
functions, this reads as "…import the library of random functions and reference them using the '_random' handle".
A few things to take into account:
-
The
import
can be anywhere in the configuration but for readability it is recommended to be at the top, before the actual usage of the functions. -
The handle name must start with an underscore
_
followed by a lower case letter, and then optionally with more letters or digits for up to 32 characters total. -
The scope of template functions libraries is within the simlet where they are imported. This is important to keep in mind for simlets defined in a single file.
-
It is not allowed to have duplicate handle names in the same simlet configuration.
To use the template functions from the imported libraries, reference them by their handle. For example:
simlet: make-a-payment
import:
random: _random
request:
- ...
response:
...
body: `
{
"token": ${ _random.token('[A-Z][0-9A-Z]{12}') }
}`
See below for how to import and use each of the built-in template functions. Notice that using Scripting is always an option, too.
Global Functions
The following applies to all global template functions:
-
There is no need to import the global functions.
-
The function’s first argument is the value on which to apply the function.
-
Unless explicitly specified, all functions treat input value of
null
on which to apply a function as an empty string. -
Functions can be nested. That is - the argument for a function call can be a function call, too.
When the HTTP response body is in JSON format and one wants to use a template function that expects a string argument but the value of the argument itself contains single quotes, then escape the single quotes inside the argument’s value (\'
). For example:
{
"create_time": "${ formatDateTime(CreateTimeParm, 'yyyy-MM-dd\'T\'HH:mm:ss.SSS\'Z\'', 'GMT') }"
}
These are the currently supported global functions:
String trim(s)
Trims leading and trailing spaces from the input string s
. Safely handles a null
value.
String format(value, pattern)
Formats the input value
using the given pattern
. Uses the default locale for the JVM in which APU Simulator is running. See General Formatting for pattern details
String format(value, pattern, language)
Formats the input value
using the given pattern
and language for locale-specific formatting. See General Formatting for pattern details.
String format(value, pattern, language, country)
Formats the input value
using the given pattern
, and language and country for locale-specific formatting. See General Formatting for pattern details.
String formatDateTime(value, pattern)
Deprecated. Please use the corresponding format
function from Date/Time Functions.
String formatDateTime(value, pattern, timeZone)
Deprecated. Please use the corresponding format
function from Date/Time Functions.
String httpDateTime(value)
Formats the input value
as a date/time string per the HTTP specification using E, dd MMM yyyy HH:mm:ss z
pattern and GMT
time zone. The input value is expected to be a Datetime
which internally is a long number representing the number of milliseconds since epoch.
String jsonEncode(s)
Deprecated. Please use the corresponding encode
function from JSON Data Functions.
String xmlEncode(s)
Deprecated. Please use the corresponding encode
function from XML Data Functions.
String htmlEncode(s)
String urlEncode(s)
String base64Encode(s)
String base64Encode(s, charset)
String base64UrlEncode(s)
Defaults to UTF-8
charset.
String base64UrlEncode(s, charset)
General Formatting
The format of specifiers for types other than datetime has the following syntax:
%[flags][width][.precision]type
Type
The type is required. The following table describes the expected type codes and their meaning:
Type Code | Type | Description |
---|---|---|
|
boolean |
If the value is |
|
string |
If the value is |
|
character |
The result is a Unicode character. |
|
integer |
The result is formatted as a decimal integer. |
|
integer |
The result is formatted as an octal integer. |
|
integer |
The result is formatted as a hexadecimal integer. |
|
floating-point |
The result is formatted as a decimal number in computerized scientific notation. |
|
floating-point |
The result is formatted as a decimal number. |
|
floating-point |
The value is formatted using computerized scientific notation or decimal format, depending on the precision and the value after rounding. |
Flags
The optional flags is a set of characters that modify the output format. The set of valid flags depends on the type:
-
-
: For all, the result will be left-justified. -
+
: For integer and floating-point types, the result will always include a sign. -
' '
(space) : For integer and floating-point types, the result will include a leading space for positive values. -
0
: For integer and floating-point types, the result will be zero-padded. -
,
: For integer and floating-point types, the result will include locale-specific grouping separators. -
(
: For integer and floating-point types, The result will enclose negative numbers in parentheses.
Width
The optional width is a non-negative integer indicating the minimum number of characters to be written to the output for the placeholder value.
Precision
The optional precision is a non-negative integer usually used to restrict the number of characters:
-
For values of string type, the precision is the maximum number of characters to be written to the output.
-
For the floating-point conversions
e
,E
, andf
the precision is the number of digits after the decimal separator. If the conversion isg
orG
, then the precision is the total number of digits in the resulting magnitude after rounding. -
The precision is not applicable for character and integer types.
(NOTE: The formatting rules and pattern documentation is largely based on the online Java™ Platform, Standard Edition 7 API Specification, albeit distilled and simplified)
Random Data Functions
There are a few functions at your disposal to help with generating random numbers, date/time values, UUIDs, string tokens…
First, import the random data functions library:
import:
random: _random
Then, call the functions using the _random
handle (aka library reference).
Long dateTimeInRange(String fromDate, String toDate, String format)
Randomly generates a datetime value (number of milliseconds since epoch - 00:00:00 UTC on 1 January 1970) between fromDate
and toDate
; the format
argument applies to both fromDate
and toDate
and follows the rules outlined in Datetime Patterns.
Long dateTimeFromNow(long min, long max, String timeUnit)
Randomly generates a new datetime value (number of milliseconds since epoch - 00:00:00 UTC on 1 January 1970) that is in the range of ["now" + min, "now" + max], inclusive. Negative values for 'min' or max' will be subtracted from "now". The timeUnit
argument is expected to be one of the following case-insensitive values: nanoseconds, microseconds, milliseconds, seconds, minutes, hours, and days. The same timeUnit
applies to both min
and max
.
Double decimalInRange(long min, long max, int scale)
Randomly generates a decimal number which whole part is between min
and max
, inclusive, and having the given scale
(the number of digits to the right of the decimal point). For instance,
${ _random.decimalInRange(1, 100, 2) }
…will generate a decimal number in the range of 1.00
and 100.00
. Some numbers that could be generated are 99.99
, 4.6
, 57.08
, but not 100.01
or 0.99
. Use the global format
function to have the generated number be padded with zeros:
${ format(_random.decimalInRange(1, 100, 2), '%.2f') }
The above will produce 4.60
when the generated number is 4.6
.
Long numberInRange(long min, long max)
Randomly generates a whole number between min
and max
, inclusive.
String token(String pattern)
Generates a token (sequence of symbols) according to the given pattern
. See Random Token for information about the pattern.
String uuid()
Generates a UUID (Universally Unique Identifier), aka GUID (Globally Unique Identifier), that is guaranteed to be unique across time and space. Here is an example for a UUID: 707e6337-4298-46b8-8fe7-f7ce396d404
.
JSON Data Functions
There are several functions to help you work with JSON data. Import the json
library and reference the functions via the _json
handle:
import:
json: _json
String encode(String s)
Returns a JSON-encoded value that is safe to be included in a JSON string. It will return an empty string if the input argument is null
.
Object evalJsonPath(String jsonText, String jsonPath)
Evaluates jsonPath
on the jsonText
JSON and returns the value at that path or null
if there isn’t a match for the JSONPath. Here is an example:
"id": ${ _json.evalJsonPath(_request.bodyText, '.product.id') }
Object evalJsonPath(Object document, String jsonPath)
The document
argument is expected to be a value from calling parse(String jsonText)
.
Object parse(String jsonText)
If there will be multiple JSONPath evaluations on the same text, then parse it first to a document and then run the JSONPath evaluations on the parsed document. For example:
...
response:
...
body: `
<% jsonDoc = _json.parse(_request.bodyText) %>
{
"id" : ${ _json.evalJsonPath(jsonDoc, '.product.id') }
"name": ${ _json.evalJsonPath(jsonDoc, '.product.name') }
}
`
XML Data Functions
Here are functions for manipulating and working with XML. Import the xml
library and call the functions via the _xml
handle like in this example:
import:
xml: _xml
...
${ _xml.evalXPath('<product><id>56789</id><name>The Rocket Shoes</name></product>', '/product/id') }
String encode(String s)
Returns an XML-encoded value that is safe to be included in an XML string. It will return an empty string if the input argument is null
.
Object evalXPath(String xmlText, String xpath)
Evaluates the xpath
on the xmlText
and returns the value at the XPath or null
if the XPath didn’t match an element in the XML.
Object evalXPath(String xmlText, String xpath, Map<String, String> namespaces)
Evaluates xpath
on the xmlText
for the given namespace
context and returns the value at the XPath or null
if the XPath didn’t match an element in the XML. For example:
${ _xml.evalXPath(_request.bodyText, 's:Envelope/s:Body/ws:ValidateAddress/ValidateAddressInput/Address/Line1', [s: 'http://schemas.xmlsoap.org/soap/envelope/', ws: 'http://ws.v1_0.avs.example.com/']) }
The above has to be on a single line when using it in a ${ }
placeholder. To break it into multiple lines for better readability, use it in a scriptlet, or a combination of scriptlet and placeholder.
Object evalXPath(Object document, String xpath)
Evaluates xpath
on the XML document
and returns the value at the XPath or null
if the XPath didn’t match an element in the XML. The document
should have been returned as result of calling the parse
method (see below).
Object evalXPath(Object document, String xpath, Map<String, String> namespaces)
Evaluates xpath
on the XML document
for the given namespace
context and returns the value at the XPath or null
if the XPath didn’t match an element in the XML. The document
should have been returned as result of calling the parse
method (see below).
Object parse(String xmlText)
If there will be multiple XPath evaluations on the same text, then call this method to parse the text first to a document and then run the XPath evaluations on the parsed document.
Date/Time Functions
Import the dateTime
library of Date/Time helper functions and call them with the _dateTime
handle. For example:
import:
dateTime: _dateTime
...
{
"updatedOn": "${ _dateTime.format(_dateTime.now(), 'yyyy-MM-dd\'T\'HH:mm:ss.SSS\'Z\'', 'GMT') }"
"createdOn": "${ _dateTime.format(_random.dateTimeFromNow(-60, -30, 'days'), 'yyyy-MM-dd\'T\'HH:mm:ss.SSS\'Z\'', 'GMT') }"
}
Long now()
Current date and time as the number of milliseconds since epoch (00:00:00 UTC on 1 January 1970).
String format(value, pattern)
Formats the input value
as a date/time string using the given pattern
. The input value is expected to be a long number representing the number of milliseconds since epoch. See Datetime Patterns for pattern details.
String format(value, pattern, timeZone)
Formats the input value
as a date/time string using the given pattern
and timeZone
identifier. The input value is expected to be a long number representing the number of milliseconds since epoch. See Datetime Patterns for pattern details.
Long parse(value, pattern)
Returns the number of milliseconds since epoch representation of the input String value parsed based on the pattern
, or null
if the input value couldn’t be parsed.
Datetime Patterns
A pattern determines how a date/time value is formatted to a String or parsed from a String.
Within a pattern string, unquoted letters from A
to Z
and from a
to z
are interpreted as pattern letters representing the components of a datetime. To avoid interpretation, text can be quoted using single quotes '
. Two single quotes ''
represents a single quote. All other characters are not interpreted - they are simply copied into the output.
The following pattern letters are defined:
Letter | Date or Time Component | Presentation | Examples |
---|---|---|---|
|
Era designator |
Text |
|
|
Year |
Year |
|
|
Week year |
Year |
|
|
Month in year |
Month |
|
|
Week in year |
Number |
|
|
Week in month |
Number |
|
|
Day in year |
Number |
|
|
Day in month |
Number |
|
|
Day of week in month |
Number |
|
|
Day name in week |
Text |
|
|
Day number of week (1 = Monday, …, 7 = Sunday) |
Number |
|
|
AM/PM marker |
Text |
|
|
Hour in day (0-23) |
Number |
|
|
Hour in day (1-24) |
Number |
|
|
Hour in am/pm (0-11) |
Number |
|
|
Hour in am/pm (1-12) |
Number |
|
|
Minute in hour |
Number |
|
|
Second in minute |
Number |
|
|
Millisecond |
Number |
|
|
Time zone |
General time zone |
|
|
Time zone |
RFC 822 time zone |
|
|
Time zone |
ISO 8601 time zone |
|
When pattern letters are repeated, their count determines the exact presentation as follows:
-
Text: For formatting, if the number of pattern letters is 4 or more, the full form is used; otherwise, a short or abbreviated form is used if available. For parsing, both forms are accepted, independent of the number of pattern letters.
-
Number: The number of pattern letters is the minimum number of digits, and shorter numbers are zero-padded to this amount.
-
Year: If the number of pattern letters is 2, the year is truncated to 2 digits; otherwise, it is interpreted as a number.
-
Month: If the number of pattern letters is 3 or more, the month is interpreted as text; otherwise, it is interpreted as a number.
-
General time zone: Time zones are interpreted as text if they have names. For time zones representing a GMT offset value, the following syntax is used:
-
GMT Sign Hours : Minutes
-
Sign: + | -
-
Hours: Digit | Digit Digit
Hours must be between 0 and 23
-
Minutes: Digit | Digit
Minutes must be between 00 and 59.
-
Digit: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
-
-
RFC 822 time zone: It uses 4-digit time zone.
-
ISO 8601 Time zone
(NOTE: The formatting rules and pattern documentation is largely based on the online Java™ Platform, Standard Edition 7 API Specification, distilled and simplified)
Complex Values
Placeholder values may be of a scalar data type - string, boolean, integer, floating point number (decimal), or datetime - or they can have complex data structure. Placeholders in a Simula template can use expressions to access properties and elements of such complex values. For example, to access a list element by its index/position in the list or to access a map element by key (map is a data structure with key/value pairs for elements and where a value - scalar or complex - is identified by its key).
This can be very useful when used on its own or when used in scriptlets.
The following examples demonstrates working with URI Query String Parameters. A URI Query String Parameters parameter is like a map with elements having Query String Parameter names as keys and values are lists of zero or more values of type string:
# An example that shows how to work with placeholder values of a complex data type
request:
- where: method
equals: GET
- where: uriPath
equals: "/api/places/nearby"
- where: uriQueryParameter
named: "postalCode"
equals: "12345"
- where: uriQueryParameter
named: "radius"
equals: "10"
# Supposedly, an indicator that the requests are for debugging
- where: uriQueryParameter
named: "debug"
exists: true
# At least one query string parameter with name 'placeType' and value 'restaurant'
- where: uriQueryParameter
named: "placeType"
equals: "restaurant"
PlaceTypeParm:
is: parameter
# There can be more than one query string parameter with the same name.
# This parameter will have them all in a list with items accessible by index.
from: uriQueryParameter
named: "placeType"
QueryStringParms:
is: parameter
from: uriQueryParameters
response:
from: template
template: Simula
status: 200
headers:
- "Content-Type: application/yaml"
# Use single and not double quotes for map keys!
# Also, notice the mix of single and double quotes
body: `
query:
queryStringParms:
total: ${ QueryStringParms.count() }
postalCode-count: ${ QueryStringParms.count('postalCode') }
placeTypes-count: ${ QueryStringParms.count('placeType') }
placeTypes-first: '${ QueryStringParms.get('placeType', 0) }'
placeTypes-last: "${ QueryStringParms.get('placeType', QueryStringParms.count('placeType') - 1) }"
placeTypeParm:
count: ${ PlaceTypeParm.count() }
first: "${ PlaceTypeParm.get(0) }"
last: '${ PlaceTypeParm[PlaceTypeParm.count() - 1] }'
`
Assuming this GET request:
GET /api/places/nearby?postalCode=12345&radius=10&placeType=restaurant&placeType=cafe&debug
…here is how the response body built with the template will look like:
query:
queryStringParms:
total: 4
postalCode-count: 1
placeTypes-count: 2
placeTypes-first: 'restaurant'
placeTypes-last: "cafe"
placeTypeParm:
count: 2
first: "restaurant"
last: 'cafe'
The example demonstrated calling methods inside placeholders like count()
and get(name, index)
, accessing an element in a list by index - .get(index)
or [index]
…and even an embedded calculation - computing the last index (when we know that there’s at least one element).
See also Request Object Reference for all the methods valid for URI Query String Parameters or an individual URI Query String Parameter.
Defaulting a Value
When a variable or parameter is not defined or is null
, this is a way to specify a default value for it:
${ parm ?: defautValue }
Placeholders and Scriptlets
While it is possible to have ${ … }
placeholders inside <% … %>
scriptlets, it is an error to place <% … %>
scriptlets inside ${ … }
placeholders.
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!