### Install Athena::Validator Source: https://github.com/athena-framework/validator/blob/master/docs/README.md Instructions for adding the Athena::Validator component to your project's dependencies using `shard.yml` and installing it with `shards install`. ```yaml dependencies: athena-validator: github: athena-framework/validator version: ~> 0.4.0 ``` -------------------------------- ### Obtain Validator Instance and Validate Values in Crystal Source: https://context7.com/athena-framework/validator/llms.txt Demonstrates how to get a validator instance using `AVD.validator` and then use it to validate individual values against single or multiple constraints. It shows how to check the result for emptiness to determine validity. ```crystal require "athena-validator" # Obtain a validator instance validator = AVD.validator # Validate a value against a single constraint violations = validator.validate "foo", AVD::Constraints::NotBlank.new # Check if valid violations.empty? # => true # Validate against multiple constraints violations = validator.validate "", [ AVD::Constraints::NotBlank.new, AVD::Constraints::Email.new ] puts violations # : # This value should not be blank. (code: 0d0c3254-3642-4cb0-9882-46ee5918e6e3) ``` -------------------------------- ### Implementing Sequential Validation with GroupSequence Source: https://github.com/athena-framework/validator/blob/master/docs/README.md Shows how to use `AVD::Constraints::GroupSequence` to define an ordered sequence of validation constraint batches. Validation proceeds to the next batch only if all constraints in the current batch are valid. This example illustrates how a violation in an early group prevents subsequent groups from being validated. ```crystal @[Assert::GroupSequence("User", "Secondary")] class User include AVD::Validatable @[Assert::NotBlank] getter username : String @[Assert::NotBlank(groups: "Secondary")] getter password : String def initialize(@username : String, @password : String); end end # Instantiate a new `User` object where both properties are invalid. user = User.new "", "" # Notice there is only one violation since there was a violation in the `User` group, # it did not advance to the `Secondary` group. AVD.validator.validate user # => # Object(User).username: # This value should not be blank. (code: 0d0c3254-3642-4cb0-9882-46ee5918e6e3) ``` -------------------------------- ### Validate Objects with Annotations in Crystal Source: https://context7.com/athena-framework/validator/llms.txt Shows how to validate entire objects by including `AVD::Validatable` and applying constraint annotations directly to class properties using the `Assert` alias. Includes examples of both valid and invalid object validation. ```crystal require "athena-validator" class User include AVD::Validatable def initialize(@name : String, @email : String, @age : Int32); end @[Assert::NotBlank] @[Assert::Length(3..50)] getter name : String @[Assert::NotBlank] @[Assert::Email] getter email : String @[Assert::GreaterThan(0)] getter age : Int32 end validator = AVD.validator # Valid user user = User.new("John Doe", "john@example.com", 25) violations = validator.validate(user) violations.empty? # => true # Invalid user invalid_user = User.new("", "not-an-email", -5) violations = validator.validate(invalid_user) puts violations # Object(User).name: # This value should not be blank. (code: 0d0c3254-3642-4cb0-9882-46ee5918e6e3) # Object(User).email: # This value is not a valid email address. (code: ad9d877d-9ad1-4dd7-b77b-e419934e5910) # Object(User).age: # This value should be greater than 0. (code: a221096d-d125-44e8-a865-4270379ac11a) ``` -------------------------------- ### Apply Constraints to Getter Methods Source: https://github.com/athena-framework/validator/blob/master/docs/README.md Shows how to use method-level annotations to perform dynamic validation based on the return value of a getter method. ```crystal class User include AVD::Validatable property name : String property password : String def initialize(@name : String, @password : String); end @[Assert::IsTrue(message: "Your password cannot be the same as your name.")] def is_safe_password? : Bool @name != @password end end validator = AVD.validator user = User.new "foo", "foo" validator.validate(user).empty? # => false ``` -------------------------------- ### Basic Value Validation with NotBlank Constraint (Crystal) Source: https://github.com/athena-framework/validator/blob/master/docs/README.md Demonstrates how to obtain a validator instance and use it to validate a string value against the `NotBlank` constraint. It shows the expected output for a valid input, resulting in an empty violation list. ```crystal # Obtain a validator instance. validator = AVD.validator # Use the validator to validate a value. violations = validator.validate "foo", AVD::Constraints::NotBlank.new # The validator returns an empty list of violations, indicating the value is valid. violations.inspect # => Athena::Validator::Violation::ConstraintViolationList(@violations=[]) ``` -------------------------------- ### Dynamic Group Sequence Provider Source: https://github.com/athena-framework/validator/blob/master/docs/README.md Illustrates how to make the validation sequence dynamic at runtime by including the `AVD::Constraints::GroupSequence::Provider` module. This allows an object to determine its validation sequence based on its current state or other runtime conditions. ```crystal class User include AVD::Validatable include AVD::Constraints::GroupSequence::Provider # ... def group_sequence : Array(Array(String) | String) | AVD::Constraints::GroupSequence # Build out and return the sequence `self` should use. end end ``` -------------------------------- ### Create Custom Constraints Source: https://github.com/athena-framework/validator/blob/master/docs/README.md Explains how to implement a custom constraint by inheriting from AVD::Constraint and defining a nested Validator class inheriting from AVD::ConstraintValidator. ```crystal class AVD::Constraints::AlphaNumeric < AVD::Constraint NOT_ALPHANUMERIC_ERROR = "1a83a8bd-ff79-4d5c-96e7-86d0b25b8a09" def initialize(message : String = "This value should contain only alphanumeric characters.", groups : Array(String) | String | Nil = nil, payload : Hash(String, String)? = nil) super message, groups, payload end class Validator < AVD::ConstraintValidator def validate(value : _, constraint : AVD::Constraints::AlphaNumeric) : Nil return if value.nil? || value == "" value = value.to_s return if value.each_char.all? &.alphanumeric? self.context.add_violation constraint.message, NOT_ALPHANUMERIC_ERROR, value end end end puts AVD.validator.validate "$", AVD::Constraints::AlphaNumeric.new ``` -------------------------------- ### Define and Validate Class Metadata Source: https://github.com/athena-framework/validator/blob/master/docs/README.md Demonstrates how to include AVD::Validatable in a class and use self.load_metadata to define property constraints. The validator instance is then used to check object integrity. ```crystal class User include AVD::Validatable def self.load_metadata(metadata : AVD::Metadata::ClassMetadata) : Nil metadata.add_property_constraint "name", AVD::Constraints::NotBlank.new end def initialize(@name : String); end getter name : String end validator = AVD.validator validator.validate(User.new("Jim")).empty? # => true validator.validate User.new "" ``` -------------------------------- ### Validate Object Properties with Annotations (Crystal) Source: https://github.com/athena-framework/validator/blob/master/docs/README.md Shows how to define a class that includes AVD::Validatable and uses annotations like @Assert::NotBlank and @Assert::NotNil to specify validation rules for its properties. It then demonstrates validating instances of this class. ```crystal # Define a class that can be validated. class User include AVD::Validatable def initialize(@name : String, @age : Int32? = nil); end # Specify that we want to assert that the user's name is not blank. # Multiple constraints can be defined on a single property. @[Assert::NotBlank] getter name : String # Arguments to the constraint can be used normally as well. # The constraint's default argument can also be supplied positionally: `@[Assert::GreaterThan(0)]`. @[Assert::NotNil(message: "A user's age cannot be null")] getter age : Int32? end # Obtain a validator instance. validator = AVD.validator # Validate a user instance, notice we're not passing in any constraints. validator.validate(User.new("Jim", 10)).empty? # => true validator.validate User.new "", 10 # => # Object(User).name: # This value should not be blank. (code: 0d0c3254-3642-4cb0-9882-46ee5918e6e3) ``` -------------------------------- ### Applying Validation Groups to Constraints Source: https://github.com/athena-framework/validator/blob/master/docs/README.md Demonstrates how to assign constraints to specific validation groups using the `groups` argument. This allows selective validation of an object's properties based on the group specified during the validation process. If no group is provided, the 'default' group is used. ```crystal class User include AVD::Validatable def initialize(@email : String, @password : String, @city : String); end @[Assert::Email(groups: "create")] getter email : String @[Assert::NotBlank(groups: "create")] @[Assert::Size(7.., groups: "create")] getter password : String @[Assert::Size(2..)] getter city : String end user = User.new "contact@athenaframework.org", "monkey123", "" # Validate the user object, but only for those in the "create" group, # if no groups are supplied, then all constraints in the "default" group will be used. violations = AVD.validator.validate user, groups: "create" # There are no violations since the city's size is not validated since it's not in the "create" group. violations.empty? # => true ``` -------------------------------- ### Programmatic Constraint Configuration in Crystal Source: https://context7.com/athena-framework/validator/llms.txt Illustrates how to configure validation constraints programmatically by overriding the `self.load_metadata` class method, instead of using annotations. This approach allows for dynamic constraint definition. ```crystal require "athena-validator" class Product include AVD::Validatable def self.load_metadata(metadata : AVD::Metadata::ClassMetadata) : Nil metadata.add_property_constraint "name", AVD::Constraints::NotBlank.new metadata.add_property_constraint "price", AVD::Constraints::GreaterThan.new(value: 0) metadata.add_property_constraints "sku", [ AVD::Constraints::NotBlank.new, AVD::Constraints::Length.new(8..12) ] end def initialize(@name : String, @price : Float64, @sku : String); end getter name : String getter price : Float64 getter sku : String end validator = AVD.validator product = Product.new("", -10.0, "ABC") violations = validator.validate(product) puts violations # Object(Product).name: # This value should not be blank. (code: 0d0c3254-3642-4cb0-9882-46ee5918e6e3) # Object(Product).price: # This value should be greater than 0. (code: a221096d-d125-44e8-a865-4270379ac11a) # Object(Product).sku: # This value is too short. It should have 8 characters or more. (code: 643f9d15-a5fd-41b7-b6d8-85f40855ba11) ``` -------------------------------- ### NotBlank Constraint Usage in Crystal Source: https://context7.com/athena-framework/validator/llms.txt Explains and demonstrates the `NotBlank` constraint in Athena Validator. It covers validating various types (string, array, boolean) for emptiness and shows how to allow `nil` values by configuring the constraint. ```crystal require "athena-validator" validator = AVD.validator # Valid values validator.validate("hello", AVD::Constraints::NotBlank.new).empty? # => true validator.validate([1, 2, 3], AVD::Constraints::NotBlank.new).empty? # => true validator.validate(true, AVD::Constraints::NotBlank.new).empty? # => true # Invalid values violations = validator.validate("", AVD::Constraints::NotBlank.new) puts violations # : # This value should not be blank. (code: 0d0c3254-3642-4cb0-9882-46ee5918e6e3) # Allow nil values constraint = AVD::Constraints::NotBlank.new(allow_nil: true) validator.validate(nil, constraint).empty? # => true ``` -------------------------------- ### Validate Collections and Hashes with Athena Validator (Crystal) Source: https://context7.com/athena-framework/validator/llms.txt This snippet demonstrates how to validate the structure and values of collections and hashes using `AVD::Constraints::Collection`. It shows how to define constraints for specific keys, including multiple constraints for a single key, and how to handle optional and required fields. It also illustrates how to disallow extra fields in the validated data. ```crystal require "athena-validator" validator = AVD.validator # Validate a hash structure constraint = AVD::Constraints::Collection.new({ "email" => AVD::Constraints::Email.new, "name" => [ AVD::Constraints::NotBlank.new, AVD::Constraints::Length.new(2..50) ], "age" => AVD::Constraints::GreaterThan.new(value: 0) }) data = { "email" => "invalid", "name" => "", "age" => -5 } violations = validator.validate(data, constraint) puts violations # [email]: # This value is not a valid email address. (code: ad9d877d-9ad1-4dd7-b77b-e419934e5910) # [name]: # This value should not be blank. (code: 0d0c3254-3642-4cb0-9882-46ee5918e6e3) # [age]: # This value should be greater than 0. (code: a221096d-d125-44e8-a865-4270379ac11a) # With optional and required fields constraint = AVD::Constraints::Collection.new({ "username" => AVD::Constraints::Required.new([ AVD::Constraints::NotBlank.new, AVD::Constraints::Length.new(3..20) ]), "nickname" => AVD::Constraints::Optional.new([ AVD::Constraints::Length.new(..30) ] of AVD::Constraint) }) # Missing required field violations = validator.validate({"nickname" => "nick"}, constraint) puts violations # [username]: # This field is missing. (code: af103ee5-3bcb-448e-98ad-b4ef76c05060) # Extra field detection constraint = AVD::Constraints::Collection.new( fields: {"name" => AVD::Constraints::NotBlank.new}, allow_extra_fields: false ) violations = validator.validate({"name" => "John", "extra" => "value"}, constraint) puts violations # [extra]: # This field was not expected. (code: 70e60467-4078-4f92-acf9-d1e6683d0922) ``` -------------------------------- ### Value Validation Failure with NotBlank Constraint (Crystal) Source: https://github.com/athena-framework/validator/blob/master/docs/README.md Illustrates a scenario where validating an empty string against the `NotBlank` constraint results in a `ConstraintViolation`. It shows the detailed structure of the violation list and the individual violation object. ```crystal # Using the validator instance from the previous example violations = validator.validate "", AVD::Constraints::NotBlank.new violations.inspect # => # Athena::Validator::Violation::ConstraintViolationList( # @violations=[ # Athena::Validator::Violation::ConstraintViolation( # @cause=nil, # @code="0d0c3254-3642-4cb0-9882-46ee5918e6e3", # @constraint=#, # @invalid_value_container=Athena::Validator::ValueContainer(String)(@value=""), # @message="This value should not be blank.", # @message_template="This value should not be blank.", # @parameters={"{{ value }}" => ""}, # @plural=nil, # @property_path="", # @root_container=Athena::Validator::ValueContainer(String)(@value="") # ) # ] # ) # Both the ConstraintViolationList and ConstraintViolation implement a `#to_s` method. puts violations # => # : ``` -------------------------------- ### Define Constraints with Annotations Source: https://context7.com/athena-framework/validator/llms.txt Demonstrates how to apply validation constraints directly to class properties using Athena Validator annotations. This approach allows for declarative validation rules within domain models. ```crystal class Product include AVD::Validatable def initialize(@sku : String); end @[Assert::AlphaNumeric] getter sku : String end ``` -------------------------------- ### Validate Value with Custom Message (Crystal) Source: https://github.com/athena-framework/validator/blob/master/docs/README.md Demonstrates validating a single value against a constraint (PositiveOrZero) and providing a custom error message with a placeholder. The output shows the violation object, including the custom message and a unique code. ```crystal validator = AVD.validator # Instantiate a constraint with a custom message, using a placeholder. violations = validator.validate -4, AVD::Constraints::PositiveOrZero.new message: "{{ value }} is not a valid age. A user cannot have a negative age." puts violations # => # -4: # -4 is not a valid age. A user cannot have a negative age. (code: e09e52d0-b549-4ba1-8b4e-420aad76f0de) ``` -------------------------------- ### Access Violation Details Source: https://context7.com/athena-framework/validator/llms.txt Illustrates how to inspect individual violation objects to retrieve metadata such as the property path, error code, invalid value, and constraint parameters. ```crystal require "athena-validator" class Order include AVD::Validatable def initialize(@quantity : Int32); end @[Assert::Range(1..100, not_in_range_message: "Order quantity must be between {{ min }} and {{ max }}.")] getter quantity : Int32 end validator = AVD.validator order = Order.new(150) violations = validator.validate(order) violations.each do |violation| puts "Property: #{violation.property_path}" puts "Message: #{violation.message}" puts "Code: #{violation.code}" puts "Invalid Value: #{violation.invalid_value}" puts "Root: #{violation.root.class}" puts "Constraint: #{violation.constraint.class}" puts "Parameters: #{violation.parameters}" end ``` -------------------------------- ### Create Compound Reusable Constraints in Crystal Source: https://context7.com/athena-framework/validator/llms.txt Defines reusable sets of constraints by extending AVD::Constraints::Compound. This allows for consistent validation logic across different models via a single custom annotation. ```crystal require "athena-validator" class AVD::Constraints::StrongPassword < AVD::Constraints::Compound def constraints : Type [ AVD::Constraints::NotBlank.new, AVD::Constraints::Length.new(12..), AVD::Constraints::Regex.new(/[A-Z]/, message: "Password must contain uppercase."), AVD::Constraints::Regex.new(/[a-z]/, message: "Password must contain lowercase."), AVD::Constraints::Regex.new(/\d/, message: "Password must contain a digit."), AVD::Constraints::Regex.new(/[!@#$%^&*]/, message: "Password must contain special char."), ] end end ``` -------------------------------- ### Validate against Regex Pattern using Crystal Source: https://context7.com/athena-framework/validator/llms.txt Checks if a string conforms to a specified regular expression pattern using the Regex constraint. This is useful for validating formats like usernames, passwords, or custom identifiers. ```crystal require "athena-validator" validator = AVD.validator # Username pattern: alphanumeric with underscores, 3-20 chars pattern = /^[a-zA-Z0-9_]{3,20}$/ constraint = AVD::Constraints::Regex.new(pattern) validator.validate("john_doe123", constraint).empty? # => true violations = validator.validate("a", constraint) puts violations # a: ``` -------------------------------- ### Comparison Constraints (GreaterThan, LessThan, EqualTo) Source: https://context7.com/athena-framework/validator/llms.txt Enforces numerical or temporal comparisons. Supports GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, EqualTo, and NotEqualTo constraints. Can be used with primitive types and Time objects. ```crystal require "athena-validator" validator = AVD.validator # GreaterThan validator.validate(10, AVD::Constraints::GreaterThan.new(value: 5)).empty? # => true violations = validator.validate(3, AVD::Constraints::GreaterThan.new(value: 5)) puts violations # 3: # This value should be greater than 5. (code: a221096d-d125-44e8-a865-4270379ac11a) # GreaterThanOrEqual validator.validate(5, AVD::Constraints::GreaterThanOrEqual.new(value: 5)).empty? # => true # LessThan validator.validate(3, AVD::Constraints::LessThan.new(value: 5)).empty? # => true # LessThanOrEqual validator.validate(5, AVD::Constraints::LessThanOrEqual.new(value: 5)).empty? # => true # EqualTo validator.validate("test", AVD::Constraints::EqualTo.new(value: "test")).empty? # => true # NotEqualTo validator.validate("different", AVD::Constraints::NotEqualTo.new(value: "test")).empty? # => true # Time comparisons now = Time.utc constraint = AVD::Constraints::GreaterThan.new(value: now - 1.day) validator.validate(now, constraint).empty? # => true # With annotation shorthand class Order include AVD::Validatable def initialize(@quantity : Int32, @discount : Float64); end @[Assert::GreaterThan(0)] getter quantity : Int32 @[Assert::LessThanOrEqual(100)] getter discount : Float64 end ``` -------------------------------- ### Test Custom Validators Source: https://context7.com/athena-framework/validator/llms.txt Utilizes the AVD::Spec::ConstraintValidatorTestCase helper to create unit tests for custom validation logic, ensuring constraints behave as expected under various inputs. ```crystal require "athena-validator/spec" private alias CONSTRAINT = AVD::Constraints::NotBlank struct NotBlankValidatorTest < AVD::Spec::ConstraintValidatorTestCase def create_validator : AVD::ConstraintValidatorInterface CONSTRAINT::Validator.new end def constraint_class : AVD::Constraint.class CONSTRAINT end def test_valid_value : Nil self.validator.validate "hello", self.new_constraint self.assert_no_violation end def test_blank_string_is_invalid : Nil self.validator.validate "", self.new_constraint message: "my_message" self .build_violation("my_message", CONSTRAINT::IS_BLANK_ERROR, "") .assert_violation end end ``` -------------------------------- ### Develop Custom Constraint Validators in Crystal Source: https://context7.com/athena-framework/validator/llms.txt Implements custom validation logic by extending AVD::Constraint and providing a nested Validator class. This is useful for complex domain-specific validation rules. ```crystal class AVD::Constraints::AlphaNumeric < AVD::Constraint NOT_ALPHANUMERIC_ERROR = "1a83a8bd-ff79-4d5c-96e7-86d0b25b8a09" class Validator < AVD::ConstraintValidator def validate(value : _, constraint : AVD::Constraints::AlphaNumeric) : Nil return if value.nil? || value == "" value = value.to_s return if value.each_char.all? &.alphanumeric? self.context.add_violation constraint.message, NOT_ALPHANUMERIC_ERROR, value end end end ``` -------------------------------- ### Callback Constraint for Custom Validation Source: https://context7.com/athena-framework/validator/llms.txt Enables custom validation logic through instance methods or procs. Allows complex validation rules that cannot be expressed by built-in constraints. ```crystal require "athena-validator" # Instance method callback class Account include AVD::Validatable RESERVED_USERNAMES = ["admin", "root", "system"] def initialize(@username : String, @password : String); end @[Assert::NotBlank] getter username : String @[Assert::NotBlank] getter password : String @[Assert::Callback] def validate_username(context : AVD::ExecutionContextInterface, payload : Hash(String, String)?) : Nil return unless RESERVED_USERNAMES.includes?(@username.downcase) context .build_violation("This username is reserved and cannot be used.") .at_path("username") .add end @[Assert::IsTrue(message: "Password cannot be the same as username.")] def is_password_safe? : Bool @username.downcase != @password.downcase end end validator = AVD.validator account = Account.new("admin", "secret123") violations = validator.validate(account) puts violations # Object(Account).username: # This username is reserved and cannot be used. # Proc-based callback for direct validation constraint = AVD::Constraints::Callback.with_callback do |value, context, payload| num = value.get(Int32) next if num.even? context.add_violation "This value should be even." end violations = validator.validate(7, constraint) puts violations # 7: # This value should be even. ``` -------------------------------- ### Regex Constraint Source: https://context7.com/athena-framework/validator/llms.txt Validates that a value matches a specific regular expression pattern. ```APIDOC ## Regex Constraint ### Description Validates that a value matches a regular expression pattern. ### Parameters - **pattern** (Regex) - Required - The pattern to match against. ### Request Example ```crystal validator.validate("john_doe123", AVD::Constraints::Regex.new(/^[a-z0-9_]{3,20}$/)) ``` ``` -------------------------------- ### URL Constraint Validation Source: https://context7.com/athena-framework/validator/llms.txt Validates if a string is a well-formed URL. Supports options to require a Top-Level Domain (TLD), specify allowed protocols, and handle relative protocol URLs. ```crystal require "athena-validator" validator = AVD.validator # Valid URLs validator.validate("https://example.com", AVD::Constraints::URL.new).empty? # => true validator.validate("http://example.com/path?query=1", AVD::Constraints::URL.new).empty? # => true # Invalid URLs violations = validator.validate("not-a-url", AVD::Constraints::URL.new) puts violations # not-a-url: # This value is not a valid URL. (code: e87ceba6-a896-4906-9957-b102045272ee) # URL without TLD violations = validator.validate("https://example", AVD::Constraints::URL.new) puts violations # https://example: # This URL is missing a top-level domain. (code: 4507f4cc-90fd-4616-989b-2166fc0d1083) # Allow URLs without TLD constraint = AVD::Constraints::URL.new(require_tld: false) validator.validate("https://localhost", constraint).empty? # => true # Custom protocols constraint = AVD::Constraints::URL.new(protocols: ["http", "https", "ftp"]) validator.validate("ftp://files.example.com", constraint).empty? # => true # Relative protocol URLs constraint = AVD::Constraints::URL.new(relative_protocol: true) validator.validate("//example.com/path", constraint).empty? # => true ``` -------------------------------- ### Selective Validation with Groups using Athena Validator (Crystal) Source: https://context7.com/athena-framework/validator/llms.txt This snippet demonstrates how to use validation groups in Athena Validator to selectively apply constraints. By assigning `groups` to annotations, you can control which constraints are validated when a specific group or a list of groups is requested during the validation process. This is useful for scenarios like differentiating validation rules for registration versus profile updates. ```crystal require "athena-validator" class User include AVD::Validatable def initialize(@email : String, @password : String, @address : String); end # Only validated in "registration" group @[Assert::Email(groups: "registration")] @[Assert::NotBlank(groups: "registration")] getter email : String @[Assert::NotBlank(groups: "registration")] @[Assert::Length(8.., groups: "registration")] getter password : String # In default group (always validated unless specific group requested) @[Assert::NotBlank] getter address : String end validator = AVD.validator user = User.new("", "", "") # Validate only "registration" group violations = validator.validate(user, groups: "registration") puts violations # Object(User).email: # This value should not be blank. (code: 0d0c3254-3642-4cb0-9882-46ee5918e6e3) # Object(User).password: # This value should not be blank. (code: 0d0c3254-3642-4cb0-9882-46ee5918e6e3) # Validate default group (address constraint) violations = validator.validate(user) puts violations # Object(User).address: # This value should not be blank. (code: 0d0c3254-3642-4cb0-9882-46ee5918e6e3) # Validate multiple groups violations = validator.validate(user, groups: ["registration", "default"]) # Validates both registration and default constraints ``` -------------------------------- ### Regex Constraint Validation Source: https://context7.com/athena-framework/validator/llms.txt Validates that a string matches or does not match a given regular expression pattern. Supports custom error messages and inverted matching. ```crystal require "athena-validator" validator = AVD.validator # Inverted match (value should NOT match pattern) bad_words_pattern = /spam|banned/i constraint = AVD::Constraints::Regex.new(bad_words_pattern, match: false) validator.validate("hello world", constraint).empty? # => true violations = validator.validate("this is spam", constraint) puts violations.empty? # => false # With custom message constraint = AVD::Constraints::Regex.new( pattern: /^[A-Z]{2}\d{4}$/, message: "Product code must be 2 uppercase letters followed by 4 digits." ) violations = validator.validate("invalid", constraint) puts violations.first.message # => "Product code must be 2 uppercase letters followed by 4 digits." ``` -------------------------------- ### Validate Choice from a Set using Crystal Source: https://context7.com/athena-framework/validator/llms.txt Verifies if a value is present in a predefined list of acceptable options using the Choice constraint. It supports single value selection and multiple selections with range constraints. ```crystal require "athena-validator" validator = AVD.validator # Single value choice constraint = AVD::Constraints::Choice.new(["draft", "published", "archived"]) validator.validate("published", constraint).empty? # => true violations = validator.validate("invalid", constraint) puts violations # invalid: # This value is not a valid choice. (code: c7398ea5-e787-4ee9-9fca-5f2c130614d6) # Multiple choices constraint = AVD::Constraints::Choice.new( choices: ["php", "crystal", "ruby", "python"], multiple: true, range: 1..3 # Must select 1-3 choices ) validator.validate(["crystal", "ruby"], constraint).empty? # => true # Too many selections violations = validator.validate(["php", "crystal", "ruby", "python"], constraint) puts violations # ["php", "crystal", "ruby", "python"]: # You must select at most 3 choices. (code: 91d0d22b-a693-4b9c-8b41-bc6392cf89f4) # With annotation class Article include AVD::Validatable def initialize(@status : String); end @[Assert::Choice(["draft", "review", "published"])] getter status : String end ``` -------------------------------- ### Validate Nested Objects with Athena Validator (Crystal) Source: https://context7.com/athena-framework/validator/llms.txt This snippet shows how to validate nested objects using the `@[Assert::Valid]` annotation in Athena Validator. It defines `Address` and `Person` classes, where `Person` contains an `Address` object. The `@[Assert::Valid]` on the `address` getter ensures that the constraints defined within the `Address` class are also checked during validation. ```crystal require "athena-validator" class Address include AVD::Validatable def initialize(@street : String, @city : String, @zip : String); end @[Assert::NotBlank] getter street : String @[Assert::NotBlank] getter city : String @[Assert::Regex(/^\d{5}$/)] getter zip : String end class Person include AVD::Validatable def initialize(@name : String, @address : Address); end @[Assert::NotBlank] getter name : String # Without @[Assert::Valid], Address constraints would NOT be validated @[Assert::Valid] getter address : Address end validator = AVD.validator # Valid person with valid address person = Person.new( "John", Address.new("123 Main St", "Anytown", "12345") ) validator.validate(person).empty? # => true # Person with invalid address person = Person.new( "Jane", Address.new("", "", "invalid") ) violations = validator.validate(person) puts violations # Object(Person).address.street: # This value should not be blank. (code: 0d0c3254-3642-4cb0-9882-46ee5918e6e3) # Object(Person).address.city: # This value should not be blank. (code: 0d0c3254-3642-4cb0-9882-46ee5918e6e3) # Object(Person).address.zip: # This value should match '/^\d{5}$/'. (code: 108987a0-2d81-44a0-b8d4-1c7ab8815343) ``` -------------------------------- ### Choice Constraint Source: https://context7.com/athena-framework/validator/llms.txt Validates that a value exists within a provided set of allowed options. ```APIDOC ## Choice Constraint ### Description Validates that a value is one of a predefined set of valid choices. ### Parameters - **choices** (Array) - Required - List of valid options. - **multiple** (Boolean) - Optional - Whether multiple selections are allowed. - **range** (Range) - Optional - Allowed count of selections if multiple is true. ### Request Example ```crystal validator.validate("published", AVD::Constraints::Choice.new(["draft", "published"])) ``` ``` -------------------------------- ### Perform Sequential Property Validation in Crystal Source: https://context7.com/athena-framework/validator/llms.txt Applies multiple constraints to a single property in a specific order using the Sequentially annotation. The validator halts execution for that property upon the first encountered violation. ```crystal require "athena-validator" class Location include AVD::Validatable def initialize(@address : String); end @[Assert::Sequentially([ @[Assert::NotBlank], @[Assert::Length(10..)], @[Assert::Regex(/^\d+\s+\w+/)], ])] getter address : String end validator = AVD.validator location = Location.new("") violations = validator.validate(location) puts violations ``` -------------------------------- ### Email Constraint Source: https://context7.com/athena-framework/validator/llms.txt Validates that a string value adheres to a valid email address format. ```APIDOC ## Email Constraint ### Description Validates that a value is a valid email address using HTML5 email validation pattern. ### Parameters - **mode** (Enum) - Optional - Validation mode (e.g., HTML5_ALLOW_NO_TLD) - **message** (String) - Optional - Custom error message ### Request Example ```crystal validator.validate("user@example.com", AVD::Constraints::Email.new) ``` ### Response #### Success Response (200) - **valid** (Boolean) - Returns true if the email is valid. #### Error Response - **violations** (Collection) - Returns a list of violations if the email format is invalid. ``` -------------------------------- ### Validate String Length using Crystal Source: https://context7.com/athena-framework/validator/llms.txt Ensures that a string's length falls within a specified range using the Length constraint. It supports minimum, maximum, exact length, and different character units like graphemes. ```crystal require "athena-validator" validator = AVD.validator # Length within range validator.validate("hello", AVD::Constraints::Length.new(3..10)).empty? # => true # Too short violations = validator.validate("hi", AVD::Constraints::Length.new(3..10)) puts violations # hi: # This value is too short. It should have 3 characters or more. (code: 643f9d15-a5fd-41b7-b6d8-85f40855ba11) # Too long violations = validator.validate("hello world!", AVD::Constraints::Length.new(3..10)) puts violations # hello world!: # This value is too long. It should have 10 characters or less. (code: e07eee2c-be7a-4ac3-be6b-2ea344250f99) # Minimum only (endless range) validator.validate("hello", AVD::Constraints::Length.new(3..)).empty? # => true # Exact length violations = validator.validate("abc", AVD::Constraints::Length.new(5..5)) puts violations # abc: # This value should have exactly 5 characters. (code: 03ef6899-6e39-4e7a-9ac9-5f4374736273) # Different length units constraint = AVD::Constraints::Length.new(1..5, unit: AVD::Constraints::Length::Unit::GRAPHEMES) validator.validate("hello", constraint).empty? # => true ``` -------------------------------- ### Customize Error Messages and Payloads Source: https://context7.com/athena-framework/validator/llms.txt Shows how to provide custom error messages with placeholders and attach domain-specific metadata via the payload property. This is useful for internationalization or passing severity levels to the UI layer. ```crystal require "athena-validator" class User include AVD::Validatable def initialize(@age : Int32, @email : String); end @[Assert::GreaterThanOrEqual( value: 18, message: "You must be at least {{ compared_value }} years old to register.", payload: {"severity" => "error"} )] getter age : Int32 @[Assert::Email( message: "The email '{{ value }}' is not valid. Please use format: name@domain.com", payload: {"severity" => "warning"} )] getter email : String end validator = AVD.validator user = User.new(15, "not-an-email") violations = validator.validate(user) violations.each do |violation| severity = violation.constraint.payload.try(&.["severity"]?) || "info" puts "[#{severity.upcase}] #{violation.property_path}: #{violation.message}" end ``` -------------------------------- ### Implement Group Sequence Validation in Crystal Source: https://context7.com/athena-framework/validator/llms.txt Validates object constraints in sequential batches using the GroupSequence annotation. Validation stops immediately if a batch fails, preventing subsequent groups from being processed. ```crystal require "athena-validator" @[Assert::GroupSequence("User", "Strict")] class User include AVD::Validatable def initialize(@username : String, @password : String); end @[Assert::NotBlank] @[Assert::Length(3..)] getter username : String @[Assert::NotBlank] getter password : String @[Assert::IsTrue(message: "Password cannot match username.", groups: "Strict")] def is_password_different? : Bool @username.downcase != @password.downcase end end validator = AVD.validator user = User.new("", "secret") violations = validator.validate(user) puts violations ``` -------------------------------- ### Length Constraint Source: https://context7.com/athena-framework/validator/llms.txt Validates that the length of a string or collection falls within a specified range. ```APIDOC ## Length Constraint ### Description Validates that the length of a string is within a specified range or matches an exact length. ### Parameters - **range** (Range) - Required - The allowed length range (e.g., 3..10) - **unit** (Enum) - Optional - The unit of measurement (e.g., GRAPHEMES) ### Request Example ```crystal validator.validate("hello", AVD::Constraints::Length.new(3..10)) ``` ``` -------------------------------- ### Validate Email Address using Crystal Source: https://context7.com/athena-framework/validator/llms.txt Validates if a given string is a valid email address using the Email constraint. It supports different modes, including HTML5 validation patterns, and can be used with object annotations for class properties. ```crystal require "athena-validator" validator = AVD.validator # Valid emails validator.validate("user@example.com", AVD::Constraints::Email.new).empty? # => true # Invalid emails violations = validator.validate("invalid-email", AVD::Constraints::Email.new) puts violations # invalid-email: # This value is not a valid email address. (code: ad9d877d-9ad1-4dd7-b77b-e419934e5910) # Different validation modes constraint = AVD::Constraints::Email.new(mode: AVD::Constraints::Email::Mode::HTML5_ALLOW_NO_TLD) validator.validate("user@localhost", constraint).empty? # => true # With object annotation class Newsletter include AVD::Validatable def initialize(@subscriber_email : String); end @[Assert::Email(message: "Please provide a valid email address.")] getter subscriber_email : String end violations = validator.validate(Newsletter.new("bad-email")) puts violations.first.message # => "Please provide a valid email address." ``` -------------------------------- ### Range Constraint Source: https://context7.com/athena-framework/validator/llms.txt Validates that a numeric or time value is within a defined boundary. ```APIDOC ## Range Constraint ### Description Validates that a number or time value is within a specified range. ### Parameters - **range** (Range) - Required - The allowed numeric or time range. ### Request Example ```crystal validator.validate(50, AVD::Constraints::Range.new(1..100)) ``` ``` -------------------------------- ### Validate Number or Time Range using Crystal Source: https://context7.com/athena-framework/validator/llms.txt Validates that a numerical or time value falls within a specified range using the Range constraint. It supports inclusive ranges, minimum-only, and maximum-only bounds. ```crystal require "athena-validator" validator = AVD.validator # Number in range validator.validate(50, AVD::Constraints::Range.new(1..100)).empty? # => true # Number out of range violations = validator.validate(150, AVD::Constraints::Range.new(1..100)) puts violations # 150: # This value should be between 1 and 100. (code: 7e62386d-30ae-4e7c-918f-1b7e571c6d69) # Minimum only violations = validator.validate(-5, AVD::Constraints::Range.new(0..)) puts violations # -5: # This value should be 0 or more. (code: f0316644-882e-4779-a404-ee7ac97ddecc) # Maximum only violations = validator.validate(200, AVD::Constraints::Range.new(..100)) puts violations # 200: # This value should be 100 or less. (code: 5d9aed01-ac49-4d8e-9c16-e4aab74ea774) # Time ranges start_date = Time.utc(2024, 1, 1) end_date = Time.utc(2024, 12, 31) constraint = AVD::Constraints::Range.new(start_date..end_date) validator.validate(Time.utc(2024, 6, 15), constraint).empty? # => true ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.