lib/documents/session/models/session_document.ex

defmodule Ravix.Documents.Session.SessionDocument do
  @moduledoc """
  Representation of a document inside the Store Session

  ## Fields
  - entity: The document itself
  - key: The document key identity
  - original_value: the document value as it is in the database
  - change_vector: The change_vector string to deal with cluster concurrency
  """
  defstruct entity: nil,
            key: nil,
            metadata: %{},
            original_value: nil,
            change_vector: ""

  alias Ravix.Documents.Session.{State, SessionDocument}
  alias Ravix.Documents.Metadata

  @type t :: %SessionDocument{
          entity: map(),
          key: binary(),
          metadata: map(),
          change_vector: binary(),
          original_value: map()
        }

  @doc """
  Upserts a document in the informed session state

  ## Parameters
  - session_state: the session to be updated
  - document: the document to be upserted

  ## Returns
  - `{:error, :document_is_null}` if the document is not informed
  - `Ravix.Documents.Session.SessionDocument` if the session was updated correctly
  """
  @spec upsert_document(State.t(), nil | map) ::
          nil | {:error, :document_is_null} | SessionDocument.t()
  def upsert_document(_session_state, nil), do: {:error, :document_is_null}

  def upsert_document(session_state, document) when is_map_key(document, "@id") do
    upsert_document(session_state, document["@id"], document)
  end

  def upsert_document(session_state, document) when is_map_key(document, "@metadata") do
    document_metadata = document["@metadata"]
    document_without_metadata = Map.drop(document, ["@metadata"])

    case State.fetch_document(session_state, document_metadata["@id"]) do
      {:ok, existing_document} ->
        %SessionDocument{
          existing_document
          | entity: document_without_metadata,
            key: document["@id"],
            metadata: document_metadata,
            change_vector: document_metadata["@change-vector"],
            original_value: existing_document.entity
        }

      _ ->
        %SessionDocument{
          key: document_metadata["@id"],
          entity: document_without_metadata,
          metadata: document_metadata,
          change_vector: document_metadata["@change-vector"]
        }
    end
  end

  @doc """
  Upserts a document in the informed session state

  ## Parameters
  - session_state: the session to be updated
  - document_id: the key of the document to be upserted
  - metadata: The metadata of the document

  ## Returns
  - `{:error, :document_is_null}` if the document is not informed
  - `Ravix.Documents.Session.SessionDocument` if the session was updated correctly
  """
  @spec upsert_document(State.t(), any, any) :: nil | SessionDocument.t()
  def upsert_document(session_state, document_id, metadata) do
    case State.fetch_document(session_state, document_id) do
      {:ok, existing_document} ->
        updated_document = Metadata.add_metadata(existing_document.entity, metadata)

        %SessionDocument{
          existing_document
          | change_vector: metadata["@change-vector"],
            key: document_id,
            entity: updated_document,
            metadata: metadata,
            original_value: existing_document.entity
        }

      _ ->
        %SessionDocument{
          key: document_id,
          entity: nil,
          metadata: metadata,
          change_vector: metadata["@change-vector"]
        }
    end
  end
end