axn 0.1.0.pre.alpha.2.5.3.1 → 0.1.0.pre.alpha.2.6

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.
@@ -7,264 +7,203 @@ require "action/core/validation/fields"
7
7
  require "action/core/context_facade"
8
8
 
9
9
  module Action
10
- module Contract
11
- def self.included(base)
12
- base.class_eval do
13
- class_attribute :internal_field_configs, :external_field_configs, default: []
14
-
15
- extend ClassMethods
16
- include InstanceMethods
17
- include ValidationInstanceMethods
18
-
19
- # Remove public context accessor
20
- remove_method :context
21
-
22
- around do |hooked|
23
- _apply_inbound_preprocessing!
24
- _apply_defaults!(:inbound)
25
- _validate_contract!(:inbound)
26
- hooked.call
27
- _apply_defaults!(:outbound)
28
- _validate_contract!(:outbound)
10
+ module Core
11
+ module Contract
12
+ def self.included(base)
13
+ base.class_eval do
14
+ class_attribute :internal_field_configs, :external_field_configs, default: []
15
+
16
+ extend ClassMethods
17
+ include InstanceMethods
29
18
  end
30
19
  end
31
- end
32
-
33
- FieldConfig = Data.define(:field, :validations, :default, :preprocess, :sensitive)
34
-
35
- module ClassMethods
36
- def expects(
37
- *fields,
38
- on: nil,
39
- allow_blank: false,
40
- allow_nil: false,
41
- default: nil,
42
- preprocess: nil,
43
- sensitive: false,
44
- **validations
45
- )
46
- return _expects_subfields(*fields, on:, allow_blank:, allow_nil:, default:, preprocess:, sensitive:, **validations) if on.present?
47
-
48
- fields.each do |field|
49
- raise ContractViolation::ReservedAttributeError, field if RESERVED_FIELD_NAMES_FOR_EXPECTATIONS.include?(field.to_s)
50
- end
51
20
 
52
- _parse_field_configs(*fields, allow_blank:, allow_nil:, default:, preprocess:, sensitive:, **validations).tap do |configs|
53
- duplicated = internal_field_configs.map(&:field) & configs.map(&:field)
54
- raise Action::DuplicateFieldError, "Duplicate field(s) declared: #{duplicated.join(", ")}" if duplicated.any?
21
+ FieldConfig = Data.define(:field, :validations, :default, :preprocess, :sensitive)
55
22
 
56
- # NOTE: avoid <<, which would update value for parents and children
57
- self.internal_field_configs += configs
58
- end
59
- end
23
+ module ClassMethods
24
+ def expects(
25
+ *fields,
26
+ on: nil,
27
+ allow_blank: false,
28
+ allow_nil: false,
29
+ default: nil,
30
+ preprocess: nil,
31
+ sensitive: false,
32
+ **validations
33
+ )
34
+ return _expects_subfields(*fields, on:, allow_blank:, allow_nil:, default:, preprocess:, sensitive:, **validations) if on.present?
60
35
 
61
- def exposes(
62
- *fields,
63
- allow_blank: false,
64
- allow_nil: false,
65
- default: nil,
66
- sensitive: false,
67
- **validations
68
- )
69
- fields.each do |field|
70
- raise ContractViolation::ReservedAttributeError, field if RESERVED_FIELD_NAMES_FOR_EXPOSURES.include?(field.to_s)
71
- end
36
+ fields.each do |field|
37
+ raise ContractViolation::ReservedAttributeError, field if RESERVED_FIELD_NAMES_FOR_EXPECTATIONS.include?(field.to_s)
38
+ end
72
39
 
73
- _parse_field_configs(*fields, allow_blank:, allow_nil:, default:, preprocess: nil, sensitive:, **validations).tap do |configs|
74
- duplicated = external_field_configs.map(&:field) & configs.map(&:field)
75
- raise Action::DuplicateFieldError, "Duplicate field(s) declared: #{duplicated.join(", ")}" if duplicated.any?
40
+ _parse_field_configs(*fields, allow_blank:, allow_nil:, default:, preprocess:, sensitive:, **validations).tap do |configs|
41
+ duplicated = internal_field_configs.map(&:field) & configs.map(&:field)
42
+ raise Action::DuplicateFieldError, "Duplicate field(s) declared: #{duplicated.join(", ")}" if duplicated.any?
76
43
 
