request_handler 0.10.0 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 252b6dd26a2f9989d6bb8782a7cfec3c745db91d
4
- data.tar.gz: 95efc2b338c327105d3bd1e6836c92848d1d51c8
3
+ metadata.gz: 1d0758f2d7885ad1d5bc730ab95fbc00428effed
4
+ data.tar.gz: bc11401b7013aa968bd7d22a4d86b674fedf147f
5
5
  SHA512:
6
- metadata.gz: 348b442311e1f02780bb6e6207534dd00d23a614d443a384240fb1814853df6f996f59cad3eecd463a88763c732a16f4a1621e95a061068b6d058cebfbc56f65
7
- data.tar.gz: 941fd8a4714ff60b793475125b0e74fe2187f669ca603cd2821f8bcbf59fa41191e41a32f3eb9189acbff46ed6bb3f0a4b4c2e6aaa882c50279b189208bd589a
6
+ metadata.gz: e9d5ab0fd020c635be86bfd6c028768c9c6eb6c9e401be243bb3118c7a4e98f1af7db529cf44d5e41fcd178562c805042cc8a60d96a26ce5554cfcc2004da0d8
7
+ data.tar.gz: 5273a2cbf95cb99f33d243f174832a9597a27b0280785b0ea39f5a4ee0d6e3fd8bf2478b80b4e170d92658c5d41901e3b78fda8224c2190c7a7c2c6cbda58a91
data/CHANGELOG.md CHANGED
@@ -3,6 +3,11 @@ Changelog
3
3
 
4
4
  ## master
5
5
 
6
+ ## 0.11.0
7
+
8
+ - Parse `included` array from request body
9
+ - Body no longer accepts a default
10
+
6
11
  ## 0.10.0
7
12
 
8
13
  - raise an error if mandatory options are missing in the handler configuration
data/README.md CHANGED
@@ -90,17 +90,17 @@ class DemoHandler < RequestHandler::Base
90
90
  options(->(_handler, _request) { { foo: "bar" } })
91
91
  # options({foo: "bar"}) # also works for hash options instead of procs
92
92
  end
93
+ end
93
94
 
94
- def to_dto
95
- OpenStruct.new(
96
- body: body_params,
97
- page: page_params,
98
- include: include_params,
99
- filter: filter_params,
100
- sort: sort_params,
101
- headers: headers
102
- )
103
- end
95
+ def to_dto
96
+ OpenStruct.new(
97
+ body: body_params,
98
+ page: page_params,
99
+ include: include_params,
100
+ filter: filter_params,
101
+ sort: sort_params,
102
+ headers: headers
103
+ )
104
104
  end
105
105
  end
106
106
 
@@ -146,6 +146,132 @@ include_options = [:posts__comments]
146
146
  sort_options = SortOption.new(:posts__published_on, :asc)
