grape 2.1.3 → 2.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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/README.md +9 -7
  4. data/UPGRADING.md +27 -0
  5. data/grape.gemspec +7 -6
  6. data/lib/grape/api/instance.rb +22 -58
  7. data/lib/grape/api.rb +2 -11
  8. data/lib/grape/content_types.rb +13 -8
  9. data/lib/grape/dsl/desc.rb +27 -24
  10. data/lib/grape/dsl/helpers.rb +7 -3
  11. data/lib/grape/dsl/inside_route.rb +18 -24
  12. data/lib/grape/dsl/parameters.rb +2 -2
  13. data/lib/grape/dsl/request_response.rb +14 -18
  14. data/lib/grape/dsl/routing.rb +5 -12
  15. data/lib/grape/endpoint.rb +90 -82
  16. data/lib/grape/error_formatter/base.rb +51 -21
  17. data/lib/grape/error_formatter/json.rb +7 -15
  18. data/lib/grape/error_formatter/jsonapi.rb +7 -0
  19. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  20. data/lib/grape/error_formatter/txt.rb +13 -20
  21. data/lib/grape/error_formatter/xml.rb +3 -13
  22. data/lib/grape/error_formatter.rb +5 -25
  23. data/lib/grape/exceptions/base.rb +18 -30
  24. data/lib/grape/exceptions/validation.rb +5 -4
  25. data/lib/grape/exceptions/validation_errors.rb +2 -2
  26. data/lib/grape/formatter/base.rb +16 -0
  27. data/lib/grape/formatter/json.rb +4 -6
  28. data/lib/grape/formatter/serializable_hash.rb +1 -1
  29. data/lib/grape/formatter/txt.rb +3 -5
  30. data/lib/grape/formatter/xml.rb +4 -6
  31. data/lib/grape/formatter.rb +7 -25
  32. data/lib/grape/http/headers.rb +1 -0
  33. data/lib/grape/locale/en.yml +1 -0
  34. data/lib/grape/middleware/base.rb +14 -13
  35. data/lib/grape/middleware/error.rb +13 -9
  36. data/lib/grape/middleware/formatter.rb +3 -3
  37. data/lib/grape/middleware/versioner/accept_version_header.rb +7 -30
  38. data/lib/grape/middleware/versioner/base.rb +82 -0
  39. data/lib/grape/middleware/versioner/header.rb +89 -10
  40. data/lib/grape/middleware/versioner/param.rb +4 -22
  41. data/lib/grape/middleware/versioner/path.rb +10 -32
  42. data/lib/grape/middleware/versioner.rb +7 -14
  43. data/lib/grape/namespace.rb +1 -1
  44. data/lib/grape/parser/base.rb +16 -0
  45. data/lib/grape/parser/json.rb +6 -8
  46. data/lib/grape/parser/jsonapi.rb +7 -0
  47. data/lib/grape/parser/xml.rb +6 -8
  48. data/lib/grape/parser.rb +5 -23
  49. data/lib/grape/path.rb +39 -56
  50. data/lib/grape/request.rb +2 -2
  51. data/lib/grape/router/base_route.rb +2 -2
  52. data/lib/grape/router/greedy_route.rb +2 -2
  53. data/lib/grape/router/pattern.rb +23 -18
  54. data/lib/grape/router/route.rb +13 -5
  55. data/lib/grape/router.rb +5 -5
  56. data/lib/grape/util/registry.rb +27 -0
  57. data/lib/grape/validations/contract_scope.rb +2 -39
  58. data/lib/grape/validations/params_scope.rb +7 -11
  59. data/lib/grape/validations/types/dry_type_coercer.rb +10 -6
  60. data/lib/grape/validations/validator_factory.rb +2 -2
  61. data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
  62. data/lib/grape/validations/validators/base.rb +5 -9
  63. data/lib/grape/validations/validators/coerce_validator.rb +1 -1
  64. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  65. data/lib/grape/validations/validators/default_validator.rb +1 -1
  66. data/lib/grape/validations/validators/except_values_validator.rb +1 -1
  67. data/lib/grape/validations/validators/length_validator.rb +11 -4
  68. data/lib/grape/validations/validators/regexp_validator.rb +1 -1
  69. data/lib/grape/validations/validators/values_validator.rb +15 -57
  70. data/lib/grape/validations.rb +8 -17
  71. data/lib/grape/version.rb +1 -1
  72. data/lib/grape.rb +1 -1
  73. metadata +15 -12
  74. data/lib/grape/util/accept_header_handler.rb +0 -105
  75. data/lib/grape/util/registrable.rb +0 -15
  76. data/lib/grape/validations/types/build_coercer.rb +0 -92
