# JSON-RPC

> Call JSON-RPC 2.0 services over HTTP, and check their results and their errors, against their OpenRPC documents.

Source: https://axx.nimbusxr.us/next/references/packs/jsonrpc/

Call JSON-RPC 2.0 services over HTTP, and check their results and their errors, against their OpenRPC documents.

Register the service once, usually in the `Background`:

```gherkin
Given the depots jsonrpc service with the following properties:
  | url     | http://localhost:8400/rpc |
  | openrpc | rpc.discover              |
```

- **Params** come from a table (by name, set as the REST pack's request properties are) or a doc string (an object by name, or an array by position).
- **The checks read the service's last call** in the scenario. A call that answers an error does not fail the step that makes it: check its error.
- **OpenRPC:** an `openrpc` row names the service's OpenRPC document: a file of the project, a URL, or `rpc.discover` to ask the service for it. Every call's params are then checked against its method before it is sent, and every result and error when it comes back. A call that breaks the document fails the step that makes it, and names the rule it broke.

OpenRPC findings have keys and levels, like the REST pack's OpenAPI findings:

| Key | Found when |
|---|---|
| `validation.method.unknown` | the document has no such method |
| `validation.params.structure` | the params are by name for a method that takes them by position, or the other way round |
| `validation.params.missing` | a required param is missing |
| `validation.params.unknown` | a param the method does not have |
| `validation.params.schema.<keyword>` | a param breaks a keyword of its schema (`required`, `type`, `pattern`...) |
| `validation.result.schema.<keyword>` | the result breaks a keyword of its schema |
| `validation.error.unknown` | the error is not one the method declares, nor one of JSON-RPC's own (-32700, -32600 to -32603, -32000 to -32099) |

- **Levels:** `ERROR` (or `FAIL`) fails the step, `WARN` and `INFO` log the finding, `IGNORE` drops it. Every finding is an `ERROR` unless `packs.jsonrpc.openrpc.levels` in axx.yaml, or the scenario's `the OpenRPC validation levels are:`, says otherwise.
- **Keys cover the keys below them:** `validation.params` sets every params finding; the most specific key set wins.
- **Secrets stay secret:** `${env:..}` values in the properties are masked in logs and failures, and `${token:..}` names a token of the scenario.

Add it to a project with `axx pack add jsonrpc`.

## Settings

In `axx.yaml`, under `packs.jsonrpc`:

| Setting | Takes | Values |
|---|---|---|
| `openrpc.levels` | Validation key -> ERROR (or FAIL), WARN, INFO or IGNORE, for every scenario. | names and values |

## Steps

### `jsonrpc.service`

```gherkin
Given the {word} jsonrpc service with the following properties:
  | property | value |
```

The table may start with the row naming its columns, as above, or go straight to its rows.

Register a JSON-RPC 2.0 service, which takes calls over HTTP, under a name.

| Parameter | Takes | For example |
|---|---|---|
| `{word}` | one word, with no spaces | `parcels`, `PX-4101` |

| Property | Takes | Default |
|---|---|---|
| `url` | the URL calls are posted to | _required_ |
| `header.<name>` | a header sent with every call, such as `header.Authorization` | |
| `timeout` | how long a call may take, like `5s` | `10s` |
| `openrpc` | the service's OpenRPC document, which its calls are checked against: a file of the project, a URL, or `rpc.discover` to ask the service for it | |

Values are expanded (`${env:..}`, `${sys:..}`, `${token:..}`); what `${env:..}` references expand to is masked everywhere.

**Example:**

```gherkin
Given the depots jsonrpc service with the following properties:
  | url     | http://localhost:8400/rpc |
  | openrpc | rpc.discover              |
Given the depots jsonrpc service with the following properties:
  | url                  | https://depots.parcels.example/rpc |
  | header.Authorization | Bearer ${env:DEPOTS_TOKEN}         |
  | openrpc              | openrpc/depots.json                |
```

_Since 0.1.5._

### `jsonrpc.call`

```gherkin
When the {word} method is called on the {word} jsonrpc service
```

Call a method without params.

| Parameter | Takes | For example |
|---|---|---|
| `{word}` | one word, with no spaces | `parcels`, `PX-4101` |

**Example:**

```gherkin
When the depot.list method is called on the depots jsonrpc service
```

_Since 0.1.5._

### `jsonrpc.call.params`

```gherkin
When the {word} method is called on the {word} jsonrpc service with the following params:
  | path | value |
```

The table may start with the row naming its columns, as above, or go straight to its rows.

Call a method with params by name, from the table.

| Parameter | Takes | For example |
|---|---|---|
| `{word}` | one word, with no spaces | `parcels`, `PX-4101` |

Each row is a param, or a path into one (`address.postcode`), and its value: numbers and booleans are JSON's, double quotes make a value text, `null` is null and `undefined` leaves it out.

**Example:**

```gherkin
When the parcel.hold method is called on the depots jsonrpc service with the following params:
  | reference | PX-RPC-7101 |
  | until     | 2026-10-02  |
```

_Since 0.1.5._

### `jsonrpc.call.doc`

```gherkin
When the {word} method is called on the {word} jsonrpc service with the params:
  """
  ...
  """
```

Call a method with the params of the doc string: an object, by name, or an array, by position.

| Parameter | Takes | For example |
|---|---|---|
| `{word}` | one word, with no spaces | `parcels`, `PX-4101` |

**Example:**

```gherkin
When the parcel.get method is called on the depots jsonrpc service with the params:
  """
  ["PX-RPC-7102"]
  """
```

_Since 0.1.5._

### `jsonrpc.result`

```gherkin
Then the {word} jsonrpc service's result has the following properties:
  | path | value |
```

The table may start with the row naming its columns, as above, or go straight to its rows.

Check the result of the service's last call.

| Parameter | Takes | For example |
|---|---|---|
| `{word}` | one word, with no spaces | `parcels`, `PX-4101` |

Each row is a path into the result (a field name, a dotted path or a JSONPath) and its value, compared as text: `null` for null and `undefined` for absent.

**Example:**

```gherkin
Then the depots jsonrpc service's result has the following properties:
  | reference | PX-RPC-7101 |
  | status    | ON_HOLD     |
```

_Since 0.1.5._

### `jsonrpc.result.is`

```gherkin
Then the {word} jsonrpc service's result is {string}
```

Check the whole result of the service's last call: a text's text, or the JSON of any other result.

| Parameter | Takes | For example |
|---|---|---|
| `{word}` | one word, with no spaces | `parcels`, `PX-4101` |
| `{string}` | text in double or single quotes, which the step leaves out | `"Get a quote"`, `'Express'` |

**Example:**

```gherkin
Then the depots jsonrpc service's result is 'true'
```

_Since 0.1.5._

### `jsonrpc.error`

```gherkin
Then the {word} jsonrpc service answered the error {int}[[ with a message containing {string}]]
```

Check the error code the service's last call answered, and the text its message contains.

| Parameter | Takes | For example |
|---|---|---|
| `{word}` | one word, with no spaces | `parcels`, `PX-4101` |
| `{int}` | a whole number | `200`, `3` |
| `{string}` | text in double or single quotes, which the step leaves out | `"Get a quote"`, `'Express'` |

**Variants**, the parts in `[[...]]` said or left out:

- `the {word} jsonrpc service answered the error {int}`
- `the {word} jsonrpc service answered the error {int} with a message containing {string}`

**Example:**

```gherkin
Then the depots jsonrpc service answered the error -32602
Then the depots jsonrpc service answered the error -32010 with a message containing 'out for delivery'
```

_Since 0.1.5._

### `jsonrpc.error.properties`

```gherkin
Then the {word} jsonrpc service's error has the following properties:
  | path | value |
```

The table may start with the row naming its columns, as above, or go straight to its rows.

Check the error the service's last call answered: its `code`, its `message` and its `data`.

| Parameter | Takes | For example |
|---|---|---|
| `{word}` | one word, with no spaces | `parcels`, `PX-4101` |

Each row is a path into the error object (`code`, `message`, `data.reason`) and its value, compared as text: `null` for null and `undefined` for absent.

**Example:**

```gherkin
Then the depots jsonrpc service's error has the following properties:
  | code        | -32010           |
  | data.status | OUT_FOR_DELIVERY |
```

_Since 0.1.5._

### `jsonrpc.openrpc.levels`

```gherkin
Given the OpenRPC validation levels are:
  | validation key | level |
```

The table may start with the row naming its columns, as above, or go straight to its rows.

Set the level of OpenRPC validation findings for this scenario, on every JSON-RPC service it calls.

- A key also sets the keys below it: `validation.params` relaxes `validation.params.schema.required` too. The most specific key set wins.
- The rows are merged over `packs.jsonrpc.openrpc.levels` of axx.yaml.
- The pack's documentation lists the keys, and what each level does.

A row is a validation key and its level: `ERROR` (or `FAIL`), `WARN`, `INFO` or `IGNORE`. The key must be one the pack reports, or a prefix of such keys, like `validation.params`: any other fails the step, which names the closest keys.

**Example:**

```gherkin
Given the OpenRPC validation levels are:
  | validation.params | IGNORE |
```

_Since 0.1.5._
