Pardon Templates
Pardon’s template engine is the foundation for how Pardon understands and builds requests.
For the following discussion, basic understanding of programming terms is assumed.
Templates
Section titled “Templates”Templates are a novel way of building JSON data, or really any data that can be structures as dictionaries and lists (or objects and arrays). Unlike most schema implementations, Pardon’s templates are designed to look like the data they represent as much as possible.
Templates are composed of nodes that represent values, variables, structures (such as objects or arrays). There are also nodes that transform data, operating on an interior representation with an external encoding.
Nodes support two operations: merge and render. Merging is used to match and extend requests, as well as to handle responses. Rendering a template is (obviously) necessary for building requests but can also be useful to process responses (e.g., hide secrets, etc).
Template interpolations
Section titled “Template interpolations”Some parts of templates represent and break down “atomic” values like numbers, strings, booleans, or null
, while
others are suitable for representing structures like objects and arrays.
There is both a string interpolation and a javascript syntax representation for templating. The string interpolation form is necessary for the request origin, pathname, query params and headers, while the syntax version is more suitable for processing the JSON request body.
Interpolations are structured with the following pieces, all of which are optional.
{{ ? name = expr % /regex/ }}
The name identifies this interpolation: if a value is matched,… or a value is passed,… or a value is evaluated somewhere else.
Names can be hyphenated (but cannot start with a hyphen.)
{{ ? name = expr % /regex/ }}
The hint adds some flavor to how this value is processed, a ?
allows this value to be omitted,
a @
marks it as a secret, ...
modifies the default regex in some contexts (like in the pathname,
to allow a value to match multiple path segments), while -
removes a value from rendering entirely.
{{ ? name = expr % /regex/ }}
The expression provides a default value if one is not available through matching and merging. Expressions can be arbitrary asynchronous javascript expressions.
{{ ? name = expr % /regex/ }}
The regex can be used to specify exactly what values qualify for this field.
Interpolations can be used to match, capture, reuse, and redact specific data in an http exchange.
Template References
Section titled “Template References”In JSON bodies, references might be preferable, as they are shorter to write. Any raw identifier
in the JSON is treated as a reference! For example, the POST task api in the introduction
can be represented a little more ergonomically with references, especially the task
which appears
as a shorthand property:
POST https://todo.example.com/todosAuthorization: User {{@token}}Content-Type: application/json
{ "task": "{{task}}", "done": "{{?done}}"}
POST https://todo.example.com/todosAuthorization: User {{@token}}Content-Type: application/json
{ task, done: done.$optional}
The done: done.$optional
might be longer than even the canonical JSON form "done": "{{?done}}"
, but
one might argue that it’s simpler to read and type (no need to open and close with "{{
and }}"
, just add .$optional
).
For simple values, the choice to use references or interpolations is purely a stylistic one.
Expressions in the javascript syntax is “(anything in parentheses)”. For instance a default
value of done
can be provided with
{ done: done = (false)}
This is essentially the same as the interpolated template
{ "done": "{{done = false}}"}
We can omit the optional hint with this expression since there will always be a default.
Try it out, here’s the above template, but try editing it
task="learn pardon"
{ task, done: done = (false)}
The values below the dashed line are the values of any variables and references bound by the template.
Encodings
Section titled “Encodings”Sometimes values have specific encodings that can be broken down further, for instance, you might have an APIs that takes (or return) base64-encoded JSON strings.
Pardon can describe this with base64(json(...))
.
hello=world
{ data: base64(json({ hello }))}
We can bind references to each step here to see what’s going on, try updating the above with
data: base64(j = json(o = { hello }))
to see the j
and o
values.
Encodings can also be used to extract data from encoded values! In the following example,
we’re matching the template with an already base64-encoded value, and we still get hello=world
out.
{ data: base64(json({ hello }))}---{ "data": "eyJoZWxsbyI6IndvcmxkIn0=" }
HTTP Templates
Section titled “HTTP Templates”HTTP is parsed before being matched into a base http template. The template text on the left is parsed into an object for further processing.
POST http://origin/path?x=yheader: value
content
{ method: "POST", origin: "http://origin", pathname: "/path", searchParams: new URLSearchParams("?x=y"), headers: new Headers({ "header": "value" }), body: 'content'}
This parsed object data is then merged with a template that represents all http requessts.
{ method: "POST", origin: "http://service", pathname: "/path", searchParams: new URLSearchParams("?x=y"), headers: new Headers({ "header": "value" }), body: 'content'}
{ method: "{{method}}", origin: "{{origin}}", pathname: "{{...pathname}}", searchParams: search = ...,
headers: headers = ...,
body: body = ...}
Merging objects naturally merges their field values,
i.e., the value "POST"
is merged with {{method}}
, "http://service"
with {{origin}}
, etc…
Some parts transform what they match to an internal representation, like base64(json(...))
but more
specialized for processing those parts:
In particular, the search
params, headers
and body
templates further break down their values.
body
can act as a json, form, or text encoding.
Arrays in templates
Section titled “Arrays in templates”Processing arrays requires setting up a template to process multiple items. Let’s start with the template for one item:
[ { id, task, done }]---[ { id: "T1001", task: "hello", done: false }]
adding a second value { id: "T1002", task: "world", done: false }
, to the data does not
work as the arrays now have different lengths.
To work around this, template needs to specify any number of items…
[ ...{ id, task, done }]---[ { id: "T1001", task: "hello", done: false }, { id: "T1002", task: "world", done: false }]
… identifying the item as an example of what goes into the array, without specifying a size.
This template now merges, but notice there’s no values exported!
This is because because each id
, task
, and done
value
are in the scope of each array element. For them to show up in the output, we need
to give them a property path:
[ ...{ id: todos.id, task: todos.task, done: todos.done }]---[ { id: "T1001", task: "hello", done: false }, { id: "T1002", task: "world", done: false }]
Now, as a user of this data, you might not want to have to search the array for
a particular id
: it would be more natural to bind this as a map.
Pardon supports this with a little syntax: { id: key } * [...items]
.
The first part is a template which is merged with each item purely to deteremine a value
for key
, and then the items are represented internally as a map.
{ id: key } * [ ...{ task: todos.task, done: todos.done }]---[ { id: "T1001", task: "hello", done: false }, { id: "T1002", task: "world", done: false }]
These structures might be even more useful in reverse, although it does
require adding todos.$key
to render the id back into the list.
todos={ T1001={ task=hello done=false } T1002={ task=world done=false }}{ id: key } * [ ...{ id: todos.$key, task: todos.task, done: todos.done }]
Note that within expressions (parenthesized), references should be direct names,
you can refer to todos
anywhere in the above template, and directly to task
, done
, etc…
only within each item (you can’t use todos.task
because it would be ambiguous whether
todos
was the value object or todos.task
was the scoped reference).
Next Steps
Section titled “Next Steps”The following docs need a refresh.