77
- # NOTE: avoid <<, which would update value for parents and children
78
- self.external_field_configs += configs
44
+ # NOTE: avoid <<, which would update value for parents and children
45
+ self.internal_field_configs += configs
46
+ end
79
47
  end
80
- end
81
48
 
82
- private
83
-
84
- RESERVED_FIELD_NAMES_FOR_EXPECTATIONS = %w[
85
- called! fail! rollback! success? ok?
86
- inspect default_error
87
- each_pair
88
- ].freeze
89
-
90
- RESERVED_FIELD_NAMES_FOR_EXPOSURES = %w[
91
- called! fail! rollback! success? ok?
92
- inspect each_pair default_error
93
- ok error success message
94
- ].freeze
95
-
96
- def _parse_field_configs(
97
- *fields,
98
- allow_blank: false,
99
- allow_nil: false,
100
- default: nil,
101
- preprocess: nil,
102
- sensitive: false,
103
- **validations
104
- )
105
- _parse_field_validations(*fields, allow_nil:, allow_blank:, **validations).map do |field, parsed_validations|
106
- _define_field_reader(field)
107
- _define_model_reader(field, parsed_validations[:model]) if parsed_validations.key?(:model)
108
- FieldConfig.new(field:, validations: parsed_validations, default:, preprocess:, sensitive:)
109
- end
110
- end
49
+ def exposes(
50
+ *fields,
51
+ allow_blank: false,
52
+ allow_nil: false,
53
+ default: nil,
54
+ sensitive: false,
55
+ **validations
56
+ )
57
+ fields.each do |field|
58
+ raise ContractViolation::ReservedAttributeError, field if RESERVED_FIELD_NAMES_FOR_EXPOSURES.include?(field.to_s)
59
+ end
111
60
 
112
- def define_memoized_reader_method(field, &block)
113
- define_method(field) do
114
- ivar = :"@_memoized_reader_#{field}"
115
- cached_val = instance_variable_get(ivar)
116
- return cached_val if cached_val.present?
61
+ _parse_field_configs(*fields, allow_blank:, allow_nil:, default:, preprocess: nil, sensitive:, **validations).tap do |configs|
62
+ duplicated = external_field_configs.map(&:field) & configs.map(&:field)
63
+ raise Action::DuplicateFieldError, "Duplicate field(s) declared: #{duplicated.join(", ")}" if duplicated.any?
117
64
 
118
- value = instance_exec(&block)
119
- instance_variable_set(ivar, value)
65
+ # NOTE: avoid <<, which would update value for parents and children
66
+ self.external_field_configs += configs
67
+ end
120
68
  end
121
- end
122
-
123
- def _define_field_reader(field)
124
- # Allow local access to explicitly-expected fields -- even externally-expected needs to be available locally
125
- # (e.g. to allow success message callable to reference exposed fields)
126
- define_method(field) { internal_context.public_send(field) }
127
- end
128
-
129
- def _define_model_reader(field, klass)
130
- name = field.to_s.delete_suffix("_id")
131
- raise ArgumentError, "Model validation expects to be given a field ending in _id (given: #{field})" unless field.to_s.end_with?("_id")
132
- raise ArgumentError, "Failed to define model reader - #{name} is already defined" if method_defined?(name)
133
69
 