147
147
  ```
148
148
 
149
+ ### Included relations
150
+
151
+ Sometimes you want to create a single resource with its relations in a single
152
+ request, ensuring that everything or nothing at all is created. However, the
153
+ current JSON API specification does not mention anything about how to achieve
154
+ this at all, it is expected that all associated resources already exist.
155
+ `request_handler` attempts to solve this problem by allowing the request body
156
+ to contain an `included` array with all the resources that have to be created.
157
+
158
+ #### Example
159
+
160
+ With this request handler:
161
+
162
+ ```ruby
163
+ class CreateQuestionHandler < RequestHandler::Base
164
+ options do
165
+ body do
166
+ schema(
167
+ Dry::Validation.JSON do
168
+ required(:id).filled(:str?)
169
+ required(:type).filled(:str?)
170
+ required(:content).filled(:str?)
171
+
172
+ optional(:media).schema do
173
+ required(:id).filled(:str?)
174
+ required(:type).filled(:str?)
175
+ end
176
+ end
177
+ )
178
+
179
+ included do
180
+ media(
181
+ Dry::Validation.JSON do
182
+ required(:id).filled(:str?)
183
+ required(:type).filled(:str?)
184
+ required(:url).filled(:str?)
185
+
186
+ optional(:categories).schema do
187
+ required(:id).filled(:str?)
188
+ required(:type).filled(:str?)
189
+ end
190
+ end
191
+ )
192
+ end
193
+ end
194
+ end
195
+
196
+ def to_dto
197
+ # see the resulting body_params below
198
+ { body: body_params }
199
+ end
200
+ end
201
+ ```
202
+
203
+ The following JSON object including its included items is validated with the
204
+ defined schema:
205
+
206
+ ``` json
207
+ {
208
+ "data": {
209
+ "id": "1",
210
+ "type": "questions",
211
+ "attributes": {
212
+ "content": "How much is the fish?"
213
+ },
214
+ "relationships": {
215
+ "media": {
216
+ "data": {
217
+ "id": "image-123456",
218
+ "type": "media"
219
+ }
220
+ }
221
+ }
222
+ }
223
+ },
224
+ "included": [
225
+ {
226
+ "id": "image-123456",
227
+ "type": "media",
228
+ "attributes": {
229
+ "url": "https://example.com/fish.jpg"
230
+ },
231
+ "relationships": {
232
+ "categories": {
233
+ "data": {
234
+ "id": "123",
235
+ "type": "categories"
236
+ }
237
+ }
238
+ }
239
+ }
240
+ ]
241
+ }
242
+ ```
243
+
244
+ The resulting `body_params` will be this:
245
+
246
+ ``` ruby
247
+ [
248
+ # The first object is the main resource object, i.e. the one that is about to
249
+ # be created
250
+ {
251
+ id: '1',
252
+ type: 'questions',
253
+ content: 'How much is the fish?'
254
+ media: [
255
+ {
256
+ id: 'image-123456',
257
+ type: 'media'
258
+ }
259
+ ]
260
+ },
261
+ # The remaining objects are every included object, validated with the schema
262
+ # defined above
263
+ {
264
+ id: 'image-123456',
265
+ type: 'media',
266
+ url: 'https://example.com/fish.jpg',
267
+ categories: {
268
+ id: '123',
269
+ type: 'categories'
270
+ }
271
+ }
272
+ ]
273
+ ```
274
+
149
275
  ### Configuration
150
276
 
151
277
  The default logger and separator can be changed globally by using
@@ -95,12 +95,12 @@ module RequestHandler
95
95
  end
96
96
 
97
97
  def parse_body_params
98
- defaults = fetch_defaults('body.defaults', {})
99
- defaults.merge(BodyParser.new(
100
- request: request,
101
- schema: lookup!('body.schema'),
102
- schema_options: execute_options(lookup('body.options'))
103
- ).run)
98
+ BodyParser.new(
99
+ request: request,
100
+ schema: lookup!('body.schema'),
101
+ schema_options: execute_options(lookup('body.options')),
102
+ included_schemas: lookup('body.included')
103
+ ).run
104
104
  end
105
105
 
106
106
  def parse_fieldsets_params
@@ -3,14 +3,21 @@ require 'request_handler/schema_parser'
3
3
  require 'request_handler/error'
4
4
  module RequestHandler
5
5
  class BodyParser < SchemaParser
6
- def initialize(request:, schema:, schema_options: {})
6
+ def initialize(request:, schema:, schema_options: {}, included_schemas: {})
7
7
  raise MissingArgumentError, :"request.body" => 'is missing' if request.body.nil?
8
8
  super(schema: schema, schema_options: schema_options)
9
9
  @request = request
10
+ @included_schemas = included_schemas
10
11
  end
11
12
 
12
13
  def run
13
- validate_schema(flattened_request_body)
14
+ body, *included = flattened_request_body
15
+ unless included_schemas?
16
+ raise SchemaValidationError, included: 'must be empty' unless included.empty?
17
+ return validate_schema(body)
18
+ end
19
+
20
+ validate_schemas(body, included)
14
21
  end
15
22
 
16
23
  private
@@ -19,10 +26,13 @@ module RequestHandler
19
26
  body = request_body.fetch('data') do
20
27
  raise ExternalArgumentError, body: 'must contain data'
21
28
  end
22
- body.merge!(body.delete('attributes') { {} })
23
- relationships = flatten_relationship_resource_linkages(body.delete('relationships') { {} })
24
- body.merge!(relationships)
25
- body
29
+ [flatten_resource!(body), *parse_included]
30
+ end
31
+
32
+ def flatten_resource!(resource)
33
+ resource.merge!(resource.delete('attributes') { {} })
34
+ relationships = flatten_relationship_resource_linkages(resource.delete('relationships') { {} })
35
+ resource.merge!(relationships)
26
36
  end
27
37
 
28
38
  def flatten_relationship_resource_linkages(relationships)
@@ -33,6 +43,13 @@ module RequestHandler
33
43
  end
34
44
  end
35
45
 
46
+ def parse_included
47
+ included = request_body.fetch('included') { [] }
48
+ included.each do |hsh|
49
+ flatten_resource!(hsh)
50
+ end
51
+ end
52
+
36
53
  def request_body
37
54
  b = request.body
38
55
  b.rewind
@@ -40,6 +57,20 @@ module RequestHandler
40
57
  b.empty? ? {} : MultiJson.load(b)
41
58
  end
42
59
 
43
- attr_reader :request
60
+ def included_schemas?
61
+ !(included_schemas.nil? || included_schemas.empty?)
62
+ end
63
+
64
+ def validate_schemas(body, included)
65
+ schemas = [validate_schema(body)]
66
+ included_schemas.each do |type, schema|
67
+ included.select { |inc| inc['type'] == type.to_s }.each do |inc|
68
+ schemas << validate_schema(inc, with: schema)
69
+ end
70
+ end
71
+ schemas
72
+ end
73
+
74
+ attr_reader :request, :included_schemas
44
75
  end
45
76
  end
@@ -14,14 +14,14 @@ module RequestHandler
14
14
 
15
15
  private
16
16
 
17
- def validate_schema(data)
17
+ def validate_schema(data, with: schema)
18
18
  raise MissingArgumentError, data: 'is missing' if data.nil?
19
- validator = validate(data)
19
+ validator = validate(data, schema: with)
20
20
  validation_failure?(validator)
21
21
  validator.output
22
22
  end
23
23
 
24
- def validate(data)
24
+ def validate(data, schema:)
25
25
  if schema_options.empty?
26
26
  schema.call(data)
27
27
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RequestHandler
4
- VERSION = '0.10.0'.freeze
4
+ VERSION = '0.11.0'.freeze
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: request_handler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Eger
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2017-02-16 00:00:00.000000000 Z
12
+ date: 2017-03-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: dry-validation