request_handler 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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