treaty 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/config/locales/en.yml +3 -3
- data/lib/treaty/engine.rb +1 -1
- data/lib/treaty/{attribute/entity → entity/attribute}/attribute.rb +4 -4
- data/lib/treaty/entity/attribute/base.rb +184 -0
- data/lib/treaty/entity/attribute/builder/base.rb +275 -0
- data/lib/treaty/entity/attribute/collection.rb +67 -0
- data/lib/treaty/entity/attribute/dsl.rb +92 -0
- data/lib/treaty/entity/attribute/helper_mapper.rb +74 -0
- data/lib/treaty/entity/attribute/option/base.rb +190 -0
- data/lib/treaty/entity/attribute/option/conditionals/base.rb +92 -0
- data/lib/treaty/entity/attribute/option/conditionals/if_conditional.rb +136 -0
- data/lib/treaty/entity/attribute/option/conditionals/unless_conditional.rb +153 -0
- data/lib/treaty/entity/attribute/option/modifiers/as_modifier.rb +93 -0
- data/lib/treaty/entity/attribute/option/modifiers/cast_modifier.rb +285 -0
- data/lib/treaty/entity/attribute/option/modifiers/computed_modifier.rb +128 -0
- data/lib/treaty/entity/attribute/option/modifiers/default_modifier.rb +105 -0
- data/lib/treaty/entity/attribute/option/modifiers/transform_modifier.rb +114 -0
- data/lib/treaty/entity/attribute/option/registry.rb +157 -0
- data/lib/treaty/entity/attribute/option/registry_initializer.rb +117 -0
- data/lib/treaty/entity/attribute/option/validators/format_validator.rb +222 -0
- data/lib/treaty/entity/attribute/option/validators/inclusion_validator.rb +94 -0
- data/lib/treaty/entity/attribute/option/validators/required_validator.rb +100 -0
- data/lib/treaty/entity/attribute/option/validators/type_validator.rb +219 -0
- data/lib/treaty/entity/attribute/option_normalizer.rb +168 -0
- data/lib/treaty/entity/attribute/option_orchestrator.rb +192 -0
- data/lib/treaty/entity/attribute/validation/attribute_validator.rb +147 -0
- data/lib/treaty/entity/attribute/validation/base.rb +76 -0
- data/lib/treaty/entity/attribute/validation/nested_array_validator.rb +207 -0
- data/lib/treaty/entity/attribute/validation/nested_object_validator.rb +105 -0
- data/lib/treaty/entity/attribute/validation/nested_transformer.rb +432 -0
- data/lib/treaty/entity/attribute/validation/orchestrator/base.rb +262 -0
- data/lib/treaty/entity/base.rb +90 -0
- data/lib/treaty/entity/builder.rb +44 -0
- data/lib/treaty/{info/entity → entity/info}/builder.rb +8 -8
- data/lib/treaty/{info/entity → entity/info}/dsl.rb +2 -2
- data/lib/treaty/{info/entity → entity/info}/result.rb +2 -2
- data/lib/treaty/entity.rb +7 -79
- data/lib/treaty/request/attribute/attribute.rb +1 -1
- data/lib/treaty/request/attribute/builder.rb +2 -2
- data/lib/treaty/request/entity.rb +1 -1
- data/lib/treaty/request/factory.rb +5 -5
- data/lib/treaty/request/validator.rb +1 -1
- data/lib/treaty/response/attribute/attribute.rb +1 -1
- data/lib/treaty/response/attribute/builder.rb +2 -2
- data/lib/treaty/response/entity.rb +1 -1
- data/lib/treaty/response/factory.rb +5 -5
- data/lib/treaty/response/validator.rb +1 -1
- data/lib/treaty/version.rb +1 -1
- metadata +35 -34
- data/lib/treaty/attribute/base.rb +0 -182
- data/lib/treaty/attribute/builder/base.rb +0 -273
- data/lib/treaty/attribute/collection.rb +0 -65
- data/lib/treaty/attribute/dsl.rb +0 -90
- data/lib/treaty/attribute/entity/builder.rb +0 -46
- data/lib/treaty/attribute/helper_mapper.rb +0 -72
- data/lib/treaty/attribute/option/base.rb +0 -188
- data/lib/treaty/attribute/option/conditionals/base.rb +0 -90
- data/lib/treaty/attribute/option/conditionals/if_conditional.rb +0 -134
- data/lib/treaty/attribute/option/conditionals/unless_conditional.rb +0 -151
- data/lib/treaty/attribute/option/modifiers/as_modifier.rb +0 -91
- data/lib/treaty/attribute/option/modifiers/cast_modifier.rb +0 -283
- data/lib/treaty/attribute/option/modifiers/computed_modifier.rb +0 -126
- data/lib/treaty/attribute/option/modifiers/default_modifier.rb +0 -103
- data/lib/treaty/attribute/option/modifiers/transform_modifier.rb +0 -112
- data/lib/treaty/attribute/option/registry.rb +0 -155
- data/lib/treaty/attribute/option/registry_initializer.rb +0 -115
- data/lib/treaty/attribute/option/validators/format_validator.rb +0 -220
- data/lib/treaty/attribute/option/validators/inclusion_validator.rb +0 -92
- data/lib/treaty/attribute/option/validators/required_validator.rb +0 -98
- data/lib/treaty/attribute/option/validators/type_validator.rb +0 -217
- data/lib/treaty/attribute/option_normalizer.rb +0 -166
- data/lib/treaty/attribute/option_orchestrator.rb +0 -190
- data/lib/treaty/attribute/validation/attribute_validator.rb +0 -145
- data/lib/treaty/attribute/validation/base.rb +0 -74
- data/lib/treaty/attribute/validation/nested_array_validator.rb +0 -205
- data/lib/treaty/attribute/validation/nested_object_validator.rb +0 -103
- data/lib/treaty/attribute/validation/nested_transformer.rb +0 -430
- data/lib/treaty/attribute/validation/orchestrator/base.rb +0 -260
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Treaty
|
|
4
|
-
module Attribute
|
|
5
|
-
module Option
|
|
6
|
-
module Modifiers
|
|
7
|
-
# Transforms attribute values using custom lambda functions.
|
|
8
|
-
#
|
|
9
|
-
# ## Usage Examples
|
|
10
|
-
#
|
|
11
|
-
# Simple mode:
|
|
12
|
-
# integer :amount, transform: ->(value:) { value * 100 }
|
|
13
|
-
# string :title, transform: ->(value:) { value.strip.upcase }
|
|
14
|
-
#
|
|
15
|
-
# Advanced mode with custom error message:
|
|
16
|
-
# integer :amount, transform: {
|
|
17
|
-
# is: ->(value:) { value * 100 },
|
|
18
|
-
# message: "Failed to transform amount"
|
|
19
|
-
# }
|
|
20
|
-
#
|
|
21
|
-
# ## Use Cases
|
|
22
|
-
#
|
|
23
|
-
# 1. **Request transformation**:
|
|
24
|
-
# ```ruby
|
|
25
|
-
# request do
|
|
26
|
-
# integer :amount_cents, transform: ->(value:) { value * 100 }
|
|
27
|
-
# end
|
|
28
|
-
# # Input: { amount_cents: 10 }
|
|
29
|
-
# # Service receives: { amount_cents: 1000 }
|
|
30
|
-
# ```
|
|
31
|
-
#
|
|
32
|
-
# 2. **Response transformation**:
|
|
33
|
-
# ```ruby
|
|
34
|
-
# response 200 do
|
|
35
|
-
# string :title, transform: ->(value:) { value.titleize }
|
|
36
|
-
# end
|
|
37
|
-
# # Service returns: { title: "hello world" }
|
|
38
|
-
# # Output: { title: "Hello World" }
|
|
39
|
-
# ```
|
|
40
|
-
#
|
|
41
|
-
# 3. **Complex transformations**:
|
|
42
|
-
# ```ruby
|
|
43
|
-
# string :email, transform: ->(value:) { value.downcase.strip }
|
|
44
|
-
# datetime :timestamp, transform: ->(value:) { value.iso8601 }
|
|
45
|
-
# ```
|
|
46
|
-
#
|
|
47
|
-
# ## Important Notes
|
|
48
|
-
#
|
|
49
|
-
# - Lambda must accept named argument `value:`
|
|
50
|
-
# - All exceptions raised in lambda are caught and re-raised as Validation errors
|
|
51
|
-
# - Transformation is applied during Phase 3 (after validation)
|
|
52
|
-
# - Can be combined with other options (required, default, as, etc.)
|
|
53
|
-
#
|
|
54
|
-
# ## Error Handling
|
|
55
|
-
#
|
|
56
|
-
# If the lambda raises any exception, it's caught and converted to a
|
|
57
|
-
# Treaty::Exceptions::Validation with appropriate error message.
|
|
58
|
-
#
|
|
59
|
-
# ## Advanced Mode
|
|
60
|
-
#
|
|
61
|
-
# Schema format: `{ is: lambda, message: nil }`
|
|
62
|
-
class TransformModifier < Treaty::Attribute::Option::Base
|
|
63
|
-
# Validates that transform value is a lambda
|
|
64
|
-
#
|
|
65
|
-
# @raise [Treaty::Exceptions::Validation] If transform is not a Proc/lambda
|
|
66
|
-
# @return [void]
|
|
67
|
-
def validate_schema!
|
|
68
|
-
transform_lambda = option_value
|
|
69
|
-
|
|
70
|
-
return if transform_lambda.respond_to?(:call)
|
|
71
|
-
|
|
72
|
-
raise Treaty::Exceptions::Validation,
|
|
73
|
-
I18n.t(
|
|
74
|
-
"treaty.attributes.modifiers.transform.invalid_type",
|
|
75
|
-
attribute: @attribute_name,
|
|
76
|
-
type: transform_lambda.class
|
|
77
|
-
)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Applies transformation to the value using the provided lambda
|
|
81
|
-
# Catches all exceptions and re-raises as Validation errors
|
|
82
|
-
# Skips transformation for nil values (handled by RequiredValidator)
|
|
83
|
-
#
|
|
84
|
-
# @param value [Object] The current value
|
|
85
|
-
# @param _root_data [Hash] Unused root data parameter
|
|
86
|
-
# @return [Object] Transformed value
|
|
87
|
-
def transform_value(value, _root_data = {}) # rubocop:disable Metrics/MethodLength
|
|
88
|
-
return value if value.nil? # Transform doesn't modify nil, required validator handles it.
|
|
89
|
-
|
|
90
|
-
transform_lambda = option_value
|
|
91
|
-
|
|
92
|
-
# Call lambda with named argument
|
|
93
|
-
transform_lambda.call(value:)
|
|
94
|
-
rescue StandardError => e
|
|
95
|
-
attributes = {
|
|
96
|
-
attribute: @attribute_name,
|
|
97
|
-
error: e.message
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
# Catch all exceptions from lambda execution
|
|
101
|
-
error_message = resolve_custom_message(**attributes) || I18n.t(
|
|
102
|
-
"treaty.attributes.modifiers.transform.execution_error",
|
|
103
|
-
**attributes
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
raise Treaty::Exceptions::Validation, error_message
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Treaty
|
|
4
|
-
module Attribute
|
|
5
|
-
module Option
|
|
6
|
-
# Central registry for all option processors (validators, modifiers, and conditionals).
|
|
7
|
-
#
|
|
8
|
-
# ## Purpose
|
|
9
|
-
#
|
|
10
|
-
# Provides a centralized registry pattern for managing all option processors.
|
|
11
|
-
# Enables dynamic discovery and extensibility of the option system.
|
|
12
|
-
#
|
|
13
|
-
# ## Responsibilities
|
|
14
|
-
#
|
|
15
|
-
# 1. **Registration** - Stores option processor classes
|
|
16
|
-
# 2. **Retrieval** - Provides access to registered processors
|
|
17
|
-
# 3. **Categorization** - Organizes processors by category (validator/modifier/conditional)
|
|
18
|
-
# 4. **Validation** - Checks if options are registered
|
|
19
|
-
#
|
|
20
|
-
# ## Registered Options
|
|
21
|
-
#
|
|
22
|
-
# ### Validators (sorted by position)
|
|
23
|
-
# - `:type` → TypeValidator (position: 100)
|
|
24
|
-
# - `:required` → RequiredValidator (position: 200)
|
|
25
|
-
# - `:inclusion` → InclusionValidator (position: 300)
|
|
26
|
-
# - `:format` → FormatValidator (position: 400)
|
|
27
|
-
#
|
|
28
|
-
# ### Modifiers (sorted by position)
|
|
29
|
-
# - `:transform` → TransformModifier (position: 500)
|
|
30
|
-
# - `:cast` → CastModifier (position: 600)
|
|
31
|
-
# - `:computed` → ComputedModifier (position: 700)
|
|
32
|
-
# - `:default` → DefaultModifier (position: 800)
|
|
33
|
-
# - `:as` → AsModifier (position: 900)
|
|
34
|
-
#
|
|
35
|
-
# ### Conditionals (no position - handled separately)
|
|
36
|
-
# - `:if` → IfConditional
|
|
37
|
-
# - `:unless` → UnlessConditional
|
|
38
|
-
#
|
|
39
|
-
# ## Usage
|
|
40
|
-
#
|
|
41
|
-
# Registration (done in RegistryInitializer):
|
|
42
|
-
# Registry.register(:required, RequiredValidator, category: :validator, position: 200)
|
|
43
|
-
# Registry.register(:if, IfConditional, category: :conditional)
|
|
44
|
-
#
|
|
45
|
-
# Retrieval (done in OptionOrchestrator):
|
|
46
|
-
# processor_class = Registry.processor_for(:required)
|
|
47
|
-
# processor = processor_class.new(...)
|
|
48
|
-
#
|
|
49
|
-
# ## Extensibility
|
|
50
|
-
#
|
|
51
|
-
# To add a new option:
|
|
52
|
-
# 1. Create processor class inheriting from Option::Base
|
|
53
|
-
# 2. Register it: `Registry.register(:my_option, MyProcessor, category: :validator)`
|
|
54
|
-
# 3. Option becomes available in DSL immediately
|
|
55
|
-
#
|
|
56
|
-
# ## Architecture
|
|
57
|
-
#
|
|
58
|
-
# Works with:
|
|
59
|
-
# - RegistryInitializer - Populates registry with built-in options
|
|
60
|
-
# - OptionOrchestrator - Uses registry to build processors
|
|
61
|
-
# - Option::Base - Base class for all registered processors
|
|
62
|
-
class Registry
|
|
63
|
-
class << self
|
|
64
|
-
# Register an option processor
|
|
65
|
-
#
|
|
66
|
-
# @param option_name [Symbol] The name of the option (e.g., :required, :as, :default)
|
|
67
|
-
# @param processor_class [Class] The processor class
|
|
68
|
-
# @param category [Symbol] The category (:validator, :modifier, or :conditional)
|
|
69
|
-
# @param position [Integer, nil] Execution order position (nil for conditionals)
|
|
70
|
-
def register(option_name, processor_class, category:, position: nil)
|
|
71
|
-
registry[option_name] = {
|
|
72
|
-
processor_class:,
|
|
73
|
-
category:,
|
|
74
|
-
position:
|
|
75
|
-
}
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Get processor class for an option
|
|
79
|
-
#
|
|
80
|
-
# @param option_name [Symbol] The name of the option
|
|
81
|
-
# @return [Class, nil] The processor class or nil if not found
|
|
82
|
-
def processor_for(option_name)
|
|
83
|
-
registry.dig(option_name, :processor_class)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Get category for an option
|
|
87
|
-
#
|
|
88
|
-
# @param option_name [Symbol] The name of the option
|
|
89
|
-
# @return [Symbol, nil] The category (:validator or :modifier) or nil if not found
|
|
90
|
-
def category_for(option_name)
|
|
91
|
-
registry.dig(option_name, :category)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Get position for an option
|
|
95
|
-
#
|
|
96
|
-
# @param option_name [Symbol] The name of the option
|
|
97
|
-
# @return [Integer, nil] The execution order position or nil if not set
|
|
98
|
-
def position_for(option_name)
|
|
99
|
-
registry.dig(option_name, :position)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Check if an option is registered
|
|
103
|
-
#
|
|
104
|
-
# @param option_name [Symbol] The name of the option
|
|
105
|
-
# @return [Boolean]
|
|
106
|
-
def registered?(option_name)
|
|
107
|
-
registry.key?(option_name)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Get all registered option names
|
|
111
|
-
#
|
|
112
|
-
# @return [Array<Symbol>]
|
|
113
|
-
def all_options
|
|
114
|
-
registry.keys
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Get all validators
|
|
118
|
-
#
|
|
119
|
-
# @return [Hash] Hash of option_name => processor_class for validators
|
|
120
|
-
def validators
|
|
121
|
-
registry.select { |_, info| info.fetch(:category) == :validator }
|
|
122
|
-
.transform_values { |info| info.fetch(:processor_class) }
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Get all modifiers
|
|
126
|
-
#
|
|
127
|
-
# @return [Hash] Hash of option_name => processor_class for modifiers
|
|
128
|
-
def modifiers
|
|
129
|
-
registry.select { |_, info| info.fetch(:category) == :modifier }
|
|
130
|
-
.transform_values { |info| info.fetch(:processor_class) }
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Get all conditionals
|
|
134
|
-
#
|
|
135
|
-
# @return [Hash] Hash of option_name => processor_class for conditionals
|
|
136
|
-
def conditionals
|
|
137
|
-
registry.select { |_, info| info.fetch(:category) == :conditional }
|
|
138
|
-
.transform_values { |info| info.fetch(:processor_class) }
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# Reset registry (mainly for testing)
|
|
142
|
-
def reset!
|
|
143
|
-
@registry = nil
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
private
|
|
147
|
-
|
|
148
|
-
def registry
|
|
149
|
-
@registry ||= {}
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
end
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Treaty
|
|
4
|
-
module Attribute
|
|
5
|
-
module Option
|
|
6
|
-
# Initializes and registers all built-in option processors with the Registry.
|
|
7
|
-
#
|
|
8
|
-
# ## Purpose
|
|
9
|
-
#
|
|
10
|
-
# Centralized registration point for all option processors (validators, modifiers, and conditionals).
|
|
11
|
-
# Automatically registers all built-in options when loaded.
|
|
12
|
-
#
|
|
13
|
-
# ## Responsibilities
|
|
14
|
-
#
|
|
15
|
-
# 1. **Validator Registration** - Registers all built-in validators
|
|
16
|
-
# 2. **Modifier Registration** - Registers all built-in modifiers
|
|
17
|
-
# 3. **Conditional Registration** - Registers all built-in conditionals
|
|
18
|
-
# 4. **Auto-Loading** - Executes automatically when file is loaded
|
|
19
|
-
#
|
|
20
|
-
# ## Built-in Validators (sorted by position)
|
|
21
|
-
#
|
|
22
|
-
# - `:type` → TypeValidator (position: 100) - Validates value types
|
|
23
|
-
# - `:required` → RequiredValidator (position: 200) - Validates required/optional attributes
|
|
24
|
-
# - `:inclusion` → InclusionValidator (position: 300) - Validates value is in allowed set
|
|
25
|
-
# - `:format` → FormatValidator (position: 400) - Validates string values match specific formats
|
|
26
|
-
#
|
|
27
|
-
# ## Built-in Modifiers (sorted by position)
|
|
28
|
-
#
|
|
29
|
-
# - `:transform` → TransformModifier (position: 500) - Transforms values using custom lambdas
|
|
30
|
-
# - `:cast` → CastModifier (position: 600) - Converts values between types automatically
|
|
31
|
-
# - `:computed` → ComputedModifier (position: 700) - Computes values from all raw data
|
|
32
|
-
# - `:default` → DefaultModifier (position: 800) - Provides default values
|
|
33
|
-
# - `:as` → AsModifier (position: 900) - Renames attributes
|
|
34
|
-
#
|
|
35
|
-
# ## Built-in Conditionals (no position - handled separately)
|
|
36
|
-
#
|
|
37
|
-
# - `:if` → IfConditional - Conditionally includes attributes based on runtime data
|
|
38
|
-
# - `:unless` → UnlessConditional - Conditionally excludes attributes based on runtime data
|
|
39
|
-
#
|
|
40
|
-
# ## Auto-Registration
|
|
41
|
-
#
|
|
42
|
-
# This file calls `register_all!` when loaded, ensuring all processors
|
|
43
|
-
# are available immediately.
|
|
44
|
-
#
|
|
45
|
-
# ## Adding New Options
|
|
46
|
-
#
|
|
47
|
-
# To add a new option processor:
|
|
48
|
-
#
|
|
49
|
-
# 1. Create the processor class (inherit from Option::Base)
|
|
50
|
-
# 2. Add registration call here:
|
|
51
|
-
# ```ruby
|
|
52
|
-
# def register_validators!
|
|
53
|
-
# Registry.register(:required, Validators::RequiredValidator, category: :validator)
|
|
54
|
-
# Registry.register(:my_option, Validators::MyValidator, category: :validator)
|
|
55
|
-
# end
|
|
56
|
-
# ```
|
|
57
|
-
#
|
|
58
|
-
# ## Architecture
|
|
59
|
-
#
|
|
60
|
-
# Works with:
|
|
61
|
-
# - Registry - Stores processor registrations
|
|
62
|
-
# - Option::Base - Base class for all processors
|
|
63
|
-
# - OptionOrchestrator - Uses registered processors
|
|
64
|
-
module RegistryInitializer
|
|
65
|
-
class << self
|
|
66
|
-
# Registers all built-in option processors
|
|
67
|
-
# Called automatically when this file is loaded
|
|
68
|
-
#
|
|
69
|
-
# @return [void]
|
|
70
|
-
def register_all!
|
|
71
|
-
register_validators!
|
|
72
|
-
register_modifiers!
|
|
73
|
-
register_conditionals!
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
private
|
|
77
|
-
|
|
78
|
-
# Registers all built-in validators
|
|
79
|
-
# Position determines execution order (lower = earlier)
|
|
80
|
-
#
|
|
81
|
-
# @return [void]
|
|
82
|
-
def register_validators!
|
|
83
|
-
Registry.register(:type, Validators::TypeValidator, category: :validator, position: 100)
|
|
84
|
-
Registry.register(:required, Validators::RequiredValidator, category: :validator, position: 200)
|
|
85
|
-
Registry.register(:inclusion, Validators::InclusionValidator, category: :validator, position: 300)
|
|
86
|
-
Registry.register(:format, Validators::FormatValidator, category: :validator, position: 400)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Registers all built-in modifiers
|
|
90
|
-
# Position determines execution order (lower = earlier)
|
|
91
|
-
#
|
|
92
|
-
# @return [void]
|
|
93
|
-
def register_modifiers!
|
|
94
|
-
Registry.register(:transform, Modifiers::TransformModifier, category: :modifier, position: 500)
|
|
95
|
-
Registry.register(:cast, Modifiers::CastModifier, category: :modifier, position: 600)
|
|
96
|
-
Registry.register(:computed, Modifiers::ComputedModifier, category: :modifier, position: 700)
|
|
97
|
-
Registry.register(:default, Modifiers::DefaultModifier, category: :modifier, position: 800)
|
|
98
|
-
Registry.register(:as, Modifiers::AsModifier, category: :modifier, position: 900)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Registers all built-in conditionals
|
|
102
|
-
#
|
|
103
|
-
# @return [void]
|
|
104
|
-
def register_conditionals!
|
|
105
|
-
Registry.register(:if, Conditionals::IfConditional, category: :conditional)
|
|
106
|
-
Registry.register(:unless, Conditionals::UnlessConditional, category: :conditional)
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Auto-register all options when this file is loaded
|
|
115
|
-
Treaty::Attribute::Option::RegistryInitializer.register_all!
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Treaty
|
|
4
|
-
module Attribute
|
|
5
|
-
module Option
|
|
6
|
-
module Validators
|
|
7
|
-
# Validates that string attribute value matches a specific format.
|
|
8
|
-
#
|
|
9
|
-
# ## Supported Formats
|
|
10
|
-
#
|
|
11
|
-
# - `:uuid` - Universally unique identifier
|
|
12
|
-
# - `:email` - Email address (RFC 2822 compliant)
|
|
13
|
-
# - `:password` - Password (8-16 chars, must contain digit, lowercase, and uppercase)
|
|
14
|
-
# - `:duration` - ActiveSupport::Duration compatible string (e.g., "1 day", "2 hours")
|
|
15
|
-
# - `:date` - ISO 8601 date string (e.g., "2025-01-15")
|
|
16
|
-
# - `:datetime` - ISO 8601 datetime string (e.g., "2025-01-15T10:30:00Z")
|
|
17
|
-
# - `:time` - Time string (e.g., "10:30:00", "10:30 AM")
|
|
18
|
-
# - `:boolean` - Boolean string ("true", "false", "0", "1")
|
|
19
|
-
#
|
|
20
|
-
# ## Usage Examples
|
|
21
|
-
#
|
|
22
|
-
# Simple mode:
|
|
23
|
-
# string :email, format: :email
|
|
24
|
-
# string :started_on, format: :date
|
|
25
|
-
#
|
|
26
|
-
# Advanced mode:
|
|
27
|
-
# string :email, format: { is: :email }
|
|
28
|
-
# string :started_on, format: { is: :date, message: "Invalid date format" }
|
|
29
|
-
# string :started_on, format: { is: :date, message: ->(attribute:, value:, **) { "#{attribute} has invalid date: #{value}" } } # rubocop:disable Layout/LineLength
|
|
30
|
-
#
|
|
31
|
-
# ## Validation Rules
|
|
32
|
-
#
|
|
33
|
-
# - Only works with `:string` type attributes
|
|
34
|
-
# - Raises Treaty::Exceptions::Validation if used with non-string types
|
|
35
|
-
# - Skips validation for nil values (handled by RequiredValidator)
|
|
36
|
-
# - Each format has a pattern and/or validator for flexible validation
|
|
37
|
-
#
|
|
38
|
-
# ## Extensibility
|
|
39
|
-
#
|
|
40
|
-
# To add new formats, extend DEFAULT_FORMATS hash with format definition:
|
|
41
|
-
# DEFAULT_FORMATS[:custom_format] = {
|
|
42
|
-
# pattern: /regex/,
|
|
43
|
-
# validator: ->(value) { custom_validation_logic }
|
|
44
|
-
# }
|
|
45
|
-
class FormatValidator < Treaty::Attribute::Option::Base # rubocop:disable Metrics/ClassLength
|
|
46
|
-
# UUID format regex (8-4-4-4-12 hexadecimal pattern)
|
|
47
|
-
UUID_PATTERN = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
|
|
48
|
-
|
|
49
|
-
# Password format regex (8-16 chars, at least one digit, lowercase, and uppercase)
|
|
50
|
-
PASSWORD_PATTERN = /\A(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{8,16}\z/
|
|
51
|
-
|
|
52
|
-
# Boolean string format regex (accepts "true", "false", "0", "1" case-insensitive)
|
|
53
|
-
BOOLEAN_PATTERN = /\A(true|false|0|1)\z/i
|
|
54
|
-
|
|
55
|
-
# Default format definitions with patterns and validators
|
|
56
|
-
# Each format can have:
|
|
57
|
-
# - pattern: Regex for pattern matching
|
|
58
|
-
# - validator: Lambda for custom validation logic
|
|
59
|
-
DEFAULT_FORMATS = {
|
|
60
|
-
uuid: {
|
|
61
|
-
pattern: UUID_PATTERN,
|
|
62
|
-
validator: nil
|
|
63
|
-
},
|
|
64
|
-
email: {
|
|
65
|
-
pattern: URI::MailTo::EMAIL_REGEXP,
|
|
66
|
-
validator: nil
|
|
67
|
-
},
|
|
68
|
-
password: {
|
|
69
|
-
pattern: PASSWORD_PATTERN,
|
|
70
|
-
validator: nil
|
|
71
|
-
},
|
|
72
|
-
duration: {
|
|
73
|
-
pattern: nil,
|
|
74
|
-
validator: lambda do |value|
|
|
75
|
-
ActiveSupport::Duration.parse(value)
|
|
76
|
-
true
|
|
77
|
-
rescue StandardError
|
|
78
|
-
false
|
|
79
|
-
end
|
|
80
|
-
},
|
|
81
|
-
date: {
|
|
82
|
-
pattern: nil,
|
|
83
|
-
validator: lambda do |value|
|
|
84
|
-
Date.parse(value)
|
|
85
|
-
true
|
|
86
|
-
rescue ArgumentError, TypeError
|
|
87
|
-
false
|
|
88
|
-
end
|
|
89
|
-
},
|
|
90
|
-
datetime: {
|
|
91
|
-
pattern: nil,
|
|
92
|
-
validator: lambda do |value|
|
|
93
|
-
DateTime.parse(value)
|
|
94
|
-
true
|
|
95
|
-
rescue ArgumentError, TypeError
|
|
96
|
-
false
|
|
97
|
-
end
|
|
98
|
-
},
|
|
99
|
-
time: {
|
|
100
|
-
pattern: nil,
|
|
101
|
-
validator: lambda do |value|
|
|
102
|
-
Time.parse(value)
|
|
103
|
-
true
|
|
104
|
-
rescue ArgumentError, TypeError
|
|
105
|
-
false
|
|
106
|
-
end
|
|
107
|
-
},
|
|
108
|
-
boolean: {
|
|
109
|
-
pattern: BOOLEAN_PATTERN,
|
|
110
|
-
validator: nil
|
|
111
|
-
}
|
|
112
|
-
}.freeze
|
|
113
|
-
|
|
114
|
-
# Validates that format is only used with string type attributes
|
|
115
|
-
# and that the format name is valid
|
|
116
|
-
#
|
|
117
|
-
# @raise [Treaty::Exceptions::Validation] If format is used with non-string type
|
|
118
|
-
# @raise [Treaty::Exceptions::Validation] If format name is unknown
|
|
119
|
-
# @return [void]
|
|
120
|
-
def validate_schema! # rubocop:disable Metrics/MethodLength
|
|
121
|
-
# Format option only works with string types
|
|
122
|
-
unless @attribute_type == :string
|
|
123
|
-
raise Treaty::Exceptions::Validation,
|
|
124
|
-
I18n.t(
|
|
125
|
-
"treaty.attributes.validators.format.type_mismatch",
|
|
126
|
-
attribute: @attribute_name,
|
|
127
|
-
type: @attribute_type
|
|
128
|
-
)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
format_name = option_value
|
|
132
|
-
|
|
133
|
-
# Validate that format name exists
|
|
134
|
-
return if formats.key?(format_name)
|
|
135
|
-
|
|
136
|
-
raise Treaty::Exceptions::Validation,
|
|
137
|
-
I18n.t(
|
|
138
|
-
"treaty.attributes.validators.format.unknown_format",
|
|
139
|
-
attribute: @attribute_name,
|
|
140
|
-
format_name:,
|
|
141
|
-
allowed: formats.keys.join(", ")
|
|
142
|
-
)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Validates that the value matches the specified format
|
|
146
|
-
# Skips validation for nil values (handled by RequiredValidator)
|
|
147
|
-
#
|
|
148
|
-
# @param value [String] The value to validate
|
|
149
|
-
# @raise [Treaty::Exceptions::Validation] If value doesn't match format
|
|
150
|
-
# @return [void]
|
|
151
|
-
def validate_value!(value) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
152
|
-
return if value.nil? # Format validation doesn't check for nil, required does.
|
|
153
|
-
|
|
154
|
-
format_name = option_value
|
|
155
|
-
format_definition = formats[format_name]
|
|
156
|
-
|
|
157
|
-
# Allow blank values (empty strings should be caught by required validator)
|
|
158
|
-
return if value.to_s.strip.empty?
|
|
159
|
-
|
|
160
|
-
# Apply pattern matching if defined
|
|
161
|
-
if format_definition.fetch(:pattern)
|
|
162
|
-
return if value.match?(format_definition.fetch(:pattern))
|
|
163
|
-
|
|
164
|
-
# Pattern failed, and no validator - raise error
|
|
165
|
-
unless format_definition.fetch(:validator)
|
|
166
|
-
attributes = {
|
|
167
|
-
attribute: @attribute_name,
|
|
168
|
-
value:,
|
|
169
|
-
format_name:
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
message = resolve_custom_message(**attributes) || default_message(**attributes)
|
|
173
|
-
|
|
174
|
-
raise Treaty::Exceptions::Validation, message
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
# Apply validator if defined
|
|
179
|
-
return unless format_definition.fetch(:validator)
|
|
180
|
-
return if format_definition.fetch(:validator).call(value)
|
|
181
|
-
|
|
182
|
-
attributes = {
|
|
183
|
-
attribute: @attribute_name,
|
|
184
|
-
value:,
|
|
185
|
-
format_name:
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
message = resolve_custom_message(**attributes) || default_message(**attributes)
|
|
189
|
-
|
|
190
|
-
raise Treaty::Exceptions::Validation, message
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
private
|
|
194
|
-
|
|
195
|
-
# Returns the available formats (allows for extension)
|
|
196
|
-
#
|
|
197
|
-
# @return [Hash] Hash of available formats with their definitions
|
|
198
|
-
def formats
|
|
199
|
-
DEFAULT_FORMATS
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
# Generates default error message for format validation failure using I18n
|
|
203
|
-
#
|
|
204
|
-
# @param attribute [Symbol] The attribute name
|
|
205
|
-
# @param value [Object] The actual value that failed validation
|
|
206
|
-
# @param format_name [Symbol] The format name
|
|
207
|
-
# @return [String] Default error message
|
|
208
|
-
def default_message(attribute:, value:, format_name:)
|
|
209
|
-
I18n.t(
|
|
210
|
-
"treaty.attributes.validators.format.mismatch",
|
|
211
|
-
attribute:,
|
|
212
|
-
value:,
|
|
213
|
-
format_name:
|
|
214
|
-
)
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
end
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Treaty
|
|
4
|
-
module Attribute
|
|
5
|
-
module Option
|
|
6
|
-
module Validators
|
|
7
|
-
# Validates that attribute value is included in allowed set.
|
|
8
|
-
#
|
|
9
|
-
# ## Usage Examples
|
|
10
|
-
#
|
|
11
|
-
# Simple mode:
|
|
12
|
-
# string :provider, in: %w[twitter linkedin github]
|
|
13
|
-
#
|
|
14
|
-
# Advanced mode:
|
|
15
|
-
# string :provider, inclusion: { in: %w[twitter linkedin github], message: "Invalid provider" }
|
|
16
|
-
#
|
|
17
|
-
# ## Advanced Mode
|
|
18
|
-
#
|
|
19
|
-
# Uses `:in` as the value key (instead of default `:is`).
|
|
20
|
-
# Schema format: `{ in: [...], message: nil }`
|
|
21
|
-
class InclusionValidator < Treaty::Attribute::Option::Base
|
|
22
|
-
# Validates that allowed values are provided as non-empty array
|
|
23
|
-
#
|
|
24
|
-
# @raise [Treaty::Exceptions::Validation] If allowed values are not valid
|
|
25
|
-
# @return [void]
|
|
26
|
-
def validate_schema!
|
|
27
|
-
allowed_values = option_value
|
|
28
|
-
|
|
29
|
-
return if allowed_values.is_a?(Array) && !allowed_values.empty?
|
|
30
|
-
|
|
31
|
-
raise Treaty::Exceptions::Validation,
|
|
32
|
-
I18n.t(
|
|
33
|
-
"treaty.attributes.validators.inclusion.invalid_schema",
|
|
34
|
-
attribute: @attribute_name
|
|
35
|
-
)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Validates that value is included in allowed set
|
|
39
|
-
# Skips validation for nil values (handled by RequiredValidator)
|
|
40
|
-
#
|
|
41
|
-
# @param value [Object] The value to validate
|
|
42
|
-
# @raise [Treaty::Exceptions::Validation] If value is not in allowed set
|
|
43
|
-
# @return [void]
|
|
44
|
-
def validate_value!(value)
|
|
45
|
-
return if value.nil? # Inclusion validation doesn't check for nil, required does.
|
|
46
|
-
|
|
47
|
-
allowed_values = option_value
|
|
48
|
-
|
|
49
|
-
return if allowed_values.include?(value)
|
|
50
|
-
|
|
51
|
-
attributes = {
|
|
52
|
-
attribute: @attribute_name,
|
|
53
|
-
value:,
|
|
54
|
-
allowed_values:
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
message = resolve_custom_message(**attributes) || default_message(**attributes)
|
|
58
|
-
|
|
59
|
-
raise Treaty::Exceptions::Validation, message
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
protected
|
|
63
|
-
|
|
64
|
-
# Returns the value key for inclusion validator
|
|
65
|
-
# Uses :in instead of default :is
|
|
66
|
-
#
|
|
67
|
-
# @return [Symbol] The value key (:in)
|
|
68
|
-
def value_key
|
|
69
|
-
:in
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
private
|
|
73
|
-
|
|
74
|
-
# Generates default error message with allowed values using I18n
|
|
75
|
-
#
|
|
76
|
-
# @param attribute [Symbol] The attribute name
|
|
77
|
-
# @param value [Object] The actual value that failed validation
|
|
78
|
-
# @param allowed_values [Array] Array of allowed values
|
|
79
|
-
# @return [String] Default error message
|
|
80
|
-
def default_message(attribute:, value:, allowed_values:)
|
|
81
|
-
I18n.t(
|
|
82
|
-
"treaty.attributes.validators.inclusion.not_included",
|
|
83
|
-
attribute:,
|
|
84
|
-
allowed: allowed_values.join(", "),
|
|
85
|
-
value:
|
|
86
|
-
)
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|