Packages

Advanced schema definition and validation library for Elixir

Current section

Files

Jump to
exdantic examples advanced_config.exs
Raw

examples/advanced_config.exs

#!/usr/bin/env elixir
# Advanced Configuration Example
# Run with: elixir examples/advanced_config.exs
Mix.install([{:exdantic, path: "."}])
IO.puts("""
โš™๏ธ Exdantic Advanced Configuration Example
=========================================
This example demonstrates runtime configuration modification, presets,
and the builder pattern for flexible validation behavior.
""")
# Example 1: Basic Configuration Creation
IO.puts("\n๐Ÿ“ Example 1: Basic Configuration Creation")
# Create configurations with different options
basic_config = Exdantic.Config.create(%{
strict: true,
extra: :forbid,
coercion: :safe
})
IO.puts("โœ… Basic configuration created:")
IO.inspect(Exdantic.Config.summary(basic_config), pretty: true)
# Create configuration from keyword list
keyword_config = Exdantic.Config.create([
strict: false,
extra: :allow,
coercion: :aggressive,
error_format: :simple
])
IO.puts("โœ… Keyword configuration created:")
IO.inspect(Exdantic.Config.summary(keyword_config), pretty: true)
# Example 2: Configuration Presets
IO.puts("\n๐ŸŽ›๏ธ Example 2: Configuration Presets")
# Test all available presets
presets = [:strict, :lenient, :api, :json_schema, :development, :production]
for preset <- presets do
config = Exdantic.Config.preset(preset)
summary = Exdantic.Config.summary(config)
IO.puts("โœ… #{preset} preset: #{summary.validation_mode} validation, #{summary.coercion} coercion")
end
# Example 3: Configuration Merging
IO.puts("\n๐Ÿ”€ Example 3: Configuration Merging")
# Start with a base configuration
base_config = Exdantic.Config.create(%{
strict: false,
extra: :allow,
coercion: :safe
})
# Merge with overrides
strict_override = Exdantic.Config.merge(base_config, %{
strict: true,
extra: :forbid,
error_format: :detailed
})
IO.puts("Base config: #{Exdantic.Config.summary(base_config).validation_mode}")
IO.puts("Merged config: #{Exdantic.Config.summary(strict_override).validation_mode}")
# Test frozen configuration behavior
frozen_config = Exdantic.Config.create(%{frozen: true, strict: true})
IO.puts("โœ… Frozen config created")
# Try to merge with frozen config (empty merge should work)
try do
_empty_merge = Exdantic.Config.merge(frozen_config, %{})
IO.puts("โœ… Empty merge with frozen config succeeded")
rescue
RuntimeError ->
IO.puts("โŒ Empty merge with frozen config failed (unexpected)")
end
# Try to merge with non-empty override (should fail)
try do
Exdantic.Config.merge(frozen_config, %{strict: false})
IO.puts("โŒ Non-empty merge with frozen config succeeded (unexpected)")
rescue
RuntimeError ->
IO.puts("โœ… Non-empty merge with frozen config failed (expected)")
end
# Example 4: Builder Pattern
IO.puts("\n๐Ÿ—๏ธ Example 4: Builder Pattern")
# Build configuration fluently
api_config = Exdantic.Config.builder()
|> Exdantic.Config.Builder.strict(true)
|> Exdantic.Config.Builder.forbid_extra()
|> Exdantic.Config.Builder.safe_coercion()
|> Exdantic.Config.Builder.detailed_errors()
|> Exdantic.Config.Builder.frozen(true)
|> Exdantic.Config.Builder.build()
IO.puts("โœ… API config built:")
IO.inspect(Exdantic.Config.summary(api_config), pretty: true)
# Build configuration with convenience methods
dev_config = Exdantic.Config.builder()
|> Exdantic.Config.Builder.allow_extra()
|> Exdantic.Config.Builder.aggressive_coercion()
|> Exdantic.Config.Builder.case_insensitive()
|> Exdantic.Config.Builder.simple_errors()
|> Exdantic.Config.Builder.build()
IO.puts("โœ… Development config built:")
IO.inspect(Exdantic.Config.summary(dev_config), pretty: true)
# Example 5: Conditional Configuration
IO.puts("\n๐Ÿ”€ Example 5: Conditional Configuration")
# Build configuration based on environment
env = :production # Simulate production environment
prod_config = Exdantic.Config.builder()
|> Exdantic.Config.Builder.when_true(env == :production, fn builder ->
builder
|> Exdantic.Config.Builder.strict(true)
|> Exdantic.Config.Builder.forbid_extra()
|> Exdantic.Config.Builder.frozen(true)
end)
|> Exdantic.Config.Builder.when_true(env == :development, fn builder ->
builder
|> Exdantic.Config.Builder.allow_extra()
|> Exdantic.Config.Builder.aggressive_coercion()
end)
|> Exdantic.Config.Builder.build()
IO.puts("โœ… Environment-specific config (#{env}):")
IO.inspect(Exdantic.Config.summary(prod_config), pretty: true)
# Example 6: Preset Application with Builder
IO.puts("\n๐ŸŽจ Example 6: Preset Application with Builder")
# Start with a preset and customize it
custom_api_config = Exdantic.Config.builder()
|> Exdantic.Config.Builder.apply_preset(:api)
|> Exdantic.Config.Builder.case_insensitive() # Override case sensitivity
|> Exdantic.Config.Builder.max_union_length(10) # Custom union length
|> Exdantic.Config.Builder.build()
IO.puts("โœ… Customized API config:")
IO.inspect(Exdantic.Config.summary(custom_api_config), pretty: true)
# Example 7: Configuration for Different Use Cases
IO.puts("\n๐ŸŽฏ Example 7: Configuration for Different Use Cases")
# Configuration for API endpoints
api_endpoint_config = Exdantic.Config.builder()
|> Exdantic.Config.Builder.for_api()
|> Exdantic.Config.Builder.build()
# Configuration for JSON schema generation
schema_gen_config = Exdantic.Config.builder()
|> Exdantic.Config.Builder.for_json_schema()
|> Exdantic.Config.Builder.build()
# Configuration for development
development_config = Exdantic.Config.builder()
|> Exdantic.Config.Builder.for_development()
|> Exdantic.Config.Builder.build()
# Configuration for production
production_config = Exdantic.Config.builder()
|> Exdantic.Config.Builder.for_production()
|> Exdantic.Config.Builder.build()
use_cases = [
{"API Endpoint", api_endpoint_config},
{"JSON Schema", schema_gen_config},
{"Development", development_config},
{"Production", production_config}
]
for {name, config} <- use_cases do
summary = Exdantic.Config.summary(config)
IO.puts("โœ… #{name}: #{summary.validation_mode}, extra: #{summary.extra_fields}")
end
# Example 8: Configuration Validation and Error Handling
IO.puts("\n๐Ÿšจ Example 8: Configuration Validation and Error Handling")
# Create a valid configuration
valid_config = Exdantic.Config.create(%{strict: true, extra: :forbid})
case Exdantic.Config.validate_config(valid_config) do
:ok ->
IO.puts("โœ… Valid configuration passed validation")
{:error, reasons} ->
IO.puts("โŒ Valid configuration failed: #{inspect(reasons)}")
end
# Test invalid configuration options
try do
Exdantic.Config.create(%{invalid_option: true})
IO.puts("โŒ Invalid option accepted (unexpected)")
rescue
ArgumentError ->
IO.puts("โœ… Invalid option rejected (expected)")
end
# Example 9: Configuration Conversion
IO.puts("\n๐Ÿ”„ Example 9: Configuration Conversion")
# Create a comprehensive configuration
comprehensive_config = Exdantic.Config.create(%{
strict: true,
extra: :forbid,
coercion: :safe,
validate_assignment: true,
case_sensitive: false,
error_format: :detailed,
use_enum_values: true,
max_anyof_union_len: 3
})
# Convert to validation options
validation_opts = Exdantic.Config.to_validation_opts(comprehensive_config)
IO.puts("โœ… Validation options:")
IO.inspect(validation_opts, pretty: true)
# Convert to JSON schema options
json_schema_opts = Exdantic.Config.to_json_schema_opts(comprehensive_config)
IO.puts("โœ… JSON Schema options:")
IO.inspect(json_schema_opts, pretty: true)
# Example 10: Configuration Testing and Validation
IO.puts("\n๐Ÿงช Example 10: Configuration Testing with Real Data")
# Create test schema and data
test_schema = Exdantic.Runtime.create_schema([
{:name, :string, [required: true, min_length: 2]},
{:email, :string, [required: true, format: ~r/@/]},
{:age, :integer, [required: false, gt: 0]}
])
test_data = %{
name: "John",
email: "john@example.com",
age: "30", # String that can be coerced
extra_field: "should be handled per config"
}
# Test different configurations
test_configs = [
{"Strict + No Coercion", Exdantic.Config.preset(:strict)},
{"Lenient + Safe Coercion", Exdantic.Config.preset(:lenient)},
{"Development", Exdantic.Config.preset(:development)},
{"Production", Exdantic.Config.preset(:production)}
]
for {name, config} <- test_configs do
case Exdantic.EnhancedValidator.validate(test_schema, test_data, config: config) do
{:ok, validated} ->
IO.puts("โœ… #{name}: Validation succeeded")
type_name = cond do
is_integer(validated.age) -> "integer"
is_binary(validated.age) -> "string"
true -> "other"
end
IO.puts(" Age coerced: #{inspect(validated.age)} (#{type_name})")
{:error, errors} ->
IO.puts("โŒ #{name}: Validation failed")
IO.puts(" Reason: #{hd(errors).message}")
end
end
# Helper function to show type
# Helper function definitions removed - replaced with inline logic above
# Example 11: Builder Validation and Error Recovery
IO.puts("\n๐Ÿ”ง Example 11: Builder Validation and Error Recovery")
# Create a builder and validate before building
builder = Exdantic.Config.builder()
|> Exdantic.Config.Builder.strict(true)
|> Exdantic.Config.Builder.forbid_extra()
|> Exdantic.Config.Builder.safe_coercion()
case Exdantic.Config.Builder.validate_and_build(builder) do
{:ok, config} ->
IO.puts("โœ… Builder validation succeeded")
IO.inspect(Exdantic.Config.summary(config), pretty: true)
{:error, reasons} ->
IO.puts("โŒ Builder validation failed: #{inspect(reasons)}")
end
# Example 12: Configuration Introspection
IO.puts("\n๐Ÿ” Example 12: Configuration Introspection")
# Create a complex configuration
complex_config = Exdantic.Config.builder()
|> Exdantic.Config.Builder.strict(true)
|> Exdantic.Config.Builder.safe_coercion()
|> Exdantic.Config.Builder.validate_assignment(true)
|> Exdantic.Config.Builder.use_enum_values(true)
|> Exdantic.Config.Builder.case_sensitive(false)
|> Exdantic.Config.Builder.build()
# Inspect configuration properties
IO.puts("โœ… Configuration introspection:")
IO.puts(" Allow extra fields: #{Exdantic.Config.allow_extra_fields?(complex_config)}")
IO.puts(" Should coerce: #{Exdantic.Config.should_coerce?(complex_config)}")
IO.puts(" Coercion level: #{Exdantic.Config.coercion_level(complex_config)}")
summary = Exdantic.Config.summary(complex_config)
IO.puts(" Enabled features: #{inspect(summary.features)}")
IO.puts("""
๐ŸŽฏ Summary
==========
This example demonstrated:
1. ๐Ÿ“ Basic configuration creation from maps and keywords
2. ๐ŸŽ›๏ธ Predefined configuration presets for common scenarios
3. ๐Ÿ”€ Configuration merging with frozen config behavior
4. ๐Ÿ—๏ธ Fluent builder pattern for readable configuration
5. ๐Ÿ”€ Conditional configuration based on environment
6. ๐ŸŽจ Preset application with builder customization
7. ๐ŸŽฏ Purpose-built configurations for different use cases
8. ๐Ÿšจ Configuration validation and error handling
9. ๐Ÿ”„ Configuration conversion to validation options
10. ๐Ÿงช Real-world testing with different configurations
11. ๐Ÿ”ง Builder validation and error recovery
12. ๐Ÿ” Configuration introspection and property checking
Advanced Configuration enables DSPy-style ConfigDict patterns with
runtime modification, immutability controls, and flexible validation behavior.
""")
# Clean exit
:ok