request_handler 1.2.0 → 1.3.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
  SHA256:
3
- metadata.gz: fb27be2f216ddde5e61c75588e2fcd838ec4d252919fac9761372f63d1187c80
4
- data.tar.gz: dbac14a26b2de68aa1d7cf506622f849bc77a2b949424db1ffa33d70a9bbf76c
3
+ metadata.gz: fbf5f683f749e9a5431fe1d07a250c43e72446ddc4fdbb472acd7b06d737fad6
4
+ data.tar.gz: e12067caf9c5ec19b306a00d89666fac1a862449f8a68d17cb3ccda42e4a0cde
5
5
  SHA512:
6
- metadata.gz: fac2d974663982067de6d60c16e47e63bca678925f29b6d4ccbc4e25857929ca85f7da1512c61215bd01b502c145846d738c6aa3590b21f395e39eadd6f119d6
7
- data.tar.gz: 7656a364baaa0ccc75a179682541e0fe3052972b4272f1f6ced7dedf7ba06e925cbc0936f41d9cadff6b984666e04add629ea96f0d282c25e31bc871090f57d9
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
@@ -9,4 +9,5 @@ group :development, :test do
9
9
  gem 'danger'
10
10
  gem 'danger-commit_lint'
11
11
  gem 'danger-rubocop'
12
+ gem 'rspec_junit_formatter'
12
13
  end
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
- [![Build Status](https://travis-ci.org/runtastic/request_handler.svg?branch=master)](https://travis-ci.org/runtastic/request_handler)
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
@@ -19,6 +19,7 @@ module RequestHandler
19
19
  logger Logger.new(STDOUT)
20
20
  separator '__'
21
21
  validation_engine Validation::DryEngine
22
+ raise_jsonapi_errors false
22
23
  end
23
24
  end
24
25
 
@@ -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, params: 'must be a Hash' unless request.params.is_a?(Hash)
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 < ExternalBaseError
34
+ class ExternalArgumentError < JsonApiError
24
35
  end
25
36
  class InternalArgumentError < InternalBaseError
26
37
  end
27
- class SchemaValidationError < ExternalBaseError
38
+ class SchemaValidationError < JsonApiError
28
39
  end
29
- class OptionNotAllowedError < ExternalBaseError
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, fieldsets: "invalid field: <#{option}> for type: #{type}"
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
- return fieldsets if (required - fieldsets.keys).empty?
50
- raise FieldsetsParamsError, fieldsets: 'missing required fieldsets parameter'
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, fieldsets: "fieldsets for type: #{type} not allowed"
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
- raise FieldsetsParamsError, fieldsets: 'missing required fields options'
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, filter: 'must be a Hash' unless @filter.is_a?(Hash)
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
- raise IncludeParamsError, include: 'must not contain a space' if options.include? ' '
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
- raise OptionNotAllowedError, option.to_sym => 'is not an allowed include option'
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
- raise IncludeParamsError, include_options: 'query paramter must not be empty' if empty_param?('include')
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, resource: 'must contain data'
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
- missing_arguments = []
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
- raise MultipartParamsError, multipart: "#{name} missing" if config[:required] && !params.key?(name.to_s)
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 MultipartParamsError, multipart_file: 'invalid JSON'
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
- error_msg = { :"#{prefix}#{::RequestHandler.separator}number" => 'must be a positive Integer' }
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
- error_msg = { :"#{prefix}#{::RequestHandler.separator}size" => 'must be a positive Integer' }
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:, error_msg:)
64
+ def check_int(string:, param:)
67
65
  output = Integer(string)
68
- raise PageParamsError, error_msg unless output > 0
66
+ raise_page_param_error!(param) unless output > 0
69
67
  output
70
68
  rescue ArgumentError
71
- raise PageParamsError, error_msg
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
- errors = validator.errors.each_with_object({}) do |(k, v), memo|
32
- add_note(v, k, memo)
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, sort_options: 'must be unique' if duplicates?(sort_options)
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, sort_options: 'the query paramter must not be empty' if empty_param?('sort')
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, sort_options: 'must not contain a space' if option.include? ' '
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.to_sym => 'is not an allowed sort option'
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
@@ -37,6 +37,14 @@ module RequestHandler
37
37
  Result.new(output: result, errors: {})
38
38
  end
39
39
  end
40
+
41
+ def self.error_message(validation_error)
42
+ validation_error
43
+ end
44
+
45
+ def self.error_pointer(_validation_error)
46
+ nil
47
+ end
40
48
  end
41
49
  end
42
50
  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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RequestHandler
4
- VERSION = '1.2.0'.freeze
4
+ VERSION = '1.3.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: 1.2.0
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-04-15 00:00:00.000000000 Z
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