#!/usr/bin/env elixir # Enhanced Validator Example # Run with: elixir examples/enhanced_validator.exs Mix.install([{:exdantic, path: "."}]) IO.puts(""" šŸš€ Exdantic Enhanced Validator Example ===================================== This example demonstrates the universal validation interface that works with runtime schemas, compiled schemas, type specifications, and advanced configuration. """) # First, let's create some schemas for testing defmodule UserSchema do use Exdantic schema "User account information" do field :name, :string do description("User's full name") min_length(2) max_length(50) end field :email, :string do description("User's email address") format(~r/^[^\s@]+@[^\s@]+\.[^\s@]+$/) end field :age, :integer do description("User's age") optional() gt(0) lt(150) end config do title("User Schema") strict(true) end end end # Example 1: Universal Validation Interface IO.puts("\nšŸŽÆ Example 1: Universal Validation Interface") user_data = %{ name: "John Doe", email: "john@example.com", age: 30 } # Validate against compiled schema case Exdantic.EnhancedValidator.validate(UserSchema, user_data) do {:ok, validated} -> IO.puts("āœ… Compiled schema validation succeeded:") IO.inspect(validated, pretty: true) {:error, _errors} -> IO.puts("āŒ Compiled schema validation failed") end # Create a runtime schema runtime_schema = Exdantic.Runtime.create_schema([ {:product_name, :string, [required: true, min_length: 1]}, {:price, :float, [required: true, gt: 0.0]}, {:category, :string, [required: false]} ]) product_data = %{ product_name: "Laptop", price: 999.99, category: "Electronics" } # Validate against runtime schema case Exdantic.EnhancedValidator.validate(runtime_schema, product_data) do {:ok, validated} -> IO.puts("āœ… Runtime schema validation succeeded:") IO.inspect(validated, pretty: true) {:error, _errors} -> IO.puts("āŒ Runtime schema validation failed") end # Validate against type specification type_spec = {:array, {:map, {:string, :integer}}} type_data = [%{"count" => 1}, %{"total" => 100}] case Exdantic.EnhancedValidator.validate(type_spec, type_data) do {:ok, validated} -> IO.puts("āœ… Type specification validation succeeded:") IO.inspect(validated, pretty: true) {:error, _errors} -> IO.puts("āŒ Type specification validation failed") end # Example 2: Configuration-Driven Validation IO.puts("\nāš™ļø Example 2: Configuration-Driven Validation") test_data = %{ name: "Jane", email: "jane@example.com", extra_field: "should be handled based on config" } # Create different configurations strict_config = Exdantic.Config.create(%{ strict: true, extra: :forbid, coercion: :none, error_format: :detailed }) lenient_config = Exdantic.Config.create(%{ strict: false, extra: :allow, coercion: :safe, error_format: :simple }) # Test with strict configuration IO.puts("Testing with strict configuration:") case Exdantic.EnhancedValidator.validate(UserSchema, test_data, config: strict_config) do {:ok, _validated} -> IO.puts("āœ… Strict validation passed (unexpected)") {:error, errors} -> IO.puts("āŒ Strict validation rejected extra field (expected):") Enum.each(errors, &IO.puts(" - #{Exdantic.Error.format(&1)}")) end # Test with lenient configuration IO.puts("Testing with lenient configuration:") case Exdantic.EnhancedValidator.validate(UserSchema, test_data, config: lenient_config) do {:ok, validated} -> IO.puts("āœ… Lenient validation passed:") IO.inspect(validated, pretty: true) {:error, _errors} -> IO.puts("āŒ Lenient validation failed") end # Example 3: Wrapper Validation IO.puts("\nšŸŽ Example 3: Wrapper Validation") # Validate single values with coercion coercion_config = Exdantic.Config.create(coercion: :safe) coercion_tests = [ {:score, :integer, "85"}, {:percentage, :float, "87.5"}, {:active, :boolean, "true"}, {:tags, {:array, :string}, ["elixir", "programming"]} ] for {field, type, value} <- coercion_tests do case Exdantic.EnhancedValidator.validate_wrapped(field, type, value, config: coercion_config) do {:ok, validated} -> IO.puts("āœ… #{field}: #{inspect(value)} -> #{inspect(validated)}") {:error, _errors} -> IO.puts("āŒ #{field}: #{inspect(value)} -> Error") end end # Example 4: Batch Validation IO.puts("\nšŸ“¦ Example 4: Batch Validation") # Validate multiple values of the same type usernames = ["alice", "bob", "charlie", "x", "very_long_username_that_exceeds_limit"] username_type = Exdantic.Types.string() |> Exdantic.Types.with_constraints([min_length: 2, max_length: 20]) case Exdantic.EnhancedValidator.validate_many(username_type, usernames) do {:ok, validated_usernames} -> IO.puts("āœ… All usernames valid: #{inspect(validated_usernames)}") {:error, errors_by_index} -> IO.puts("āŒ Some usernames invalid:") for {index, errors} <- errors_by_index do username = Enum.at(usernames, index) IO.puts(" #{username}: #{hd(errors).message}") end end # Example 5: Validation with Schema Generation IO.puts("\nšŸ“‹ Example 5: Validation with Schema Generation") # Validate and generate JSON schema simultaneously api_schema = Exdantic.Runtime.create_schema([ {:endpoint, :string, [required: true]}, {:method, :string, [choices: ["GET", "POST", "PUT", "DELETE"]]}, {:headers, {:map, {:string, :string}}, [required: false]} ]) api_request = %{ endpoint: "/api/users", method: "GET", headers: %{"Authorization" => "Bearer token123"} } case Exdantic.EnhancedValidator.validate_with_schema(api_schema, api_request) do {:ok, validated_data, json_schema} -> IO.puts("āœ… API request validated:") IO.inspect(validated_data, pretty: true) IO.puts("Generated JSON Schema:") IO.puts(Jason.encode!(json_schema, pretty: true)) {:error, _errors} -> IO.puts("āŒ API request validation failed") end # Example 6: LLM Provider Optimization IO.puts("\nšŸ¤– Example 6: LLM Provider Optimization") # Create a schema and optimize it for different LLM providers llm_schema = Exdantic.Runtime.create_schema([ {:reasoning, :string, [required: true, description: "Chain of thought"]}, {:answer, :string, [required: true, description: "Final answer"]}, {:confidence, :float, [gteq: 0.0, lteq: 1.0, description: "Confidence score"]} ]) llm_response = %{ reasoning: "The user is asking about...", answer: "Based on the analysis...", confidence: 0.95 } # Optimize for OpenAI case Exdantic.EnhancedValidator.validate_for_llm(llm_schema, llm_response, :openai) do {:ok, _validated, openai_schema} -> IO.puts("āœ… OpenAI-optimized validation succeeded") IO.puts("OpenAI schema constraints:") IO.puts(" additionalProperties: #{openai_schema["additionalProperties"]}") {:error, _errors} -> IO.puts("āŒ OpenAI validation failed") end # Optimize for Anthropic case Exdantic.EnhancedValidator.validate_for_llm(llm_schema, llm_response, :anthropic) do {:ok, _validated, anthropic_schema} -> IO.puts("āœ… Anthropic-optimized validation succeeded") IO.puts("Anthropic schema has required array: #{Map.has_key?(anthropic_schema, "required")}") {:error, _errors} -> IO.puts("āŒ Anthropic validation failed") end # Example 7: Validation Pipelines IO.puts("\nšŸ”„ Example 7: Validation Pipelines") # Create a validation and transformation pipeline text_processing_pipeline = [ :string, # Validate as string fn s -> {:ok, String.trim(s)} end, # Trim whitespace fn s -> {:ok, String.downcase(s)} end, # Convert to lowercase fn s -> if String.length(s) > 0, do: {:ok, s}, else: {:error, "empty string"} end, :string # Final validation ] pipeline_inputs = [ " Hello World ", "ELIXIR", " ", "programming" ] for input <- pipeline_inputs do case Exdantic.EnhancedValidator.pipeline(text_processing_pipeline, input) do {:ok, result} -> IO.puts("āœ… Pipeline: #{inspect(input)} -> #{inspect(result)}") {:error, {step_index, _errors}} -> IO.puts("āŒ Pipeline failed at step #{step_index}: #{inspect(input)}") end end # Example 8: Validation Reports for Debugging IO.puts("\nšŸ” Example 8: Validation Reports for Debugging") # Generate comprehensive validation reports debug_data = %{ user_id: 12345, preferences: %{ theme: "dark", notifications: true, language: "en" }, metadata: [ %{key: "created_at", value: "2023-01-01"}, %{key: "updated_at", value: "2023-06-15"} ] } debug_schema = Exdantic.Runtime.create_schema([ {:user_id, :integer, [required: true]}, {:preferences, {:map, {:string, :any}}, [required: true]}, {:metadata, {:array, {:map, {:string, :string}}}, [required: false]} ]) report = Exdantic.EnhancedValidator.validation_report(debug_schema, debug_data) IO.puts("āœ… Validation Report Generated:") IO.puts(" Validation result: #{elem(report.validation_result, 0)}") IO.puts(" Target type: #{report.target_info.type}") IO.puts(" Input type: #{report.input_analysis.type}") IO.puts(" Duration: #{report.performance_metrics.duration_milliseconds}ms") IO.puts(" Configuration: #{report.configuration.validation_mode}") # Example 9: Error Recovery Patterns IO.puts("\n🚨 Example 9: Error Recovery Patterns") # Demonstrate error handling with fallback strategies unreliable_data = [ %{name: "Valid User", email: "valid@example.com"}, %{name: "No Email User"}, # Missing required field %{name: "Bad Email", email: "invalid-email"}, # Invalid format %{email: "orphan@example.com"}, # Missing name ] recovery_config = Exdantic.Config.create(error_format: :detailed) validated_users = [] failed_users = [] for {user_data, index} <- Enum.with_index(unreliable_data) do case Exdantic.EnhancedValidator.validate(UserSchema, user_data, config: recovery_config) do {:ok, validated} -> _validated_users = [validated | validated_users] IO.puts("āœ… User #{index}: Validated successfully") {:error, errors} -> _failed_users = [{index, user_data, errors} | failed_users] IO.puts("āŒ User #{index}: Validation failed") Enum.each(errors, &IO.puts(" #{Exdantic.Error.format(&1)}")) end end IO.puts("Summary: #{length(validated_users)} valid, #{length(failed_users)} failed") # Example 10: Performance Benchmarking IO.puts("\n⚔ Example 10: Performance Benchmarking") # Compare different validation approaches benchmark_data = for i <- 1..1000 do %{id: i, name: "User #{i}", email: "user#{i}@example.com"} end # Benchmark compiled schema validation {time_compiled_us, _} = :timer.tc(fn -> Enum.each(benchmark_data, fn data -> Exdantic.EnhancedValidator.validate(UserSchema, data) end) end) # Benchmark runtime schema validation benchmark_schema = Exdantic.Runtime.create_schema([ {:id, :integer, [required: true]}, {:name, :string, [required: true]}, {:email, :string, [required: true]} ]) {time_runtime_us, _} = :timer.tc(fn -> Enum.each(benchmark_data, fn data -> Exdantic.EnhancedValidator.validate(benchmark_schema, data) end) end) # Benchmark type specification validation type_spec = {:map, {:string, :any}} {time_typespec_us, _} = :timer.tc(fn -> Enum.each(benchmark_data, fn data -> Exdantic.EnhancedValidator.validate(type_spec, data) end) end) IO.puts("āœ… Performance comparison (1000 validations):") IO.puts(" Compiled schema: #{Float.round(time_compiled_us / 1000, 2)}ms") IO.puts(" Runtime schema: #{Float.round(time_runtime_us / 1000, 2)}ms") IO.puts(" Type spec: #{Float.round(time_typespec_us / 1000, 2)}ms") IO.puts(""" šŸŽÆ Summary ========== This example demonstrated: 1. šŸŽÆ Universal validation interface (compiled, runtime, type specs) 2. āš™ļø Configuration-driven validation behavior 3. šŸŽ Wrapper validation for single values 4. šŸ“¦ Batch validation for multiple values 5. šŸ“‹ Validation with simultaneous JSON schema generation 6. šŸ¤– LLM provider-specific optimizations 7. šŸ”„ Validation and transformation pipelines 8. šŸ” Comprehensive validation reports for debugging 9. 🚨 Error recovery and handling patterns 10. ⚔ Performance benchmarking across approaches Enhanced Validator provides a unified interface for all Exdantic validation capabilities, making it perfect for complex applications with varying validation requirements. """) # Clean exit :ok