134
- define_memoized_reader_method(name) do
135
- Validators::ModelValidator.instance_for(field:, klass:, id: public_send(field))
70
+ private
71
+
72
+ RESERVED_FIELD_NAMES_FOR_EXPECTATIONS = %w[
73
+ fail! success? ok?
74
+ inspect default_error
75
+ each_pair
76
+ ].freeze
77
+
78
+ RESERVED_FIELD_NAMES_FOR_EXPOSURES = %w[
79
+ fail! success? ok?
80
+ inspect each_pair default_error
81
+ ok error success message
82
+ ].freeze
83
+
84
+ def _parse_field_configs(
85
+ *fields,
86
+ allow_blank: false,
87
+ allow_nil: false,
88
+ default: nil,
89
+ preprocess: nil,
90
+ sensitive: false,
91
+ **validations
92
+ )
93
+ _parse_field_validations(*fields, allow_nil:, allow_blank:, **validations).map do |field, parsed_validations|
94
+ _define_field_reader(field)
95
+ _define_model_reader(field, parsed_validations[:model]) if parsed_validations.key?(:model)
96
+ FieldConfig.new(field:, validations: parsed_validations, default:, preprocess:, sensitive:)
97
+ end
136
98
  end
137
- end
138
99
 
139
- def _parse_field_validations(
140
- *fields,
141
- allow_nil: false,
142
- allow_blank: false,
143
- **validations
144
- )
145
- if allow_blank
146
- validations.transform_values! do |v|
147
- v = { value: v } unless v.is_a?(Hash)
148
- { allow_blank: true }.merge(v)
149
- end
150
- elsif allow_nil
151
- validations.transform_values! do |v|
152
- v = { value: v } unless v.is_a?(Hash)
153
- { allow_nil: true }.merge(v)
100
+ def define_memoized_reader_method(field, &block)
101
+ define_method(field) do
102
+ ivar = :"@_memoized_reader_#{field}"
103
+ cached_val = instance_variable_get(ivar)
104
+ return cached_val if cached_val.present?
105
+
106
+ value = instance_exec(&block)
107
+ instance_variable_set(ivar, value)
154
108
  end
155
- else
156
- validations[:presence] = true unless validations.key?(:presence) || Array(validations[:type]).include?(:boolean)
157
109
  end
158
110
 
159
- fields.map { |field| [field, validations] }
160
- end
161
- end
162
-
163
- module InstanceMethods
164
- def internal_context = @internal_context ||= _build_context_facade(:inbound)
165
- def external_context = @external_context ||= _build_context_facade(:outbound)
111
+ def _define_field_reader(field)
112
+ # Allow local access to explicitly-expected fields -- even externally-expected needs to be available locally
113
+ # (e.g. to allow success message callable to reference exposed fields)
114
+ define_method(field) { internal_context.public_send(field) }
115
+ end
166
116
 
167
- # NOTE: ideally no direct access from client code, but we need to expose this for internal Interactor methods
168
- # (and passing through control methods to underlying context) in order to avoid rewriting internal methods.
169
- def context = external_context
117
+ def _define_model_reader(field, klass)
118
+ name = field.to_s.delete_suffix("_id")
119
+ raise ArgumentError, "Model validation expects to be given a field ending in _id (given: #{field})" unless field.to_s.end_with?("_id")
120
+ raise ArgumentError, "Failed to define model reader - #{name} is already defined" if method_defined?(name)
170
121
 
171
- # Accepts either two positional arguments (key, value) or a hash of key/value pairs
172
- def expose(*args, **kwargs)
173
- if args.any?
174
- if args.size != 2
175
- raise ArgumentError,
176
- "expose must be called with exactly two positional arguments (or a hash of key/value pairs)"
122
+ define_memoized_reader_method(name) do
123
+ Validators::ModelValidator.instance_for(field:, klass:, id: public_send(field))
177
124
  end
178
-
179
- kwargs.merge!(args.first => args.last)
180
125
  end
181
126
 
