Current section
Files
Jump to
Current section
Files
lib/validator.ex
defmodule ExEtlFramework.Validator do
@moduledoc """
Provides functions for validating data structures.
This module offers a flexible way to define validation rules and apply them
to your data. It's particularly useful for ensuring data integrity at various
stages of your ETL pipeline.
## Example
schema = %{
name: [&Validator.required/1, &Validator.type(String)],
age: [&Validator.type(Integer)]
}
Validator.validate(%{name: "John", age: 30}, schema)
"""
@doc """
Validates data against a given schema.
## Parameters
- `data`: The data structure to validate.
- `schema`: A map where keys are field names and values are lists of validator functions.
## Returns
Returns either:
- `{:ok, data}` if all validations pass.
- `{:error, field, reason}` if any validation fails.
## Example
schema = %{name: [&Validator.required/1, &Validator.type(String)]}
Validator.validate(%{name: "John"}, schema)
"""
def validate(data, schema) do
Enum.reduce_while(schema, {:ok, data}, fn {field, validators}, {:ok, acc} ->
case validate_field(data, field, validators) do
:ok -> {:cont, {:ok, acc}}
{:error, reason} -> {:halt, {:error, field, reason}}
{:error, failed_field, reason} -> {:halt, {:error, failed_field, reason}}
end
end)
end
defp validate_field(data, field, validators) when is_list(validators) do
value = Map.get(data, field)
Enum.reduce_while(validators, :ok, fn validator, _acc ->
case validator.(value) do
:ok -> {:cont, :ok}
{:error, reason} -> {:halt, {:error, reason}}
other ->
{:halt, other}
end
end)
end
@doc """
Validates that a value is not nil.
## Parameters
- `value`: The value to check.
## Returns
Returns either:
- `:ok` if the value is not nil.
- `{:error, "Field is required"}` if the value is nil.
## Example
Validator.required("Some value") # Returns :ok
Validator.required(nil) # Returns {:error, "Field is required"}
"""
def required(value) when is_nil(value), do: {:error, "Field is required"}
def required(_value), do: :ok
@doc """
Creates a validator function that checks if a value is of a specific type.
## Parameters
- `expected_type`: The type to check against (e.g., String, Integer).
## Returns
Returns a function that takes a value and returns:
- `:ok` if the value is of the expected type or nil.
- `{:error, reason}` if the value is not of the expected type.
## Example
string_validator = Validator.type(String)
string_validator.("Hello") # Returns :ok
string_validator.(123) # Returns {:error, "Expected type String, got 123"}
"""
def type(expected_type) do
fn value ->
if is_nil(value) or is_type?(value, expected_type) do
:ok
else
{:error, "Expected type #{inspect(expected_type)}, got #{inspect(value)}"}
end
end
end
defp is_type?(value, String), do: is_binary(value)
defp is_type?(value, Integer), do: is_integer(value)
defp is_type?(value, Float), do: is_float(value)
defp is_type?(value, Number), do: is_number(value)
defp is_type?(value, Atom), do: is_atom(value)
defp is_type?(value, List), do: is_list(value)
defp is_type?(value, Boolean), do: is_boolean(value)
defp is_type?(value, Tuple), do: is_tuple(value)
defp is_type?(value, Map), do: is_map(value)
defp is_type?(value, Function), do: is_function(value)
defp is_type?(value, PID), do: is_pid(value)
defp is_type?(value, Port), do: is_port(value)
defp is_type?(value, Reference), do: is_reference(value)
defp is_type?(_value, Any), do: true
defp is_type?(value, expected_type), do: is_struct(value, expected_type)
end