request_handler 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +75 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +1 -0
- data/README.md +48 -1
- data/lib/request_handler.rb +1 -0
- data/lib/request_handler/base.rb +1 -1
- data/lib/request_handler/error.rb +14 -3
- data/lib/request_handler/fieldsets_parser.rb +28 -5
- data/lib/request_handler/filter_parser.rb +25 -1
- data/lib/request_handler/include_option_parser.rb +16 -3
- data/lib/request_handler/json_api_document_parser.rb +15 -1
- data/lib/request_handler/multipart_parser.rb +17 -6
- data/lib/request_handler/page_parser.rb +14 -7
- data/lib/request_handler/query_parser.rb +8 -0
- data/lib/request_handler/schema_parser.rb +36 -2
- data/lib/request_handler/sort_option_parser.rb +15 -4
- data/lib/request_handler/validation/definition_engine.rb +8 -0
- data/lib/request_handler/validation/dry_engine.rb +8 -0
- data/lib/request_handler/validation/engine.rb +8 -0
- data/lib/request_handler/version.rb +1 -1
- metadata +4 -4
- data/.travis.yml +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbf5f683f749e9a5431fe1d07a250c43e72446ddc4fdbb472acd7b06d737fad6
|
4
|
+
data.tar.gz: e12067caf9c5ec19b306a00d89666fac1a862449f8a68d17cb3ccda42e4a0cde
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df12c3277ca4cb9d73bd8a1661427db90b257bc263e10f40344775a4e3f6f7c8f558a5873cfacab95d888cd7d3ea6e835b72ebc2aa9c176117196530e5a585a2
|
7
|
+
data.tar.gz: a53b49ccdfde6d9fb929263729972bc5732f01e2997848299ce1dbec187be23a565b5a745ca00dc6cd5765b20bff62ccc2b729abd80bb8ad3965f1ce457e4808
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# Ruby CircleCI 2.0 configuration file
|
2
|
+
#
|
3
|
+
# Check https://circleci.com/docs/2.0/language-ruby/ for more details
|
4
|
+
#
|
5
|
+
|
6
|
+
common_steps: &common_steps
|
7
|
+
- checkout
|
8
|
+
- run: gem update --system
|
9
|
+
- restore_cache:
|
10
|
+
key: gem-cache-{{ .Branch }}-{{ checksum "request_handler.gemspec" }}
|
11
|
+
- run:
|
12
|
+
name: install dependencies
|
13
|
+
command: |
|
14
|
+
gem update bundler
|
15
|
+
bundle install --jobs=4 --retry=3 --path vendor/bundle
|
16
|
+
- save_cache:
|
17
|
+
key: gem-cache-{{ .Branch }}-{{ checksum "request_handler.gemspec" }}
|
18
|
+
paths:
|
19
|
+
- vendor/bundle
|
20
|
+
|
21
|
+
- run: ruby -v
|
22
|
+
- run: bundle exec danger
|
23
|
+
- run:
|
24
|
+
name: run tests
|
25
|
+
command: |
|
26
|
+
mkdir /tmp/test-results
|
27
|
+
TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
|
28
|
+
circleci tests split --split-by=timings)"
|
29
|
+
|
30
|
+
bundle exec rspec \
|
31
|
+
--format progress \
|
32
|
+
--format RspecJunitFormatter \
|
33
|
+
--out /tmp/test-results/rspec.xml \
|
34
|
+
--format progress \
|
35
|
+
$TEST_FILES
|
36
|
+
# collect reports
|
37
|
+
- store_test_results:
|
38
|
+
path: /tmp/test-results
|
39
|
+
- store_artifacts:
|
40
|
+
path: /tmp/test-results
|
41
|
+
destination: test-results
|
42
|
+
|
43
|
+
version: 2
|
44
|
+
jobs:
|
45
|
+
ruby-2.4:
|
46
|
+
docker:
|
47
|
+
- image: circleci/ruby:2.4
|
48
|
+
steps:
|
49
|
+
*common_steps
|
50
|
+
ruby-2.5:
|
51
|
+
docker:
|
52
|
+
- image: circleci/ruby:2.5
|
53
|
+
steps:
|
54
|
+
*common_steps
|
55
|
+
jruby-9.2:
|
56
|
+
docker:
|
57
|
+
- image: circleci/jruby:9.2
|
58
|
+
steps:
|
59
|
+
*common_steps
|
60
|
+
jruby-9.2-indy:
|
61
|
+
docker:
|
62
|
+
- image: circleci/jruby:9.2
|
63
|
+
environment:
|
64
|
+
JRUBY_OPTS: '-Xcompile.invokedynamic=true'
|
65
|
+
steps:
|
66
|
+
*common_steps
|
67
|
+
|
68
|
+
workflows:
|
69
|
+
version: 2
|
70
|
+
build:
|
71
|
+
jobs:
|
72
|
+
- ruby-2.4
|
73
|
+
- ruby-2.5
|
74
|
+
- jruby-9.2
|
75
|
+
- jruby-9.2-indy
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
+
## [1.3.0] - 2019-05-21
|
8
|
+
### Added
|
9
|
+
- Serializable JSONAPI error objects out of validation failures
|
10
|
+
- Configuration option to enable returning validation failures in errors method of exceptions
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
- Format of error messages for external causes (bad requests)
|
14
|
+
- Inheritance chain of error classes
|
15
|
+
|
7
16
|
## [1.2.0] - 2019-04-15
|
8
17
|
### Added
|
9
18
|
- Apart from dry-validation, now definition or any other validation library can be used
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# RequestHandler
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/request_handler.svg)](https://badge.fury.io/rb/request_handler)
|
4
|
-
[![
|
4
|
+
[![CircleCI](https://circleci.com/gh/andreaseger/receptacle.svg?style=svg)](https://circleci.com/gh/runtastic/request_handler)
|
5
5
|
[![codecov](https://codecov.io/gh/runtastic/request_handler/branch/master/graph/badge.svg)](https://codecov.io/gh/runtastic/request_handler)
|
6
6
|
|
7
7
|
This gem allows easy and dry handling of requests based on the dry-validation
|
@@ -36,6 +36,15 @@ RequestHandler.configure do
|
|
36
36
|
end
|
37
37
|
```
|
38
38
|
|
39
|
+
JSON:API-style error data can be included in validation errors raised by `RequestHandler`.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
RequestHandler.configure do
|
43
|
+
raise_jsonapi_errors = true # default: false
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
|
39
48
|
### Validation Engine
|
40
49
|
Per default this gem uses the `DryEngine` which relies on dry-validation. All
|
41
50
|
examples in this Readme assume you are using this default engine. However
|
@@ -256,6 +265,44 @@ and an additional file `image.png`, the resulting `multipart_params` will be the
|
|
256
265
|
|
257
266
|
Please note that each part's content has to be uploaded as a separate file currently.
|
258
267
|
|
268
|
+
### JSON:API errors
|
269
|
+
|
270
|
+
Errors caused by bad requests respond to `:errors`.
|
271
|
+
|
272
|
+
When the gem is configured to `raise_jsonapi_errors`, this method returns a list of hashes
|
273
|
+
containing `code`, `status`, `detail`, (`links`) and `source` for each specific issue
|
274
|
+
that contributed to the error. Otherwise it returns an empty array.
|
275
|
+
|
276
|
+
The exception message contains `<error code>: <source> <detail>` for every issue,
|
277
|
+
with one issue per line.
|
278
|
+
|
279
|
+
| `:code` | `:status` | What is it? |
|
280
|
+
|:--------------------------|:----------|:------------|
|
281
|
+
| INVALID_RESOURCE_SCHEMA | 422 | Resource did not pass configured validation |
|
282
|
+
| INVALID_QUERY_PARAMETER | 400 | Query parameter violates syntax or did not pass configured validation |
|
283
|
+
| MISSING_QUERY_PARAMETER | 400 | Query parameter required in configuration is missing |
|
284
|
+
| INVALID_JSON_API | 400 | Request body violates JSON:API syntax |
|
285
|
+
| INVALID_MULTIPART_REQUEST | 400 | Sidecar resource missing or invalid JSON |
|
286
|
+
|
287
|
+
#### Example
|
288
|
+
```ruby
|
289
|
+
rescue RequestHandler::SchemaValidationError => e
|
290
|
+
puts e.errors
|
291
|
+
end
|
292
|
+
```
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
[
|
296
|
+
{
|
297
|
+
status: '422',
|
298
|
+
code: 'INVALID_RESOURCE_SCHEMA',
|
299
|
+
title: 'Invalid resource',
|
300
|
+
detail: 'is missing',
|
301
|
+
source: { pointer: '/data/attributes/name' }
|
302
|
+
}
|
303
|
+
]
|
304
|
+
```
|
305
|
+
|
259
306
|
### Caveats
|
260
307
|
|
261
308
|
It is currently expected that _url_ parameter are already parsed and included in
|
data/lib/request_handler.rb
CHANGED
data/lib/request_handler/base.rb
CHANGED
@@ -160,7 +160,7 @@ module RequestHandler
|
|
160
160
|
|
161
161
|
def params
|
162
162
|
raise MissingArgumentError, params: 'is missing' if request.params.nil?
|
163
|
-
raise ExternalArgumentError,
|
163
|
+
raise ExternalArgumentError, [] unless request.params.is_a?(Hash)
|
164
164
|
@params ||= Helper.deep_transform_keys_in_object(request.params) do |k|
|
165
165
|
k.to_s.gsub('.', ::RequestHandler.separator)
|
166
166
|
end
|
@@ -18,15 +18,26 @@ module RequestHandler
|
|
18
18
|
end
|
19
19
|
class ExternalBaseError < BaseError
|
20
20
|
end
|
21
|
+
class JsonApiError < ExternalBaseError
|
22
|
+
def message
|
23
|
+
@errors.map do |error|
|
24
|
+
"#{error[:code]}: #{error[:source]} #{error[:detail]}"
|
25
|
+
end.join(',\n')
|
26
|
+
end
|
27
|
+
|
28
|
+
def errors
|
29
|
+
RequestHandler.configuration.raise_jsonapi_errors ? @errors : []
|
30
|
+
end
|
31
|
+
end
|
21
32
|
class MissingArgumentError < InternalBaseError
|
22
33
|
end
|
23
|
-
class ExternalArgumentError <
|
34
|
+
class ExternalArgumentError < JsonApiError
|
24
35
|
end
|
25
36
|
class InternalArgumentError < InternalBaseError
|
26
37
|
end
|
27
|
-
class SchemaValidationError <
|
38
|
+
class SchemaValidationError < JsonApiError
|
28
39
|
end
|
29
|
-
class OptionNotAllowedError <
|
40
|
+
class OptionNotAllowedError < JsonApiError
|
30
41
|
end
|
31
42
|
class NoConfigAvailableError < InternalBaseError
|
32
43
|
end
|
@@ -42,22 +42,45 @@ module RequestHandler
|
|
42
42
|
RequestHandler.engine.validate!(option, allowed[type]).output.to_sym
|
43
43
|
end
|
44
44
|
rescue Validation::Error
|
45
|
-
raise FieldsetsParamsError,
|
45
|
+
raise FieldsetsParamsError, [{ code: 'INVALID_QUERY_PARAMETER',
|
46
|
+
status: '400',
|
47
|
+
detail: "allowed fieldset does not include '#{option}'",
|
48
|
+
source: { param: "fields[#{type}]" } }]
|
46
49
|
end
|
47
50
|
|
48
51
|
def check_required_fieldsets_types(fieldsets)
|
49
|
-
|
50
|
-
|
52
|
+
missing = required - fieldsets.keys
|
53
|
+
return fieldsets if missing.empty?
|
54
|
+
raise_missing_fieldsets!(missing)
|
51
55
|
end
|
52
56
|
|
53
57
|
def raise_invalid_field_option(type)
|
54
58
|
return if allowed.key?(type)
|
55
|
-
raise OptionNotAllowedError,
|
59
|
+
raise OptionNotAllowedError, [
|
60
|
+
{
|
61
|
+
code: 'INVALID_QUERY_PARAMETER',
|
62
|
+
status: '400',
|
63
|
+
detail: "fieldset for '#{type}' not allowed",
|
64
|
+
source: { param: "fields[#{type}]" }
|
65
|
+
}
|
66
|
+
]
|
56
67
|
end
|
57
68
|
|
58
69
|
def raise_missing_fields_param
|
59
70
|
return if required.empty?
|
60
|
-
|
71
|
+
raise_missing_fieldsets!(required)
|
72
|
+
end
|
73
|
+
|
74
|
+
def raise_missing_fieldsets!(missing)
|
75
|
+
errors = missing.map do |type|
|
76
|
+
{
|
77
|
+
code: 'MISSING_QUERY_PARAMETER',
|
78
|
+
status: '400',
|
79
|
+
source: { param: '' },
|
80
|
+
detail: "missing required parameter fields[#{type}]"
|
81
|
+
}
|
82
|
+
end
|
83
|
+
raise FieldsetsParamsError, errors
|
61
84
|
end
|
62
85
|
|
63
86
|
attr_reader :params, :allowed, :required
|
@@ -7,7 +7,7 @@ module RequestHandler
|
|
7
7
|
def initialize(params:, schema:, additional_url_filter:, schema_options: {})
|
8
8
|
super(schema: schema, schema_options: schema_options)
|
9
9
|
@filter = params.fetch('filter') { {} }
|
10
|
-
raise FilterParamsError,
|
10
|
+
raise FilterParamsError, [jsonapi_filter_syntax_error] unless @filter.is_a?(Hash)
|
11
11
|
Array(additional_url_filter).each do |key|
|
12
12
|
key = key.to_s
|
13
13
|
raise build_error(key) unless @filter[key].nil?
|
@@ -17,6 +17,14 @@ module RequestHandler
|
|
17
17
|
|
18
18
|
def run
|
19
19
|
validate_schema(filter)
|
20
|
+
rescue SchemaValidationError => e
|
21
|
+
raise FilterParamsError, (e.errors.map do |schema_error|
|
22
|
+
source_param = "filter[#{schema_error[:source][:pointer]}]"
|
23
|
+
{
|
24
|
+
detail: schema_error[:detail],
|
25
|
+
**jsonapi_filter_base_error(source_param: source_param)
|
26
|
+
}
|
27
|
+
end)
|
20
28
|
end
|
21
29
|
|
22
30
|
private
|
@@ -25,6 +33,22 @@ module RequestHandler
|
|
25
33
|
InternalArgumentError.new(filter: 'the filter key was set twice')
|
26
34
|
end
|
27
35
|
|
36
|
+
def jsonapi_filter_base_error(source_param:)
|
37
|
+
{
|
38
|
+
status: '400',
|
39
|
+
code: 'INVALID_QUERY_PARAMETER',
|
40
|
+
source: { param: source_param }
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def jsonapi_filter_syntax_error
|
45
|
+
{
|
46
|
+
**jsonapi_filter_base_error(source_param: 'filter'),
|
47
|
+
links: { about: 'https://jsonapi.org/recommendations/#filtering' },
|
48
|
+
detail: 'Filter parameter must conform to JSON API recommendation'
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
28
52
|
attr_reader :filter
|
29
53
|
end
|
30
54
|
end
|
@@ -7,7 +7,7 @@ module RequestHandler
|
|
7
7
|
def run
|
8
8
|
return [] unless params.key?('include')
|
9
9
|
options = fetch_options
|
10
|
-
|
10
|
+
raise_error('INVALID_QUERY_PARAMETER', 'must not contain a space') if options.include?(' ')
|
11
11
|
allowed_options(options.split(','))
|
12
12
|
end
|
13
13
|
|
@@ -17,14 +17,27 @@ module RequestHandler
|
|
17
17
|
begin
|
18
18
|
RequestHandler.engine.validate!(option, allowed_options_type).output.to_sym
|
19
19
|
rescue Validation::Error
|
20
|
-
|
20
|
+
raise_error('OPTION_NOT_ALLOWED', "#{option} is not an allowed include option", OptionNotAllowedError)
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
def fetch_options
|
26
|
-
|
26
|
+
raise_error('INVALID_QUERY_PARAMETER', 'must not be empty') if empty_param?('include')
|
27
27
|
params.fetch('include') { '' }
|
28
28
|
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def raise_error(code, detail, error_klass = IncludeParamsError)
|
33
|
+
raise error_klass, [
|
34
|
+
{
|
35
|
+
status: '400',
|
36
|
+
code: code,
|
37
|
+
detail: detail,
|
38
|
+
source: { param: 'include' }
|
39
|
+
}
|
40
|
+
]
|
41
|
+
end
|
29
42
|
end
|
30
43
|
end
|
@@ -4,6 +4,8 @@ require 'request_handler/schema_parser'
|
|
4
4
|
require 'request_handler/error'
|
5
5
|
module RequestHandler
|
6
6
|
class JsonApiDocumentParser < SchemaParser
|
7
|
+
NON_ATTRIBUTE_MEMBERS = %i[id type meta links].freeze
|
8
|
+
|
7
9
|
def initialize(document:, schema:, schema_options: {})
|
8
10
|
raise MissingArgumentError, "data": 'is missing' if document.nil?
|
9
11
|
super(schema: schema, schema_options: schema_options)
|
@@ -19,7 +21,11 @@ module RequestHandler
|
|
19
21
|
|
20
22
|
def flattened_document
|
21
23
|
resource = document.fetch('data') do
|
22
|
-
raise BodyParamsError,
|
24
|
+
raise BodyParamsError, [{ code: 'INVALID_JSON_API',
|
25
|
+
status: '400',
|
26
|
+
title: 'Body is not valid JSON API payload',
|
27
|
+
detail: "Member 'data' is missing",
|
28
|
+
source: { pointer: '/' } }]
|
23
29
|
end
|
24
30
|
flatten_resource!(resource)
|
25
31
|
end
|
@@ -37,6 +43,14 @@ module RequestHandler
|
|
37
43
|
end
|
38
44
|
end
|
39
45
|
|
46
|
+
def build_pointer(error)
|
47
|
+
non_nested_identifier = error[:schema_pointer] == error[:element].to_s
|
48
|
+
non_attribute_member = NON_ATTRIBUTE_MEMBERS.include?(error[:element])
|
49
|
+
['/data',
|
50
|
+
('attributes' unless non_attribute_member && non_nested_identifier),
|
51
|
+
error[:schema_pointer]].compact.join('/')
|
52
|
+
end
|
53
|
+
|
40
54
|
attr_reader :document
|
41
55
|
end
|
42
56
|
end
|
@@ -10,14 +10,12 @@ module RequestHandler
|
|
10
10
|
@request = request
|
11
11
|
@params = request.params
|
12
12
|
@multipart_config = multipart_config
|
13
|
-
|
14
|
-
missing_arguments << { multipart_config: 'is missing' } if multipart_config.nil?
|
15
|
-
raise MissingArgumentError, missing_arguments unless missing_arguments.empty?
|
13
|
+
raise MissingArgumentError, [{ multipart_config: 'is missing' }] if multipart_config.nil?
|
16
14
|
end
|
17
15
|
|
18
16
|
def run
|
19
17
|
multipart_config.each_with_object({}) do |(name, config), memo|
|
20
|
-
|
18
|
+
validate_presence!(name) if config[:required]
|
21
19
|
next if params[name.to_s].nil?
|
22
20
|
memo[name] = parse_part(name.to_s)
|
23
21
|
end
|
@@ -25,8 +23,21 @@ module RequestHandler
|
|
25
23
|
|
26
24
|
private
|
27
25
|
|
26
|
+
def validate_presence!(sidecar_name)
|
27
|
+
return if params.key?(sidecar_name.to_s)
|
28
|
+
raise multipart_params_error("missing required sidecar resource: #{sidecar_name}")
|
29
|
+
end
|
30
|
+
|
31
|
+
def multipart_params_error(detail = '')
|
32
|
+
MultipartParamsError.new([{
|
33
|
+
status: '400',
|
34
|
+
code: 'INVALID_MULTIPART_REQUEST',
|
35
|
+
detail: detail
|
36
|
+
}])
|
37
|
+
end
|
38
|
+
|
28
39
|
def parse_part(name)
|
29
|
-
params[name].fetch(:tempfile) { raise MultipartParamsError, multipart_file: 'missing' }
|
40
|
+
params[name].fetch(:tempfile) { raise MultipartParamsError, [{ multipart_file: 'missing' }] }
|
30
41
|
if lookup("#{name}.schema")
|
31
42
|
parse_data(name)
|
32
43
|
else
|
@@ -51,7 +62,7 @@ module RequestHandler
|
|
51
62
|
file = file.read
|
52
63
|
MultiJson.load(file)
|
53
64
|
rescue MultiJson::ParseError
|
54
|
-
raise
|
65
|
+
raise multipart_params_error('sidecar resource is not valid JSON')
|
55
66
|
end
|
56
67
|
|
57
68
|
def multipart_file(name)
|
@@ -38,8 +38,7 @@ module RequestHandler
|
|
38
38
|
|
39
39
|
def extract_number(prefix: nil)
|
40
40
|
number_string = lookup_nested_params_key('number', prefix) || 1
|
41
|
-
|
42
|
-
check_int(string: number_string, error_msg: error_msg)
|
41
|
+
check_int(string: number_string, param: "#{prefix}.number")
|
43
42
|
end
|
44
43
|
|
45
44
|
def extract_size(prefix: nil)
|
@@ -59,16 +58,24 @@ module RequestHandler
|
|
59
58
|
def fetch_and_check_size(prefix)
|
60
59
|
size_string = lookup_nested_params_key('size', prefix)
|
61
60
|
return nil if size_string.nil?
|
62
|
-
|
63
|
-
check_int(string: size_string, error_msg: error_msg) unless size_string.nil?
|
61
|
+
check_int(string: size_string, param: "#{prefix}.size") unless size_string.nil?
|
64
62
|
end
|
65
63
|
|
66
|
-
def check_int(string:,
|
64
|
+
def check_int(string:, param:)
|
67
65
|
output = Integer(string)
|
68
|
-
|
66
|
+
raise_page_param_error!(param) unless output > 0
|
69
67
|
output
|
70
68
|
rescue ArgumentError
|
71
|
-
|
69
|
+
raise_page_param_error!(param)
|
70
|
+
end
|
71
|
+
|
72
|
+
def raise_page_param_error!(param)
|
73
|
+
raise PageParamsError, [{
|
74
|
+
code: 'INVALID_QUERY_PARAMETER',
|
75
|
+
status: '400',
|
76
|
+
detail: 'must be a positive integer',
|
77
|
+
source: { param: "page[#{param}]" }
|
78
|
+
}]
|
72
79
|
end
|
73
80
|
|
74
81
|
def apply_max_size_constraint(size, prefix)
|
@@ -14,6 +14,14 @@ module RequestHandler
|
|
14
14
|
|
15
15
|
def run
|
16
16
|
validate_schema(query)
|
17
|
+
rescue SchemaValidationError => e
|
18
|
+
raise ExternalArgumentError, (e.errors.map do |schema_error|
|
19
|
+
param = schema_error[:source][:pointer]
|
20
|
+
{ status: '400',
|
21
|
+
code: "#{query[param] ? 'INVALID' : 'MISSING'}_QUERY_PARAMETER",
|
22
|
+
detail: schema_error[:detail],
|
23
|
+
source: { param: param } }
|
24
|
+
end)
|
17
25
|
end
|
18
26
|
|
19
27
|
private
|
@@ -28,12 +28,46 @@ module RequestHandler
|
|
28
28
|
|
29
29
|
def validation_failure?(validator)
|
30
30
|
return if validator.valid?
|
31
|
-
|
32
|
-
|
31
|
+
|
32
|
+
errors = build_errors(validator.errors).map do |error|
|
33
|
+
jsonapi_error(error)
|
33
34
|
end
|
34
35
|
raise SchemaValidationError, errors
|
35
36
|
end
|
36
37
|
|
38
|
+
def build_errors(error_hash, path = [])
|
39
|
+
errors = []
|
40
|
+
error_hash.each do |k, v|
|
41
|
+
errors += build_errors(v, path << k).flatten if v.is_a?(Hash)
|
42
|
+
v.each { |error| errors << error(path, k, error) } if v.is_a?(Array)
|
43
|
+
errors << error(path, k, v) if v.is_a?(String)
|
44
|
+
end
|
45
|
+
errors
|
46
|
+
end
|
47
|
+
|
48
|
+
def error(path, element, failure)
|
49
|
+
schema_pointer = RequestHandler.engine.error_pointer(failure) || (path + [element]).join('/')
|
50
|
+
{
|
51
|
+
schema_pointer: schema_pointer,
|
52
|
+
element: element,
|
53
|
+
message: RequestHandler.engine.error_message(failure)
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def jsonapi_error(error)
|
58
|
+
{
|
59
|
+
status: '422',
|
60
|
+
code: 'INVALID_RESOURCE_SCHEMA',
|
61
|
+
title: 'Invalid resource',
|
62
|
+
detail: error[:message],
|
63
|
+
source: { pointer: build_pointer(error) }
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_pointer(error)
|
68
|
+
error[:schema_pointer]
|
69
|
+
end
|
70
|
+
|
37
71
|
def add_note(v, k, memo)
|
38
72
|
memo[k] = if v.is_a? Array
|
39
73
|
v.join(' ')
|
@@ -8,12 +8,12 @@ module RequestHandler
|
|
8
8
|
def run
|
9
9
|
return [] unless params.key?('sort')
|
10
10
|
sort_options = parse_options(fetch_options)
|
11
|
-
raise SortParamsError,
|
11
|
+
raise SortParamsError, [jsonapi_error('sort options must be unique')] if duplicates?(sort_options)
|
12
12
|
sort_options
|
13
13
|
end
|
14
14
|
|
15
15
|
def fetch_options
|
16
|
-
raise SortParamsError,
|
16
|
+
raise SortParamsError, [jsonapi_error('must not be empty')] if empty_param?('sort')
|
17
17
|
params.fetch('sort') { '' }.split(',')
|
18
18
|
end
|
19
19
|
|
@@ -27,7 +27,7 @@ module RequestHandler
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def parse_option(option)
|
30
|
-
raise SortParamsError,
|
30
|
+
raise SortParamsError, [jsonapi_error('must not contain spaces')] if option.include? ' '
|
31
31
|
if option.start_with?('-')
|
32
32
|
[option[1..-1], :desc]
|
33
33
|
else
|
@@ -38,11 +38,22 @@ module RequestHandler
|
|
38
38
|
def allowed_option(name)
|
39
39
|
RequestHandler.engine.validate!(name, allowed_options_type).output
|
40
40
|
rescue Validation::Error
|
41
|
-
raise OptionNotAllowedError, name
|
41
|
+
raise OptionNotAllowedError, [jsonapi_error("#{name} is not an allowed sort option")]
|
42
42
|
end
|
43
43
|
|
44
44
|
def duplicates?(options)
|
45
45
|
!options.uniq!(&:field).nil?
|
46
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def jsonapi_error(detail)
|
51
|
+
{
|
52
|
+
code: 'INVALID_QUERY_PARAMETER',
|
53
|
+
status: '400',
|
54
|
+
source: { param: 'sort' },
|
55
|
+
detail: detail
|
56
|
+
}
|
57
|
+
end
|
47
58
|
end
|
48
59
|
end
|
@@ -21,6 +21,14 @@ module RequestHandler
|
|
21
21
|
raise Validation::Error unless result.valid?
|
22
22
|
end
|
23
23
|
end
|
24
|
+
|
25
|
+
def self.error_message(validation_error)
|
26
|
+
validation_error.message
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.error_pointer(validation_error)
|
30
|
+
validation_error.error_path.join('/')
|
31
|
+
end
|
24
32
|
end
|
25
33
|
end
|
26
34
|
end
|
@@ -16,6 +16,14 @@ module RequestHandler
|
|
16
16
|
raise NotImplementedError
|
17
17
|
end
|
18
18
|
|
19
|
+
def self.error_message(_validation_error)
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.error_pointer(_validation_error)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
19
27
|
private
|
20
28
|
|
21
29
|
attr_accessor :value, :schema, :options
|
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: 1.
|
4
|
+
version: 1.3.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: 2019-
|
12
|
+
date: 2019-05-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: dry-validation
|
@@ -283,10 +283,10 @@ executables: []
|
|
283
283
|
extensions: []
|
284
284
|
extra_rdoc_files: []
|
285
285
|
files:
|
286
|
+
- ".circleci/config.yml"
|
286
287
|
- ".gitignore"
|
287
288
|
- ".rspec"
|
288
289
|
- ".rubocop.yml"
|
289
|
-
- ".travis.yml"
|
290
290
|
- CHANGELOG.md
|
291
291
|
- CODE_OF_CONDUCT.md
|
292
292
|
- Dangerfile
|
@@ -344,7 +344,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
344
344
|
version: '0'
|
345
345
|
requirements: []
|
346
346
|
rubyforge_project:
|
347
|
-
rubygems_version: 2.7.6
|
347
|
+
rubygems_version: 2.7.6.2
|
348
348
|
signing_key:
|
349
349
|
specification_version: 4
|
350
350
|
summary: shared base for request_handler using dry-* gems
|
data/.travis.yml
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
cache: bundler
|
3
|
-
rvm:
|
4
|
-
- 2.4.4
|
5
|
-
- 2.5.3
|
6
|
-
- jruby-9.2.0.0
|
7
|
-
- jruby-9.2.7.0
|
8
|
-
jdk:
|
9
|
-
- oraclejdk8
|
10
|
-
env:
|
11
|
-
- "JRUBY_OPTS='--dev --debug'"
|
12
|
-
- "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
|
13
|
-
matrix:
|
14
|
-
exclude:
|
15
|
-
- rvm: 2.4.4
|
16
|
-
jdk: oraclejdk8
|
17
|
-
env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
|
18
|
-
- rvm: 2.5.3
|
19
|
-
jdk: oraclejdk8
|
20
|
-
env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
|
21
|
-
allow_failures:
|
22
|
-
- rvm: jruby-9.2.0.0
|
23
|
-
jdk: oraclejdk8
|
24
|
-
env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
|
25
|
-
- rvm: jruby-9.2.7.0
|
26
|
-
jdk: oraclejdk8
|
27
|
-
env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
|
28
|
-
- rvm: 2.6.2
|
29
|
-
before_install:
|
30
|
-
- gem update --system
|
31
|
-
- gem install bundler -v 2.0.1
|
32
|
-
before_script:
|
33
|
-
- bundle exec danger
|