grape 2.1.3 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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