action_spec 1.4.0 → 1.6.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.
@@ -30,7 +30,19 @@ module ActionSpec
30
30
  Array(scopes).each { |scope_name| scope_bucket(scope_name)[key] = value }
31
31
  end
32
32
 
33
- def add_error(attribute, type, **options)
33
+ def apply_scope_options!(options_by_scope)
34
+ options_by_scope.each do |scope_name, options|
35
+ bucket = px.scope[scope_name]
36
+ next unless bucket
37
+
38
+ bucket.compact! if options[:compact]
39
+ bucket.delete_if { |_key, value| value.blank? } if options[:compact_blank]
40
+ end
41
+ end
42
+
43
+ def add_error(attribute, type, message: nil, **options)
44
+ return errors.add(attribute, message) if message
45
+
34
46
  if (message = ActionSpec.config.message_for(attribute, type, options))
35
47
  errors.add(attribute, message)
36
48
  else
@@ -16,6 +16,8 @@ module ActionSpec
16
16
  merge_body!(result)
17
17
  merge_group!(result, endpoint.request.header, source: header_source, location: :headers)
18
18
  merge_group!(result, endpoint.request.cookie, source: cookie_source, location: :cookies)
19
+ result.apply_scope_options!(endpoint.request.scope_options)
20
+ apply_custom_validations!(result)
19
21
  result
20
22
  end
21
23
 
@@ -23,6 +25,14 @@ module ActionSpec
23
25
 
24
26
  attr_reader :endpoint, :controller, :coerce
25
27
 
28
+ BUILT_IN_GROUPS = {
29
+ path: ->(request) { request.path },
30
+ query: ->(request) { request.query },
31
+ body: ->(request) { request.body },
32
+ headers: ->(request) { request.header },
33
+ cookies: ->(request) { request.cookie }
34
+ }.freeze
35
+
26
36
  def merge_body!(result)
27
37
  if endpoint.request.body_required? && body_source.blank?
28
38
  result.add_error("body", :required)
@@ -90,6 +100,100 @@ module ActionSpec
90
100
 
91
101
  field.name
92
102
  end
103
+
104
+ def apply_custom_validations!(result)
105
+ return unless endpoint.request.custom_validation?
106
+
107
+ with_controller_px(result.px) do
108
+ BUILT_IN_GROUPS.each do |location, group_reader|
109
+ validate_group!(
110
+ result,
111
+ group_reader.call(endpoint.request),
112
+ values: result.px.scope.fetch(location),
113
+ location:
114
+ )
115
+ end
116
+ end
117
+ end
118
+
119
+ def validate_group!(result, group, values:, location:)
120
+ return unless group.custom_validation?
121
+
122
+ group.fields.each do |field|
123
+ next unless field.custom_validation?
124
+
125
+ key = storage_key(field, location)
126
+ next unless values.key?(key)
127
+
128
+ validate_field!(field, values[key], result:, path: [field.name])
129
+ end
130
+ end
131
+
132
+ def validate_field!(field, value, result:, path:)
133
+ validate_nested_schema!(field.schema, value, result:, path:)
134
+ return if field.validate_value(value, context: controller)
135
+
136
+ field.add_error(result, path:, type: :invalid, value:, context: controller)
137
+ end
138
+
139
+ def validate_nested_schema!(schema, value, result:, path:)
140
+ return unless schema.custom_validation?
141
+
142
+ case schema
143
+ when ActionSpec::Schema::ObjectOf
144
+ return unless value.is_a?(Hash)
145
+
146
+ source = value.with_indifferent_access
147
+ schema.fields.each_value do |field|
148
+ next unless field.custom_validation?
149
+ next unless source.key?(field.output_name)
150
+
151
+ validate_field!(field, source[field.output_name], result:, path: [*path, field.name])
152
+ end
153
+ when ActionSpec::Schema::ArrayOf
154
+ return unless value.is_a?(Array)
155
+
156
+ value.each_with_index do |entry, index|
157
+ validate_array_item!(schema.item, entry, result:, path: [*path, index])
158
+ end
159
+ end
160
+ end
161
+
162
+ def validate_array_item!(schema, value, result:, path:)
163
+ return unless schema.custom_validation?
164
+
165
+ case schema
166
+ when ActionSpec::Schema::ObjectOf
167
+ return unless value.is_a?(Hash)
168
+
169
+ source = value.with_indifferent_access
170
+ schema.fields.each_value do |field|
171
+ next unless field.custom_validation?
172
+ next unless source.key?(field.output_name)
173
+
174
+ validate_field!(field, source[field.output_name], result:, path: [*path, field.name])
175
+ end
176
+ when ActionSpec::Schema::ArrayOf
177
+ return unless value.is_a?(Array)
178
+
179
+ value.each_with_index do |entry, index|
180
+ validate_array_item!(schema.item, entry, result:, path: [*path, index])
181
+ end
182
+ end
183
+ end
184
+
185
+ def with_controller_px(px)
186
+ previous_defined = controller.instance_variable_defined?(:@px)
187
+ previous = controller.instance_variable_get(:@px)
188
+ controller.instance_variable_set(:@px, px)
189
+ yield
190
+ ensure
191
+ if previous_defined
192
+ controller.instance_variable_set(:@px, previous)
193
+ else
194
+ controller.remove_instance_variable(:@px) if controller.instance_variable_defined?(:@px)
195
+ end
196
+ end
93
197
  end
94
198
  end
95
199
  end
@@ -1,3 +1,3 @@
1
1
  module ActionSpec
2
- VERSION = "1.4.0"
2
+ VERSION = "1.6.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_spec
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - zhandao