182
- kwargs.each do |key, value|
183
- raise Action::ContractViolation::UnknownExposure, key unless external_context.respond_to?(key)
127
+ def _parse_field_validations(
128
+ *fields,
129
+ allow_nil: false,
130
+ allow_blank: false,
131
+ **validations
132
+ )
133
+ if allow_blank
134
+ validations.transform_values! do |v|
135
+ v = { value: v } unless v.is_a?(Hash)
136
+ { allow_blank: true }.merge(v)
137
+ end
138
+ elsif allow_nil
139
+ validations.transform_values! do |v|
140
+ v = { value: v } unless v.is_a?(Hash)
141
+ { allow_nil: true }.merge(v)
142
+ end
143
+ else
144
+ validations[:presence] = true unless validations.key?(:presence) || Array(validations[:type]).include?(:boolean)
145
+ end
184
146
 
185
- @context.public_send("#{key}=", value)
147
+ fields.map { |field| [field, validations] }
186
148
  end
187
149
  end
188
150
 
189
- private
190
-
191
- def _build_context_facade(direction)
192
- raise ArgumentError, "Invalid direction: #{direction}" unless %i[inbound outbound].include?(direction)
151
+ module InstanceMethods
152
+ def internal_context = @internal_context ||= _build_context_facade(:inbound)
153
+ def result = @result ||= _build_context_facade(:outbound)
193
154
 
194
- klass = direction == :inbound ? Action::InternalContext : Action::Result
195
- implicitly_allowed_fields = direction == :inbound ? _declared_fields(:outbound) : []
155
+ # Accepts either two positional arguments (key, value) or a hash of key/value pairs
156
+ def expose(*args, **kwargs)
157
+ if args.any?
158
+ if args.size != 2
159
+ raise ArgumentError,
160
+ "expose must be called with exactly two positional arguments (or a hash of key/value pairs)"
161
+ end
196
162
 
197
- klass.new(action: self, context: @context, declared_fields: _declared_fields(direction), implicitly_allowed_fields:)
198
- end
199
- end
163
+ kwargs.merge!(args.first => args.last)
164
+ end
200
165
 
201
- module ValidationInstanceMethods
202
- def _apply_inbound_preprocessing!
203
- internal_field_configs.each do |config|
204
- next unless config.preprocess
166
+ kwargs.each do |key, value|
167
+ raise Action::ContractViolation::UnknownExposure, key unless result.respond_to?(key)
205
168
 
206
- initial_value = @context.public_send(config.field)
207
- new_value = config.preprocess.call(initial_value)
208
- @context.public_send("#{config.field}=", new_value)
209
- rescue StandardError => e
210
- raise Action::ContractViolation::PreprocessingError, "Error preprocessing field '#{config.field}': #{e.message}"
169
+ @context.public_send("#{key}=", value)
170
+ end
211
171
  end
212
- end
213
-
214
- def _validate_contract!(direction)
215
- raise ArgumentError, "Invalid direction: #{direction}" unless %i[inbound outbound].include?(direction)
216
172
 
217
- configs = direction == :inbound ? internal_field_configs : external_field_configs
218
- validations = configs.each_with_object({}) do |config, hash|
219
- hash[config.field] = config.validations
173
+ def context_for_logging(direction = nil)
174
+ inspection_filter.filter(@context.to_h.slice(*_declared_fields(direction)))
220
175
  end
221
- context = direction == :inbound ? internal_context : external_context
222
- exception_klass = direction == :inbound ? Action::InboundValidationError : Action::OutboundValidationError
223
176
 
224
- Validation::Fields.validate!(validations:, context:, exception_klass:)
225
- end
177
+ private
226
178
 
227
- def _apply_defaults!(direction)
228
- raise ArgumentError, "Invalid direction: #{direction}" unless %i[inbound outbound].include?(direction)
179
+ def _build_context_facade(direction)
180
+ raise ArgumentError, "Invalid direction: #{direction}" unless %i[inbound outbound].include?(direction)
229
181
 
230
- configs = direction == :inbound ? internal_field_configs : external_field_configs
231
- defaults_mapping = configs.each_with_object({}) do |config, hash|
232
- hash[config.field] = config.default
233
- end.compact
182
+ klass = direction == :inbound ? Action::InternalContext : Action::Result
183
+ implicitly_allowed_fields = direction == :inbound ? _declared_fields(:outbound) : []
234
184
 
