Collection Reuse & Inheritance

The Problem

Let's start with an example of something that sucks. Let's say you're making requests to a fish-themed JSON API, and it requires authentication. Gotta protect your fish! Your request collection might look like so:

profiles:
  production:
    data:
      host: https://myfishes.fish
      fish_id: 6

chains:
  token:
    source: !file ./api_token.txt

requests:
  list_fish:
    method: GET
    url: "{{host}}/fishes"
    headers:
      Accept: application/json
      Authorization: Bearer {{chains.token}}

  get_fish:
    method: GET
    url: "{{host}}/fishes/{{fish_id}}"
    headers:
      Accept: application/json
      Authorization: Bearer {{chains.token}}

The Solution

You've heard of DRY, so you know this is bad. Every new request recipe requires re-specifying the headers, and if anything about the authorization changes, you have to change it in multiple places.

You can easily re-use components of your collection using YAML's merge feature.

profiles:
  production:
    data:
      host: https://myfishes.fish

chains:
  token:
    source: !file ./api_token.txt

# The name here is arbitrary, pick any name you like
request_base: &request_base
  headers:
    Accept: application/json
    Authorization: Bearer {{chains.auth_token}}

requests:
  list_fish:
    <<: *request_base
    method: GET
    url: "{{host}}/fishes"

  get_fish:
    <<: *request_base
    method: GET
    url: "{{host}}/fishes/{{chains.fish_id}}"

Great! That's so much cleaner. Now each recipe can inherit whatever base properties you want just by including <<: *request_base. This is still a bit repetitive, but it has the advantage of being explicit. You may have some requests that don't want to include those values.

Recursive Inheritance

But wait! What if you have a new request that needs an additional header? Unfortunately, YAML's merge feature does not support recursive merging. If you need to extend the headers map from the base request, you'll need to pull that map in manually:

profiles:
  production:
    data:
      host: https://myfishes.fish

chains:
  token:
    source: !file ./api_token.txt

# The name here is arbitrary, pick any name you like
request_base: &request_base
  headers: &headers_base # This will let us pull in the header map to extend it
    Accept: application/json
    Authorization: Bearer {{chains.auth_token}}

requests:
  list_fish:
    <<: *request_base
    method: GET
    url: "{{host}}/fishes"

  get_fish:
    <<: *request_base
    method: GET
    url: "{{host}}/fishes/{{chains.fish_id}}"

  create_fish:
    # Note: in this case, pulling in request_base doesn't do anything since we
    # then overwite its only field (headers), but this is good practice in case
    # you add an additional field to request_base
    <<: *request_base
    method: POST
    url: "{{host}}/fishes"
    headers:
      <<: *headers_base
      Content-Type: application/json
    body: >
      {"kind": "barracuda", "name": "Jimmy"}