Skip to content

Introduction to Pardon

Pardon is a polite http framework.

Pardon is a template processor: templates are used to describe, match, format, extend, and parse http requests and the responses. Templates are written in a dialect of the “HTTP format”.

Templates supply the “etiquette” that is easy to forget. “Did you remember your content-type header?”. Of course you did, you were focused on the data. Pardon takes care of the syntax part of the request so you can focus on the semantics.

Authorization tokens? Request signing? Switching environments? All easily done in Pardon,… and yet nothing is built-in.

Let’s consider the requests for the backend of a simple multi-user todo-style application.

  • The API manages users and todos.
  • The login API exchanges password-based authentication for a bearer token.
  • Logged in users can edit their todo list (create, read, update, delete, and list).
  • Todo objects have a “task” (description) and a “done” flag.

Please familiarize yourself with the following 8 requests to manage users and todos.

manage users

Create a user

POST https://todo.example.com/users
Content-Type: application/json
{
"username": "{{username}}",
"password": "{{@password}}"
}

A pardon collection would contain the above templates inside .https files, or “HTTP-schema” files of request and response templates.

Response templates extract and redact values in a response, for example, the user login HTTP schema can specifies a redacted-value placeholder in the response:

>>>
PUT https://todo.example.com/users
Content-Type: application/json
{
"username": "{{username}}",
"password": "{{@password = secrets.password}}"
}
<<<
200 OK
{
"token": "{{@token}}"
}

The {{@token}} here is important for two reasons, primarily it identifies the field as a secret which masks it in logs, but also a script can use the extracted value when using pardon()’s Javascript API to access the authorization call.

Here’s what that script might look like.

import { pardon } from "pardon";
export async function authorizeUser(username) {
const {
ingress: { response: res, secrets: { token } },
} = await pardon({ username })`
PUT https://todo.example.com/users
`();
if (!token) {
throw new Error(
`failed to authorize user ${username}: (${res.status}) ${res.body}`,
);
}
return token;
}

Using our todo API, we can get a feel for Pardon works.

We will go through some basic flows.

  • create a user.
  • create a todo item.
  • mark the todo as completed.

In this tutorial, changing tabs updates the request: you will need to run each request before the following one.

Register a new user, with a password.

@password=pw
POST https://todo.example.com/users
Loading Pardon Playground...

Let’s go over what we just did.

First we created a user with name “test” using input values. The @password is a secret input, Pardon won’t save this directly in the request history.

input
username=test
@password=pw
POST https://todo.example.com/users
request
POST https://todo.example.com/users
content-type: application/json
{
"username": "test",
"password": "pw"
}

See pardon also supplies the necessary but forgettable content-type header here. It’s not a guess, nor is it based on the request body: it’s in the template.

After sending this out, the server-emulator in this page registered the test user.

The full https schema for the create user endpoint includes a response matcher and a post request script. The response matcher assures that the script is only run if the call was successful. What it does is register the password for the user in the context of the value of { username }.

Assigning secrets allows the secrets.password expression in the login script to retrieve the password.

>>>
POST https://todo.example.com/users
Content-Type: application/json
{
username: "{{username}}",
password: "{{@password}}"
}
<<<
200
!!!
secrets({ username }).password = password

Now that the user is created and the password is stored, we moved on to creating a todo.

If you navigate back to that second request and unlock the secrets for viewing, you will see

POST https://todo.example.com/todos
authorization: User jwt.eyJ1c2VybmFtZSI6InRlc3QifQ==
content-type: application/json
{
"task": "learn pardon"
}

while this authorization token is just {"username":"test"} in base64, it is being generated from the login call.

The todo create call has the following schema,

import:
./authorization.ts:
- authorizeUser
>>>
POST https://todo.example.com/todos
Content-Type: application/json
Authorization: User {{@token = authorizeUser(username)}}
{
task: "{{task}}",
done: "{{?done}}"
}
<<<
200
{
"id": "{{+todo}}"
}

At the top, we see a yaml-format configuration section. Configuration can provide defaults, runtime (or “render-time”) values for the templates, imports from scripts, and environment configuration.

Here the configuration is importing the method used in the template, {{@token = authorizeUser({ username, origin })}}, when we render this request, the result of running authorizeUser({ username, origin }) is interpolated here. Since the collection is in control of this script, practically any authorization mechanism can be supported (read from a file, execute a local process, etc…).

Looking at the request and response, we can see that some of the variables have “hints”: The plain {{task}} is mandatory input, and the request will not render without it. However, the {{?done}} is marked optional by the ?-mark, if the value is not supplied pardon can omimt the entire field.

In the response, the + in {{+todo}} marks a dataflow output, this is why the todo=T1001 was added automatically for the next request.

Feel free to explore the other rest calls, listing todos, deleting todos and/or users, by editing the request input.

Here are some ideas for exploration.

Try using input values in the template, instead of the request body.

username=test
done=true
PUT https://todo.example.com/todos/T1001

(Note that the saved passwords which are supplied by secrets.password will be lost if the page is reloaded, you can resupply them with the login command, just change the create user call from POST to PUT, or reset the server state).

For better or worse, this tutorial only scratches the surface of Pardon’s capabilities.

For more hands-on experience running pardon, where we will run the desktop application against the same server we have in this page.

For a deeper dive into the template runtime, and the programming model.