Class: Servactory::ToolKit::DynamicOptions::Format

Inherits:
Must
  • Object
show all
Defined in:
lib/servactory/tool_kit/dynamic_options/format.rb

Overview

Validates string values against predefined or custom format patterns.

## Purpose

Format provides pattern-based validation for string attributes using regular expressions and custom validators. It includes built-in formats for common use cases (UUID, email, date, etc.) and supports custom format definitions.

## Usage

This option is **NOT included by default**. Register it for each attribute type where you want to use it:

“‘ruby configuration do

input_option_helpers([
  Servactory::ToolKit::DynamicOptions::Format.use
])

internal_option_helpers([
  Servactory::ToolKit::DynamicOptions::Format.use
])

output_option_helpers([
  Servactory::ToolKit::DynamicOptions::Format.use
])

end “‘

Use built-in formats in your service:

“‘ruby class MyService < ApplicationService::Base

input :user_id, type: String, format: :uuid
input :email, type: String, format: :email
input :birth_date, type: String, format: :date

end “‘

Add custom formats:

“‘ruby Format.use(formats:

phone: {
  pattern: /\A\+?[1-9]\d{6,14\z/,
  validator: ->(value:) { value.present? }
}

}) “‘

## Supported Formats

| Format | Description | |——–|————-| | ‘:uuid` | Standard UUID format (8-4-4-4-12 hex digits) | | `:email` | Email address per RFC 2822 | | `:password` | 8-16 chars with digit, lowercase, uppercase | | `:duration` | ISO 8601 duration (e.g., “PT1H30M”) | | `:date` | Parseable date string | | `:datetime` | Parseable datetime string | | `:time` | Parseable time string | | `:boolean` | Truthy boolean string (“true” or “1”) |

## Simple Mode

Specify format directly as the option value:

“‘ruby class ValidateUserService < ApplicationService::Base

input :uuid, type: String, format: :uuid
input :email, type: String, format: :email
input :password, type: String, format: :password

end “‘

## Advanced Mode

Specify format with custom error message using a hash:

With static message:

“‘ruby input :email, type: String, format: {

is: :email,
message: "Input `email` must be a valid email address"

} “‘

With dynamic lambda message:

“‘ruby input :email, type: String, format: {

is: :email,
message: lambda do |input:, value:, option_value:, **|
  "Input `#{input.name}` with value `#{value}` does not match `#{option_value}` format"
end

} “‘

Lambda receives the following parameters:

  • For inputs: ‘input:, value:, option_value:, reason:, **`

  • For internals: ‘internal:, value:, option_value:, reason:, **`

  • For outputs: ‘output:, value:, option_value:, reason:, **`

## Important Notes

  • Optional inputs with blank values skip validation

  • Custom patterns can be strings or Regexp objects

  • Validators receive ‘value:` keyword argument

  • Unknown format names return ‘:unknown` error

  • Format validation is two-phase: pattern check (if defined), then validator callback

  • The ‘:boolean` format pattern matches “true”, “false”, “0”, “1”, but validator only passes for truthy values (“true”, “1”); “false” and “0” will fail validation

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Must

#equivalent_with, #initialize, #must, #must_content_message_with, #must_content_value_with, #must_content_with

Constructor Details

This class inherits a constructor from Servactory::ToolKit::DynamicOptions::Must

Class Method Details

.use(option_name = :format, formats: {}) ⇒ Servactory::Maintenance::Attributes::OptionHelper

Creates a Format validator instance.

Parameters:

  • option_name (Symbol) (defaults to: :format)

    The option name (default: :format)

  • formats (Hash) (defaults to: {})

    Custom format definitions to merge with defaults

Returns:



183
184
185
186
187
# File 'lib/servactory/tool_kit/dynamic_options/format.rb', line 183

def self.use(option_name = :format, formats: {})
  instance = new(option_name)
  instance.assign(formats)
  instance.must(:be_in_format)
end

Instance Method Details

#assign(formats = {}) ⇒ void

This method returns an undefined value.

Assigns format definitions, merging with defaults.

Parameters:

  • formats (Hash) (defaults to: {})

    Custom formats to add



193
194
195
# File 'lib/servactory/tool_kit/dynamic_options/format.rb', line 193

def assign(formats = {})
  @formats = formats.is_a?(Hash) ? DEFAULT_FORMATS.merge(formats) : DEFAULT_FORMATS
end

#common_condition_with(value:, option:, input: nil, internal: nil, output: nil) ⇒ Boolean, Array

Common format validation logic.

rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

Parameters:

  • value (Object)

    Value to validate

  • option (WorkOption)

    Format configuration

  • input (Object, nil) (defaults to: nil)

    Input attribute if applicable

  • internal (Object, nil) (defaults to: nil)

    Internal attribute if applicable

  • output (Object, nil) (defaults to: nil)

    Output attribute if applicable

Returns:

  • (Boolean, Array)

    true if valid, or [false, reason]



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/servactory/tool_kit/dynamic_options/format.rb', line 236