235
- defaults_mapping.each do |field, default_value_getter|
236
- next if @context.public_send(field).present?
237
-
238
- default_value = default_value_getter.respond_to?(:call) ? instance_exec(&default_value_getter) : default_value_getter
239
-
240
- @context.public_send("#{field}=", default_value)
185
+ klass.new(action: self, context: @context, declared_fields: _declared_fields(direction), implicitly_allowed_fields:)
241
186
  end
242
- end
243
-
244
- def context_for_logging(direction = nil)
245
- inspection_filter.filter(@context.to_h.slice(*_declared_fields(direction)))
246
- end
247
-
248
- protected
249
187
 
250
- def inspection_filter
251
- @inspection_filter ||= ActiveSupport::ParameterFilter.new(sensitive_fields)
252
- end
188
+ def inspection_filter
189
+ @inspection_filter ||= ActiveSupport::ParameterFilter.new(sensitive_fields)
190
+ end
253
191
 
254
- def sensitive_fields
255
- (internal_field_configs + external_field_configs).select(&:sensitive).map(&:field)
256
- end
192
+ def sensitive_fields
193
+ (internal_field_configs + external_field_configs).select(&:sensitive).map(&:field)
194
+ end
257
195
 
258
- def _declared_fields(direction)
259
- raise ArgumentError, "Invalid direction: #{direction}" unless direction.nil? || %i[inbound outbound].include?(direction)
196
+ def _declared_fields(direction)
197
+ raise ArgumentError, "Invalid direction: #{direction}" unless direction.nil? || %i[inbound outbound].include?(direction)
260
198
 
261
- configs = case direction
262
- when :inbound then internal_field_configs
263
- when :outbound then external_field_configs
264
- else (internal_field_configs + external_field_configs)
265
- end
199
+ configs = case direction
200
+ when :inbound then internal_field_configs
201
+ when :outbound then external_field_configs
202
+ else (internal_field_configs + external_field_configs)
203
+ end
266
204
 
267
- configs.map(&:field)
205
+ configs.map(&:field)
206
+ end
268
207
  end
269
208
  end
270
209
  end
@@ -3,103 +3,105 @@
3
3
  require "action/core/validation/subfields"
4
4
 
5
5
  module Action
6
- module ContractForSubfields
7
- # TODO: add default, preprocess, sensitive options for subfields?
8
- # SubfieldConfig = Data.define(:field, :validations, :default, :preprocess, :sensitive)
9
- SubfieldConfig = Data.define(:field, :validations, :on)
6
+ module Core
7
+ module ContractForSubfields
8
+ # TODO: add default, preprocess, sensitive options for subfields?
9
+ # SubfieldConfig = Data.define(:field, :validations, :default, :preprocess, :sensitive)
10
+ SubfieldConfig = Data.define(:field, :validations, :on)
10
11
 
11
- def self.included(base)
12
- base.class_eval do
13
- class_attribute :subfield_configs, default: []
12
+ def self.included(base)
13
+ base.class_eval do
14
+ class_attribute :subfield_configs, default: []
14
15
 
15
- extend ClassMethods
16
- include InstanceMethods
16
+ extend ClassMethods
17
+ include InstanceMethods
17
18
 
18
- before { _validate_subfields_contract! }
19
+ before { _validate_subfields_contract! }
20
+ end
19
21
  end
20
- end
21
22
 
