View Source Custom codecs for EdgeDB scalars

EdgeDB.Protocol.Codec is a structure that knows how to encode or decode Elixir types into EdgeDB types and vice versa using the EdgeDB binary format.

Custom codecs can be useful when your EdgeDB scalars need their own processing.

You can use the EdgeDB.Protocol.Codec.defscalarcodec/1 macros to define a custom codec. It will generate code with EdgeDB.Protocol.Codec behavior, which will require implementing EdgeDB.Protocol.Codec.encode_instance/1 and EdgeDB.Protocol.Codec.decode_instance/1 callbacks.

In most cases you can use already defined codecs to work with the EdgeDB binary protocol. Otherwise, you will need to check to the EdgeDB binary protocol documentation.

As an example, let's create a custom codec for a scalar that extends the standard std::json type.

module default {
    scalar type JSONPayload extending json;

    type User {
        required property name -> str {
            constraint exclusive;
        };

        required property payload -> JSONPayload;
    }
};

We will convert the following structure to default::JSONPayload:

defmodule MyApp.Users.Payload do
  defstruct [
    :public_id,
    :first_name,
    :last_name
  ]

  @type t() :: %__MODULE__{
          public_id: integer(),
          first_name: String.t(),
          last_name: String.t()
        }
end

The implementation of the codec itself:

defmodule MyApp.EdgeDB.Codecs.JSONPayload do
  use EdgeDB.Protocol.Codec

  alias EdgeDB.Protocol.Codecs

  alias MyApp.Users.Payload

  defscalarcodec(
    type_name: "default::JSONPayload",
    type: Payload.t(),
    calculate_size: false  # we need that because the JSON codec calculates its own size and we rely on JSON codec
  )

  @impl EdgeDB.Protocol.Codec
  def encode_instance(%Payload{} = payload) do
    payload
    |> Map.from_struct()
    |> Codecs.Builtin.JSON.encode_instance()
  end

  @impl EdgeDB.Protocol.Codec
  def decode_instance(binary_data) do
    %{
      "public_id" => public_id,
      "first_name" => first_name,
      "last_name" => last_name
    } = Codecs.Builtin.JSON.decode_instance(binary_data)

    %Payload{
      public_id: public_id,
      first_name: first_name,
      last_name: last_name
    }
  end
end

Now let's test this codec:

iex(1)> {:ok, pid} = EdgeDB.start_link(codecs: [MyApp.EdgeDB.Codecs.JSONPayload])
iex(1)> payload = %MyApp.Users.Payload{public_id: 1, first_name: "Harry", last_name: "Potter"}
iex(2)> EdgeDB.query!(pid, "INSERT User { name := <str>$username, payload := <JSONPayload>$payload }", username: "user", payload: payload)
iex(3) EdgeDB.Object{} = EdgeDB.query_required_single!(pid, "SELECT User {name, payload} FILTER .name = 'user' LIMIT 1")
#EdgeDB.Object<name := "user", payload := %MyApp.Users.Payload{
  first_name: "Harry",
  last_name: "Potter",
  public_id: 1
}>