Alembic v2.1.0 Alembic.ResourceLinkage

Resource linkage in a compound document allows a client to link together all of the included resource objects without having to GET any URLs via links.

Resource linkage MUST be represented as one of the following:

  • null for empty to-one relationships.
  • an empty array ([]) for empty to-many relationships.
  • a single resource identifier object for non-empty to-one relationships.
  • an array of resource identifier objects for non-empty to-many relationships.

JSON API - Document Structure - Resource Objects - Resource Linkage

Summary

Functions

Converts a resource linkage to one or more Alembic.ResoureIdentifier.t

Converts resource linkage to params format used by Ecto.Changeset.cast/4

Functions

from_json(json, error_template)

Specs

Converts a resource linkage to one or more Alembic.ResoureIdentifier.t.

To-one

A to-one resource linkage, when present, can be a single Alembic.Resource.t or Alembic.ResourceIdentifier.t

A JSON object is assumed to be an resource object if it has "attributes"

iex> Alembic.ResourceLinkage.from_json(
...>   %{
...>     "attributes" => %{
...>       "name" => "Alice"
...>     },
...>     "id" => "1",
...>     "type" => "author"
...>   },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships/author/data"
...>     }
...>   }
...> )
{
  :ok,
  %Alembic.Resource{
    attributes: %{
      "name" => "Alice"
    },
    id: "1",
    type: "author"
  }
}

Or if the JSON object has "relationships"

iex> Alembic.ResourceLinkage.from_json(
...>   %{
...>     "id" => "1",
...>     "relationships" => %{
...>       "author" => %{
...>         "data" => %{
...>           "id" => "1",
...>           "type" => "author"
...>         }
...>       }
...>     },
...>     "type" => "post"
...>   },
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :ok,
  %Alembic.Resource{
    id: "1",
    relationships: %{
      "author" => %Alembic.Relationship{
        data: %Alembic.ResourceIdentifier{
          id: "1",
          meta: nil,
          type: "author"
        }
      }
    },
    type: "post"
  }
}

If neither "attributes" nor "relationships" is present, then JSON object is assumed to be an Alembic.ResourceIdentifier.t.

iex> Alembic.ResourceLinkage.from_json(
...>   %{
...>     "id" => "1",
...>     "type" => "author"
...>   },
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships/author/data"
...>     }
...>   }
...> )
{
  :ok,
  %Alembic.ResourceIdentifier{
    id: "1",
    type: "author"
  }
}

An empty to-one resource linkage can be signified with nil, which would have been null in the original JSON.

iex> Alembic.ResourceLinkage.from_json(
...>   nil,
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships/author/data"
...>     }
...>   }
...> )
{:ok, nil}

To-many

A to-many resource linkage, when present, can be a list of Alembic.Resource.t.

iex> Alembic.ResourceLinkage.from_json(
...>   [
...>     %{
...>       "attributes" => %{
...>         "text" => "First Post!"
...>       },
...>       "id" => "1",
...>       "relationships" => %{
...>         "comments" => %{
...>           "data" => [
...>             %{
...>               "id" => "1",
...>               "type" => "comment"
...>             }
...>           ]
...>         }
...>       },
...>       "type" => "post"
...>     }
...>   ],
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :ok,
  [
    %Alembic.Resource{
      attributes: %{
        "text" => "First Post!"
      },
      id: "1",
      links: nil,
      relationships: %{
        "comments" => %Alembic.Relationship{
          data: [
            %Alembic.ResourceIdentifier{
              id: "1",
              type: "comment"
            }
          ]
        }
      },
      type: "post"
    }
  ]
}

Or a list of Alembic.ResourceIdentifier.t.

iex> Alembic.ResourceLinkage.from_json(
...>   [
...>     %{
...>       "id" => "1",
...>       "type" => "post"
...>     }
...>   ],
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :ok,
  [
    %Alembic.ResourceIdentifier{
      id: "1",
      type: "post"
    }
  ]
}

A mix of resources and resource identifiers is an error

