Dowser.Type behaviour (Dowser.Client v0.5.0)

View Source

Field types: casting one value between its JSON representation and an Elixir term, given the mapping entry that describes it.

A type knows nothing about where the value sits in a document — objects, nested fields and arrays are Dowser.Source's business, which hands each leaf value here with its own mapping entry (field). This module is both the behaviour every type implements and the default dispatcher over the built-in ones, selected by the entry's "type":

mapping typetype moduleElixir term
date, date_nanosDowser.Type.DateDate, Time, DateTime, NaiveDateTime (by "format")
date_rangeDowser.Type.DateRangeDate.Range (strict_date only)
integer_range, long_rangeDowser.Type.RangeRange
ipDowser.Type.IP:inet tuple
binaryDowser.Type.Binaryraw binary
geo_pointDowser.Type.GeoPoint{lat, lon}

Only the types JSON can't represent natively are cast; any other entry — like a nil value — falls back to identity.

iex> Dowser.Type.load("127.0.0.1", %{"type" => "ip"})
{127, 0, 0, 1}

iex> Dowser.Type.dump({127, 0, 0, 1}, %{"type" => "ip"})
"127.0.0.1"

The behaviour

load/2 and dump/2 take one value and the mapping entry describing it, and return the cast term. A value a type doesn't recognize should pass through unchanged rather than raise, so a bad cast degrades to identity instead of failing the whole document.

Adding types

A type module is a plain module, so covering one more type is a clause per direction and a delegation back here for everything else:

defmodule MyApp.Type do
  @behaviour Dowser.Type

  @impl true
  def load(value, %{"type" => "scaled_float", "scaling_factor" => factor})
      when is_integer(value) do
    value / factor
  end

  def load(value, field), do: Dowser.Type.load(value, field)

  @impl true
  def dump(value, %{"type" => "scaled_float", "scaling_factor" => factor})
      when is_float(value) do
    round(value * factor)
  end

  def dump(value, field), do: Dowser.Type.dump(value, field)
end

Delegating last inherits the built-in casts — including the nil short-circuit and the fall-through to identity. Matching a type this module already handles replaces that cast, since your clause comes first. A backend package's codec takes such a module as its :codec option.

Summary

Callbacks

Casts value back into its JSON representation.

Casts value from its JSON representation into a richer term.

Functions

Casts value back into its JSON representation, dispatching on field's "type".

Casts value from its JSON representation, dispatching on field's "type".

Callbacks

dump(value, field)

@callback dump(value :: term(), field :: map()) :: term()

Casts value back into its JSON representation.

load(value, field)

@callback load(value :: term(), field :: map()) :: term()

Casts value from its JSON representation into a richer term.

Functions

dump(value, field)

Casts value back into its JSON representation, dispatching on field's "type".

The mirror image of load/2, with the same fallbacks.

load(value, field)

Casts value from its JSON representation, dispatching on field's "type".

A nil value, a mapping entry with no known "type", or no mapping entry at all, all return value untouched.