lib/hugs.ex

defmodule Hugs do
  alias Hugs.StructDefinition
  alias Hugs.PropsDefinition
  alias Hugs.FieldDefinition

  # ---------------------------------------------------------------------------
  #  Contracts definition helpers
  # ---------------------------------------------------------------------------

  @doc """
  Writes the given definition in the calling module.
  """
  defmacro define(dfn) do
    dfn_code = Macro.to_string(dfn)

    quote bind_quoted: [dfn: dfn, dfn_code: dfn_code], location: :keep do
      @__hugs_dfn dfn

      require Hugs.Compiler

      case @__hugs_dfn do
        %Hugs.StructDefinition{} = sdef ->
          Hugs.Compiler.define_struct(sdef)

        %Hugs.PropsDefinition{} = sdef ->
          Hugs.Compiler.define_props(sdef)

        other ->
          raise """
          Hugs.define/1 received unexpected definition.

          Got:
          #{inspect(other, pretty: true)}

          Code:
          #{dfn_code}
          """
      end

      def __hugs__(:definition), do: @__hugs_dfn
    end
  end

  def build_struct(module), do: StructDefinition.new(module)
  def build_props(), do: PropsDefinition.new()

  defmacro build_struct() do
    quote do
      Hugs.StructDefinition.new(__MODULE__)
    end
  end

  def field(%cmod{} = container, key, opts \\ []) do
    cmod.add_field(container, FieldDefinition.new(key, opts))
  end

  def constraint(%cmod{} = container, module, fun, args)
      when is_atom(module) and is_atom(fun) and is_list(args),
      do: cmod.add_constraint(container, module, fun, args)

  # ---------------------------------------------------------------------------
  #  Normalization / Denormalization helpers
  # ---------------------------------------------------------------------------

  def denormalize(data, target) do
    Hugs.Norde.denormalize(data, target)
  end

  def denormalize!(data, target) do
    case denormalize(data, target) do
      {:ok, value} -> value
      {:error, reason} -> raise reason
    end
  end

  # flipped version of denormalize to pipe the definition instead of the data
  def denormalize_data(target, data) do
    denormalize(data, target)
  end
end