request_handler 0.11.0 → 0.12.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 +4 -4
- data/.rubocop.yml +5 -1
- data/.travis.yml +9 -13
- data/CHANGELOG.md +9 -0
- data/Dangerfile +5 -5
- data/Gemfile +1 -0
- data/Guardfile +1 -0
- data/README.md +69 -0
- data/Rakefile +1 -0
- data/lib/request_handler/base.rb +14 -1
- data/lib/request_handler/body_parser.rb +13 -53
- data/lib/request_handler/error.rb +16 -0
- data/lib/request_handler/fieldsets_parser.rb +13 -7
- data/lib/request_handler/filter_parser.rb +2 -1
- data/lib/request_handler/header_parser.rb +1 -0
- data/lib/request_handler/helper.rb +1 -0
- data/lib/request_handler/include_option_parser.rb +3 -2
- data/lib/request_handler/json_api_document_parser.rb +70 -0
- data/lib/request_handler/multipart_parser.rb +67 -0
- data/lib/request_handler/option_parser.rb +1 -0
- data/lib/request_handler/page_parser.rb +5 -4
- data/lib/request_handler/schema_parser.rb +1 -0
- data/lib/request_handler/sort_option_parser.rb +4 -3
- data/lib/request_handler/version.rb +1 -1
- data/lib/request_handler.rb +1 -0
- data/request_handler.gemspec +5 -4
- metadata +21 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6cf97cca0464ee4cd04d685441600aadfa55197
|
4
|
+
data.tar.gz: dde5d38dac0cf15bb3103a78862219d0fcc26925
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d0f9d84f1864af7c3ba99ae27d225ba3fcb6afc8e35c447916e5ef9d84eb7438cc861295429fd715f7667f5fc5689c27d4923b1c8cb185a12d53eea7f7fce11
|
7
|
+
data.tar.gz: afdf18bfb0c580d0a63a4053a6717a5d78cd69f154e9d93de058e190461a6707d31c62abd30e8b6d84116a96b7b0d2dc3f172581581d74d9b039e03eeaa6894e
|
data/.rubocop.yml
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# minitest specifics
|
2
2
|
AllCops:
|
3
|
-
TargetRubyVersion: 2.
|
3
|
+
TargetRubyVersion: 2.2
|
4
|
+
Exclude:
|
5
|
+
- Dangerfile
|
4
6
|
Metrics/LineLength:
|
5
7
|
Max: 119
|
6
8
|
Metrics/MethodLength:
|
@@ -13,5 +15,7 @@ Style/ClassAndModuleChildren:
|
|
13
15
|
Exclude:
|
14
16
|
- test/**/*
|
15
17
|
|
18
|
+
Metrics/BlockLength:
|
19
|
+
Enabled: false
|
16
20
|
Style/Documentation:
|
17
21
|
Enabled: false
|
data/.travis.yml
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
language: ruby
|
2
2
|
cache: bundler
|
3
3
|
rvm:
|
4
|
-
- 2.
|
5
|
-
- 2.
|
6
|
-
- 2.
|
7
|
-
-
|
8
|
-
- jruby-9.1.7.0
|
4
|
+
- 2.2.7
|
5
|
+
- 2.3.4
|
6
|
+
- 2.4.1
|
7
|
+
- jruby-9.1.8.0
|
9
8
|
jdk:
|
10
9
|
- oraclejdk8
|
11
10
|
env:
|
@@ -13,24 +12,21 @@ env:
|
|
13
12
|
- "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
|
14
13
|
matrix:
|
15
14
|
exclude:
|
16
|
-
- rvm: 2.
|
15
|
+
- rvm: 2.2.7
|
17
16
|
jdk: oraclejdk8
|
18
17
|
env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
|
19
|
-
- rvm: 2.
|
18
|
+
- rvm: 2.3.4
|
20
19
|
jdk: oraclejdk8
|
21
20
|
env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
|
22
|
-
- rvm: 2.
|
23
|
-
jdk: oraclejdk8
|
24
|
-
env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
|
25
|
-
- rvm: 2.4.0
|
21
|
+
- rvm: 2.4.1
|
26
22
|
jdk: oraclejdk8
|
27
23
|
env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
|
28
24
|
allow_failures:
|
29
|
-
- rvm: jruby-9.1.
|
25
|
+
- rvm: jruby-9.1.8.0
|
30
26
|
jdk: oraclejdk8
|
31
27
|
env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
|
32
28
|
before_install:
|
33
29
|
- gem update --system
|
34
|
-
- gem install bundler -v 1.
|
30
|
+
- gem install bundler -v 1.14.6
|
35
31
|
before_script:
|
36
32
|
- bundle exec danger
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,15 @@ Changelog
|
|
3
3
|
|
4
4
|
## master
|
5
5
|
|
6
|
+
## 0.12.0
|
7
|
+
|
8
|
+
- support for multipart-requests
|
9
|
+
- update rubocop and fix danger check
|
10
|
+
- drop support for ruby 2.1 (dry-types does not support it anymore either which makes supporting it here pointless)
|
11
|
+
- adapt fieldset validation to allow all fields in addition to a specific enum
|
12
|
+
- no need for 'required' field in fieldsets_parser anymore
|
13
|
+
- throw different errors for each parser, all new errors inherit from ExternalArgumentError
|
14
|
+
|
6
15
|
## 0.11.0
|
7
16
|
|
8
17
|
- Parse `included` array from request body
|
data/Dangerfile
CHANGED
@@ -3,24 +3,24 @@
|
|
3
3
|
# --------------------------------------------------------------------------------------------------------------------
|
4
4
|
has_app_changes = !git.modified_files.grep(/lib/).empty?
|
5
5
|
has_test_changes = !git.modified_files.grep(/spec/).empty?
|
6
|
-
is_version_bump = git.modified_files.sort == [
|
6
|
+
is_version_bump = git.modified_files.sort == ['CHANGELOG.md', 'lib/request_handler/version.rb'].sort
|
7
7
|
|
8
8
|
if has_app_changes && !has_test_changes && !is_version_bump
|
9
9
|
warn("Tests were not updated. That's OK if you're refactoring existing code.", sticky: false)
|
10
10
|
end
|
11
11
|
|
12
|
-
if !git.modified_files.include?(
|
12
|
+
if !git.modified_files.include?('CHANGELOG.md') && has_app_changes
|
13
13
|
fail("Please include a CHANGELOG entry. \nYou can find it at [CHANGELOG.md](https://github.com/request_handler/request_handler/blob/master/CHANGELOG.md).")
|
14
14
|
message "Note, we hard-wrap at 80 chars and use 2 spaces after the last line."
|
15
15
|
end
|
16
16
|
|
17
17
|
# Make it more obvious that a PR is a work in progress and shouldn't be merged yet
|
18
|
-
warn(
|
18
|
+
warn('PR is classed as Work in Progress') if github.pr_title.include? 'WIP'
|
19
19
|
|
20
20
|
# Warn when there is a big PR
|
21
|
-
warn(
|
21
|
+
warn('Big PR') if git.lines_of_code > 500
|
22
22
|
|
23
23
|
commit_lint.check warn: :all, disable: [:subject_cap]
|
24
24
|
|
25
25
|
# rubocop
|
26
|
-
rubocop.lint
|
26
|
+
rubocop.lint
|
data/Gemfile
CHANGED
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -272,6 +272,75 @@ The resulting `body_params` will be this:
|
|
272
272
|
]
|
273
273
|
```
|
274
274
|
|
275
|
+
### Multipart requests
|
276
|
+
It is also possible to process and validate multipart requests, consisting of an arbitrary number of parts.
|
277
|
+
|
278
|
+
The following request handler accepts a question (which will be uploaded as a json-file) and an additional
|
279
|
+
file related to the question
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
class CreateQuestionHandler < RequestHandler::Base
|
283
|
+
options do
|
284
|
+
multipart do
|
285
|
+
question do
|
286
|
+
schema(
|
287
|
+
Dry::Validation.JSON do
|
288
|
+
required(:id).filled(:str?)
|
289
|
+
required(:type).filled(:str?)
|
290
|
+
required(:content).filled(:str?)
|
291
|
+
end
|
292
|
+
)
|
293
|
+
end
|
294
|
+
|
295
|
+
file do
|
296
|
+
# no validation necessary
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def to_dto
|
302
|
+
# see the resulting multipart_params below
|
303
|
+
{ multipart: multipart_params }
|
304
|
+
end
|
305
|
+
end
|
306
|
+
```
|
307
|
+
|
308
|
+
Assuming that the request consists of a json file `question.json` containing
|
309
|
+
``` json
|
310
|
+
{
|
311
|
+
"data": {
|
312
|
+
"id": "1",
|
313
|
+
"type": "questions",
|
314
|
+
"attributes": {
|
315
|
+
"content": "How much is the fish?"
|
316
|
+
}
|
317
|
+
}
|
318
|
+
}
|
319
|
+
```
|
320
|
+
|
321
|
+
and an additional file `image.png`, the resulting `multipart_params` will be the following:
|
322
|
+
|
323
|
+
``` ruby
|
324
|
+
{
|
325
|
+
question:
|
326
|
+
{
|
327
|
+
id: '1',
|
328
|
+
type: 'questions',
|
329
|
+
content: 'How much is the fish?'
|
330
|
+
},
|
331
|
+
file:
|
332
|
+
{
|
333
|
+
filename: 'image.png',
|
334
|
+
type: 'application/octet-stream'
|
335
|
+
name: 'file',
|
336
|
+
tempfile: #<Tempfile:/...>,
|
337
|
+
head: 'Content-Disposition: form-data;...'
|
338
|
+
}
|
339
|
+
}
|
340
|
+
```
|
341
|
+
|
342
|
+
Please note that each part's content has to be uploaded as a separate file currently.
|
343
|
+
|
275
344
|
### Configuration
|
276
345
|
|
277
346
|
The default logger and separator can be changed globally by using
|
data/Rakefile
CHANGED
data/lib/request_handler/base.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'request_handler/filter_parser'
|
3
4
|
require 'request_handler/page_parser'
|
4
5
|
require 'request_handler/include_option_parser'
|
5
6
|
require 'request_handler/sort_option_parser'
|
6
7
|
require 'request_handler/header_parser'
|
7
8
|
require 'request_handler/body_parser'
|
9
|
+
require 'request_handler/multipart_parser'
|
8
10
|
require 'request_handler/fieldsets_parser'
|
9
11
|
require 'request_handler/helper'
|
10
12
|
require 'confstruct'
|
@@ -55,6 +57,10 @@ module RequestHandler
|
|
55
57
|
@body_params ||= parse_body_params
|
56
58
|
end
|
57
59
|
|
60
|
+
def multipart_params
|
61
|
+
@multipart_params ||= parse_multipart_params
|
62
|
+
end
|
63
|
+
|
58
64
|
def fieldsets_params
|
59
65
|
@fieldsets_params ||= parse_fieldsets_params
|
60
66
|
end
|
@@ -103,10 +109,17 @@ module RequestHandler
|
|
103
109
|
).run
|
104
110
|
end
|
105
111
|
|
112
|
+
def parse_multipart_params
|
113
|
+
MultipartsParser.new(
|
114
|
+
request: request,
|
115
|
+
multipart_config: lookup!('multipart')
|
116
|
+
).run
|
117
|
+
end
|
118
|
+
|
106
119
|
def parse_fieldsets_params
|
107
120
|
FieldsetsParser.new(params: params,
|
108
121
|
allowed: lookup!('fieldsets.allowed'),
|
109
|
-
required: lookup
|
122
|
+
required: lookup('fieldsets.required') || []).run
|
110
123
|
end
|
111
124
|
|
112
125
|
def fetch_defaults(key, default)
|
@@ -1,55 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'request_handler/schema_parser'
|
3
4
|
require 'request_handler/error'
|
5
|
+
require 'request_handler/json_api_document_parser'
|
4
6
|
module RequestHandler
|
5
|
-
class BodyParser
|
7
|
+
class BodyParser
|
6
8
|
def initialize(request:, schema:, schema_options: {}, included_schemas: {})
|
7
|
-
raise MissingArgumentError,
|
8
|
-
super(schema: schema, schema_options: schema_options)
|
9
|
+
raise MissingArgumentError, "request.body": 'is missing' if request.body.nil?
|
9
10
|
@request = request
|
11
|
+
@schema = schema
|
12
|
+
@schema_options = schema_options
|
10
13
|
@included_schemas = included_schemas
|
11
14
|
end
|
12
15
|
|
13
16
|
def run
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
validate_schemas(body, included)
|
17
|
+
JsonApiDocumentParser.new(
|
18
|
+
document: request_body,
|
19
|
+
schema: schema,
|
20
|
+
schema_options: schema_options,
|
21
|
+
included_schemas: included_schemas
|
22
|
+
).run
|
21
23
|
end
|
22
24
|
|
23
25
|
private
|
24
26
|
|
25
|
-
def flattened_request_body
|
26
|
-
body = request_body.fetch('data') do
|
27
|
-
raise ExternalArgumentError, body: 'must contain data'
|
28
|
-
end
|
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)
|
36
|
-
end
|
37
|
-
|
38
|
-
def flatten_relationship_resource_linkages(relationships)
|
39
|
-
relationships.each_with_object({}) do |(k, v), memo|
|
40
|
-
resource_linkage = v['data']
|
41
|
-
next if resource_linkage.nil?
|
42
|
-
memo[k] = resource_linkage
|
43
|
-
end
|
44
|
-
end
|
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
|
-
|
53
27
|
def request_body
|
54
28
|
b = request.body
|
55
29
|
b.rewind
|
@@ -57,20 +31,6 @@ module RequestHandler
|
|
57
31
|
b.empty? ? {} : MultiJson.load(b)
|
58
32
|
end
|
59
33
|
|
60
|
-
|
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
|
34
|
+
attr_reader :request, :schema, :schema_options, :included_schemas
|
75
35
|
end
|
76
36
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module RequestHandler
|
3
4
|
class BaseError < StandardError
|
4
5
|
attr_reader :errors
|
@@ -29,4 +30,19 @@ module RequestHandler
|
|
29
30
|
end
|
30
31
|
class NoConfigAvailableError < InternalBaseError
|
31
32
|
end
|
33
|
+
|
34
|
+
class BodyParamsError < ExternalArgumentError
|
35
|
+
end
|
36
|
+
class FieldsetsParamsError < ExternalArgumentError
|
37
|
+
end
|
38
|
+
class FilterParamsError < ExternalArgumentError
|
39
|
+
end
|
40
|
+
class IncludeParamsError < ExternalArgumentError
|
41
|
+
end
|
42
|
+
class PageParamsError < ExternalArgumentError
|
43
|
+
end
|
44
|
+
class SortParamsError < ExternalArgumentError
|
45
|
+
end
|
46
|
+
class MultipartParamsError < ExternalArgumentError
|
47
|
+
end
|
32
48
|
end
|
@@ -1,22 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'request_handler/schema_parser'
|
3
4
|
require 'request_handler/error'
|
4
5
|
module RequestHandler
|
5
6
|
class FieldsetsParser
|
6
7
|
def initialize(params:, allowed: {}, required: [])
|
7
8
|
@params = params
|
9
|
+
allowed.reject! { |_k, v| v == false }
|
8
10
|
allowed.each_value do |option|
|
9
|
-
raise InternalArgumentError, allowed: 'must be a Enum' unless
|
11
|
+
raise InternalArgumentError, allowed: 'must be a Enum or a Boolean' unless
|
12
|
+
option.is_a?(Dry::Types::Enum) || option.is_a?(TrueClass)
|
10
13
|
end
|
11
14
|
@allowed = allowed
|
12
|
-
raise InternalArgumentError,
|
15
|
+
raise InternalArgumentError, required: 'must be an Array' unless required.is_a?(Array)
|
13
16
|
@required = required
|
14
17
|
end
|
15
18
|
|
16
19
|
def run
|
17
20
|
fields = params['fields']
|
18
21
|
raise_missing_fields_param unless fields
|
19
|
-
|
20
22
|
fieldsets = fields.to_h.each_with_object({}) do |(type, values), memo|
|
21
23
|
type = type.to_sym
|
22
24
|
raise_invalid_field_option(type)
|
@@ -34,14 +36,18 @@ module RequestHandler
|
|
34
36
|
end
|
35
37
|
|
36
38
|
def parse_option(type, option)
|
37
|
-
allowed[type]
|
39
|
+
if allowed[type] == true
|
40
|
+
option.to_sym
|
41
|
+
else
|
42
|
+
allowed[type].call(option).to_sym
|
43
|
+
end
|
38
44
|
rescue Dry::Types::ConstraintError
|
39
|
-
raise
|
45
|
+
raise FieldsetsParamsError, fieldsets: "invalid field: <#{option}> for type: #{type}"
|
40
46
|
end
|
41
47
|
|
42
48
|
def check_required_fieldsets_types(fieldsets)
|
43
49
|
return fieldsets if (required - fieldsets.keys).empty?
|
44
|
-
raise
|
50
|
+
raise FieldsetsParamsError, fieldsets: 'missing required fieldsets parameter'
|
45
51
|
end
|
46
52
|
|
47
53
|
def raise_invalid_field_option(type)
|
@@ -51,7 +57,7 @@ module RequestHandler
|
|
51
57
|
|
52
58
|
def raise_missing_fields_param
|
53
59
|
return if required.empty?
|
54
|
-
raise
|
60
|
+
raise FieldsetsParamsError, fieldsets: 'missing required fields options'
|
55
61
|
end
|
56
62
|
|
57
63
|
attr_reader :params, :allowed, :required
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'request_handler/schema_parser'
|
3
4
|
require 'request_handler/error'
|
4
5
|
module RequestHandler
|
@@ -6,7 +7,7 @@ module RequestHandler
|
|
6
7
|
def initialize(params:, schema:, additional_url_filter:, schema_options: {})
|
7
8
|
super(schema: schema, schema_options: schema_options)
|
8
9
|
@filter = params.fetch('filter') { {} }
|
9
|
-
raise
|
10
|
+
raise FilterParamsError, filter: 'must be a Hash' unless @filter.is_a?(Hash)
|
10
11
|
Array(additional_url_filter).each do |key|
|
11
12
|
key = key.to_s
|
12
13
|
raise build_error(key) unless @filter[key].nil?
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'request_handler/option_parser'
|
3
4
|
require 'request_handler/error'
|
4
5
|
module RequestHandler
|
@@ -6,7 +7,7 @@ module RequestHandler
|
|
6
7
|
def run
|
7
8
|
return [] unless params.key?('include')
|
8
9
|
options = fetch_options
|
9
|
-
raise
|
10
|
+
raise IncludeParamsError, include: 'must not contain a space' if options.include? ' '
|
10
11
|
allowed_options(options.split(','))
|
11
12
|
end
|
12
13
|
|
@@ -22,7 +23,7 @@ module RequestHandler
|
|
22
23
|
end
|
23
24
|
|
24
25
|
def fetch_options
|
25
|
-
raise
|
26
|
+
raise IncludeParamsError, include_options: 'query paramter must not be empty' if empty_param?('include')
|
26
27
|
params.fetch('include') { '' }
|
27
28
|
end
|
28
29
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'request_handler/schema_parser'
|
4
|
+
require 'request_handler/error'
|
5
|
+
module RequestHandler
|
6
|
+
class JsonApiDocumentParser < SchemaParser
|
7
|
+
def initialize(document:, schema:, schema_options: {}, included_schemas: {})
|
8
|
+
raise MissingArgumentError, "data": 'is missing' if document.nil?
|
9
|
+
super(schema: schema, schema_options: schema_options)
|
10
|
+
@document = document
|
11
|
+
@included_schemas = included_schemas
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
resource, *included = flattened_document
|
16
|
+
unless included_schemas?
|
17
|
+
raise SchemaValidationError, included: 'must be empty' unless included.empty?
|
18
|
+
return validate_schema(resource)
|
19
|
+
end
|
20
|
+
|
21
|
+
validate_schemas(resource, included)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def flattened_document
|
27
|
+
resource = document.fetch('data') do
|
28
|
+
raise BodyParamsError, resource: 'must contain data'
|
29
|
+
end
|
30
|
+
[flatten_resource!(resource), *parse_included]
|
31
|
+
end
|
32
|
+
|
33
|
+
def flatten_resource!(resource)
|
34
|
+
resource.merge!(resource.delete('attributes') { {} })
|
35
|
+
relationships = flatten_relationship_resource_linkages(resource.delete('relationships') { {} })
|
36
|
+
resource.merge!(relationships)
|
37
|
+
end
|
38
|
+
|
39
|
+
def flatten_relationship_resource_linkages(relationships)
|
40
|
+
relationships.each_with_object({}) do |(k, v), memo|
|
41
|
+
resource_linkage = v['data']
|
42
|
+
next if resource_linkage.nil?
|
43
|
+
memo[k] = resource_linkage
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_included
|
48
|
+
included = document.fetch('included') { [] }
|
49
|
+
included.each do |hsh|
|
50
|
+
flatten_resource!(hsh)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def included_schemas?
|
55
|
+
!(included_schemas.nil? || included_schemas.empty?)
|
56
|
+
end
|
57
|
+
|
58
|
+
def validate_schemas(resource, included)
|
59
|
+
schemas = [validate_schema(resource)]
|
60
|
+
included_schemas.each do |type, schema|
|
61
|
+
included.select { |inc| inc['type'] == type.to_s }.each do |inc|
|
62
|
+
schemas << validate_schema(inc, with: schema)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
schemas
|
66
|
+
end
|
67
|
+
|
68
|
+
attr_reader :document, :included_schemas
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'request_handler/error'
|
4
|
+
require 'request_handler/schema_parser'
|
5
|
+
require 'request_handler/error'
|
6
|
+
require 'request_handler/json_api_document_parser'
|
7
|
+
module RequestHandler
|
8
|
+
class MultipartsParser
|
9
|
+
def initialize(request:, multipart_config:)
|
10
|
+
@request = request
|
11
|
+
@params = request.params
|
12
|
+
@multipart_config = multipart_config
|
13
|
+
missing_arguments = []
|
14
|
+
missing_arguments << { multipart_config: 'is missing' } if multipart_config.nil?
|
15
|
+
raise MissingArgumentError, missing_arguments unless missing_arguments.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
multipart_config.keys.each_with_object({}) do |name, memo|
|
20
|
+
params.fetch(name.to_s) { raise MultipartParamsError, multipart: 'missing' }
|
21
|
+
memo[name] = parse_part(name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def parse_part(name)
|
28
|
+
params[name.to_s].fetch(:tempfile) { raise MultipartParamsError, multipart_file: 'missing' }
|
29
|
+
if lookup("#{name}.schema")
|
30
|
+
parse_data(name)
|
31
|
+
else
|
32
|
+
params[name.to_s]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_data(name)
|
37
|
+
JsonApiDocumentParser.new(
|
38
|
+
document: load_json(name),
|
39
|
+
schema: lookup("#{name}.schema"),
|
40
|
+
schema_options: execute_options(lookup("#{name}.options")),
|
41
|
+
included_schemas: lookup("#{name}.included")
|
42
|
+
).run
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_json(name)
|
46
|
+
MultiJson.load(multipart_file(name).read)
|
47
|
+
rescue MultiJson::ParseError
|
48
|
+
raise MultipartParamsError, multipart_file: 'invalid'
|
49
|
+
end
|
50
|
+
|
51
|
+
def multipart_file(name)
|
52
|
+
params[name.to_s][:tempfile]
|
53
|
+
end
|
54
|
+
|
55
|
+
def lookup(key)
|
56
|
+
multipart_config.lookup!(key)
|
57
|
+
end
|
58
|
+
|
59
|
+
def execute_options(options)
|
60
|
+
return {} if options.nil?
|
61
|
+
return options unless options.respond_to?(:call)
|
62
|
+
options.call(self, request)
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :params, :request, :multipart_config
|
66
|
+
end
|
67
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'request_handler/error'
|
3
4
|
module RequestHandler
|
4
5
|
class PageParser
|
@@ -8,7 +9,7 @@ module RequestHandler
|
|
8
9
|
missing_arguments << { page_config: 'is missing' } if page_config.nil?
|
9
10
|
raise MissingArgumentError, missing_arguments unless missing_arguments.empty?
|
10
11
|
@page_options = params.fetch('page') { {} }
|
11
|
-
raise
|
12
|
+
raise PageParamsError, page: 'must be a Hash' unless @page_options.is_a?(Hash)
|
12
13
|
@config = page_config
|
13
14
|
end
|
14
15
|
|
@@ -25,7 +26,7 @@ module RequestHandler
|
|
25
26
|
|
26
27
|
private
|
27
28
|
|
28
|
-
TOP_LEVEL_PAGE_KEYS = Set.new([
|
29
|
+
TOP_LEVEL_PAGE_KEYS = Set.new(%i[default_size max_size])
|
29
30
|
attr_reader :page_options, :config
|
30
31
|
|
31
32
|
def check_for_missing_options(config)
|
@@ -64,10 +65,10 @@ module RequestHandler
|
|
64
65
|
|
65
66
|
def check_int(string:, error_msg:)
|
66
67
|
output = Integer(string)
|
67
|
-
raise
|
68
|
+
raise PageParamsError, error_msg unless output > 0
|
68
69
|
output
|
69
70
|
rescue ArgumentError
|
70
|
-
raise
|
71
|
+
raise PageParamsError, error_msg
|
71
72
|
end
|
72
73
|
|
73
74
|
def apply_max_size_constraint(size, prefix)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'request_handler/option_parser'
|
3
4
|
require 'request_handler/error'
|
4
5
|
require 'request_handler/sort_option'
|
@@ -7,12 +8,12 @@ module RequestHandler
|
|
7
8
|
def run
|
8
9
|
return [] unless params.key?('sort')
|
9
10
|
sort_options = parse_options(fetch_options)
|
10
|
-
raise
|
11
|
+
raise SortParamsError, sort_options: 'must be unique' if duplicates?(sort_options)
|
11
12
|
sort_options
|
12
13
|
end
|
13
14
|
|
14
15
|
def fetch_options
|
15
|
-
raise
|
16
|
+
raise SortParamsError, sort_options: 'the query paramter must not be empty' if empty_param?('sort')
|
16
17
|
params.fetch('sort') { '' }.split(',')
|
17
18
|
end
|
18
19
|
|
@@ -26,7 +27,7 @@ module RequestHandler
|
|
26
27
|
end
|
27
28
|
|
28
29
|
def parse_option(option)
|
29
|
-
raise
|
30
|
+
raise SortParamsError, sort_options: 'must not contain a space' if option.include? ' '
|
30
31
|
if option.start_with?('-')
|
31
32
|
[option[1..-1], :desc]
|
32
33
|
else
|
data/lib/request_handler.rb
CHANGED
data/request_handler.gemspec
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
|
+
|
3
4
|
lib = File.expand_path('../lib', __FILE__)
|
4
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
6
|
require 'request_handler/version'
|
6
|
-
|
7
|
-
# rubocop:disable Metrics/BlockLength
|
8
7
|
Gem::Specification.new do |spec|
|
9
8
|
spec.name = 'request_handler'
|
10
9
|
spec.version = RequestHandler::VERSION
|
@@ -15,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
15
14
|
spec.description = 'shared base for request_handler using dry-* gems'
|
16
15
|
spec.homepage = 'https://github.com/runtastic/request_handler'
|
17
16
|
spec.license = 'MIT'
|
18
|
-
spec.required_ruby_version = '~> 2.
|
17
|
+
spec.required_ruby_version = '~> 2.2'
|
19
18
|
|
20
19
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
21
20
|
f.match(%r{^(test|spec|features)/})
|
@@ -36,9 +35,11 @@ Gem::Specification.new do |spec|
|
|
36
35
|
spec.add_development_dependency 'codecov'
|
37
36
|
|
38
37
|
spec.add_development_dependency 'rubocop_runner', '~> 2.0'
|
39
|
-
spec.add_development_dependency 'rubocop', '~> 0.
|
38
|
+
spec.add_development_dependency 'rubocop', '~> 0.48.1'
|
40
39
|
|
41
40
|
spec.add_development_dependency 'guard'
|
42
41
|
spec.add_development_dependency 'guard-rspec'
|
43
42
|
spec.add_development_dependency 'guard-rubocop'
|
43
|
+
|
44
|
+
spec.add_development_dependency 'rack'
|
44
45
|
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.
|
4
|
+
version: 0.12.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-
|
12
|
+
date: 2017-05-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: dry-validation
|
@@ -157,14 +157,14 @@ dependencies:
|
|
157
157
|
requirements:
|
158
158
|
- - "~>"
|
159
159
|
- !ruby/object:Gem::Version
|
160
|
-
version: 0.
|
160
|
+
version: 0.48.1
|
161
161
|
type: :development
|
162
162
|
prerelease: false
|
163
163
|
version_requirements: !ruby/object:Gem::Requirement
|
164
164
|
requirements:
|
165
165
|
- - "~>"
|
166
166
|
- !ruby/object:Gem::Version
|
167
|
-
version: 0.
|
167
|
+
version: 0.48.1
|
168
168
|
- !ruby/object:Gem::Dependency
|
169
169
|
name: guard
|
170
170
|
requirement: !ruby/object:Gem::Requirement
|
@@ -207,6 +207,20 @@ dependencies:
|
|
207
207
|
- - ">="
|
208
208
|
- !ruby/object:Gem::Version
|
209
209
|
version: '0'
|
210
|
+
- !ruby/object:Gem::Dependency
|
211
|
+
name: rack
|
212
|
+
requirement: !ruby/object:Gem::Requirement
|
213
|
+
requirements:
|
214
|
+
- - ">="
|
215
|
+
- !ruby/object:Gem::Version
|
216
|
+
version: '0'
|
217
|
+
type: :development
|
218
|
+
prerelease: false
|
219
|
+
version_requirements: !ruby/object:Gem::Requirement
|
220
|
+
requirements:
|
221
|
+
- - ">="
|
222
|
+
- !ruby/object:Gem::Version
|
223
|
+
version: '0'
|
210
224
|
description: shared base for request_handler using dry-* gems
|
211
225
|
email:
|
212
226
|
- andreas.eger@runtastic.com
|
@@ -238,6 +252,8 @@ files:
|
|
238
252
|
- lib/request_handler/header_parser.rb
|
239
253
|
- lib/request_handler/helper.rb
|
240
254
|
- lib/request_handler/include_option_parser.rb
|
255
|
+
- lib/request_handler/json_api_document_parser.rb
|
256
|
+
- lib/request_handler/multipart_parser.rb
|
241
257
|
- lib/request_handler/option_parser.rb
|
242
258
|
- lib/request_handler/page_parser.rb
|
243
259
|
- lib/request_handler/schema_parser.rb
|
@@ -257,7 +273,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
257
273
|
requirements:
|
258
274
|
- - "~>"
|
259
275
|
- !ruby/object:Gem::Version
|
260
|
-
version: '2.
|
276
|
+
version: '2.2'
|
261
277
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
262
278
|
requirements:
|
263
279
|
- - ">="
|