Alembic v4.0.0 Alembic.ResourceLinkage View Source

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

Link to this section 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

Unlike to_params/2, if type and id of convertable already exists in converted_by_id_by_type, then the params returned are only %{ "id" => id } without any further expansion, that is, a resource identifier, so that loops are prevented

Link to this section Functions

Link to this function from_json(json, error_template) View Source
from_json(nil, Alembic.Error.t()) :: {:ok, nil}
from_json([], Alembic.Error.t()) :: {:ok, []}
from_json(Alembic.json_object(), Alembic.Error.t()) ::
  {:ok, Alembic.Resource.t() | Alembic.ResourceIdentifier.t()}
  | Alembic.FromJson.error()
from_json([Alembic.json_object(), ...], Alembic.Error.t()) ::
  {:ok, [Alembic.Resource.t() | Alembic.ResourceIdentifier.t()]}
  | Alembic.FromJson.error()
from_json(true | false | float() | integer(), Alembic.Error.t()) ::
  Alembic.FromJson.error()

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"
      }
    ]
  }
}

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"
  }
]

Unlike to_params/2, if type and id of convertable already exists in converted_by_id_by_type, then the params returned are only %{ "id" => id } without any further expansion, that is, a resource identifier, so that loops are prevented.

Parameters

  • convertable - an Alembic.Document.t hierarchy data structure
  • resources_by_id_by_type - A nest map with the outer layer keyed by the Alembic.Resource.type, then the next layer keyed by the Alembic.Resource.id with the values being the full Alembic.Resource.t from Alembic.Document.t included.
  • converted_by_id_by_type - Tracks which (type, id) have been converted already to prevent infinite recursion when expanding indirect relationships.

Returns

Success

  • {nil} if an empty singleton
  • %{} - if a non-empty singleton
  • [] - if an empty collection
  • [%{}] - if a non-empty collection

Errors

  • {:error, :already_converted} - if the type and id of convertable already exists in converted_by_id_by_type
  • {:error, :unset} - if the convertable data is not set

Callback implementation for Alembic.ToParams.to_params/3.