treaty 0.17.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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/config/locales/en.yml +6 -2
  4. data/lib/treaty/engine.rb +1 -1
  5. data/lib/treaty/{attribute/entity → entity/attribute}/attribute.rb +4 -4
  6. data/lib/treaty/entity/attribute/base.rb +184 -0
  7. data/lib/treaty/entity/attribute/builder/base.rb +275 -0
  8. data/lib/treaty/entity/attribute/collection.rb +67 -0
  9. data/lib/treaty/entity/attribute/dsl.rb +92 -0
  10. data/lib/treaty/entity/attribute/helper_mapper.rb +74 -0
  11. data/lib/treaty/entity/attribute/option/base.rb +190 -0
  12. data/lib/treaty/entity/attribute/option/conditionals/base.rb +92 -0
  13. data/lib/treaty/entity/attribute/option/conditionals/if_conditional.rb +136 -0
  14. data/lib/treaty/entity/attribute/option/conditionals/unless_conditional.rb +153 -0
  15. data/lib/treaty/entity/attribute/option/modifiers/as_modifier.rb +93 -0
  16. data/lib/treaty/entity/attribute/option/modifiers/cast_modifier.rb +285 -0
  17. data/lib/treaty/entity/attribute/option/modifiers/computed_modifier.rb +128 -0
  18. data/lib/treaty/entity/attribute/option/modifiers/default_modifier.rb +105 -0
  19. data/lib/treaty/entity/attribute/option/modifiers/transform_modifier.rb +114 -0
  20. data/lib/treaty/entity/attribute/option/registry.rb +157 -0
  21. data/lib/treaty/entity/attribute/option/registry_initializer.rb +117 -0
  22. data/lib/treaty/entity/attribute/option/validators/format_validator.rb +222 -0
  23. data/lib/treaty/entity/attribute/option/validators/inclusion_validator.rb +94 -0
  24. data/lib/treaty/entity/attribute/option/validators/required_validator.rb +100 -0
  25. data/lib/treaty/entity/attribute/option/validators/type_validator.rb +219 -0
  26. data/lib/treaty/entity/attribute/option_normalizer.rb +168 -0
  27. data/lib/treaty/entity/attribute/option_orchestrator.rb +192 -0
  28. data/lib/treaty/entity/attribute/validation/attribute_validator.rb +147 -0
  29. data/lib/treaty/entity/attribute/validation/base.rb +76 -0
  30. data/lib/treaty/entity/attribute/validation/nested_array_validator.rb +207 -0
  31. data/lib/treaty/entity/attribute/validation/nested_object_validator.rb +105 -0
  32. data/lib/treaty/entity/attribute/validation/nested_transformer.rb +432 -0
  33. data/lib/treaty/entity/attribute/validation/orchestrator/base.rb +262 -0
  34. data/lib/treaty/entity/base.rb +90 -0
  35. data/lib/treaty/entity/builder.rb +44 -0
  36. data/lib/treaty/{info/entity → entity/info}/builder.rb +8 -8
  37. data/lib/treaty/{info/entity → entity/info}/dsl.rb +2 -2
  38. data/lib/treaty/{info/entity → entity/info}/result.rb +2 -2
  39. data/lib/treaty/entity.rb +7 -75
  40. data/lib/treaty/request/attribute/attribute.rb +1 -1
  41. data/lib/treaty/request/attribute/builder.rb +24 -1
  42. data/lib/treaty/request/entity.rb +1 -1
  43. data/lib/treaty/request/factory.rb +6 -6
  44. data/lib/treaty/request/validator.rb +1 -1
  45. data/lib/treaty/response/attribute/attribute.rb +1 -1
  46. data/lib/treaty/response/attribute/builder.rb +24 -1
  47. data/lib/treaty/response/entity.rb +1 -1
  48. data/lib/treaty/response/factory.rb +6 -6
  49. data/lib/treaty/response/validator.rb +1 -1
  50. data/lib/treaty/version.rb +1 -1
  51. metadata +35 -34
  52. data/lib/treaty/attribute/base.rb +0 -182
  53. data/lib/treaty/attribute/builder/base.rb +0 -143
  54. data/lib/treaty/attribute/collection.rb +0 -65
  55. data/lib/treaty/attribute/dsl.rb +0 -90
  56. data/lib/treaty/attribute/entity/builder.rb +0 -23
  57. data/lib/treaty/attribute/helper_mapper.rb +0 -72
  58. data/lib/treaty/attribute/option/base.rb +0 -188
  59. data/lib/treaty/attribute/option/conditionals/base.rb +0 -90
  60. data/lib/treaty/attribute/option/conditionals/if_conditional.rb +0 -134
  61. data/lib/treaty/attribute/option/conditionals/unless_conditional.rb +0 -151
  62. data/lib/treaty/attribute/option/modifiers/as_modifier.rb +0 -91
  63. data/lib/treaty/attribute/option/modifiers/cast_modifier.rb +0 -283
  64. data/lib/treaty/attribute/option/modifiers/computed_modifier.rb +0 -126
  65. data/lib/treaty/attribute/option/modifiers/default_modifier.rb +0 -103
  66. data/lib/treaty/attribute/option/modifiers/transform_modifier.rb +0 -112
  67. data/lib/treaty/attribute/option/registry.rb +0 -155
  68. data/lib/treaty/attribute/option/registry_initializer.rb +0 -115
  69. data/lib/treaty/attribute/option/validators/format_validator.rb +0 -220
  70. data/lib/treaty/attribute/option/validators/inclusion_validator.rb +0 -92
  71. data/lib/treaty/attribute/option/validators/required_validator.rb +0 -98
  72. data/lib/treaty/attribute/option/validators/type_validator.rb +0 -217
  73. data/lib/treaty/attribute/option_normalizer.rb +0 -166
  74. data/lib/treaty/attribute/option_orchestrator.rb +0 -190
  75. data/lib/treaty/attribute/validation/attribute_validator.rb +0 -145
  76. data/lib/treaty/attribute/validation/base.rb +0 -74
  77. data/lib/treaty/attribute/validation/nested_array_validator.rb +0 -205
  78. data/lib/treaty/attribute/validation/nested_object_validator.rb +0 -103
  79. data/lib/treaty/attribute/validation/nested_transformer.rb +0 -430
  80. data/lib/treaty/attribute/validation/orchestrator/base.rb +0 -260