@@ -2,14 +2,12 @@
2
2
 
3
3
  module Grape
4
4
  module Parser
5
- module Json
6
- class << self
7
- def call(object, _env)
8
- ::Grape::Json.load(object)
9
- rescue ::Grape::Json::ParseError
10
- # handle JSON parsing errors via the rescue handlers or provide error message
11
- raise Grape::Exceptions::InvalidMessageBody.new('application/json')
12
- end
5
+ class Json < Base
6
+ def self.call(object, _env)
7
+ ::Grape::Json.load(object)
8
+ rescue ::Grape::Json::ParseError
9
+ # handle JSON parsing errors via the rescue handlers or provide error message
10
+ raise Grape::Exceptions::InvalidMessageBody.new('application/json')
13
11
  end
14
12
  end
15
13
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Parser
5
+ class Jsonapi < Json; end
6
+ end
7
+ end
@@ -2,14 +2,12 @@
2
2
 
3
3
  module Grape
4
4
  module Parser
5
- module Xml
6
- class << self
7
- def call(object, _env)
8
- ::Grape::Xml.parse(object)
9
- rescue ::Grape::Xml::ParseError
10
- # handle XML parsing errors via the rescue handlers or provide error message
11
- raise Grape::Exceptions::InvalidMessageBody.new('application/xml')
12
- end
5
+ class Xml < Base
6
+ def self.call(object, _env)
7
+ ::Grape::Xml.parse(object)
8
+ rescue ::Grape::Xml::ParseError
9
+ # handle XML parsing errors via the rescue handlers or provide error message
10
+ raise Grape::Exceptions::InvalidMessageBody.new('application/xml')
13
11
  end
14
12
  end
15
13
  end
data/lib/grape/parser.rb CHANGED
@@ -2,32 +2,14 @@
2
2
 
3
3
  module Grape
4
4
  module Parser
5
- extend Util::Registrable
5
+ extend Grape::Util::Registry
6
6
 
7
- class << self
8
- def builtin_parsers
9
- @builtin_parsers ||= {
10
- json: Grape::Parser::Json,
11
- jsonapi: Grape::Parser::Json,
12
- xml: Grape::Parser::Xml
13
- }
14
- end
7
+ module_function
15
8
 
16
- def parsers(**options)
17
- builtin_parsers.merge(default_elements).merge!(options[:parsers] || {})
18
- end
9
+ def parser_for(format, parsers = nil)
10
+ return parsers[format] if parsers&.key?(format)
19
11
 
20
- def parser_for(api_format, **options)
21
- spec = parsers(**options)[api_format]
22
- case spec
23
- when nil
24
- nil
25
- when Symbol
26
- method(spec)
27
- else
28
- spec
29
- end
30
- end
12
+ registry[format]
31
13
  end
32
14
  end
33
15
  end
data/lib/grape/path.rb CHANGED
@@ -3,65 +3,66 @@
3
3
  module Grape
4
4
  # Represents a path to an endpoint.
5
5
  class Path
6
- attr_reader :raw_path, :namespace, :settings
6
+ DEFAULT_FORMAT_SEGMENT = '(/.:format)'
7
+ NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT = '(.:format)'
8
+ VERSION_SEGMENT = ':version'
7
9
 
8
- def initialize(raw_path, namespace, settings)
9
- @raw_path = raw_path
10
- @namespace = namespace
11
- @settings = settings
12
- end
10
+ attr_reader :origin, :suffix
13
11
 
14
- def mount_path
15
- settings[:mount_path]
12
+ def initialize(raw_path, raw_namespace, settings)
13
+ @origin = PartsCache[build_parts(raw_path, raw_namespace, settings)]
14
+ @suffix = build_suffix(raw_path, raw_namespace, settings)
16
15
  end
