request_handler 0.11.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
- - ">="
|