iex> Alembic.ResourceLinkage.from_json(
...>   [
...>     %{
...>       "attributes" => %{
...>         "text" => "First Post!"
...>       },
...>       "id" => "1",
...>       "type" => "post"
...>     },
...>     %{
...>       "id" => "2",
...>       "type" => "post"
...>     }
...>   ],
...>   %Alembic.Error{
...>     meta: %{
...>       "action" => :create,
...>       "sender" => :client
...>     },
...>     source: %Alembic.Source{
...>       pointer: "/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data` type is not resource linkage",
        meta: %{
          "type" => "resource linkage"
        },
        source: %Alembic.Source{
          pointer: "/data"
        },
        status: "422",
        title: "Type is wrong"
      }
    ]
  }
}

An empty to-many resource linkage can be signified with [].

iex> Alembic.ResourceLinkage.from_json(
...>   [],
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships/comments/data"
...>     }
...>   }
...> )
{:ok, []}

Invalid

If the json isn’t any of the above, valid formats, then a type error will be returned

iex> Alembic.ResourceLinkage.from_json(
...>   "that resource over there",
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships/resource/data"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/relationships/resource/data` type is not resource linkage",
        meta: %{
          "type" => "resource linkage"
        },
        source: %Alembic.Source{
          pointer: "/data/relationships/resource/data"
        },
        status: "422",
        title: "Type is wrong"
      }
    ]
  }
}
to_params(resource_linkage, resource_by_id_by_type)

Converts resource linkage to params format used by Ecto.Changeset.cast/4.

To-one

An empty to-one, nil, is nil when converted to params.

iex> Alembic.ResourceLinkage.to_params(nil, %{})
nil

A resource identifier uses resource_by_id_by_type to fill in the attributes of the referenced resource. type is dropped as Ecto.Changeset.cast/4 doesn’t verify types in the params.

iex> Alembic.ResourceLinkage.to_params(
...>   %Alembic.ResourceIdentifier{
...>     type: "shirt",
...>     id: "1"
...>   },
...>   %{
...>     "shirt" => %{
...>       "1" => %Alembic.Resource{
...>         type: "shirt",
...>         id: "1",
...>         attributes: %{
...>           "size" => "L"
...>         }
...>       }
...>     }
...>   }
...> )
%{
  "id" => "1",
  "size" => "L"
}

On create or update, a relationship can be created by having an Alembic.Resource.t, in which case the attributes are supplied by the Alembic.Resource.t, instead of resource_by_id_by_type.

iex> Alembic.ResourceLinkage.to_params(
...>   %Alembic.Resource{
...>     attributes: %{
...>       "size" => "L"
...>     },
...>     type: "shirt"
...>   },
...>   %{}
...> )
%{
  "size" => "L"
}

To-many

An empty to-many, [], is [] when converted to params

iex> Alembic.ResourceLinkage.to_params([], %{})
[]

A list of resource identifiers uses attributes_by_id_by_type to fill in the attributes of the referenced resources. type is dropped as Ecto.Changeset.cast/4 doesn’t verify types in the params.

iex> Alembic.ResourceLinkage.to_params(
...>   [
...>     %Alembic.ResourceIdentifier{
...>       type: "shirt",
...>       id: "1"
...>     }
...>   ],
...>   %{
...>     "shirt" => %{
...>       "1" => %Alembic.Resource{
...>         type: "shirt",
...>         id: "1",
...>         attributes: %{
...>           "size" => "L"
...>         }
...>       }
...>     }
...>  }
...> )
[
  %{
    "id" => "1",
    "size" => "L"
  }
]

On create or update, a relationship can be created by having an Alembic.Resource, in which case the attributes are supplied by the Alembic.Resource, instead of attributes_by_id_by_type.

iex> Alembic.ResourceLinkage.to_params(
...>   [
...>     %Alembic.Resource{
...>       attributes: %{
...>         "size" => "L"
...>       },
...>       type: "shirt"
...>     }
...>   ],
...>   %{}
...> )
[
  %{
    "size" => "L"
  }
]