17
16
 
18
- def root_prefix
19
- settings[:root_prefix]
17
+ def to_s
18
+ "#{origin}#{suffix}"
20
19
  end
21
20
 
22
- def uses_specific_format?
23
- return false unless settings.key?(:format) && settings.key?(:content_types)
21
+ private
24
22
 
25
- settings[:format] && Array(settings[:content_types]).size == 1
23
+ def build_suffix(raw_path, raw_namespace, settings)
24
+ if uses_specific_format?(settings)
25
+ "(.#{settings[:format]})"
26
+ elsif !uses_path_versioning?(settings) || (valid_part?(raw_namespace) || valid_part?(raw_path))
27
+ NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT
28
+ else
29
+ DEFAULT_FORMAT_SEGMENT
30
+ end
26
31
  end
27
32
 
28
- def uses_path_versioning?
29
- return false unless settings.key?(:version) && settings[:version_options]&.key?(:using)
30
-
31
- settings[:version] && settings[:version_options][:using] == :path
33
+ def build_parts(raw_path, raw_namespace, settings)
34
+ [].tap do |parts|
35
+ add_part(parts, settings[:mount_path])
36
+ add_part(parts, settings[:root_prefix])
37
+ parts << VERSION_SEGMENT if uses_path_versioning?(settings)
38
+ add_part(parts, raw_namespace)
39
+ add_part(parts, raw_path)
40
+ end
32
41
  end
33
42
 
34
- def namespace?
35
- namespace&.match?(/^\S/) && not_slash?(namespace)
43
+ def add_part(parts, value)
44
+ parts << value if value && not_slash?(value)
36
45
  end
37
46
 
38
- def path?
39
- raw_path&.match?(/^\S/) && not_slash?(raw_path)
47
+ def not_slash?(value)
48
+ value != '/'
40
49
  end
41
50
 
42
- def suffix
43
- if uses_specific_format?
44
- "(.#{settings[:format]})"
45
- elsif !uses_path_versioning? || (namespace? || path?)
46
- '(.:format)'
47
- else
48
- '(/.:format)'
49
- end
50
- end
51
+ def uses_specific_format?(settings)
52
+ return false unless settings.key?(:format) && settings.key?(:content_types)
51
53
 
52
- def path
53
- PartsCache[parts]
54
+ settings[:format] && Array(settings[:content_types]).size == 1
54
55
  end
55
56
 
56
- def path_with_suffix
57
- "#{path}#{suffix}"
58
- end
57
+ def uses_path_versioning?(settings)
58
+ return false unless settings.key?(:version) && settings[:version_options]&.key?(:using)
59
59
 
60
- def to_s
61
- path_with_suffix
60
+ settings[:version] && settings[:version_options][:using] == :path
62
61
  end
63
62
 
64
- private
63
+ def valid_part?(part)
64
+ part&.match?(/^\S/) && not_slash?(part)
65
+ end
65
66
 
66
67
  class PartsCache < Grape::Util::Cache
67
68
  def initialize
@@ -71,23 +72,5 @@ module Grape
71
72
  end
72
73
  end
73
74
  end
74
-
75
- def parts
76
- [].tap do |parts|
77
- add_part(parts, mount_path)
78
- add_part(parts, root_prefix)
79
- parts << ':version' if uses_path_versioning?
80
- add_part(parts, namespace)
81
- add_part(parts, raw_path)
82
- end
83
- end
84
-
85
- def add_part(parts, value)
86
- parts << value if value && not_slash?(value)
87
- end
88
-
89
- def not_slash?(value)
90
- value != '/'
91
- end
92
75
  end
93
76
  end
data/lib/grape/request.rb CHANGED
@@ -6,8 +6,8 @@ module Grape
6
6
 
7
7
  alias rack_params params
8
8
 
9
- def initialize(env, **options)
10
- extend options[:build_params_with] || Grape.config.param_builder
9
+ def initialize(env, build_params_with: nil)
10
+ extend build_params_with || Grape.config.param_builder
11
11
  super(env)
12
12
  end
