Skip to content

Commit

Permalink
Add !json variant for structured bodies
Browse files Browse the repository at this point in the history
- Add !json tag to `body` field of recipes
- !json bodies can contain any data. Strings will be treated as templates
- `Content-Type: application/json` will automatically be set for JSON bodies

The original design in the ticket was to use have the user still provide `Content-Type`, but I realized there isn't any value in that. It just requires more work for the user, and introduces another thing we need to validate (body type vs `Content-Type`). If the user needs a subset of `application/json` they can still override it. Otherwise, providing it ourselves is just more convenient.

Closes #242
  • Loading branch information
LucasPickering committed Jun 3, 2024
1 parent 5c5ec6a commit a3df32d
Show file tree
Hide file tree
Showing 30 changed files with 1,553 additions and 444 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

### Added

- JSON bodies can now be defined with the `!json` tag [#242](https://github.com/LucasPickering/slumber/issues/242)
- This should make JSON requests more convenient to write, because you no longer have to specify the `Content-Type` header yourself
- [See docs](https://slumber.lucaspickering.me/book/api/request_collection/recipe_body.html)
- Templates can now render binary values in certain contexts
- [See docs](https://slumber.lucaspickering.me/book/user_guide/templates.html#binary-templates) for more info
- [See docs](https://slumber.lucaspickering.me/book/user_guide/templates.html#binary-templates)

### Changed

Expand Down
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ rust-version = "1.76.0"

[dependencies]
anyhow = {version = "^1.0.75", features = ["backtrace"]}
async-recursion = "1.1.1"
async-trait = "^0.1.73"
bytes = {version = "1.5.0", features = ["serde"]}
bytesize = {version = "1.3.0", default-features = false}
Expand All @@ -31,7 +32,6 @@ mime = "^0.3.17"
nom = "7.1.3"
notify = {version = "^6.1.1", default-features = false, features = ["macos_fsevent"]}
open = "5.1.1"
pretty_assertions = "1.4.0"
ratatui = {version = "^0.26.0", features = ["serde", "unstable-rendered-line-info"]}
reqwest = {version = "^0.12.4", default-features = false, features = ["rustls-tls"]}
rmp-serde = "^1.1.2"
Expand All @@ -51,6 +51,7 @@ uuid = {version = "^1.4.1", default-features = false, features = ["serde", "v4"]

[dev-dependencies]
mockito = {version = "1.4.0", default-features = false}
pretty_assertions = "1.4.0"
rstest = {version = "0.19.0", default-features = false}
serde_test = "1.0.176"

Expand Down
10 changes: 5 additions & 5 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@
- [Request Collection](./api/request_collection/index.md)
- [Profile](./api/request_collection/profile.md)
- [Request Recipe](./api/request_collection/request_recipe.md)
- [Authentication](./api/request_collection/authentication.md)
- [Chain](./api/request_collection/chain.md)
- [Chain Source](./api/request_collection/chain_source.md)
- [Template](./api/request_collection/template.md)
- [Content Type](./api/request_collection/content_type.md)
- [Authentication](./api/request_collection/authentication.md)
- [Recipe Body](./api/request_collection/recipe_body.md)
- [Chain](./api/request_collection/chain.md)
- [Chain Source](./api/request_collection/chain_source.md)
- [Template](./api/request_collection/template.md)
- [Configuration](./api/configuration/index.md)
- [Input Bindings](./api/configuration/input_bindings.md)
- [Theme](./api/configuration/theme.md)
Expand Down
14 changes: 6 additions & 8 deletions docs/src/api/request_collection/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ Authentication provides shortcuts for common HTTP authentication schemes. It pop

## Variants

| Variant | Type | Value |
| -------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `basic` | [`Basic Authentication`](#basic-authentication) | [Basic authentication](https://swagger.io/docs/specification/authentication/basic-authentication/) credentials |
| `bearer` | `string` | [Bearer token](https://swagger.io/docs/specification/authentication/bearer-authentication/) |
| Variant | Type | Value |
| --------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `!basic` | [`Basic Authentication`](#basic-authentication) | [Basic authentication](https://swagger.io/docs/specification/authentication/basic-authentication/) credentials |
| `!bearer` | `string` | [Bearer token](https://swagger.io/docs/specification/authentication/bearer-authentication/) |

### Basic Authentication

Expand All @@ -26,8 +26,7 @@ requests:
create_fish: !request
method: POST
url: "{{host}}/fishes"
body: >
{"kind": "barracuda", "name": "Jimmy"}
body: !json { "kind": "barracuda", "name": "Jimmy" }
authentication: !basic
username: user
password: pass
Expand All @@ -41,7 +40,6 @@ requests:
create_fish: !request
method: POST
url: "{{host}}/fishes"
body: >
{"kind": "barracuda", "name": "Jimmy"}
body: !json { "kind": "barracuda", "name": "Jimmy" }
authentication: !bearer "{{chains.token}}"
```
14 changes: 7 additions & 7 deletions docs/src/api/request_collection/chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ To use a chain in a template, reference it as `{{chains.<id>}}`.

## Fields

| Field | Type | Description | Default |
| -------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `source` | [`ChainSource`](./chain_source.md) | Source of the chained value | Required |
| `sensitive` | `boolean` | Should the value be hidden in the UI? | `false` |
| `selector` | [`JSONPath`](https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html) | Selector to transform/narrow down results in a chained value. See [Filtering & Querying](../../user_guide/filter_query.md) | `null` |
| `content_type` | [`ContentType`](./content_type.md) | Force content type. Not required for `request` and `file` chains, as long as the `Content-Type` header/file extension matches the data | |
| `trim` | [`ChainOutputTrim`](#chain-output-trim) | Trim whitespace from the rendered output | `none` |
| Field | Type | Description | Default |
| -------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `source` | [`ChainSource`](./chain_source.md) | Source of the chained value | Required |
| `sensitive` | `boolean` | Should the value be hidden in the UI? | `false` |
| `selector` | [`JSONPath`](https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html) | Selector to transform/narrow down results in a chained value. See [Filtering & Querying](../../user_guide/filter_query.md) | `null` |
| `content_type` | `string` | Force content type. Not required for `request` and `file` chains, as long as the `Content-Type` header/file extension matches the data. See [here](./recipe_body.md#supported-content-types) for a list of supported types. | |
| `trim` | [`ChainOutputTrim`](#chain-output-trim) | Trim whitespace from the rendered output | `none` |

See the [`ChainSource`](./chain_source.md) docs for detail on the different types of chainable values.

Expand Down
12 changes: 6 additions & 6 deletions docs/src/api/request_collection/chain_source.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ message: Enter Password
## Variants
| Variant | Type | Description |
| --------- | ---------------------------------- | --------------------------------------------------------------- |
| `request` | [`ChainSource::Request`](#request) | Body of the most recent response for a specific request recipe. |
| `command` | [`ChainSource::Command`](#command) | Stdout of the executed command |
| `file` | [`ChainSource::File`](#file) | Contents of the file |
| `prompt` | [`ChainSource::Prompt`](#prompt) | Value entered by the user |
| Variant | Type | Description |
| ---------- | ---------------------------------- | --------------------------------------------------------------- |
| `!request` | [`ChainSource::Request`](#request) | Body of the most recent response for a specific request recipe. |
| `!command` | [`ChainSource::Command`](#command) | Stdout of the executed command |
| `!file` | [`ChainSource::File`](#file) | Contents of the file |
| `!prompt` | [`ChainSource::Prompt`](#prompt) | Value entered by the user |

### Request

Expand Down
11 changes: 0 additions & 11 deletions docs/src/api/request_collection/content_type.md

This file was deleted.

12 changes: 5 additions & 7 deletions docs/src/api/request_collection/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,21 @@ chains:
recipe: login
selector: $.token

# Use YAML anchors for de-duplication (Anything under .ignore is ignored)
# Use YAML anchors for de-duplication (Anything under .ignore will not trigger an error for unknown fields)
.ignore:
base: &base
headers:
Accept: application/json
Content-Type: application/json

requests:
login: !request
<<: *base
method: POST
url: "{{host}}/anything/login"
body: |
{
body:
!json {
"username": "{{chains.username}}",
"password": "{{chains.password}}"
"password": "{{chains.password}}",
}

# Folders can be used to keep your recipes organized
Expand All @@ -92,6 +91,5 @@ requests:
method: PUT
url: "{{host}}/anything/current-user"
authentication: !bearer "{{chains.auth_token}}"
body: >
{"username": "Kenny"}
body: !json { "username": "Kenny" }
```
49 changes: 49 additions & 0 deletions docs/src/api/request_collection/recipe_body.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Recipe Body

There are a variety of ways to define the body of your request. Slumber supports structured bodies for a fixed set of known content types (see table below).

In addition, you can pass any [`Template`](./template.md) to render any text or binary data. In this case, you'll probably want to explicitly set the `Content-Type` header to tell the server what kind of data you're sending. This may not be necessary though, depending on the server implementation.

## Supported Content Types

The following content types have first-class support. The meaning of each column is as follows:

- Variant - Tag used when defining a body of this type
- Type - Type of the associated value
- `Content-Type` - Value to insert for the `Content-Type` request header
- Additionally, this is the associated content type when determining how to [parse chained responses for the purposes of filtering/querying](../../user_guide/filter_query.md).
- File Extension - File extension(s) that map to this content type when determining how to [parse chained files for the purposes of filtering/querying](../../user_guide/filter_query.md)

| Variant | Type | `Content-Type` | File Extension | Description |
| ------- | ---- | ------------------ | -------------- | ---------------------------------------------------------------- |
| `!json` | Any | `application/json` | `json` | Structured JSON body, where all strings are treated as templates |

## Examples

```yaml
chains:
image:
source: !file
path: ./fish.png

requests:
text_body: !request
method: POST
url: "{{host}}/fishes/{{fish_id}}/name"
headers:
Content-Type: text/plain
body: Balthazar

binary_body: !request
method: POST
url: "{{host}}/fishes/{{fish_id}}/image"
headers:
Content-Type: image/jpg
body: "{{chains.fish_image}}"

json_body: !request
method: POST
url: "{{host}}/fishes/{{fish_id}}"
# Content-Type header will be set automatically based on the body type
body: !json { "name": "Balthazar" }
```
12 changes: 5 additions & 7 deletions docs/src/api/request_collection/request_recipe.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The tag for a recipe is `!request` (see examples).
| `query` | [`mapping[string, Template]`](./template.md) | HTTP request query parameters | `{}` |
| `headers` | [`mapping[string, Template]`](./template.md) | HTTP request headers | `{}` |
| `authentication` | [`Authentication`](./authentication.md) | Authentication scheme | `null` |
| `body` | [`Template`](./template.md) | HTTP request body | `null` |
| `body` | [`RecipeBody`](./recipe_body.md) | HTTP request body | `null` |

## Folder Fields

Expand All @@ -39,22 +39,20 @@ recipes:
url: "{{host}}/anything/login"
headers:
accept: application/json
content-type: application/json
query:
root_access: yes_please
body: |
{
body:
!json {
"username": "{{chains.username}}",
"password": "{{chains.password}}"
"password": "{{chains.password}}",
}
fish: !folder
name: Users
requests:
create_fish: !request
method: POST
url: "{{host}}/fishes"
body: >
{"kind": "barracuda", "name": "Jimmy"}
body: !json { "kind": "barracuda", "name": "Jimmy" }

list_fish: !request
method: GET
Expand Down
3 changes: 1 addition & 2 deletions docs/src/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ requests:
create_fish: !request
method: POST
url: "{{host}}/fishes"
body: >
{"kind": "barracuda", "name": "Jimmy"}
body: !json { "kind": "barracuda", "name": "Jimmy" }
list_fish: !request
method: GET
Expand Down
14 changes: 7 additions & 7 deletions docs/src/user_guide/chains.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ requests:
login: !request
method: POST
url: "https://myfishes.fish/login"
body: |
{
body:
!json {
"username": "{{chains.username}}",
"password": "{{chains.password}}"
"password": "{{chains.password}}",
}

get_user: !request
Expand Down Expand Up @@ -99,17 +99,17 @@ chains:
recipe: login
auth_token:
source: !command
command: [ "cut", "-d':'", "-f2" ]
command: ["cut", "-d':'", "-f2"]
stdin: "{{chains.auth_token_raw}}"
requests:
login: !request
method: POST
url: "https://myfishes.fish/login"
body: |
{
body:
!json {
"username": "{{chains.username}}",
"password": "{{chains.password}}"
"password": "{{chains.password}}",
}
get_user: !request
Expand Down
9 changes: 5 additions & 4 deletions docs/src/user_guide/filter_query.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ We'll use these credentials to log in and get an API token, so the second data s
```yaml
chains:
username:
# Slumber knows how to query this file based on its extension
source: !file
path: ./creds.json
selector: $.user
Expand All @@ -43,10 +44,10 @@ requests:
login: !request
method: POST
url: "https://myfishes.fish/anything/login"
body: |
{
body:
!json {
"username": "{{chains.username}}",
"password": "{{chains.password}}"
"password": "{{chains.password}}",
}

get_user: !request
Expand Down Expand Up @@ -84,7 +85,7 @@ requests:
login: !request
method: POST
url: "https://myfishes.fish/anything/login"
body: |
body: !json
{
"username": "{{chains.username}}",
"password": "{{chains.password}}"
Expand Down
5 changes: 2 additions & 3 deletions docs/src/user_guide/inheritance.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ requests:
url: "{{host}}/fishes"
headers:
<<: *headers_base
Content-Type: application/json
body: >
{"kind": "barracuda", "name": "Jimmy"}
Host: myfishes.fish
body: !json { "kind": "barracuda", "name": "Jimmy" }
```
3 changes: 1 addition & 2 deletions docs/src/user_guide/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ requests:
create_fish: !request
method: POST
url: "{{host}}/fishes"
body: >
{"kind": "barracuda", "name": "Jimmy"}
body: !json { "kind": "barracuda", "name": "Jimmy" }
get_fish: !request
method: GET
Expand Down
Loading

0 comments on commit a3df32d

Please sign in to comment.