lib/data_access_behaviour.ex

defmodule DataSchema.DataAccessBehaviour do
  @moduledoc """
  Defines how `DataSchema.to_struct/2` should access data for each given field type.

  When we create a struct defined by the schema we will visit each struct field in turn
  and attempt to extract values from the source data. You tell the schema _how_ to
  extract that data by providing a module that implements this behaviour. Modules that
  implement this behaviour are implementing how to access data in the source for each kind
  of field.

  ### Examples

  To define a schema that consumes a map to create a struct you would first create a
  module that implements this behaviour like so:

      defmodule DataSchema.MapAccessor do
        @behaviour DataSchema.DataAccessBehaviour

        @impl true
        def field(data = %{}, field) do
          Map.get(data, field)
        end

        @impl true
        def list_of(data = %{}, field) do
          Map.get(data, field)
        end

        @impl true
        def has_one(data = %{}, field) do
          Map.get(data, field)
        end

        @impl true
        def has_many(data = %{}, field) do
          Map.get(data, field)
        end
      end

  Then you would pass it into the schema definition:

      defmodule BlogPost do
        import DataSchema, only: [data_schema: 2]

        data_schema([
          field: {:content, "content", &{:ok, to_string(&1)}}
        ], DataSchema.MapAccessor)
      end

  When we call `DataSchema.to_struct/2` the functions in `DataSchema.MapAccessor` will be
  used to access the data from the source.

  DataSchema ships with the above example (see `DataSchema.MapAccessor`) and you can use
  `DataSchema.data_schema/1` to leverage it automatically.
  """

  @doc """
  The function that will be called when a `:field` field is encountered in the schema when
  we are turning some input data into a struct.
  """
  # Should these be okay tuples? What would we do in the case of error. I guess bail out.
  # It would mean non_null is not field level though.
  @callback field(any(), any()) :: any()
  @doc """
  The function that will be called when a `:list_of` field is encountered in the schema
  when we are turning some input data into a struct.
  """
  @callback list_of(any(), any()) :: any()
  @doc """
  The function that will be called when a `:has_one` field is encountered in the schema
  when we are turning some input data into a struct.
  """
  @callback has_one(any(), any()) :: any()
  @doc """
  The function that will be called when a `:has_one` field is encountered in the schema
  when we are turning some input data into a struct.
  """
  @callback has_many(any(), any()) :: any()
end