13
13
 
@@ -7,8 +7,8 @@ module Grape
7
7
 
8
8
  attr_reader :index, :pattern, :options
9
9
 
10
- def initialize(**options)
11
- @options = ActiveSupport::OrderedOptions.new.update(options)
10
+ def initialize(options)
11
+ @options = options.is_a?(ActiveSupport::OrderedOptions) ? options : ActiveSupport::OrderedOptions.new.update(options)
12
12
  end
13
13
 
14
14
  alias attributes options
@@ -6,9 +6,9 @@
6
6
  module Grape
7
7
  class Router
8
8
  class GreedyRoute < BaseRoute
9
- def initialize(pattern:, **options)
9
+ def initialize(pattern, options)
10
10
  @pattern = pattern
11
- super(**options)
11
+ super(options)
12
12
  end
13
13
 
14
14
  # Grape::Router:Route defines params as a function
@@ -9,14 +9,14 @@ module Grape
9
9
 
10
10
  attr_reader :origin, :path, :pattern, :to_regexp
11
11
 
12
- def_delegators :pattern, :named_captures, :params
12
+ def_delegators :pattern, :params
13
13
  def_delegators :to_regexp, :===
14
14
  alias match? ===
15
15
 
16
- def initialize(pattern, **options)
17
- @origin = pattern
18
- @path = build_path(pattern, anchor: options[:anchor], suffix: options[:suffix])
19
- @pattern = build_pattern(@path, options)
16
+ def initialize(origin, suffix, options)
17
+ @origin = origin
18
+ @path = build_path(origin, options[:anchor], suffix)
19
+ @pattern = build_pattern(@path, options[:params], options[:format], options[:version], options[:requirements])
20
20
  @to_regexp = @pattern.to_regexp
21
21
  end
22
22
 
@@ -28,30 +28,31 @@ module Grape
28
28
 
29
29
  private
30
30
 
31
- def build_pattern(path, options)
31
+ def build_pattern(path, params, format, version, requirements)
32
32
  Mustermann::Grape.new(
33
33
  path,
34
34
  uri_decode: true,
35
- params: options[:params],
36
- capture: extract_capture(**options)
35
+ params: params,
36
+ capture: extract_capture(format, version, requirements)
37
37
  )
38
38
  end
39
39
 
40
- def build_path(pattern, anchor: false, suffix: nil)
41
- PatternCache[[build_path_from_pattern(pattern, anchor: anchor), suffix]]
40
+ def build_path(pattern, anchor, suffix)
41
+ PatternCache[[build_path_from_pattern(pattern, anchor), suffix]]
42
42
  end
43
43
 
44
- def extract_capture(**options)
45
- sliced_options = options
46
- .slice(:format, :version)
47
- .delete_if { |_k, v| v.blank? }
48
- .transform_values { |v| Array.wrap(v).map(&:to_s) }
49
- return sliced_options if options[:requirements].blank?
44
+ def extract_capture(format, version, requirements)
45
+ capture = {}.tap do |h|
46
+ h[:format] = map_str(format) if format.present?
47
+ h[:version] = map_str(version) if version.present?
48
+ end
49
+
50
+ return capture if requirements.blank?
50
51
 
51
- options[:requirements].merge(sliced_options)
52
+ requirements.merge(capture)
52
53
  end
53
54
 
54
- def build_path_from_pattern(pattern, anchor: false)
55
+ def build_path_from_pattern(pattern, anchor)
55
56
  if pattern.end_with?('*path')
56
57
  pattern.dup.insert(pattern.rindex('/') + 1, '?')
57
58
  elsif anchor
@@ -63,6 +64,10 @@ module Grape
63
64
  end
64
65
  end
65
66
 
67
+ def map_str(value)
68
+ Array.wrap(value).map(&:to_s)
69
+ end
70
+
66
71
  class PatternCache < Grape::Util::Cache
67
72
  def initialize
68
73
  super
@@ -5,14 +5,22 @@ module Grape
5
5
  class Route < BaseRoute
6
6
  extend Forwardable
7
7
 
