treaty 0.7.0 → 0.9.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -0
  3. data/config/locales/en.yml +8 -0
  4. data/lib/treaty/attribute/base.rb +13 -5
  5. data/lib/treaty/attribute/dsl.rb +90 -0
  6. data/lib/treaty/attribute/entity/attribute.rb +25 -0
  7. data/lib/treaty/attribute/entity/builder.rb +23 -0
  8. data/lib/treaty/attribute/option/base.rb +17 -1
  9. data/lib/treaty/attribute/option/modifiers/as_modifier.rb +5 -3
  10. data/lib/treaty/attribute/option/registry_initializer.rb +2 -0
  11. data/lib/treaty/attribute/option/validators/format_validator.rb +220 -0
  12. data/lib/treaty/attribute/option/validators/inclusion_validator.rb +20 -8
  13. data/lib/treaty/attribute/option/validators/required_validator.rb +8 -2
  14. data/lib/treaty/attribute/option/validators/type_validator.rb +51 -40
  15. data/lib/treaty/attribute/option_orchestrator.rb +7 -5
  16. data/lib/treaty/attribute/validation/nested_array_validator.rb +18 -12
  17. data/lib/treaty/attribute/validation/nested_transformer.rb +18 -12
  18. data/lib/treaty/base.rb +1 -1
  19. data/lib/treaty/controller/dsl.rb +4 -1
  20. data/lib/treaty/entity.rb +84 -0
  21. data/lib/treaty/info/entity/builder.rb +50 -0
  22. data/lib/treaty/info/entity/dsl.rb +28 -0
  23. data/lib/treaty/info/entity/result.rb +15 -0
  24. data/lib/treaty/info/rest/builder.rb +110 -0
  25. data/lib/treaty/info/rest/dsl.rb +28 -0
  26. data/lib/treaty/info/rest/result.rb +15 -0
  27. data/lib/treaty/request/attribute/attribute.rb +1 -0
  28. data/lib/treaty/request/attribute/builder.rb +1 -0
  29. data/lib/treaty/request/entity.rb +33 -0
  30. data/lib/treaty/request/factory.rb +61 -14
  31. data/lib/treaty/request/validator.rb +65 -0
  32. data/lib/treaty/response/attribute/attribute.rb +1 -0
  33. data/lib/treaty/response/attribute/builder.rb +1 -0
  34. data/lib/treaty/response/entity.rb +33 -0
  35. data/lib/treaty/response/factory.rb +61 -14
  36. data/lib/treaty/response/validator.rb +57 -0
  37. data/lib/treaty/version.rb +1 -1
  38. data/lib/treaty/versions/execution/request.rb +10 -5
  39. data/lib/treaty/versions/factory.rb +16 -5
  40. data/lib/treaty/versions/resolver.rb +8 -2
  41. data/lib/treaty/versions/workspace.rb +2 -2
  42. metadata +16 -8
  43. data/lib/treaty/info/builder.rb +0 -108
  44. data/lib/treaty/info/dsl.rb +0 -26
  45. data/lib/treaty/info/result.rb +0 -13
  46. data/lib/treaty/request/attribute/validation/orchestrator.rb +0 -19
  47. data/lib/treaty/request/attribute/validator.rb +0 -50
  48. data/lib/treaty/response/attribute/validation/orchestrator.rb +0 -19
  49. data/lib/treaty/response/attribute/validator.rb +0 -44
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e12062cde9c855fa59706b03143fc63760837b8b7ea2eacec1c78694212ec2c1
4
- data.tar.gz: 8fddef7c5bb06a1e592ff45873556eab88ef46fa2c578a7425a915803210db50
3
+ metadata.gz: 8fd8fd4a097f7ff97cdb53e0d7825d79da5562d0bb6347e8e40794c158f8cb41
4
+ data.tar.gz: b0e119a2ba1018b4eeeb49c753b2949fb4c320ca89be29d633e7e6367cbd7360
5
5
  SHA512:
6
- metadata.gz: '0684be39d9831977a7afefaf06d9ad5dc683b619b598c68e7d6afd0f751d834caaaa998cadffc921aa2d024939fa40778cdaedb7a2264f6582d43bcdbfc06405'
7
- data.tar.gz: 64b70b809d8729b9fd2b9b7cf2e5c30404c6e1e84c51cdaf85596a6b38538dd3cd3c85679b0f5d183d948ca81de15f85acf204ace86972cd47f35c8d884ed3a9
6
+ metadata.gz: 10514553e41066a04e066e6561744f69ca779bc4a87594bab7dce48b7e6217a5d5a6f2b7e28f1770876a9d3ff82d2f715cf4d167ef683dc36723b65a1549dc62
7
+ data.tar.gz: e262a6a4b2dbe068c53ced57ddcd7d9687a8e0be7d13f90ab8c2b9176015a3099829af702f59d22d28588a8c929e7178746d79356c35d4d0e899cc46991ce439
data/README.md CHANGED
@@ -12,6 +12,9 @@
12
12
 
