Class: Treaty::Attribute::Option::Validators::FormatValidator

Inherits:
Base
  • Object
show all
Defined in:
lib/treaty/attribute/option/validators/format_validator.rb

Overview

Validates that string attribute value matches a specific format.

## Supported Formats

  • ‘:uuid` - Universally unique identifier

  • ‘:email` - Email address (RFC 2822 compliant)

  • ‘:password` - Password (8-16 chars, must contain digit, lowercase, and uppercase)

  • ‘:duration` - ActiveSupport::Duration compatible string (e.g., “1 day”, “2 hours”)

  • ‘:date` - ISO 8601 date string (e.g., “2025-01-15”)

  • ‘:datetime` - ISO 8601 datetime string (e.g., “2025-01-15T10:30:00Z”)

  • ‘:time` - Time string (e.g., “10:30:00”, “10:30 AM”)

  • ‘:boolean` - Boolean string (“true”, “false”, “0”, “1”)

## Usage Examples

Simple mode:

string :email, format: :email
string :started_on, format: :date

Advanced mode:

string :email, format: { is: :email }
string :started_on, format: { is: :date, message: "Invalid date format" }
string :started_on, format: { is: :date, message: ->(attribute:, value:, **) { "#{attribute} has invalid date: #{value}" } } # rubocop:disable Layout/LineLength

## Validation Rules

  • Only works with ‘:string` type attributes

  • Raises Treaty::Exceptions::Validation if used with non-string types

  • Skips validation for nil values (handled by RequiredValidator)

  • Each format has a pattern and/or validator for flexible validation

## Extensibility

To add new formats, extend DEFAULT_FORMATS hash with format definition:

DEFAULT_FORMATS[:custom_format] = {
  pattern: /regex/,
  validator: ->(value) { custom_validation_logic }
}

Constant Summary collapse

UUID_PATTERN =

UUID format regex (8-4-4-4-12 hexadecimal pattern)

/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
PASSWORD_PATTERN =

Password format regex (8-16 chars, at least one digit, lowercase, and uppercase)

/\A(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{8,16}\z/
BOOLEAN_PATTERN =

Boolean string format regex (accepts “true”, “false”, “0”, “1” case-insensitive)

/\A(true|false|0|1)\z/i
DEFAULT_FORMATS =

Default format definitions with patterns and validators Each format can have:

  • pattern: Regex for pattern matching

  • validator: Lambda for custom validation logic

{
  uuid: {
    pattern: UUID_PATTERN,
    validator: nil
  },
  email: {
    pattern: URI::MailTo::EMAIL_REGEXP,
    validator: nil
  },
  password: {
    pattern: PASSWORD_PATTERN,
    validator: nil
  },
  duration: {
    pattern: nil,
    validator: lambda do |value|
      ActiveSupport::Duration.parse(value)
      true
    rescue StandardError
      false
    end
  },
  date: {
    pattern: nil,
    validator: lambda do |value|
      Date.parse(value)
      true
    rescue ArgumentError, TypeError
      false
    end
  },
  datetime: {
    pattern: nil,
    validator: lambda do |value|
      DateTime.parse(value)
      true
    rescue ArgumentError, TypeError
      false
    end
  },
  time: {
    pattern: nil,
    validator: lambda do |value|
      Time.parse(value)
      true
    rescue ArgumentError, TypeError
      false
    end
  },
  boolean: {
    pattern: BOOLEAN_PATTERN,
    validator: nil
  }
}.freeze

Instance Method Summary collapse

Methods inherited from Base

#initialize, #target_name, #transform_value, #transforms_name?

Constructor Details

This class inherits a constructor from Treaty::Attribute::Option::Base

Instance Method Details

#validate_schema!void

This method returns an undefined value.

Validates that format is only used with string type attributes and that the format name is valid

Raises:



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/treaty/attribute/option/validators/format_validator.rb', line 120

def validate_schema! # rubocop:disable Metrics/MethodLength
  # Format option only works with string types
  unless @attribute_type == :string
    raise Treaty::Exceptions::Validation,
          I18n.t(
            "treaty.attributes.validators.format.type_mismatch",
            attribute: @attribute_name,
            type: @attribute_type
          )
  end

  format_name = option_value

  # Validate that format name exists
  return if formats.key?(format_name)

  raise Treaty::Exceptions::Validation,
        I18n.t(
          "treaty.attributes.validators.format.unknown_format",
          attribute: @attribute_name,
          format_name:,
          allowed: formats.keys.join(", ")
        )
end

#validate_value!(value) ⇒ void

This method returns an undefined value.

Validates that the value matches the specified format Skips validation for nil values (handled by RequiredValidator)

Parameters:

  • value (String)

    The value to validate

Raises:



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/treaty/attribute/option/validators/format_validator.rb', line 151

def validate_value!(value) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
  return if value.nil? # Format validation doesn't check for nil, required does.

  format_name = option_value
  format_definition = formats[format_name]

  # Allow blank values (empty strings should be caught by required validator)
  return if value.to_s.strip.empty?

  # Apply pattern matching if defined
  if format_definition.fetch(:pattern)
    return if value.match?(format_definition.fetch(:pattern))

    # Pattern failed, and no validator - raise error
    unless format_definition.fetch(:validator)
      attributes = {
        attribute: @attribute_name,
        value:,
        format_name:
      }

      message = resolve_custom_message(**attributes) || default_message(**attributes)

      raise Treaty::Exceptions::Validation, message
    end
  end

  # Apply validator if defined
  return unless format_definition.fetch(:validator)
  return if format_definition.fetch(:validator).call(value)

  attributes = {
    attribute: @attribute_name,
    value:,
    format_name:
  }

  message = resolve_custom_message(**attributes) || default_message(**attributes)

  raise Treaty::Exceptions::Validation, message
end