#!/usr/bin/env elixir # Wrapper Models Example # Run with: elixir examples/wrapper_models.exs Mix.install([{:exdantic, path: "."}]) IO.puts(""" šŸŽ Exdantic Wrapper Models Example ================================= This example demonstrates temporary single-field validation schemas for complex type coercion, similar to Pydantic's create_model("Wrapper", value=(type, ...)). """) # Example 1: Basic Wrapper Creation and Validation IO.puts("\nšŸ“ Example 1: Basic Wrapper Creation and Validation") # Create a simple wrapper for score validation score_wrapper = Exdantic.Wrapper.create_wrapper(:score, :integer, constraints: [gteq: 0, lteq: 100], description: "Test score between 0 and 100" ) IO.puts("āœ… Created score wrapper: #{score_wrapper.name}") # Test valid scores valid_scores = [85, 92, 76, 100, 0] for score <- valid_scores do case Exdantic.Wrapper.validate_and_extract(score_wrapper, score, :score) do {:ok, validated} -> IO.puts("āœ… Score #{score} -> #{validated}") {:error, _errors} -> IO.puts("āŒ Score #{score} -> Error") end end # Test invalid scores invalid_scores = [-5, 150, "not_a_number"] for score <- invalid_scores do case Exdantic.Wrapper.validate_and_extract(score_wrapper, score, :score) do {:ok, validated} -> IO.puts("āœ… Score #{inspect(score)} -> #{validated} (unexpected)") {:error, errors} -> IO.puts("āŒ Score #{inspect(score)} -> #{hd(errors).message}") end end # Example 2: One-Step Wrapper Validation IO.puts("\n⚔ Example 2: One-Step Wrapper Validation") # Skip the wrapper creation step for simple validations one_step_tests = [ {:email, :string, "user@example.com", [constraints: [format: ~r/@/]]}, {:age, :integer, "25", [coerce: true, constraints: [gt: 0]]}, {:percentage, :float, "87.5", [coerce: true, constraints: [gteq: 0.0, lteq: 100.0]]}, {:username, :string, "john_doe", [constraints: [min_length: 3, max_length: 20]]} ] for {field, type, value, opts} <- one_step_tests do case Exdantic.Wrapper.wrap_and_validate(field, type, value, opts) do {:ok, validated} -> IO.puts("āœ… #{field}: #{inspect(value)} -> #{inspect(validated)}") {:error, errors} -> IO.puts("āŒ #{field}: #{inspect(value)} -> #{hd(errors).message}") end end # Example 3: Flexible Input Handling IO.puts("\nšŸ”€ Example 3: Flexible Input Handling") # Create a flexible wrapper that can handle different input formats age_wrapper = Exdantic.Wrapper.create_flexible_wrapper(:age, :integer, coerce: true) # Test different input formats flexible_inputs = [ {25, "Raw integer value"}, {%{age: 30}, "Map with atom key"}, {%{"age" => "35"}, "Map with string key (needs coercion)"}, {"40", "String value (needs coercion)"} ] for {input, description} <- flexible_inputs do case Exdantic.Wrapper.validate_flexible(age_wrapper, input, :age) do {:ok, validated} -> IO.puts("āœ… #{description}: #{inspect(input)} -> #{validated}") {:error, _errors} -> IO.puts("āŒ #{description}: #{inspect(input)} -> Error") end end # Example 4: Multiple Wrapper Operations IO.puts("\nšŸ“¦ Example 4: Multiple Wrapper Operations") # Create multiple wrappers for a user registration form user_specs = [ {:username, :string, [constraints: [min_length: 3, max_length: 20]]}, {:email, :string, [constraints: [format: ~r/^[^\s@]+@[^\s@]+\.[^\s@]+$/]]}, {:age, :integer, [constraints: [gteq: 13, lteq: 120]]}, {:newsletter, :boolean, [default: false]} ] user_wrappers = Exdantic.Wrapper.create_multiple_wrappers(user_specs) IO.puts("āœ… Created #{map_size(user_wrappers)} user field wrappers") # Validate a complete user registration registration_data = %{ username: "john_doe", email: "john@example.com", age: 25, newsletter: true } case Exdantic.Wrapper.validate_multiple(user_wrappers, registration_data) do {:ok, validated_user} -> IO.puts("āœ… User registration validated:") IO.inspect(validated_user, pretty: true) {:error, errors_by_field} -> IO.puts("āŒ User registration failed:") for {field, errors} <- errors_by_field do IO.puts(" #{field}: #{hd(errors).message}") end end # Example 5: Wrapper Factory Pattern IO.puts("\nšŸ­ Example 5: Wrapper Factory Pattern") # Create reusable wrapper factories for common types email_factory = Exdantic.Wrapper.create_wrapper_factory(:string, constraints: [format: ~r/^[^\s@]+@[^\s@]+\.[^\s@]+$/], description: "Email address validation" ) url_factory = Exdantic.Wrapper.create_wrapper_factory(:string, constraints: [format: ~r/^https?:\/\/.+/], description: "URL validation" ) # Use factories to create specific field wrappers user_email_wrapper = email_factory.(:user_email) admin_email_wrapper = email_factory.(:admin_email) website_url_wrapper = url_factory.(:website_url) profile_url_wrapper = url_factory.(:profile_url) # Test the factory-created wrappers factory_tests = [ {user_email_wrapper, "user@example.com", :user_email}, {admin_email_wrapper, "admin@company.com", :admin_email}, {website_url_wrapper, "https://example.com", :website_url}, {profile_url_wrapper, "invalid-url", :profile_url} ] for {wrapper, value, field} <- factory_tests do case Exdantic.Wrapper.validate_and_extract(wrapper, value, field) do {:ok, validated} -> IO.puts("āœ… #{field}: #{value} -> #{validated}") {:error, errors} -> IO.puts("āŒ #{field}: #{value} -> #{hd(errors).message}") end end # Example 6: Complex Type Wrappers IO.puts("\nšŸŽÆ Example 6: Complex Type Wrappers") # Wrapper for array validation tags_wrapper = Exdantic.Wrapper.create_wrapper(:tags, {:array, :string}, constraints: [min_items: 1, max_items: 5], description: "User tags (1-5 strings)" ) tags_data = ["elixir", "programming", "web-development"] case Exdantic.Wrapper.validate_and_extract(tags_wrapper, tags_data, :tags) do {:ok, validated} -> IO.puts("āœ… Tags validated: #{inspect(validated)}") {:error, _errors} -> IO.puts("āŒ Tags validation failed") end # Wrapper for map validation metadata_wrapper = Exdantic.Wrapper.create_wrapper(:metadata, {:map, {:string, :any}}, description: "Flexible metadata map" ) metadata = %{"version" => "1.0", "author" => "John", "settings" => %{"theme" => "dark"}} case Exdantic.Wrapper.validate_and_extract(metadata_wrapper, metadata, :metadata) do {:ok, validated} -> IO.puts("āœ… Metadata validated: #{inspect(validated)}") {:error, _errors} -> IO.puts("āŒ Metadata validation failed") end # Example 7: JSON Schema Generation from Wrappers IO.puts("\nšŸ“‹ Example 7: JSON Schema Generation from Wrappers") # Generate JSON schemas from wrapper schemas json_schema = Exdantic.Wrapper.to_json_schema(score_wrapper) IO.puts("āœ… Score wrapper JSON schema:") IO.puts(Jason.encode!(json_schema, pretty: true)) # Example 8: Wrapper Information and Introspection IO.puts("\nšŸ” Example 8: Wrapper Information and Introspection") # Get information about wrapper schemas wrapper_info = Exdantic.Wrapper.wrapper_info(score_wrapper) IO.puts("āœ… Score wrapper information:") IO.inspect(wrapper_info, pretty: true) # Check if a schema is a wrapper IO.puts("Is score_wrapper a wrapper? #{Exdantic.Wrapper.wrapper_schema?(score_wrapper)}") # Example 9: Error Handling and Recovery IO.puts("\n🚨 Example 9: Error Handling and Recovery") # Create a wrapper with multiple constraints strict_password_wrapper = Exdantic.Wrapper.create_wrapper(:password, :string, constraints: [ min_length: 8, max_length: 100, format: ~r/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/ # At least one lower, upper, digit ], description: "Strong password requirements" ) password_tests = [ "weakpass", # Too simple "SHORT", # Too short "longenoughbutnostrongpattern", # No uppercase/digits "ValidPass123" # Should pass ] for password <- password_tests do case Exdantic.Wrapper.validate_and_extract(strict_password_wrapper, password, :password) do {:ok, _validated} -> IO.puts("āœ… Password '#{password}' accepted") {:error, errors} -> IO.puts("āŒ Password '#{password}' rejected:") Enum.each(errors, &IO.puts(" - #{&1.message}")) end end # Example 10: Performance and Reuse Patterns IO.puts("\n⚔ Example 10: Performance and Reuse Patterns") # Create a wrapper once and reuse it many times id_wrapper = Exdantic.Wrapper.create_wrapper(:id, :integer, constraints: [gt: 0], description: "Positive integer ID" ) # Generate test data test_ids = 1..1000 |> Enum.to_list() # Time the validation using the same wrapper {time_us, results} = :timer.tc(fn -> Enum.map(test_ids, fn id -> Exdantic.Wrapper.validate_and_extract(id_wrapper, id, :id) end) end) successful = Enum.count(results, &match?({:ok, _}, &1)) time_ms = time_us / 1000 IO.puts("āœ… Validated #{successful} IDs in #{Float.round(time_ms, 2)}ms") IO.puts(" Average: #{Float.round(time_ms / 1000, 4)}ms per validation") # Compare with one-step validation (less efficient) {time_one_step_us, _} = :timer.tc(fn -> Enum.map(test_ids, fn id -> Exdantic.Wrapper.wrap_and_validate(:id, :integer, id, constraints: [gt: 0]) end) end) IO.puts(" One-step validation: #{Float.round(time_one_step_us / 1000, 2)}ms") IO.puts(" Reuse speedup: #{Float.round(time_one_step_us / time_us, 2)}x") IO.puts(""" šŸŽÆ Summary ========== This example demonstrated: 1. āœ… Basic wrapper creation and validation 2. ⚔ One-step wrapper validation for convenience 3. šŸ”€ Flexible input handling (raw values, maps with atom/string keys) 4. šŸ“¦ Multiple wrapper operations for complex forms 5. šŸ­ Wrapper factory pattern for reusable type definitions 6. šŸŽÆ Complex type wrappers (arrays, maps, unions) 7. šŸ“‹ JSON Schema generation from wrappers 8. šŸ” Wrapper introspection and information 9. 🚨 Comprehensive error handling and validation 10. ⚔ Performance optimization through wrapper reuse Wrapper models provide temporary validation schemas perfect for DSPy-style single-field validation with complex constraints and coercion. """)