lib/brazilian_documents/types/cnpj.ex

if Code.ensure_loaded?(Ecto.Type) do
  defmodule BrazilianDocuments.Types.CNPJ do
    @moduledoc """
    An `Ecto.Type` for CNPJ.
    """
    use Ecto.Type

    defstruct [:number]

    def type, do: :string

    def cast(value) when is_binary(value) do
      raw_value = String.replace(value, ~r/\D/, "")

      if BrazilianDocuments.valid_cnpj?(raw_value) do
        {:ok, %__MODULE__{number: raw_value}}
      else
        :error
      end
    end

    def cast(%__MODULE__{number: value}) when is_binary(value) do
      cast(value)
    end

    def cast(_invalid), do: :error

    def load(value), do: cast(value)

    def dump(%__MODULE__{number: number}), do: {:ok, number}

    def dump(value) when is_binary(value) do
      case cast(value) do
        {:ok, cnpj} -> dump(cnpj)
        :error -> :error
      end
    end

    def dump(_invalid), do: :error
  end

  defimpl Inspect, for: BrazilianDocuments.Types.CNPJ do
    def inspect(%{number: cnpj_number}, _opts) do
      case BrazilianDocuments.format_cnpj(cnpj_number) do
        {:ok, formatted_cnpj} -> "#CNPJ<#{formatted_cnpj}>"
        :error -> "#Invalid CNPJ<#{cnpj_number}>"
      end
    end
  end

  defimpl String.Chars, for: BrazilianDocuments.Types.CNPJ do
    def to_string(%{number: cnpj_number}) when is_binary(cnpj_number) do
      cnpj_number
    end
  end
end