Custom codecs for Gel scalars
View SourceGel.Protocol.Codec
is a codec that knows how to encode or decode Elixir types into Gel types
and vice versa using the Gel binary format.
Custom codecs can be useful when your Gel scalars need their own processing.
NOTE
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 Gel binary protocol. Otherwise, you will need to check to the Gel binary protocol documentation.
To implement custom codec it will be required to implement Gel.Protocol.CustomCodec
behaviour
and implement Gel.Protocol.Codec
protocol.
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 name: str {
constraint exclusive;
};
required 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.Gel.Codecs.JSONPayload do
@behviour Gel.Protocol.CustomCodec
defstruct []
@impl Gel.Protocol.CustomCodec
def new do
%__MODULE__{}
end
@impl Gel.Protocol.CustomCodec
def name do
"default::JSONPayload"
end
end
defimpl Gel.Protocol.Codec, for: MyApp.Gel.Codecs.JSONPayload do
alias Gel.Protocol.{
Codec,
CodecStorage
}
alias MyApp.Gel.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 Gel.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:
iex(1)> {:ok, client} = Gel.start_link(codecs: [MyApp.Gel.Codecs.JSONPayload])
iex(2)> payload = %MyApp.Users.Payload{public_id: 1, first_name: "Harry", last_name: "Potter"}
iex(3)> Gel.query!(client, "insert User { name := <str>$username, payload := <JSONPayload>$payload }", username: "user", payload: payload)
iex(4)> object = Gel.query_required_single!(client, "select User {name, payload} filter .name = 'user' limit 1")
#Gel.Object<name := "user", payload := %MyApp.Users.Payload{
first_name: "Harry",
last_name: "Potter",
public_id: 1
}>