8
+ FORWARD_MATCH_METHOD = ->(input, pattern) { input.start_with?(pattern.origin) }
9
+ NON_FORWARD_MATCH_METHOD = ->(input, pattern) { pattern.match?(input) }
10
+
8
11
  attr_reader :app, :request_method
9
12
 
10
13
  def_delegators :pattern, :path, :origin
11
14
 
12
- def initialize(method, pattern, **options)
15
+ def initialize(method, origin, path, options)
13
16
  @request_method = upcase_method(method)
14
- @pattern = Grape::Router::Pattern.new(pattern, **options)
15
- super(**options)
17
+ @pattern = Grape::Router::Pattern.new(origin, path, options)
18
+ @match_function = options[:forward_match] ? FORWARD_MATCH_METHOD : NON_FORWARD_MATCH_METHOD
19
+ super(options)
20
+ end
21
+
22
+ def convert_to_head_request!
23
+ @request_method = Rack::HEAD
16
24
  end
17
25
 
18
26
  def exec(env)
@@ -27,7 +35,7 @@ module Grape
27
35
  def match?(input)
28
36
  return false if input.blank?
29
37
 
30
- options[:forward_match] ? input.start_with?(pattern.origin) : pattern.match?(input)
38
+ @match_function.call(input, pattern)
31
39
  end
32
40
 
33
41
  def params(input = nil)
@@ -42,7 +50,7 @@ module Grape
42
50
  private
43
51
 
44
52
  def params_without_input
45
- pattern.captures_default.merge(attributes.params)
53
+ @params_without_input ||= pattern.captures_default.merge(attributes.params)
46
54
  end
47
55
 
48
56
  def upcase_method(method)
data/lib/grape/router.rb CHANGED
@@ -38,8 +38,8 @@ module Grape
38
38
  map[route.request_method] << route
39
39
  end
40
40
 
41
- def associate_routes(pattern, **options)
42
- Grape::Router::GreedyRoute.new(pattern: pattern, **options).then do |greedy_route|
41
+ def associate_routes(pattern, options)
42
+ Grape::Router::GreedyRoute.new(pattern, options).then do |greedy_route|
43
43
  @neutral_regexes << greedy_route.to_regexp(@neutral_map.length)
44
44
  @neutral_map << greedy_route
45
45
  end
@@ -107,7 +107,7 @@ module Grape
107
107
 
108
108
  route = match?(input, '*')
109
109
 
110
- return last_neighbor_route.endpoint.call(env) if last_neighbor_route && last_response_cascade && route
110
+ return last_neighbor_route.options[:endpoint].call(env) if last_neighbor_route && last_response_cascade && route
111
111
 
112
112
  last_response_cascade = cascade_or_return_response.call(process_route(route, env)) if route
113
113
 
@@ -152,8 +152,8 @@ module Grape
152
152
 
153
153
  def call_with_allow_headers(env, route)
154
154
  prepare_env_from_route(env, route)
155
- env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header.join(', ').freeze
156
- route.endpoint.call(env)
155
+ env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.options[:allow_header]
156
+ route.options[:endpoint].call(env)
157
157
  end
158
158
 
159
159
  def prepare_env_from_route(env, route)
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Registry
6
+ def register(klass)
7
+ short_name = build_short_name(klass)
8
+ return if short_name.nil?
9
+
10
+ warn "#{short_name} is already registered with class #{klass}" if registry.key?(short_name)
11
+ registry[short_name] = klass
12
+ end
13
+
14
+ private
15
+
16
+ def build_short_name(klass)
17
+ return if klass.name.blank?
18
+
19
+ klass.name.demodulize.underscore
20
+ end
21
+
22
+ def registry
23
+ @registry ||= {}.with_indifferent_access
24
+ end
25
+ end
26
+ end
27
+ end
@@ -23,49 +23,12 @@ module Grape
23
23
  api.namespace_stackable(:contract_key_map, key_map)
24
24
 
25
25
  validator_options = {
26
- validator_class: Validator,
27
- opts: { schema: contract }
26
+ validator_class: Grape::Validations.require_validator(:contract_scope),
27
+ opts: { schema: contract, fail_fast: false }
28
28
  }
29
29
 
30
30
  api.namespace_stackable(:validations, validator_options)