22
- module ClassMethods
23
- def _expects_subfields(
24
- *fields,
25
- on:,
26
- readers: true,
27
- allow_blank: false,
28
- allow_nil: false,
29
-
30
- # TODO: add support for these three options for subfields
31
- default: nil,
32
- preprocess: nil,
33
- sensitive: false,
34
-
35
- **validations
36
- )
37
- # TODO: add support for these three options for subfields
38
- raise ArgumentError, "expects does not support :default key when also given :on" if default.present?
39
- raise ArgumentError, "expects does not support :preprocess key when also given :on" if preprocess.present?
40
- raise ArgumentError, "expects does not support :sensitive key when also given :on" if sensitive.present?
41
-
42
- unless internal_field_configs.map(&:field).include?(on) || subfield_configs.map(&:field).include?(on)
43
- raise ArgumentError,
44
- "expects called with `on: #{on}`, but no such method exists (are you sure you've declared `expects :#{on}`?)"
23
+ module ClassMethods
24
+ def _expects_subfields(
25
+ *fields,
26
+ on:,
27
+ readers: true,
28
+ allow_blank: false,
29
+ allow_nil: false,
30
+
31
+ # TODO: add support for these three options for subfields
32
+ default: nil,
33
+ preprocess: nil,
34
+ sensitive: false,
35
+
36
+ **validations
37
+ )
38
+ # TODO: add support for these three options for subfields
39
+ raise ArgumentError, "expects does not support :default key when also given :on" if default.present?
40
+ raise ArgumentError, "expects does not support :preprocess key when also given :on" if preprocess.present?
41
+ raise ArgumentError, "expects does not support :sensitive key when also given :on" if sensitive.present?
42
+
43
+ unless internal_field_configs.map(&:field).include?(on) || subfield_configs.map(&:field).include?(on)
44
+ raise ArgumentError,
45
+ "expects called with `on: #{on}`, but no such method exists (are you sure you've declared `expects :#{on}`?)"
46
+ end
47
+
48
+ raise ArgumentError, "expects does not support expecting fields on nested attributes (i.e. `on` cannot contain periods)" if on.to_s.include?(".")
49
+
50
+ # TODO: consider adding support for default, preprocess, sensitive options for subfields?
51
+ _parse_subfield_configs(*fields, on:, readers:, allow_blank:, allow_nil:, **validations).tap do |configs|
52
+ duplicated = subfield_configs.map(&:field) & configs.map(&:field)
53
+ raise Action::DuplicateFieldError, "Duplicate field(s) declared: #{duplicated.join(", ")}" if duplicated.any?
54
+
55
+ # NOTE: avoid <<, which would update value for parents and children
56
+ self.subfield_configs += configs
57
+ end
45
58
  end
46
59
 
47
- raise ArgumentError, "expects does not support expecting fields on nested attributes (i.e. `on` cannot contain periods)" if on.to_s.include?(".")
48
-
49
- # TODO: consider adding support for default, preprocess, sensitive options for subfields?
50
- _parse_subfield_configs(*fields, on:, readers:, allow_blank:, allow_nil:, **validations).tap do |configs|
51
- duplicated = subfield_configs.map(&:field) & configs.map(&:field)
52
- raise Action::DuplicateFieldError, "Duplicate field(s) declared: #{duplicated.join(", ")}" if duplicated.any?
53
-
54
- # NOTE: avoid <<, which would update value for parents and children
55
- self.subfield_configs += configs
60
+ private
61
+
62
+ def _parse_subfield_configs(
63
+ *fields,
64
+ on:,
65
+ readers:,
66
+ allow_blank: false,
67
+ allow_nil: false,
68
+ # default: nil,
69
+ # preprocess: nil,
70
+ # sensitive: false,
71
+ **validations
72
+ )
73
+ _parse_field_validations(*fields, allow_nil:, allow_blank:, **validations).map do |field, parsed_validations|
74
+ _define_subfield_reader(field, on:, validations: parsed_validations) if readers
75
+ SubfieldConfig.new(field:, validations: parsed_validations, on:)
76
+ end
56
77
  end
57
- end
58
78
 
59
- private
60
-
61
- def _parse_subfield_configs(
62
- *fields,
63
- on:,
64
- readers:,
65
- allow_blank: false,
66
- allow_nil: false,
67
- # default: nil,
68
- # preprocess: nil,
69
- # sensitive: false,
70
- **validations
71
- )
72
- _parse_field_validations(*fields, allow_nil:, allow_blank:, **validations).map do |field, parsed_validations|
73
- _define_subfield_reader(field, on:, validations: parsed_validations) if readers
74
- SubfieldConfig.new(field:, validations: parsed_validations, on:)
75
- end
76
- end
79
+ def _define_subfield_reader(field, on:, validations:)
80
+ # Don't create top-level readers for nested fields
81
+ return if field.to_s.include?(".")
77
82
 
