Skip to content

Pardon Templates

Pardon’s template/schema engine is the foundation of how Pardon understands and builds requests. The engine’s main operation is “matching”, but the logic includes both conflict detection and progressive enhancement.

The composition of multiple (compatible) templates forms a “schema” But we’ll defer the discussion of exactly what schemas represent and how they are composed for later, because we can use basic templates without understanding them.

Templates

Pardon’s templates are HTTP-like text, but commonly they represent, in part, JSON requests.

But to understand how pardon handles the overall request object we need to first look at how it would operate on a single (string) value in a JSON request.

A “simple” pattern (where the entire string is a single interpolated value) can match simple values like strings (text), matching these two (in either order) would be allowed, and additionally resolve name to "some value".

"{{name}}"
"some value"

Pardon also allows simple templates to match numbers as values, here we resolve the value name to 123 (as a number rather than as a string).

"{{name}}"
123

We can also have non-simple templates, a non-simple template can also break/compose a string (this only makes sense for strings, of course).

"{{greeting}} {{planetoid}}"
"hello world"

If multiple templates for the same value are combined, Pardon will try to resolve everything. Here "hello world" provides a value for the template, and so we can resolve both the "{{value}}" and the "{{greeting}} {{planetoid}}" template against that.

(This makes sense because /^(.*) (.*)$/ can only match one way).

"{{greeting}} {{planetoid}}"
"{{value}}"
"hello world"

This resolves the three variables as one might expect.

greeting = "hello"
planetoid = "world"
value = "hello world"

Attempting to extend/merge an incompatible template fails: Pardon recognizes these conflicts.

"hello"
"world"

Pardon would also reject this combination, as the template would match the regular expression /^(.+) (.+)$/ and "hello" is missing a space.

"hello"
"{{greeting}} {{planetoid}}"

Rendering Templates

All by itself, the template "{{hello}}" cannot be rendered without providing some value for hello. If this template were matched against the text "world", then hello would be resolved to the value "world".

The value for hello could also be provided externally as a value.

We can also provide expressions (javascript!) in templates, which will be evaluated if no value could be resolved.

"{{hello = 'world'}}"
"world"

But these expression-values match as normal, skipping evaluation.

"{{hello = 'world'}}"
"greetings"
"greetings"

Once a value is resolved (through matching or script evaluation), it can be referenced in other parts of the same template or as values in scripts.

For example, consider the following template and it’s default output

{ a: "{{hello = 'world'}}",
A: "{{HELLO = hello.toUpperCase()}}" }
{ a: "world",
A: "WORLD" }

If we merge this schema we can get different results

If we specify the value of a we resolve hello="planet" which becomes and the expression for A uses it instead of the default.

{ a: "planet" }
{ a: "planet", A: "PLANET" }

Pardon can handle any evaluation order that does not result in a cycle (including asynchronous / promise expressions).

Exploring a template

Moving on to a (slightly more) real example: a REST API that creates products with a name and a price.

>>>
POST https://example.com/products
{
"name": "{{name}}",
"price": "{{price}}"
}

This is parsed into a structure and applied to a template defined roughly as the following

{
method: "{{method}}",
url: {
origin: "{{origin}}",
pathname: "{{pathname}}"
},
headers: [...],
body: ...
}
{
method: "POST",
url: {
origin: "https://example.com",
pathname: "/products"
},
headers: [],
body: { "name": "{{name}}",
"price": "{{price}}" }
}

Anyway, let’s explore how pardon behaves with this template with these quick exercises.

Exercises
Change the values in the body of the request.

Try updating the name or price values. Note that since price is a javascript number value, the rendering of e.g., "price": 9.00 will become "price": 9.

Conversely, providing arrays or objects for these fields will currently confuse Pardon. (note: I have not decided if this is a bug or just how things work.)

POST https://example.com/products
{
"name": "Fez",
"price": 9.99
}
Loading Pardon Playground...

We’ve covered the basics of how a template supports building a request. Feel free to explore negative cases as well: change the URL path or origin, remove fields from the request, etc… and watch for when pardon can no longer match the template () or render it ().

Scripted values

Expressions can use other values from the template, either resolved through matching or evaluated in other expressions.

For example, since we have a value for {{name}} matched by the template, we can use the name value in an expression for another field.

Exercises
Change the name to "pardon‽".

You can see the price value change automatically as you add or remove letters from the name value.

POST https://example.com/products
{
"name": "pardon‽",
"price": "{{ price = name.length * 10 }}"
}
Loading Pardon Playground...

Next Steps

A thorough discussion of the pattern syntax.

Exploring a small collection of endpoints for our service example.

An explanation of how templates are composed into schemas.