def common_condition_with(value:, option:, input: nil, internal: nil, output: nil)
  option_value = option.value&.to_sym

  # Check if format exists.
  return [false, :unknown] unless @formats.key?(option_value)

  attribute = Utils.define_attribute_with(input:, internal:, output:)

  # Skip validation for blank optional values.
  if value.blank? &&
     (
       (attribute.input? && attribute.optional?) ||
         (
           (attribute.internal? || attribute.output?) &&
             attribute.types.include?(NilClass)
         )
     )
    return true
  end

  format_options = @formats.fetch(option_value)

  # Get pattern from option properties or format definition.
  format_pattern = option.properties.fetch(:pattern, format_options.fetch(:pattern))

  # Validate against pattern if defined.
  if format_pattern.present?
    return [false, :wrong_type] unless value.respond_to?(:match?)

    compiled_pattern = format_pattern.is_a?(Regexp) ? format_pattern : Regexp.compile(format_pattern)
    return [false, :wrong_pattern] unless value.match?(compiled_pattern)
  end

  # Run validator callback.
  option.properties.fetch(:validator, format_options.fetch(:validator)).call(value:)
end

#condition_for_input_withBoolean, Array

Validates format condition for input attribute.

Parameters:

  • input (Object)

    Input attribute object

  • value (Object)

    String value to validate

  • option (WorkOption)

    Format configuration

Returns:

  • (Boolean, Array)

    true if valid, or [false, reason]



203
204
205
# File 'lib/servactory/tool_kit/dynamic_options/format.rb', line 203

def condition_for_input_with(...)
  common_condition_with(...)
end

#condition_for_internal_withBoolean, Array

Validates format condition for internal attribute.

Parameters:

  • internal (Object)

    Internal attribute object

  • value (Object)

    String value to validate

  • option (WorkOption)

    Format configuration

Returns:

  • (Boolean, Array)

    true if valid, or [false, reason]



213
214
215
# File 'lib/servactory/tool_kit/dynamic_options/format.rb', line 213

def condition_for_internal_with(...)
  common_condition_with(...)
end

#condition_for_output_withBoolean, Array

Validates format condition for output attribute.

Parameters:

  • output (Object)

    Output attribute object

  • value (Object)

    String value to validate

  • option (WorkOption)

    Format configuration

Returns:

  • (Boolean, Array)

    true if valid, or [false, reason]



223
224
225
# File 'lib/servactory/tool_kit/dynamic_options/format.rb', line 223

def condition_for_output_with(...)
  common_condition_with(...)
end

#message_for_input_with(service:, input:, value:, option_name:, option_value:, reason:) ⇒ String

Generates error message for input validation failure.

Parameters:

  • service (Object)

    Service context

  • input (Object)

    Input attribute

  • value (Object)

    Failed value

  • option_name (Symbol)

    Option name

  • option_value (Symbol)

    Format name

  • reason (Symbol)

    Failure reason

Returns:

  • (String)

    Localized error message



285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/servactory/tool_kit/dynamic_options/format.rb', line 285

def message_for_input_with(service:, input:, value:, option_name:, option_value:, reason:, **)
  i18n_key = "inputs.validations.must.dynamic_options.format"
  i18n_key += reason.present? ? ".#{reason}" : ".default"

  service.translate(
    i18n_key,
    input_name: input.name,
    value:,
    option_name:,
    format_name: option_value.presence || option_value.inspect
  )
end

#message_for_internal_with(service:, internal:, value:, option_name:, option_value:, reason:) ⇒ String

Generates error message for internal validation failure.

Parameters:

  • service (Object)

    Service context

  • internal (Object)

    Internal attribute

  • value (Object)

    Failed value

  • option_name (Symbol)

    Option name

  • option_value (Symbol)

    Format name

  • reason (Symbol)

    Failure reason

Returns:

  • (String)

    Localized error message



307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/servactory/tool_kit/dynamic_options/format.rb', line 307

def message_for_internal_with(service:, internal:, value:, option_name:, option_value:, reason:, **)
  i18n_key = "internals.validations.must.dynamic_options.format"
  i18n_key += reason.present? ? ".#{reason}" : ".default"

  service.translate(
    i18n_key,
    internal_name: internal.name,
    value:,
    option_name:,
    format_name: option_value.presence || option_value.inspect
  )
end

#message_for_output_with(service:, output:, value:, option_name:, option_value:, reason:) ⇒ String

Generates error message for output validation failure.

Parameters:

  • service (Object)

    Service context

  • output (Object)

    Output attribute

  • value (Object)

    Failed value

  • option_name (Symbol)

    Option name

  • option_value (Symbol)

    Format name

  • reason (Symbol)

    Failure reason

Returns:

  • (String)

    Localized error message



329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/servactory/tool_kit/dynamic_options/format.rb', line 329

def message_for_output_with(service:, output:, value:, option_name:, option_value:, reason:, **)
  i18n_key = "outputs.validations.must.dynamic_options.format"
  i18n_key += reason.present? ? ".#{reason}" : ".default"

  service.translate(
    i18n_key,
    output_name: output.name,
    value:,
    option_name:,
    format_name: option_value.presence || option_value.inspect
  )
end