78
- def _define_subfield_reader(field, on:, validations:)
79
- # Don't create top-level readers for nested fields
80
- return if field.to_s.include?(".")
83
+ raise ArgumentError, "expects does not support duplicate sub-keys (i.e. `#{field}` is already defined)" if method_defined?(field)
81
84
 
82
- raise ArgumentError, "expects does not support duplicate sub-keys (i.e. `#{field}` is already defined)" if method_defined?(field)
85
+ define_memoized_reader_method(field) do
86
+ Action::Validation::Subfields.extract(field, public_send(on))
87
+ end
83
88
 
84
- define_memoized_reader_method(field) do
85
- Action::Validation::Subfields.extract(field, public_send(on))
89
+ _define_model_reader(field, validations[:model]) if validations.key?(:model)
86
90
  end
87
-
88
- _define_model_reader(field, validations[:model]) if validations.key?(:model)
89
91
  end
90
- end
91
92
 
92
- module InstanceMethods
93
- def _validate_subfields_contract!
94
- return if subfield_configs.blank?
95
-
96
- subfield_configs.each do |config|
97
- Validation::Subfields.validate!(
98
- field: config.field,
99
- validations: config.validations,
100
- source: public_send(config.on),
101
- exception_klass: Action::InboundValidationError,
102
- )
93
+ module InstanceMethods
94
+ def _validate_subfields_contract!
95
+ return if subfield_configs.blank?
96
+
97
+ subfield_configs.each do |config|
98
+ Validation::Subfields.validate!(
99
+ field: config.field,
100
+ validations: config.validations,
101
+ source: public_send(config.on),
102
+ exception_klass: Action::InboundValidationError,
103
+ )
104
+ end
103
105
  end
104
106
  end
105
107
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Action
4
+ module Core
5
+ module ContractValidation
6
+ private
7
+
8
+ def _apply_inbound_preprocessing!
9
+ internal_field_configs.each do |config|
10
+ next unless config.preprocess
11
+
12
+ initial_value = @context.public_send(config.field)
13
+ new_value = config.preprocess.call(initial_value)
14
+ @context.public_send("#{config.field}=", new_value)
15
+ rescue StandardError => e
16
+ raise Action::ContractViolation::PreprocessingError, "Error preprocessing field '#{config.field}': #{e.message}"
17
+ end
18
+ end
19
+
20
+ def _validate_contract!(direction)
21
+ raise ArgumentError, "Invalid direction: #{direction}" unless %i[inbound outbound].include?(direction)
22
+
23
+ configs = direction == :inbound ? internal_field_configs : external_field_configs
24
+ validations = configs.each_with_object({}) do |config, hash|
25
+ hash[config.field] = config.validations
26
+ end
27
+ context = direction == :inbound ? internal_context : result
28
+ exception_klass = direction == :inbound ? Action::InboundValidationError : Action::OutboundValidationError
29
+
30
+ Validation::Fields.validate!(validations:, context:, exception_klass:)
31
+ end
32
+
33
+ def _apply_defaults!(direction)
34
+ raise ArgumentError, "Invalid direction: #{direction}" unless %i[inbound outbound].include?(direction)
35
+
36
+ configs = direction == :inbound ? internal_field_configs : external_field_configs
37
+ defaults_mapping = configs.each_with_object({}) do |config, hash|
38
+ hash[config.field] = config.default
39
+ end.compact
40
+
41
+ defaults_mapping.each do |field, default_value_getter|
42
+ next if @context.public_send(field).present?
43
+
44
+ default_value = default_value_getter.respond_to?(:call) ? instance_exec(&default_value_getter) : default_value_getter
45
+
46
+ @context.public_send("#{field}=", default_value)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end