defmodule UUIDv7.Type do @moduledoc """ Ecto.Type implementation. Basically a copy of Ecto.UUID without the generators. """ # credo:disable-for-this-file Credo.Check.Refactor.LongQuoteBlocks defmacro __using__(_) do quote do use Ecto.Type @typedoc """ A hex-encoded UUID string. """ @type t :: <<_::288>> @typedoc """ A raw binary representation of a UUID. """ @type raw :: <<_::128>> @doc false def type, do: :uuid @doc """ Casts to a UUID. """ @spec cast(t() | raw() | any()) :: {:ok, t()} | :error def cast( <> ) do <> catch :error -> :error else hex_uuid -> {:ok, hex_uuid} end def cast(<<_::128>> = raw_uuid), do: {:ok, encode(raw_uuid)} def cast(_), do: :error @doc """ Same as `cast/1` but raises `Ecto.CastError` on invalid arguments. """ @spec cast!(t() | raw() | any()) :: t() def cast!(value) do case cast(value) do {:ok, hex_uuid} -> hex_uuid :error -> raise Ecto.CastError, type: __MODULE__, value: value end end @compile {:inline, c: 1} defp c(?0), do: ?0 defp c(?1), do: ?1 defp c(?2), do: ?2 defp c(?3), do: ?3 defp c(?4), do: ?4 defp c(?5), do: ?5 defp c(?6), do: ?6 defp c(?7), do: ?7 defp c(?8), do: ?8 defp c(?9), do: ?9 defp c(?A), do: ?a defp c(?B), do: ?b defp c(?C), do: ?c defp c(?D), do: ?d defp c(?E), do: ?e defp c(?F), do: ?f defp c(?a), do: ?a defp c(?b), do: ?b defp c(?c), do: ?c defp c(?d), do: ?d defp c(?e), do: ?e defp c(?f), do: ?f defp c(_), do: throw(:error) @doc """ Converts a string representing a UUID into a raw binary. """ @spec dump(t() | any()) :: {:ok, raw()} | :error def dump( <> ) do <> catch :error -> :error else raw_uuid -> {:ok, raw_uuid} end def dump(_), do: :error @compile {:inline, d: 1} defp d(?0), do: 0 defp d(?1), do: 1 defp d(?2), do: 2 defp d(?3), do: 3 defp d(?4), do: 4 defp d(?5), do: 5 defp d(?6), do: 6 defp d(?7), do: 7 defp d(?8), do: 8 defp d(?9), do: 9 defp d(?A), do: 10 defp d(?B), do: 11 defp d(?C), do: 12 defp d(?D), do: 13 defp d(?E), do: 14 defp d(?F), do: 15 defp d(?a), do: 10 defp d(?b), do: 11 defp d(?c), do: 12 defp d(?d), do: 13 defp d(?e), do: 14 defp d(?f), do: 15 defp d(_), do: throw(:error) @doc """ Same as `dump/1` but raises `Ecto.ArgumentError` on invalid arguments. """ @spec dump!(t() | any()) :: raw() def dump!(value) do case dump(value) do {:ok, raw_uuid} -> raw_uuid :error -> raise ArgumentError, "cannot dump given UUID to binary: #{inspect(value)}" end end @doc """ Converts a binary UUID into a string. """ @spec load(raw() | any()) :: {:ok, t()} | :error def load(<<_::128>> = raw_uuid), do: {:ok, encode(raw_uuid)} def load(<<_::64, ?-, _::32, ?-, _::32, ?-, _::32, ?-, _::96>> = string) do raise ArgumentError, "trying to load string UUID as Ecto.UUID: #{inspect(string)}. " <> "Maybe you wanted to declare :uuid as your database field?" end def load(_), do: :error @doc """ Same as `load/1` but raises `Ecto.ArgumentError` on invalid arguments. """ @spec load!(raw() | any()) :: t() def load!(value) do case load(value) do {:ok, hex_uuid} -> hex_uuid :error -> raise ArgumentError, "cannot load given binary as UUID: #{inspect(value)}" end end @spec encode(raw()) :: t() defp encode( <> ) do <> end @compile {:inline, e: 1} defp e(0), do: ?0 defp e(1), do: ?1 defp e(2), do: ?2 defp e(3), do: ?3 defp e(4), do: ?4 defp e(5), do: ?5 defp e(6), do: ?6 defp e(7), do: ?7 defp e(8), do: ?8 defp e(9), do: ?9 defp e(10), do: ?a defp e(11), do: ?b defp e(12), do: ?c defp e(13), do: ?d defp e(14), do: ?e defp e(15), do: ?f end end end