31
31
  end
32
-
33
- class Validator
34
- attr_reader :schema
35
-
36
- def initialize(*_args, schema:)
37
- @schema = schema
38
- end
39
-
40
- # Validates a given request.
41
- # @param request [Grape::Request] the request currently being handled
42
- # @raise [Grape::Exceptions::ValidationArrayErrors] if validation failed
43
- # @return [void]
44
- def validate(request)
45
- res = schema.call(request.params)
46
-
47
- if res.success?
48
- request.params.deep_merge!(res.to_h)
49
- return
50
- end
51
-
52
- errors = []
53
-
54
- res.errors.messages.each do |message|
55
- full_name = message.path.first.to_s
56
-
57
- full_name += "[#{message.path[1..].join('][')}]" if message.path.size > 1
58
-
59
- errors << Grape::Exceptions::Validation.new(params: [full_name], message: message.text)
60
- end
61
-
62
- raise Grape::Exceptions::ValidationArrayErrors.new(errors)
63
- end
64
-
65
- def fail_fast?
66
- false
67
- end
68
- end
69
32
  end
70
33
  end
71
34
  end
@@ -174,16 +174,12 @@ module Grape
174
174
 
175
175
  # Adds a parameter declaration to our list of validations.
176
176
  # @param attrs [Array] (see Grape::DSL::Parameters#requires)
177
- def push_declared_params(attrs, **opts)
178
- opts = opts.merge(declared_params_scope: self) unless opts.key?(:declared_params_scope)
179
- if lateral?
180
- @parent.push_declared_params(attrs, **opts)
181
- else
182
- push_renamed_param(full_path + [attrs.first], opts[:as]) \
183
- if opts && opts[:as]
177
+ def push_declared_params(attrs, opts = {})
178
+ opts[:declared_params_scope] = self unless opts.key?(:declared_params_scope)
179
+ return @parent.push_declared_params(attrs, opts) if lateral?
184
180
 
185
- @declared_params.concat(attrs.map { |attr| ::Grape::Validations::ParamsScope::Attr.new(attr, opts[:declared_params_scope]) })
186
- end
181
+ push_renamed_param(full_path + [attrs.first], opts[:as]) if opts[:as]
182
+ @declared_params.concat(attrs.map { |attr| ::Grape::Validations::ParamsScope::Attr.new(attr, opts[:declared_params_scope]) })
187
183
  end
188
184
 
189
185
  # Get the full path of the parameter scope in the hierarchy.
@@ -490,7 +486,7 @@ module Grape
490
486
  def validate_value_coercion(coerce_type, *values_list)
491
487
  return unless coerce_type
492
488
 
493
- coerce_type = coerce_type.first if coerce_type.is_a?(Array)
489
+ coerce_type = coerce_type.first if coerce_type.is_a?(Enumerable)
494
490
  values_list.each do |values|
495
491
  next if !values || values.is_a?(Proc)
496
492
 
@@ -529,7 +525,7 @@ module Grape
529
525
  def validates_presence(validations, attrs, doc, opts)
530
526
  return unless validations.key?(:presence) && validations[:presence]
531
527
 
532
- validate(:presence, validations.delete(:presence), attrs, doc, opts)
528
+ validate('presence', validations.delete(:presence), attrs, doc, opts)
533
529
  validations.delete(:message) if validations.key?(:message)
534
530
  end
535
531
  end
@@ -22,16 +22,20 @@ module Grape
22
22
  # collection_coercer_for(Array)
23
23
  # #=> Grape::Validations::Types::ArrayCoercer
24
24
  def collection_coercer_for(type)
25
- Grape::Validations::Types.const_get(:"#{type.name.camelize}Coercer")
25
+ case type
26
+ when Array
27
+ ArrayCoercer
28
+ when Set
29
+ SetCoercer
30
+ else
31
+ raise ArgumentError, "Unknown type: #{type}"
32
+ end
26
33
  end
27
34
 
28
35
  # Returns an instance of a coercer for a given type
29
36
  def coercer_instance_for(type, strict = false)
