Skip to content
Kyle Boe edited this page Dec 6, 2022 · 20 revisions

Scalar Recipes

UUID (using UUID)

defmodule MyApp.Schema.Types.Custom.UUID4 do
  @moduledoc """
  The UUID4 scalar type allows UUID4 compliant strings to be passed in and out.
  Requires `{ :elixir_uuid, "~> 1.2" }` package: https://github.com/zyro/elixir-uuid
  """
  use Absinthe.Schema.Notation

  scalar :uuid4, name: "UUID4" do
    description("""
    The `UUID4` scalar type represents UUID4 compliant string data, represented as UTF-8
    character sequences. The UUID4 type is most often used to represent unique
    human-readable ID strings.
    """)

    serialize(&encode/1)
    parse(&decode/1)
  end

  @spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term()} | :error
  @spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil}
  defp decode(%Absinthe.Blueprint.Input.String{value: value}) do
    with {:ok, result} <- UUID.info(value),
         %{version: 4, uuid: uuid} <- Map.new(result) do
      {:ok, uuid}
    else
      _ -> :error
    end
  end

  defp decode(%Absinthe.Blueprint.Input.Null{}) do
    {:ok, nil}
  end

  defp decode(_), do: :error

  defp encode(value), do: value
end

UUID (using Ecto.UUID)

defmodule MyApp.Schema.Types.Custom.UUID4 do
  @moduledoc """
  The UUID4 scalar type allows UUID4 compliant strings to be passed in and out.
  Requires `{ :ecto, ">= 0.0.0" }` package: https://github.com/elixir-ecto/ecto
  """
  use Absinthe.Schema.Notation

  alias Ecto.UUID

  scalar :uuid4, name: "UUID4" do
    description("""
    The `UUID4` scalar type represents UUID4 compliant string data, represented as UTF-8
    character sequences. The UUID4 type is most often used to represent unique
    human-readable ID strings.
    """)

    serialize(&encode/1)
    parse(&decode/1)
  end

  @spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term()} | :error
  @spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil}
  defp decode(%Absinthe.Blueprint.Input.String{value: value}) do
    UUID.cast(value)
  end

  defp decode(%Absinthe.Blueprint.Input.Null{}) do
    {:ok, nil}
  end

  defp decode(_), do: :error

  defp encode(value), do: value
end

JSON (using Jason)

defmodule MyApp.Schema.Types.Custom.JSON do
  @moduledoc """
  The Json scalar type allows arbitrary JSON values to be passed in and out.
  Requires `{ :jason, "~> 1.1" }` package: https://github.com/michalmuskala/jason
  """
  use Absinthe.Schema.Notation

  scalar :json, name: "Json" do
    description("""
    The `Json` scalar type represents arbitrary json string data, represented as UTF-8
    character sequences. The Json type is most often used to represent a free-form
    human-readable json string.
    """)

    serialize(&encode/1)
    parse(&decode/1)
  end

  @spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term()} | :error
  @spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil}
  defp decode(%Absinthe.Blueprint.Input.String{value: value}) do
    case Jason.decode(value) do
      {:ok, result} -> {:ok, result}
      _ -> :error
    end
  end

  defp decode(%Absinthe.Blueprint.Input.Null{}) do
    {:ok, nil}
  end

  defp decode(_), do: :error

  defp encode(value), do: value
end

Money (using Money)

defmodule MyApp.Schema.Types.Custom.Money do
  @moduledoc """
  The Money scalar type allows arbitrary currency values to be passed in and out.
  Requires `{ :money, "~> 1.8" }` package: https://github.com/elixirmoney/money
  """
  use Absinthe.Schema.Notation

  alias Absinthe.Blueprint.Input

  scalar :money, name: "Money" do
    description("""
    The `Money` scalar type represents Money compliant string data, represented as UTF-8
    character sequences. The Money type is most often used to represent currency
    values as human-readable strings.
    """)

    serialize(&Money.to_string/1)
    parse(&decode/1)
  end

  @spec decode(term()) :: {:ok, Money.t()} | {:ok, nil} | :error
  defp decode(%Input.String{value: value}) when is_binary(value) do
    Money.parse(value)
  end

  defp decode(%Input.Float{value: value}) when is_float(value) do
    Money.parse(value)
  end

  defp decode(%Input.Integer{value: value}) when is_integer(value) do
    Money.parse(value)
  end

  defp decode(%Input.Null{}) do
    {:ok, nil}
  end

  defp decode(_), do: :error
end

Duration (using Timex.Duration)

defmodule MyApp.Schema.Types.Custom.Duration do
@moduledoc """
  The Duration scalar type allows ISO 8601 Duration's to be utilized by Absinthe based Schemas

  Utilizes the Timex.Duration module.

  References:
  https://en.wikipedia.org/wiki/ISO_8601#Durations
  https://moment.github.io/luxon/api-docs/index.html#duration

  Requires `{ :timex, ">= 0.0.0" }` package: https://github.com/bitwalker/timex
  """
  use Absinthe.Schema.Notation

  scalar :duration, name: "Duration" do
    description("""
    The `Duration` scalar type represents an ISO 8601 compliant time duration. For example,
    "P3Y6M4DT12H30M5S" represents a duration of "three years, six months, four days,
    twelve hours, thirty minutes, and five seconds".
    """)

    serialize(&encode/1)
    parse(&decode/1)
  end

  @spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term()} | :error
  @spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil}
  defp decode(%Absinthe.Blueprint.Input.String{value: value}) do
    Timex.Parse.Duration.Parsers.ISO8601Parser.parse(value)
  end

  defp decode(%Absinthe.Blueprint.Input.Null{}) do
    {:ok, nil}
  end

  defp decode(_), do: :error

  @spec encode(Timex.Duration.t()) :: String.t() | {:error, term()}
  defp encode(%Timex.Duration{} = value) do
    Timex.Format.Duration.Formatters.Default.format(value)
  end

  defp encode(_), do: :error
end