@@ -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
@@ -1,98 +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 present (not nil and not empty).
8
- #
9
- # ## Usage Examples
10
- #
11
- # Helper mode:
12
- # string :title, :required # Maps to required: true
13
- # string :bio, :optional # Maps to required: false
14
- #
15
- # Simple mode:
16
- # string :title, required: true
17
- # string :bio, required: false
18
- #
19
- # Advanced mode:
20
- # string :title, required: { is: true, message: "Title is mandatory" }
21
- #
22
- # ## Default Behavior
23
- #
24
- # - Request attributes: required by default (required: true)
25
- # - Response attributes: optional by default (required: false)
26
- #
27
- # ## Validation Rules
28
- #
29
- # A value is considered present if:
30
- # - It is not nil
31
- # - It is not empty (for String, Array, Hash)
32
- #
33
- # ## Advanced Mode
34
- #
35
- # Schema format: `{ is: true/false, message: nil }`
36
- class RequiredValidator < Treaty::Attribute::Option::Base
37
- # Validates schema (no validation needed, already normalized)
38
- #
39
- # @return [void]
40
- def validate_schema!
41
- # Schema structure is already normalized by OptionNormalizer.
42
- # Nothing to validate here.
43
- end
44
-
45
- # Validates that required attribute has a present value
46
- #
47
- # @param value [Object] The value to validate
48
- # @raise [Treaty::Exceptions::Validation] If required but value is missing/empty
49
- # @return [void]
50
- def validate_value!(value)
51
- return unless required?
52
- return if present?(value)
53
-
54
- message = resolve_custom_message(
55
- attribute: @attribute_name,
56
- value:
57
- ) || default_message
58
-
59
- raise Treaty::Exceptions::Validation, message
60
- end
61
-
62
- private
63
-
64
- # Checks if attribute is required
65
- #
66
- # @return [Boolean] True if attribute is required
67
- def required?
68
- return false if @option_schema.nil?
69
-
70
- # Use option_value helper which correctly extracts value based on mode
71
- option_value == true
72
- end
73
-
74
- # Checks if value is present (not nil and not empty)
75
- #
76
- # @param value [Object] The value to check
77
- # @return [Boolean] True if value is present
78
- def present?(value)
79
- return false if value.nil?
80
- return false if value.respond_to?(:empty?) && value.empty?
81
-
82
- true
83
- end
84
-
85
- # Generates default error message using I18n
86
- #
87
- # @return [String] Default error message
88
- def default_message
89
- I18n.t(
90
- "treaty.attributes.validators.required.blank",
91
- attribute: @attribute_name
92
- )
93
- end
94
- end
95
- end
96
- end
97
- end
98
- end