Exdantic.Types (exdantic v0.0.2)

View Source

Core type system for Exdantic schemas.

Provides functions for defining and working with types:

  • Basic types (:string, :integer, :float, :boolean)
  • Complex types (arrays, maps, unions)
  • Type constraints
  • Type validation
  • Type coercion

Basic Types

# String type
Types.string()

# Integer type with constraints
Types.integer()
|> Types.with_constraints(gt: 0, lt: 100)

Complex Types

# Array of strings
Types.array(Types.string())

# Map with string keys and integer values
Types.map(Types.string(), Types.integer())

# Union of types
Types.union([Types.string(), Types.integer()])

Type Constraints

Constraints can be added to types to enforce additional rules:

Types.string()
|> Types.with_constraints([
  min_length: 3,
  max_length: 10,
  format: ~r/^[a-z]+$/
])

Summary

Functions

Coerces a value to the specified type.

Normalizes a type definition to the standard internal format.

Validates a value against a basic type.

Adds constraints to a type definition.

Adds a custom error message for a specific constraint to a type definition.

Adds multiple custom error messages for constraints to a type definition.

Adds a custom validation function to a type definition.

Types

constraint_with_message()

@type constraint_with_message() :: {atom(), any()} | {atom(), any(), String.t()}

type_definition()

@type type_definition() ::
  {:type, atom(), [any()]}
  | {:array, type_definition(), [any()]}
  | {:map, {type_definition(), type_definition()}, [any()]}
  | {:object, %{required(atom()) => type_definition()}, [any()]}
  | {:union, [type_definition()], [any()]}
  | {:ref, atom()}

Functions

array(inner_type)

@spec array(type_definition()) :: {:array, type_definition(), []}

boolean()

@spec boolean() :: {:type, :boolean, []}

coerce(arg1, value)

@spec coerce(atom(), term()) :: {:ok, term()} | {:error, String.t()}

Coerces a value to the specified type.

Parameters

  • type - The target type to coerce to
  • value - The value to coerce

Returns

  • {:ok, coerced_value} on success
  • {:error, reason} on failure

Examples

iex> Exdantic.Types.coerce(:string, 42)
{:ok, "42"}

iex> Exdantic.Types.coerce(:integer, "123")
{:ok, 123}

iex> Exdantic.Types.coerce(:integer, "abc")
{:error, "invalid integer format"}

float()

@spec float() :: {:type, :float, []}

integer()

@spec integer() :: {:type, :integer, []}

map(key_type, value_type)

@spec map(type_definition(), type_definition()) ::
  {:map, {type_definition(), type_definition()}, []}

normalize_type(type)

@spec normalize_type(term()) :: type_definition()

Normalizes a type definition to the standard internal format.

Parameters

  • type - The type definition to normalize

Returns

  • A normalized type definition tuple

Examples

iex> Exdantic.Types.normalize_type(:string)
{:type, :string, []}

iex> Exdantic.Types.normalize_type({:array, :integer})
{:array, {:type, :integer, []}, []}

object(fields)

@spec object(%{required(atom()) => type_definition()}) ::
  {:object, %{required(atom()) => type_definition()}, []}

ref(schema)

@spec ref(atom()) :: {:ref, atom()}

string()

@spec string() :: {:type, :string, []}

tuple(types)

@spec tuple([type_definition()]) :: {:tuple, [type_definition()]}

type(name)

@spec type(atom()) :: {:type, atom(), []}

union(types)

@spec union([type_definition()]) :: {:union, [type_definition()], []}

validate(type, value)

@spec validate(atom(), term()) :: {:ok, term()} | {:error, Exdantic.Error.t()}

Validates a value against a basic type.

Parameters

  • type - The type to validate against
  • value - The value to validate

Returns

  • {:ok, value} if validation succeeds
  • {:error, Exdantic.Error.t()} if validation fails

Examples

iex> Exdantic.Types.validate(:string, "hello")
{:ok, "hello"}

iex> Exdantic.Types.validate(:integer, "not a number")
{:error, %Exdantic.Error{path: [], code: :type, message: "expected integer, got "not a number""}}

with_constraints(type, constraints)

@spec with_constraints(type_definition(), [term()]) :: {atom(), term(), [term()]}

Adds constraints to a type definition.

Parameters

  • type - The type definition to add constraints to
  • constraints - List of constraints to add

Returns

  • Updated type definition with constraints

Examples

iex> string_type = Exdantic.Types.string()
iex> Exdantic.Types.with_constraints(string_type, [min_length: 3, max_length: 10])
{:type, :string, [min_length: 3, max_length: 10]}

with_error_message(type, constraint, message)

@spec with_error_message(type_definition(), atom(), String.t()) ::
  {atom(), term(), [term()]}

Adds a custom error message for a specific constraint to a type definition.

Parameters

  • type - The type definition to add the custom error message to
  • constraint - The constraint name (atom) to customize the error for
  • message - The custom error message to use when this constraint fails

Returns

  • Updated type definition with custom error message

Examples

iex> string_type = Exdantic.Types.string()
iex> |> Exdantic.Types.with_constraints([min_length: 3])
iex> |> Exdantic.Types.with_error_message(:min_length, "Name must be at least 3 characters long")
{:type, :string, [min_length: 3, {:error_message, :min_length, "Name must be at least 3 characters long"}]}

with_error_messages(type, error_messages)

@spec with_error_messages(
  type_definition(),
  [{atom(), String.t()}] | %{required(atom()) => String.t()}
) ::
  {atom(), term(), [term()]}

Adds multiple custom error messages for constraints to a type definition.

Parameters

  • type - The type definition to add the custom error messages to
  • error_messages - A keyword list or map of constraint => message pairs

Returns

  • Updated type definition with custom error messages

Examples

iex> string_type = Exdantic.Types.string()
iex> |> Exdantic.Types.with_constraints([min_length: 3, max_length: 50])
iex> |> Exdantic.Types.with_error_messages([
iex>      min_length: "Name must be at least 3 characters long",
iex>      max_length: "Name cannot exceed 50 characters"
iex>    ])
{:type, :string, [min_length: 3, max_length: 50, {:error_message, :min_length, "Name must be at least 3 characters long"}, {:error_message, :max_length, "Name cannot exceed 50 characters"}]}

with_validator(type, validator_fn)

@spec with_validator(type_definition(), (term() ->
                                     {:ok, term()} | {:error, String.t()})) ::
  {atom(), term(), [term()]}

Adds a custom validation function to a type definition.

Parameters

  • type - The type definition to add the custom validator to
  • validator_fn - A function that takes a value and returns {:ok, value} | {:error, message}

Returns

  • Updated type definition with custom validator

Examples

iex> email_type = Exdantic.Types.string()
iex> |> Exdantic.Types.with_constraints([min_length: 3])
iex> |> Exdantic.Types.with_validator(fn value ->
iex>      if String.contains?(value, "@"), do: {:ok, value}, else: {:error, "Must contain @"}
iex>    end)
{:type, :string, [min_length: 3, {:validator, #Function<...>}]}