defmodule QRNBU.Error do
@moduledoc """
Structured error types for NBU QR code library.
This module provides consistent error handling with categorized error types,
field information, and error codes for programmatic handling.
## Error Categories
- `:validation_error` - Data validation failures
- `:encoding_error` - Character encoding failures
- `:format_error` - Output formatting failures
- `:unsupported_error` - Unsupported feature/version requests
## Examples
iex> error = QRNBU.Error.validation_error(:iban, "Invalid checksum")
iex> raise error
** (QRNBU.Error) Validation error on field 'iban': Invalid checksum
iex> QRNBU.Error.encoding_error("Failed to encode to CP1251")
%QRNBU.Error{code: :encoding_error, message: "Encoding error: Failed to encode to CP1251", field: nil, details: %{}}
"""
defexception [:message, :field, :code, :details]
@type t :: %__MODULE__{
message: String.t(),
field: atom() | nil,
code: error_code(),
details: map() | nil
}
@type error_code ::
:validation_error
| :encoding_error
| :format_error
| :unsupported_error
@doc """
Creates a validation error for a specific field.
## Examples
iex> QRNBU.Error.validation_error(:iban, "Must be 29 characters")
%QRNBU.Error{
code: :validation_error,
field: :iban,
message: "Validation error on field 'iban': Must be 29 characters",
details: %{}
}
"""
@spec validation_error(atom(), String.t(), map()) :: t()
def validation_error(field, message, details \\ %{}) do
%__MODULE__{
message: "Validation error on field '#{field}': #{message}",
field: field,
code: :validation_error,
details: details
}
end
@doc """
Creates an encoding error.
## Examples
iex> QRNBU.Error.encoding_error("CP1251 encoding failed")
%QRNBU.Error{
code: :encoding_error,
message: "Encoding error: CP1251 encoding failed",
field: nil,
details: %{}
}
"""
@spec encoding_error(String.t(), map()) :: t()
def encoding_error(message, details \\ %{}) do
%__MODULE__{
message: "Encoding error: #{message}",
field: nil,
code: :encoding_error,
details: details
}
end
@doc """
Creates a formatting error.
## Examples
iex> QRNBU.Error.format_error("Invalid line ending for V003")
%QRNBU.Error{
code: :format_error,
message: "Format error: Invalid line ending for V003",
field: nil,
details: %{}
}
"""
@spec format_error(String.t(), map()) :: t()
def format_error(message, details \\ %{}) do
%__MODULE__{
message: "Format error: #{message}",
field: nil,
code: :format_error,
details: details
}
end
@doc """
Creates an unsupported feature/version error.
## Examples
iex> QRNBU.Error.unsupported_error("Version 4 is not supported")
%QRNBU.Error{
code: :unsupported_error,
message: "Unsupported: Version 4 is not supported",
field: nil,
details: %{}
}
"""
@spec unsupported_error(String.t(), map()) :: t()
def unsupported_error(message, details \\ %{}) do
%__MODULE__{
message: "Unsupported: #{message}",
field: nil,
code: :unsupported_error,
details: details
}
end
@doc """
Converts error to a simple string message.
"""
@spec to_message(t()) :: String.t()
def to_message(%__MODULE__{message: message}), do: message
@doc """
Wraps multiple error messages into a formatted string.
"""
@spec format_errors([t()]) :: String.t()
def format_errors(errors) when is_list(errors) do
error_lines =
errors
|> Enum.map(fn
%__MODULE__{field: nil, message: message} -> "- #{message}"
%__MODULE__{field: field, message: message} -> "- #{field}: #{message}"
end)
|> Enum.join("\n")
"Multiple validation errors:\n#{error_lines}"
end
# Exception protocol implementation
@impl true
def message(%__MODULE__{message: message}), do: message
end