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.
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.
Template Functions
Template Functions offer great flexibility in 'shaping' the placeholder values before they are rendered as part of a template.
The following applies to all template 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. For example, to format the placeholder value and then to apply JSON encoding to the formatted value:
NumberValueParm:
is: parameter
from: constant
value: 456
as: int32
response:
from: template
template: Simula
# ...
body: |+
{
"result": "${ jsonEncode( format(NumberValueParm, '"%06d"') ) }"
}
…will output in the response body this properly encoded JSON string:
{
"result": "\"000456\""
}
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') }"
}
Supported Functions
More functions will be added over time. These are the currently supported functions:
String trim(s)
Trims leading and trailing spaces from the input string s
.
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)
Formats the input value
as a date/time string using the given pattern
. The input value is expected to be a Datetime
which internally is a long number representing the number of milliseconds since epoch. See Datetime Formatting for pattern details.
String formatDateTime(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 Datetime
which internally is a long number representing the number of milliseconds since epoch. See Datetime Formatting for pattern details.
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)
String xmlEncode(s)
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)
Datetime Formatting
Datetime values use special formatting that allows for more options and flexibility - one can define time zone and locale-specific setting in addition to pattern.
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, albeit distilled and simplified)
Complex Values
Placeholder values may be of a scalar data type - string, boolean, integer, floating point number, 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 Query String Parameter 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!