30
- return PrimitiveCoercer.new(type, strict) if type.instance_of?(Class)
31
-
32
- # in case of a collection (Array[Integer]) the type is an instance of a collection,
33
- # so we need to figure out the actual type
34
- collection_coercer_for(type.class).new(type, strict)
37
+ klass = type.instance_of?(Class) ? PrimitiveCoercer : collection_coercer_for(type)
38
+ klass.new(type, strict)
35
39
  end
36
40
  end
37
41
 
@@ -3,12 +3,12 @@
3
3
  module Grape
4
4
  module Validations
5
5
  class ValidatorFactory
6
- def self.create_validator(**options)
6
+ def self.create_validator(options)
7
7
  options[:validator_class].new(options[:attributes],
8
8
  options[:options],
9
9
  options[:required],
10
10
  options[:params_scope],
11
- **options[:opts])
11
+ options[:opts])
12
12
  end
13
13
  end
14
14
  end
@@ -8,7 +8,7 @@ module Grape
8
8
  return if (options_key?(:value) ? @option[:value] : @option) || !params.is_a?(Hash)
9
9
 
10
10
  value = params[attr_name]
11
- value = value.strip if value.respond_to?(:strip)
11
+ value = value.scrub if value.respond_to?(:scrub)
12
12
 
13
13
  return if value == false || value.present?
14
14
 
@@ -13,15 +13,14 @@ module Grape
13
13
  # @param options [Object] implementation-dependent Validator options
14
14
  # @param required [Boolean] attribute(s) are required or optional
15
15
  # @param scope [ParamsScope] parent scope for this Validator
16
- # @param opts [Array] additional validation options
17
- def initialize(attrs, options, required, scope, *opts)
16
+ # @param opts [Hash] additional validation options
17
+ def initialize(attrs, options, required, scope, opts)
18
18
  @attrs = Array(attrs)
19
19
  @option = options
20
20
  @required = required
21
21
  @scope = scope
22
- opts = opts.any? ? opts.shift : {}
23
- @fail_fast = opts.fetch(:fail_fast, false)
24
- @allow_blank = opts.fetch(:allow_blank, false)
22
+ @fail_fast = opts[:fail_fast]
23
+ @allow_blank = opts[:allow_blank]
25
24
  end
26
25
 
27
26
  # Validates a given request.
@@ -60,10 +59,7 @@ module Grape
60
59
 
61
60
  def self.inherited(klass)
62
61
  super
63
- return if klass.name.blank?
64
-
65
- short_validator_name = klass.name.demodulize.underscore.delete_suffix('_validator')
66
- Validations.register_validator(short_validator_name, klass)
62
+ Validations.register(klass)
67
63
  end
68
64
 
69
65
  def message(default_key = nil)
@@ -4,7 +4,7 @@ module Grape
4
4
  module Validations
5
5
  module Validators
6
6
  class CoerceValidator < Base
7
- def initialize(attrs, options, required, scope, **opts)
7
+ def initialize(attrs, options, required, scope, opts)
8
8
  super
9
9
 
10
10
  @converter = if type.is_a?(Grape::Validations::Types::VariantCollectionCoercer)
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class ContractScopeValidator < Base
7
+ attr_reader :schema
8
+
9
+ def initialize(_attrs, _options, _required, _scope, opts)
10
+ super
11
+ @schema = opts.fetch(:schema)
12
+ end
13
+
14
+ # Validates a given request.
15
+ # @param request [Grape::Request] the request currently being handled
16
+ # @raise [Grape::Exceptions::ValidationArrayErrors] if validation failed
17
+ # @return [void]
18
+ def validate(request)
19
+ res = schema.call(request.params)
20
+
21
+ if res.success?
22
+ request.params.deep_merge!(res.to_h)
23
+ return
24
+ end
25
+
26
+ raise Grape::Exceptions::ValidationArrayErrors.new(build_errors_from_messages(res.errors.messages))
27
+ end
28
+
29
+ private
30
+
31
+ def build_errors_from_messages(messages)
32
+ messages.map do |message|
33
+ full_name = message.path.first.to_s
34
+ full_name << "[#{message.path[1..].join('][')}]" if message.path.size > 1
35
+ Grape::Exceptions::Validation.new(params: [full_name], message: message.text)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end