lib/jsonapi_plug/document/relationship_object.ex

defmodule JSONAPIPlug.Document.RelationshipObject do
  @moduledoc """
  JSON:API Relationship Object

  https://jsonapi.org/format/#document-resource-object-relationships
  """

  alias JSONAPIPlug.{
    Document,
    Document.LinkObject,
    Document.ResourceIdentifierObject,
    Exceptions.InvalidDocument
  }

  @type links :: %{atom() => LinkObject.t()}

  @type t :: %__MODULE__{
          data: ResourceIdentifierObject.t() | [ResourceIdentifierObject.t()] | nil,
          links: links() | nil,
          meta: Document.meta() | nil
        }

  defstruct [:data, :links, :meta]

  @spec deserialize(Document.payload()) :: t() | no_return()
  def deserialize(data) do
    %__MODULE__{}
    |> deserialize_data(data)
    |> deserialize_links(data)
    |> deserialize_meta(data)
  end

  defp deserialize_data(relationship_object, %{"data" => resource_identifier})
       when is_map(resource_identifier),
       do: %__MODULE__{
         relationship_object
         | data: ResourceIdentifierObject.deserialize(resource_identifier)
       }

  defp deserialize_data(relationship_object, %{"data" => resource_identifiers})
       when is_list(resource_identifiers),
       do: %__MODULE__{
         relationship_object
         | data: Enum.map(resource_identifiers, &ResourceIdentifierObject.deserialize/1)
       }

  defp deserialize_data(relationship_object, _data), do: relationship_object

  defp deserialize_links(relationship_object, %{"links" => links}),
    do: %__MODULE__{
      relationship_object
      | links:
          Enum.into(links, %{}, fn {name, link} ->
            {name, LinkObject.deserialize(link)}
          end)
    }

  defp deserialize_links(relationship_object, _data), do: relationship_object

  defp deserialize_meta(relationship_object, %{"meta" => meta}) when is_map(meta),
    do: %__MODULE__{relationship_object | meta: meta}

  defp deserialize_meta(_relationship_object, %{"meta" => _meta}) do
    raise InvalidDocument,
      message: "Relationship object 'meta' must be an object",
      reference: "https://jsonapi.org/format/#document-resource-object-relationships"
  end

  defp deserialize_meta(relationship_object, _data), do: relationship_object
end