Simula Template Scriptlets

Overview

definition

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
%>

The delimiters - <% and %> - can be configured to be something different.

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. 400Bad 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! Shoot us a quick email to [feedback at APISimulator.com] to let us know what you think.

Happy API Simulating!