Light
Dark
System
v4latest
v5dev
v4latest
v3
v2
v1

Custom codecs for EdgeDB scalars

EdgeDB.Protocol.Codec is a codec 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.

Although most of the client API is complete, some internal parts may be changed in the future. The implementation of the binary protocol (including the definition of custom codecs) is on the list of possible changes.

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.

To implement custom codec it will be required to implement EdgeDB.Protocol.CustomCodec behaviour and implement EdgeDB.Protocol.Codec protocol.

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

Copy
module default {
    scalar type JSONPayload extending json;

    type User {
        required name: str {
            constraint exclusive;
        };

        required payload: JSONPayload;
    }
};

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

Copy
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:

Copy
defmodule MyApp.EdgeDB.Codecs.JSONPayload do
  @behviour EdgeDB.Protocol.CustomCodec

  defstruct []

  @impl EdgeDB.Protocol.CustomCodec
  def new do
    %__MODULE__{}
  end

  @impl EdgeDB.Protocol.CustomCodec
  def name do
    "default::JSONPayload"
  end
end

defimpl EdgeDB.Protocol.Codec, for: MyApp.EdgeDB.Codecs.JSONPayload do
  alias EdgeDB.Protocol.{
    Codec,
    CodecStorage
  }

  alias MyApp.EdgeDB.Codecs.JSONPayload
  alias MyApp.Users.Payload

  @impl Codec
  def encode(_codec, %Payload{} = payload, codec_storage) do
    json_codec = CodecStorage.get_by_name(codec_storage, "std::json")
    Codec.encode(json_codec, Map.from_struct(payload), codec_storage)
  end

  @impl Codec
  def encode(_codec, value, codec_storage) do
    raise EdgeDB.InterfaceError.new(
            "unexpected value to encode as #{inspect(JSONPayload.name())}: #{inspect(value)}"
          )
  end

  @impl Codec
  def decode(_codec, data, codec_storage) do
    json_codec = CodecStorage.get_by_name(codec_storage, "std::json")
    payload = Codec.decode(json_codec, data, codec_storage)
    %Payload{
      public_id: payload["public_id"]
      first_name: payload["first_name"]
      last_name: payload["last_name"]
    }
  end
end

Now let’s test this codec:

Copy
iex(1)> 
{:ok, client} = EdgeDB.start_link(codecs: [MyApp.EdgeDB.Codecs.JSONPayload])
Copy
iex(2)> 
payload = %MyApp.Users.Payload{public_id: 1, first_name: "Harry", last_name: "Potter"}
Copy
iex(3)> 
EdgeDB.query!(client, "insert User { name := <str>$username, payload := <JSONPayload>$payload }", username: "user", payload: payload)
Copy
iex(4)> 
object = EdgeDB.query_required_single!(client, "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
}>
Light
Dark
System

We use ChatGPT with additional context from our documentation to answer your questions. Not all answers will be accurate. Please join our Discord if you need more help.