13
13
  </div>
14
14
 
15
+ > [!WARNING]
16
+ > **Development Status**: Treaty is currently under active development in the 0.x version series. Breaking changes may occur between minor versions (0.x) as we refine the API and add new features. The library will stabilize with the 1.0 release. We recommend pinning to specific patch versions in your Gemfile (e.g., `gem "treaty", "~> 0.5.0"`) until the 1.0 release.
17
+
15
18
  ## 📚 Documentation
16
19
 
17
20
  Explore comprehensive guides and documentation at [docs](./docs):
@@ -29,6 +32,8 @@ Treaty provides a complete solution for building versioned APIs in Ruby on Rails
29
32
 
30
33
  - **Type Safety** - Enforce strict type checking for request and response data
31
34
  - **API Versioning** - Manage multiple concurrent API versions effortlessly
35
+ - **Unified Architecture** - Request blocks, response blocks, and Entity classes share the same validation system
36
+ - **Entity Classes (DTOs)** - Define reusable data transfer objects for better code organization
32
37
  - **Built-in Validation** - Validate incoming requests and outgoing responses automatically
33
38
  - **Data Transformation** - Transform data seamlessly between different API versions
34
39
  - **Deprecation Management** - Mark versions as deprecated with flexible conditions
@@ -23,6 +23,11 @@ en:
23
23
  invalid_schema: "Option 'inclusion' for attribute '%{attribute}' must have a non-empty array of allowed values"
24
24
  not_included: "Attribute '%{attribute}' must be one of: %{allowed}. Got: '%{value}'"
25
25
 
26
+ format:
27
+ type_mismatch: "Option 'format' for attribute '%{attribute}' can only be used with String type. Current type: %{type}"
28
+ unknown_format: "Unknown format '%{format_name}' for attribute '%{attribute}'. Allowed formats: %{allowed}"
29
+ mismatch: "Attribute '%{attribute}' has invalid %{format_name} format: '%{value}'"
30
+
26
31
  # Nested structures validation
27
32
  nested:
28
33
  # Orchestrator errors
@@ -47,6 +52,7 @@ en:
47
52
  # Attribute builder DSL
48
53
  builder:
49
54
  not_implemented: "%{class} must implement #create_attribute"
55
+ create_attribute_not_implemented: "Subclass %{class} must implement #create_attribute method"
50
56
 
51
57
  # Attribute-level errors
52
58
  errors:
@@ -61,6 +67,7 @@ en:
61
67
  # Request factory DSL
62
68
  factory:
63
69
  unknown_method: "Unknown method '%{method}' in request definition. Use 'object :name do ... end' to define request structure"
70
+ invalid_entity_class: "Request expects a Treaty::Entity subclass, got %{type}: %{value}"
64
71
 
65
72
  # ============================================================================
66
73
  # Response: Response definition and structure
@@ -69,6 +76,7 @@ en:
69
76
  # Response factory DSL
70
77
  factory:
71
78
  unknown_method: "Unknown method '%{method}' in response definition. Use 'object :name do ... end' to define response structure"
79
+ invalid_entity_class: "Response expects a Treaty::Entity subclass, got %{type}: %{value}"
72
80
 
73
81
  # ============================================================================
74
82
  # Versioning: API version management and resolution
@@ -122,9 +122,11 @@ module Treaty
122
122
  return unless @nesting_level > Treaty::Engine.config.treaty.attribute_nesting_level
123
123
 
124
124
  raise Treaty::Exceptions::NestedAttributes,
125
- I18n.t("treaty.attributes.errors.nesting_level_exceeded",
126
- level: @nesting_level,
127
- max_level: Treaty::Engine.config.treaty.attribute_nesting_level)
125
+ I18n.t(
126
+ "treaty.attributes.errors.nesting_level_exceeded",
127
+ level: @nesting_level,
128
+ max_level: Treaty::Engine.config.treaty.attribute_nesting_level
129
+ )
128
130
  end
129
131
 
130
132
  # Extracts helper symbols from arguments
@@ -155,7 +157,10 @@ module Treaty
155
157
  def apply_defaults!
156
158
  # Must be implemented in subclasses
157
159
  raise Treaty::Exceptions::NotImplemented,
158
- I18n.t("treaty.attributes.errors.apply_defaults_not_implemented", class: self.class)
160
+ I18n.t(
161
+ "treaty.attributes.errors.apply_defaults_not_implemented",
162
+ class: self.class
163
+ )
159
164
  end
160
165
 
161
166
  # Processes nested attributes block for object/array types
@@ -167,7 +172,10 @@ module Treaty
167
172
  def process_nested_attributes
