Simula Template Scriptlets
Overview
Scriptlets are template fragments that can produce variable content. They are practically code. |
The Simula template engine executes the scriptlets code upon rendering the template. The code may produce output or drive its production by using, for example, control statements (loops, if…else, etc.).
Let’s not get into a debate around logic-less templates vs. templates that may contain explicit logic. The Simula templating engine does support scriptlets and you may as well take advantage of this powerful feature, if you need it.
Syntax
Currently, scriptlet’s code is code in the Groovy scripting language. Don’t worry if learning a new scripting language isn’t on your to-do list - a little experience in any other programming language will be more than enough to make you feel comfortable writing statements in Groovy to simulate parts of API responses. And again - only if you need to.
The syntax for a scriptlet is
<%
Groovy-language-scripting-statements
%>
It is possible to use variables in scriptlets to control loops, save off values, etc. While this is not enforced, do not use variables with names that start with an underscore _
because they can clash with internally used variables now or in the future.
It is to note that it isn’t possible to have nested scriptlets, that’s it - a scriptlet inside another scriptlet, or a scriptlet inside a ${ … }
placeholder.
Scriptlets in the Body
Let’s look at the following simlet example to demonstrate some of the aspects of using scriptlets. The comments in the scriptlet code provide additional information (even for things that may be obvious):
request:
- where: method
equals: GET
- where: uriPath
equals: "/api/places/nearby"
- where: uriQueryParameter
named: "postalCode"
equals: "54321"
- where: uriQueryParameter
named: "radius"
equals: "10"
# At least one query string parameter with name 'placeType' and value 'restaurant'
- where: uriQueryParameter
named: "placeType"
equals: "restaurant"
QueryStringParms:
is: parameter
from: uriQueryParameters
OriginHeaderParm:
is: parameter
from: header
named: "Origin"
response:
from: template
template: Simula
# Optional field for the template scriptlets language.
# It currently must be 'groovy', if field is specified
lang: groovy
status: 200
headers:
- "Content-Type: application/json; charset=UTF-8"
# Configuration for optional header field - no name or value are
# to be output unless some condition is met. Requirements:
# > The header field value must end with "\r\n" line break;
# > Set 'optional: true':
- header: "<% if (${OriginHeaderParm} != null && ${OriginHeaderParm.trim().length()} > 0) { %>Allow-Origin: ${OriginHeaderParm}\r\n<% } %>"
# "optional" is needed because at the time the template is constructed, it is
# unknown if the header field name and value will be empty or not when resolved.
# (The template placeholder resolving process doesn't know what is what). So,
# can't add "\r\n" line break automatically when needed but it is not present.
optional: true
body: `
{
"queryStringParms": {
"total": ${ QueryStringParms.count() },
"postalCode": "${ QueryStringParms.first('postalCode') }",
"placeType": [
<%
// Single-line comment in the scriptlet
/*
Multi-line
comment
*/
// Defining a local variable. It is visible in all scriptlets of this template
// Here, ${...} surrounds QueryStringParms to denote it is a placeholder
placeTypeParms = ${ QueryStringParms.get('placeType') }
// Loop over the collection of 'placeType' query string parameter values
for (i = 0; i < placeTypeParms.count(); i++)
{
// Adding semicolon ';' at the end is required only
// when using multiple statements on the same line
placeTypeValue = placeTypeParms[i];
if (i > 0)
{
// Mixing scriptlet code with direct text output
// Line break followed by spaces to achieve pretty printing
%>,\n <%
}
// Writing out the result of string concatenation.
// Parenthesis () are optional
write ('"' + placeTypeValue + '"')
}
%>
],
<%
/*
This is a scriptlet with a multi-line comment only and no other code.
It actually causes one empty line in the output because of
where its left and right delimiters appear with respect to
the text surrounding it.
The scriptlet below to produce value for "isDebug1"
doesn't use ${...} to denote it is a placeholder.
*/
%>
"isDebug1": <% if (QueryStringParms.contain('debug')) { %>true<% } else { %>false<% } %>,
"isDebug2": <%
// Ternary operator. Also, this shows coercing
// of non-null object references to true
write (QueryStringParms.get('debug') ? true : false)
%>
}
}
`
The following HTTP request
GET /api/places/nearby?postalCode=54321&radius=10&placeType=restaurant&placeType=cafe&debug
will be matched by the matchers defined in the simlet above. This is then the body of the HTTP response produced after rendering the template:
{
"queryStringParms": {
"total": 4,
"postalCode": "54321",
"placeType": [
"restaurant",
"cafe"
],
"isDebug1": true,
"isDebug2": true
}
}
Scriptlets in Header Fields
Scriptlets can be used not only in the HTTP response body definition but also in header field configuration.
The following example demonstrated the configuration of a conditional header field - header field that is output only when some condition is met:
#...
OriginHeaderParm:
is: parameter
from: header
named: "Origin"
response:
from: template
template: Simula
status: 200
headers:
- "Content-Type: application/json; charset=UTF-8"
# Configuration for optional header field - no name or value are
# to be output unless some condition is met. Requirements:
# > The header field value must end with "\r\n" line break;
# > Set 'optional: true':
- header: "<% if (OriginHeaderParm != null && OriginHeaderParm.trim().length() > 0)
{ %>Access-Control-Allow-Origin: ${OriginHeaderParm}\r\n<% } %>"
# "optional" is needed because at the time the template is constructed, it is
# unknown if the header field name and value will be empty or not when resolved.
# (The template placeholder resolving process doesn't know what is what). So,
# can't add "\r\n" line break automatically when needed but it is not present.
optional: true
#...
Dynamic Status Code via a Scriptlet
It is also possible to use a scriptlet or placeholder to dynamically determine the HTTP status code for a response. The scriptlet or placeholder is expected to output a single positive integer value for status code; that integer value is then mapped to status reason (e.g. 400
→ Bad Request
). If the status code isn’t a single positive integer value, API Simulator will return status code 500
and a custom status reason value with a description of the problem.
For proper parsing, make sure to surround the placeholder or scriptlet with single or double quotes when it contains :
.
Here is an example:
#...
response:
from: template
template: Simula
# Only one status code should be configured per response.
# Treat each status field below as a separate example.
# '200 OK'
status: '200'
# '200 OK' or '400 Bad Request'
status: '${ SomeParm ? 200 : 400 }'
# '200 OK' or '400 Bad Request'
status: <% if(SomeParm) { write '200' } else { %>400<% } %>
# '500 StatusCodeExpressionYieldedEmptyText' or '400 Bad Request'
status: <% if(SomeParm) { } else { %>400<% } %>
# '500 InvalidStatusCodeFromTemplate=200A' or '400 Bad Request'
status: <% if(SomeParm) { %>200A<% } else { %>400<% } %>
# '500 InvalidStatusCodeFromTemplate=200 What?' or '400 Bad Request'
status: <% if(CuisineParm) { %>200 What?<% } else { %>400<% } %>
# '500 InvalidStatusCodeFromTemplate=What?' or '400 Bad Request'
status: <% if(SomeRequiredParm) { %>What?<% } else { %>400<% } %>
# An invalid status code, as long it is an integer number,
# will be accepted and no status reason will be output
# '999' or '400 Bad Request'
status: "${ SomeParm ? 999 : 400 }"
The form of HTTP status response configuration with two fields - code
and reason
- requires the code
to be a positive integer number and doesn’t accept a scriptlet or placeholder:
#...
response:
from: template
template: Simula
# This is OK
status:
code: 678
reason: My Custom Status
# This will fail to load
status:
code: "${ SomeParm ? 678 : 401 }"
reason: ...
There is a lot more that scriptlets can do. They give you extreme power and flexibility in modeling API simulation responses.
We would love to hear your feedback - send us a quick email to [feedback at APISimulator.com]
Happy API Simulating!