Packages

Advanced schema definition and validation library for Elixir

Current section

Files

Jump to
exdantic examples basic_usage.exs
Raw

examples/basic_usage.exs

# Basic Usage Examples for Exdantic
# This file demonstrates the fundamental features of Exdantic
# Run with: `mix run examples/basic_usage.exs`
# Compile and load the Exdantic modules
Mix.Task.run("compile")
defmodule BasicUsageExamples do
alias Exdantic.Types
alias Exdantic.Validator
def run do
IO.puts("=== Basic Exdantic Usage Examples ===\n")
# 1. Basic Type Validation
basic_types()
# 2. Type Constraints
type_constraints()
# 3. Complex Types
complex_types()
# 4. Custom Error Messages
custom_error_messages()
# 5. Object Types (Fixed-Key Maps)
object_types()
end
defp basic_types do
IO.puts("1. Basic Type Validation")
IO.puts("------------------------")
# String validation
string_type = Types.string()
IO.puts("String validation:")
IO.puts(" ✅ Valid: \"Hello\"")
IO.inspect(Validator.validate(string_type, "Hello"), label: " Result")
IO.puts(" ❌ Invalid: 123")
IO.inspect(Validator.validate(string_type, 123), label: " Result")
# Integer validation
integer_type = Types.integer()
IO.puts("\nInteger validation:")
IO.puts(" ✅ Valid: 42")
IO.inspect(Validator.validate(integer_type, 42), label: " Result")
IO.puts(" ❌ Invalid: \"not a number\"")
IO.inspect(Validator.validate(integer_type, "not a number"), label: " Result")
# Boolean validation
boolean_type = Types.boolean()
IO.puts("\nBoolean validation:")
IO.puts(" ✅ Valid: true")
IO.inspect(Validator.validate(boolean_type, true), label: " Result")
IO.puts(" ❌ Invalid: \"yes\"")
IO.inspect(Validator.validate(boolean_type, "yes"), label: " Result")
# Atom validation
atom_type = Types.type(:atom)
IO.puts("\nAtom validation:")
IO.puts(" ✅ Valid: :active")
IO.inspect(Validator.validate(atom_type, :active), label: " Result")
IO.puts(" ❌ Invalid: \"not_atom\"")
IO.inspect(Validator.validate(atom_type, "not_atom"), label: " Result")
IO.puts("")
end
defp type_constraints do
IO.puts("2. Type Constraints")
IO.puts("-------------------")
# String constraints
name_type =
Types.string()
|> Types.with_constraints(min_length: 2, max_length: 50)
IO.puts("String with length constraints (min: 2, max: 50):")
IO.puts(" ✅ Valid: \"John\"")
IO.inspect(Validator.validate(name_type, "John"), label: " Result")
IO.puts(" ❌ Too short: \"J\"")
IO.inspect(Validator.validate(name_type, "J"), label: " Result")
IO.puts(" ❌ Too long: (51 characters)")
IO.inspect(Validator.validate(name_type, String.duplicate("A", 51)), label: " Result")
# Integer constraints
age_type =
Types.integer()
|> Types.with_constraints(gt: 0, lt: 150)
IO.puts("\nInteger with range constraints (0 < age < 150):")
IO.puts(" ✅ Valid: 25")
IO.inspect(Validator.validate(age_type, 25), label: " Result")
IO.puts(" ❌ Too low: -5")
IO.inspect(Validator.validate(age_type, -5), label: " Result")
IO.puts(" ❌ Too high: 200")
IO.inspect(Validator.validate(age_type, 200), label: " Result")
# Choices constraint
status_type =
Types.type(:atom)
|> Types.with_constraints(choices: [:active, :inactive, :pending])
IO.puts("\nAtom with choices constraint:")
IO.puts(" ✅ Valid: :active")
IO.inspect(Validator.validate(status_type, :active), label: " Result")
IO.puts(" ❌ Invalid choice: :unknown")
IO.inspect(Validator.validate(status_type, :unknown), label: " Result")
IO.puts("")
end
defp complex_types do
IO.puts("3. Complex Types")
IO.puts("----------------")
# Array of strings
tags_type = Types.array(Types.string())
IO.puts("Array of strings:")
IO.puts(" ✅ Valid: [\"elixir\", \"phoenix\", \"liveview\"]")
IO.inspect(Validator.validate(tags_type, ["elixir", "phoenix", "liveview"]), label: " Result")
IO.puts(" ❌ Mixed types: [\"valid\", 123, \"mixed\"]")
IO.inspect(Validator.validate(tags_type, ["valid", 123, "mixed"]), label: " Result")
# Array with constraints
limited_tags_type =
Types.array(Types.string())
|> Types.with_constraints(min_items: 1, max_items: 5)
IO.puts("\nArray with constraints (min: 1, max: 5 items):")
IO.puts(" ✅ Valid: [\"elixir\"]")
IO.inspect(Validator.validate(limited_tags_type, ["elixir"]), label: " Result")
IO.puts(" ❌ Empty array: []")
IO.inspect(Validator.validate(limited_tags_type, []), label: " Result")
# Union types
id_type = Types.union([Types.string(), Types.integer()])
IO.puts("\nUnion type (string OR integer):")
IO.puts(" ✅ Valid string: \"user-123\"")
IO.inspect(Validator.validate(id_type, "user-123"), label: " Result")
IO.puts(" ✅ Valid integer: 456")
IO.inspect(Validator.validate(id_type, 456), label: " Result")
IO.puts(" ❌ Invalid type: true (boolean)")
IO.inspect(Validator.validate(id_type, true), label: " Result")
# Map types
settings_type = Types.map(Types.string(), Types.boolean())
IO.puts("\nMap type (string keys → boolean values):")
IO.puts(" ✅ Valid: %{\"notifications\" => true, \"dark_mode\" => false}")
IO.inspect(Validator.validate(settings_type, %{"notifications" => true, "dark_mode" => false}), label: " Result")
IO.puts(" ❌ Wrong value type: %{\"setting\" => \"invalid\"}")
IO.inspect(Validator.validate(settings_type, %{"setting" => "invalid"}), label: " Result")
IO.puts("")
end
defp custom_error_messages do
IO.puts("4. Custom Error Messages")
IO.puts("------------------------")
# Single custom error message
name_type =
Types.string()
|> Types.with_constraints(min_length: 3)
|> Types.with_error_message(:min_length, "Name must be at least 3 characters long")
IO.puts("Single custom error message:")
IO.puts(" ❌ Input: \"Jo\" (too short)")
case Validator.validate(name_type, "Jo") do
{:error, error} -> IO.puts(" Error: #{error.message}")
result -> IO.inspect(result, label: " Result")
end
# Multiple custom error messages
password_type =
Types.string()
|> Types.with_constraints(min_length: 8, max_length: 100)
|> Types.with_error_messages(%{
min_length: "Password must be at least 8 characters long",
max_length: "Password cannot exceed 100 characters"
})
IO.puts("\nMultiple custom error messages:")
IO.puts(" ❌ Input: \"123\" (too short)")
case Validator.validate(password_type, "123") do
{:error, error} -> IO.puts(" Error: #{error.message}")
result -> IO.inspect(result, label: " Result")
end
IO.puts(" ❌ Input: (101 characters - too long)")
case Validator.validate(password_type, String.duplicate("a", 101)) do
{:error, error} -> IO.puts(" Error: #{error.message}")
result -> IO.inspect(result, label: " Result")
end
IO.puts("")
end
defp object_types do
IO.puts("5. Object Types (Fixed-Key Maps)")
IO.puts("--------------------------------")
# Simple object
user_type =
Types.object(%{
name: Types.string(),
age: Types.integer(),
active: Types.boolean()
})
IO.puts("Simple object validation:")
valid_user = %{name: "Alice", age: 30, active: true}
IO.puts(" ✅ Valid: #{inspect(valid_user)}")
IO.inspect(Validator.validate(user_type, valid_user), label: " Result")
invalid_user = %{name: "Bob", age: "thirty", active: true}
IO.puts(" ❌ Invalid: #{inspect(invalid_user)} (age should be integer)")
case Validator.validate(user_type, invalid_user) do
{:error, [error]} -> IO.puts(" Error at #{inspect(error.path)}: #{error.message}")
result -> IO.inspect(result, label: " Result")
end
# Nested object
address_type =
Types.object(%{
street: Types.string(),
city: Types.string(),
zip: Types.integer()
})
person_type =
Types.object(%{
name: Types.string(),
address: address_type
})
IO.puts("\nNested object validation:")
valid_person = %{
name: "Charlie",
address: %{street: "123 Main St", city: "Springfield", zip: 12345}
}
IO.puts(" ✅ Valid nested object:")
IO.inspect(Validator.validate(person_type, valid_person), label: " Result")
invalid_person = %{
name: "David",
address: %{street: "456 Oak Ave", city: "Springfield", zip: "invalid"}
}
IO.puts(" ❌ Invalid nested object (zip should be integer):")
case Validator.validate(person_type, invalid_person) do
{:error, [error]} -> IO.puts(" Error at #{inspect(error.path)}: #{error.message}")
result -> IO.inspect(result, label: " Result")
end
IO.puts("")
end
end
# Run the examples
BasicUsageExamples.run()