Dowser. Client. Codec. Builder
(Dowser.Client v0.1.1)
View Source
Builds a load/2 + dump/2 dispatcher from a list of Dowser.Client.Field
mappings.
use Dowser.Client.Codec.Builder turns a module into a Dowser.Client.Field-
shaped dispatcher: it exposes a cast/2 macro to declare, for a pattern
matched against the second argument (typically field metadata), which
Dowser.Client.Field module handles it. At compile time this expands into
plain load/2/dump/2 function clauses — one pattern-matched clause per
cast/2, dispatching straight to the field module, with no indirection at
runtime.
A module built this way implements Dowser.Client.Field's per-value
contract (load/2/dump/2) — it is not a Dowser.Client.Codec and
can't be set directly as :codec_adapter, which casts a whole body
(encode/2/decode/2). See the bridging example at the bottom of this
moduledoc, and Dowser.Client.Codec for the full :codec_adapter contract.
defmodule Dowser.Elasticsearch.Fields.Date do
@behaviour Dowser.Client.Field
@impl true
def load(value, %{"format" => "strict_date"}) when is_binary(value) do
case Date.from_iso8601(value) do
{:ok, date} -> date
{:error, _reason} -> value
end
end
def load(value, _field), do: value
@impl true
def dump(%Date{} = date, %{"format" => "strict_date"}) do
Date.to_iso8601(date)
end
def dump(value, _field), do: value
end
defmodule Dowser.Elasticsearch.FieldCodec do
use Dowser.Client.Codec.Builder
cast %{"type" => "date"}, Dowser.Elasticsearch.Fields.Date
endcompiles down to (roughly):
defmodule Dowser.Elasticsearch.FieldCodec do
def load(nil, _field), do: nil
def load(value, %{"type" => "date"} = field) do
Dowser.Elasticsearch.Fields.Date.load(value, field)
end
def load(value, _field), do: value
def dump(nil, _field), do: nil
def dump(value, %{"type" => "date"} = field) do
Dowser.Elasticsearch.Fields.Date.dump(value, field)
end
def dump(value, _field), do: value
endFrom field codec to :codec_adapter
Bridging FieldCodec.load/2/dump/2 into something usable as
:codec_adapter means walking a document alongside its mapping/schema —
backend-specific knowledge dowser_client doesn't have, so it can't
provide that walk generically. A backend package writes its own thin
Dowser.Client.Codec:
defmodule Dowser.Elasticsearch.Codec do
@behaviour Dowser.Client.Codec
alias Dowser.CoreExt.Keyable
alias Dowser.Elasticsearch.FieldCodec
@impl true
def decode(body, opts) do
key_fn = Keyword.fetch!(opts, :key_fn)
# `walk_mapping/2` is the backend-specific part: recurse through
# `body` alongside its index mapping, calling
# `FieldCodec.load/2` with each value's own field metadata.
{:ok, body |> Keyable.transform_keys(key_fn) |> walk_mapping(&FieldCodec.load/2)}
end
@impl true
def encode(body, _opts) do
{:ok, walk_mapping(body, &FieldCodec.dump/2)}
end
endSee Dowser.Client.Codec for the full :codec_adapter contract, including
opts[:key_fn].
Options
:inherit— a module built withDowser.Client.Codec.Builderwhosecast/2declarations are inserted ahead of this module's own. A pattern declared by the inherited module wins over one declared later for an overlapping match, since clauses are tried top to bottom. Defaultnil(no inheritance).:fallback—true(default) adds a catch-allload/2/dump/2clause returning the value as-is when nothing else matched.falseomits it, so an unmatched value raisesFunctionClauseError.:nil—true(default) adds aload(nil, _)/dump(nil, _)clause returningnilahead of every other clause, so fields never have to guard againstnilthemselves.falseomits it.
Ordering
Generated clauses are tried in this order: the nil guard (if enabled),
then the inherited module's casts (if any), then this module's own casts
in declaration order, then the fallback (if enabled). load/2 clauses are
emitted before dump/2 clauses.
Summary
Functions
Declares that a value whose field metadata matches pattern is handled by
field_module — a module implementing Dowser.Client.Field.
Functions
Declares that a value whose field metadata matches pattern is handled by
field_module — a module implementing Dowser.Client.Field.
pattern is matched against the second argument of load/2/dump/2
(conventionally the field's own metadata); the matched value is bound to
field and passed through to field_module.load/2 or field_module.dump/2
alongside the original value.