168
173
  # Must be implemented in subclasses
169
174
  raise Treaty::Exceptions::NotImplemented,
170
- I18n.t("treaty.attributes.errors.process_nested_not_implemented", class: self.class)
175
+ I18n.t(
176
+ "treaty.attributes.errors.process_nested_not_implemented",
177
+ class: self.class
178
+ )
171
179
  end
172
180
  end
173
181
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Attribute
5
+ # DSL module for defining attributes in Entity-like classes.
6
+ #
7
+ # This module provides the class-level DSL for defining attributes.
8
+ # It can be included in any class that needs attribute definition capabilities.
9
+ #
10
+ # ## Usage
11
+ #
12
+ # ```ruby
13
+ # class MyEntity
14
+ # include Treaty::Attribute::DSL
15
+ #
16
+ # string :name
17
+ # integer :age
18
+ # end
19
+ # ```
20
+ module DSL
21
+ def self.included(base)
22
+ base.extend(ClassMethods)
23
+ end
24
+
25
+ module ClassMethods
26
+ # Defines an attribute with explicit type
27
+ #
28
+ # @param name [Symbol] The attribute name
29
+ # @param type [Symbol] The attribute type
30
+ # @param helpers [Array<Symbol>] Helper symbols (:required, :optional)
31
+ # @param options [Hash] Attribute options
32
+ # @param block [Proc] Block for nested attributes
33
+ # @return [void]
34
+ def attribute(name, type, *helpers, **options, &block)
35
+ collection_of_attributes << create_attribute(
36
+ name,
37
+ type,
38
+ *helpers,
39
+ nesting_level: 0,
40
+ **options,
41
+ &block
42
+ )
43
+ end
44
+
45
+ # Returns collection of attributes for this class
46
+ #
47
+ # @return [Collection] Collection of attributes
48
+ def collection_of_attributes
49
+ @collection_of_attributes ||= Treaty::Attribute::Collection.new
50
+ end
51
+
52
+ # Handles DSL methods like `string :name` where method name is the type
53
+ #
54
+ # @param type [Symbol] The attribute type (method name)
55
+ # @param name [Symbol] The attribute name (first argument)
56
+ # @param helpers [Array<Symbol>] Helper symbols
57
+ # @param options [Hash] Attribute options
58
+ # @param block [Proc] Block for nested attributes
59
+ # @return [void]
60
+ def method_missing(type, *helpers, **options, &block)
61
+ name = helpers.shift
62
+
63
+ # If no attribute name provided, this is not an attribute definition
64
+ # Pass to super to handle it properly (e.g., for methods like 'info', 'call!', etc.)
65
+ return super if name.nil?
66
+
67
+ attribute(name, type, *helpers, **options, &block)
68
+ end
69
+
70
+ def respond_to_missing?(name, *)
71
+ super
72
+ end
73
+
74
+ private
75
+
76
+ # Creates an attribute instance (must be implemented by including class)
77
+ #
78
+ # @raise [Treaty::Exceptions::NotImplemented] If not implemented
79
+ # @return [Attribute::Base] Created attribute instance
80
+ def create_attribute(*)
81
+ raise Treaty::Exceptions::NotImplemented,
82
+ I18n.t(
83
+ "treaty.attributes.dsl.create_attribute_not_implemented",
84
+ class: self
85
+ )
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Attribute
5
+ module Entity
6
+ # Entity-specific attribute that defaults to required: true
7
+ class Attribute < Treaty::Attribute::Base
8
+ private
9
+
10
+ def apply_defaults!
11
+ # For entity: required by default (true).
12
+ # message: nil means use I18n default message from validators
13
+ @options[:required] ||= { is: true, message: nil }
14
+ end
15
+
16
+ def process_nested_attributes(&block)
17
+ return unless object_or_array?
18
+
19
+ builder = Builder.new(collection_of_attributes, @nesting_level + 1)
20
+ builder.instance_eval(&block)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Treaty
4
+ module Attribute
5
+ module Entity
6
+ # Entity-specific attribute builder
7
+ class Builder < Treaty::Attribute::Builder::Base
8
+ private
9
+
10
+ def create_attribute(name, type, *helpers, nesting_level:, **options, &block)
11
+ Attribute.new(
12
+ name,
13
+ type,
14
+ *helpers,
15
+ nesting_level:,
16
+ **options,
17
+ &block
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -134,13 +134,29 @@ module Treaty
134
134
  # Gets custom error message from advanced mode schema
135
135
  # Returns nil if no custom message, which triggers I18n default message
136
136
  #
137
- # @return [String, nil] Custom error message or nil for default message
137
+ # @return [String, Proc, nil] Custom error message, lambda, or nil for default message
138
138
  def custom_message
139
139
  return nil unless @option_schema.is_a?(Hash)
140
140
 
141
141
  @option_schema.fetch(:message, nil)
142
142
  end
143
143
 
144
+ # Resolves custom message with lambda support
145
+ # If message is a lambda, calls it with provided named arguments
146
+ #
147
+ # @param context [Hash] Named arguments to pass to lambda
148
+ # @return [String, nil] Resolved message string or nil
149
+ def resolve_custom_message(**context)
150
+ message = custom_message
151
+ return nil if message.nil?
152
+
153
+ if message.respond_to?(:call)
154
+ message.call(**context)
155
+ else
156
+ message
157
+ end
158
+ end
159
+
144
160
  # Checks if schema is in advanced mode
145
161
  #
146
162
  # @return [Boolean] True if schema is in advanced mode (hash with value key)
@@ -54,9 +54,11 @@ module Treaty
54
54
  return if target.is_a?(Symbol)
55
55
 
56
56
  raise Treaty::Exceptions::Validation,
57
- I18n.t("treaty.attributes.modifiers.as.invalid_type",
58
- attribute: @attribute_name,
59
- type: target.class)
57
+ I18n.t(
58
+ "treaty.attributes.modifiers.as.invalid_type",
59
+ attribute: @attribute_name,
60
+ type: target.class
61
+ )
60
62
  end
61
63
 
62
64
  # Indicates that AsModifier transforms attribute names
@@ -21,6 +21,7 @@ module Treaty
21
21
  # - `:required` → RequiredValidator - Validates required/optional attributes
22
22
  # - `:type` → TypeValidator - Validates value types
23
23
  # - `:inclusion` → InclusionValidator - Validates value is in allowed set
24
+ # - `:format` → FormatValidator - Validates string values match specific formats
24
25
  #
25
26
  # ## Built-in Modifiers
26
27
  #
@@ -71,6 +72,7 @@ module Treaty
71
72
  Registry.register(:required, Validators::RequiredValidator, category: :validator)
72
73
  Registry.register(:type, Validators::TypeValidator, category: :validator)
73
74
  Registry.register(:inclusion, Validators::InclusionValidator, category: :validator)
75
+ Registry.register(:format, Validators::FormatValidator, category: :validator)
74
76
  end
75
77
 
76
78
  # Registers all built-in modifiers
@@ -0,0 +1,220 @@
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
@@ -29,7 +29,10 @@ module Treaty
29
29
  return if allowed_values.is_a?(Array) && !allowed_values.empty?
30
30
 
31
31
  raise Treaty::Exceptions::Validation,
32
- I18n.t("treaty.attributes.validators.inclusion.invalid_schema", attribute: @attribute_name)
32
+ I18n.t(
33
+ "treaty.attributes.validators.inclusion.invalid_schema",
34
+ attribute: @attribute_name
35
+ )
33
36
  end
34
37
 
35
38
  # Validates that value is included in allowed set
@@ -45,7 +48,13 @@ module Treaty
45
48
 
46
49
  return if allowed_values.include?(value)
47
50
 
48
- message = custom_message || default_message(allowed_values, value)
51
+ attributes = {
52
+ attribute: @attribute_name,
53
+ value:,
54
+ allowed_values:
55
+ }
56
+
57
+ message = resolve_custom_message(**attributes) || default_message(**attributes)
49
58
 
50
59
  raise Treaty::Exceptions::Validation, message
51
60
  end
@@ -64,14 +73,17 @@ module Treaty
64
73
 
65
74
  # Generates default error message with allowed values using I18n
66
75
  #
67
- # @param allowed_values [Array] Array of allowed values
76
+ # @param attribute [Symbol] The attribute name
68
77
  # @param value [Object] The actual value that failed validation
78
+ # @param allowed_values [Array] Array of allowed values
69
79
  # @return [String] Default error message
70
- def default_message(allowed_values, value)
71
- I18n.t("treaty.attributes.validators.inclusion.not_included",
72
- attribute: @attribute_name,
73
- allowed: allowed_values.join(", "),
74
- value:)
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
+ )
75
87
  end
76
88
  end
77
89
  end
@@ -51,7 +51,10 @@ module Treaty
51
51
  return unless required?
52
52
  return if present?(value)
53
53
 
54
- message = custom_message || default_message
54
+ message = resolve_custom_message(
55
+ attribute: @attribute_name,
56
+ value:
57
+ ) || default_message
55
58
 
56
59
  raise Treaty::Exceptions::Validation, message
57
60
  end
@@ -83,7 +86,10 @@ module Treaty
83
86
  #
84
87
  # @return [String] Default error message
85
88
  def default_message
86
- I18n.t("treaty.attributes.validators.required.blank", attribute: @attribute_name)
89
+ I18n.t(
90
+ "treaty.attributes.validators.required.blank",
91
+ attribute: @attribute_name
92
+ )
87
93
  end
88
94
  end
89
95
  end