defmodule Web3.Type.Bytes do @moduledoc """ Fixed length binary, 1 to 32 bytes. """ use Ecto.Type defstruct [ :bytes, :size ] @type t :: %__MODULE__{ bytes: binary(), size: 1..32 } @impl Ecto.Type @spec type() :: :bytea def type, do: :bytea @impl Ecto.Type @spec load(term()) :: {:ok, t()} | :error def load(<<"0x", hex_string::bytes()>>), do: cast_hex(hex_string) def load(<>), do: {:ok, %__MODULE__{bytes: bytes, size: byte_size(bytes)}} def load(_), do: :error @impl Ecto.Type @spec dump(t()) :: {:ok, binary()} | :error def dump(%__MODULE__{bytes: <>}), do: {:ok, bytes} def dump(_), do: :error @impl Ecto.Type @spec cast(term()) :: {:ok, t()} | :error def cast(<<"0x", hex_string::bytes()>>), do: cast_hex(hex_string) def cast(<>), do: {:ok, %__MODULE__{bytes: bytes, size: byte_size(bytes)}} def cast(%__MODULE__{bytes: <<_::bytes()>>, size: size} = term) when size in 1..32, do: {:ok, term} def cast(_term), do: :error defp cast_hex(hex_string) do case Base.decode16(hex_string, case: :mixed) do {:ok, bytes} -> {:ok, %__MODULE__{bytes: bytes, size: byte_size(bytes)}} _ -> :error end end def to_string(%__MODULE__{bytes: nil}) do "0x" end def to_string(%__MODULE__{bytes: <>}) do "0x" <> Base.encode16(bytes, case: :lower) end def to_inspect(%{size: size} = bytes) do "Bytes#{size}<#{__MODULE__.to_string(bytes)}>" end defimpl String.Chars do def to_string(bytes) do @for.to_string(bytes) end end defimpl Inspect do def inspect(bytes, _opts) do @for.to_inspect(bytes) end end defimpl Jason.Encoder do alias Jason.Encode def encode(bytes, opts) do bytes |> to_string() |> Encode.string(opts) end end end