grape 2.2.0 → 2.4.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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +41 -18
  5. data/UPGRADING.md +75 -1
  6. data/grape.gemspec +5 -5
  7. data/lib/grape/api/instance.rb +25 -60
  8. data/lib/grape/api.rb +44 -76
  9. data/lib/grape/cookies.rb +31 -25
  10. data/lib/grape/dsl/api.rb +0 -2
  11. data/lib/grape/dsl/desc.rb +27 -24
  12. data/lib/grape/dsl/headers.rb +1 -1
  13. data/lib/grape/dsl/helpers.rb +1 -1
  14. data/lib/grape/dsl/inside_route.rb +17 -40
  15. data/lib/grape/dsl/parameters.rb +5 -5
  16. data/lib/grape/dsl/routing.rb +14 -13
  17. data/lib/grape/endpoint.rb +100 -106
  18. data/lib/grape/error_formatter/base.rb +51 -21
  19. data/lib/grape/error_formatter/json.rb +7 -24
  20. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  21. data/lib/grape/error_formatter/txt.rb +13 -20
  22. data/lib/grape/error_formatter/xml.rb +3 -13
  23. data/lib/grape/error_formatter.rb +4 -12
  24. data/lib/grape/exceptions/base.rb +18 -30
  25. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  26. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  27. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  28. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  29. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  30. data/lib/grape/exceptions/validation.rb +5 -4
  31. data/lib/grape/exceptions/validation_errors.rb +2 -2
  32. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
  33. data/lib/grape/extensions/hash.rb +2 -1
  34. data/lib/grape/extensions/hashie/mash.rb +3 -5
  35. data/lib/grape/formatter/base.rb +16 -0
  36. data/lib/grape/formatter/json.rb +4 -6
  37. data/lib/grape/formatter/serializable_hash.rb +1 -1
  38. data/lib/grape/formatter/txt.rb +3 -5
  39. data/lib/grape/formatter/xml.rb +4 -6
  40. data/lib/grape/formatter.rb +4 -12
  41. data/lib/grape/locale/en.yml +44 -44
  42. data/lib/grape/middleware/auth/base.rb +11 -32
  43. data/lib/grape/middleware/auth/dsl.rb +23 -29
  44. data/lib/grape/middleware/base.rb +30 -11
  45. data/lib/grape/middleware/error.rb +18 -24
  46. data/lib/grape/middleware/formatter.rb +39 -73
  47. data/lib/grape/middleware/stack.rb +26 -36
  48. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -3
  49. data/lib/grape/middleware/versioner/base.rb +74 -0
  50. data/lib/grape/middleware/versioner/header.rb +4 -10
  51. data/lib/grape/middleware/versioner/param.rb +2 -5
  52. data/lib/grape/middleware/versioner/path.rb +0 -2
  53. data/lib/grape/middleware/versioner.rb +5 -3
  54. data/lib/grape/namespace.rb +1 -1
  55. data/lib/grape/params_builder/base.rb +18 -0
  56. data/lib/grape/params_builder/hash.rb +11 -0
  57. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  58. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  59. data/lib/grape/params_builder.rb +32 -0
  60. data/lib/grape/parser/base.rb +16 -0
  61. data/lib/grape/parser/json.rb +6 -8
  62. data/lib/grape/parser/xml.rb +6 -8
  63. data/lib/grape/parser.rb +5 -7
  64. data/lib/grape/path.rb +39 -56
  65. data/lib/grape/request.rb +162 -23
  66. data/lib/grape/router/base_route.rb +2 -2
  67. data/lib/grape/router/greedy_route.rb +2 -2
  68. data/lib/grape/router/pattern.rb +23 -18
  69. data/lib/grape/router/route.rb +14 -6
  70. data/lib/grape/router.rb +30 -12
  71. data/lib/grape/util/registry.rb +27 -0
  72. data/lib/grape/validations/contract_scope.rb +2 -39
  73. data/lib/grape/validations/params_scope.rb +15 -14
  74. data/lib/grape/validations/types/dry_type_coercer.rb +10 -6
  75. data/lib/grape/validations/validator_factory.rb +2 -2
  76. data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
  77. data/lib/grape/validations/validators/base.rb +7 -11
  78. data/lib/grape/validations/validators/coerce_validator.rb +1 -1
  79. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  80. data/lib/grape/validations/validators/default_validator.rb +1 -1
  81. data/lib/grape/validations/validators/except_values_validator.rb +2 -2
  82. data/lib/grape/validations/validators/length_validator.rb +1 -1
  83. data/lib/grape/validations/validators/presence_validator.rb +1 -1
  84. data/lib/grape/validations/validators/regexp_validator.rb +2 -2
  85. data/lib/grape/validations/validators/values_validator.rb +15 -57
  86. data/lib/grape/validations.rb +8 -17
  87. data/lib/grape/version.rb +1 -1
  88. data/lib/grape.rb +14 -2
  89. metadata +24 -16
  90. data/lib/grape/http/headers.rb +0 -55
  91. data/lib/grape/middleware/helpers.rb +0 -12
  92. data/lib/grape/middleware/versioner_helpers.rb +0 -75
  93. data/lib/grape/util/lazy/object.rb +0 -45
  94. data/lib/grape/validations/types/build_coercer.rb +0 -92
@@ -3,16 +3,9 @@
3
3
  module Grape
4
4
  module Middleware
5
5
  class Formatter < Base
6
- CHUNKED = 'chunked'
7
- FORMAT = 'format'
8
-
9
- def default_options
10
- {
11
- default_format: :txt,
12
- formatters: {},
13
- parsers: {}
14
- }
15
- end
6
+ DEFAULT_OPTIONS = {
7
+ default_format: :txt
8
+ }.freeze
16
9
 
17
10
  def before
18
11
  negotiate_content_type
@@ -53,7 +46,7 @@ module Grape
53
46
  end
54
47
 
55
48
  def fetch_formatter(headers, options)
56
- api_format = mime_types[headers[Rack::CONTENT_TYPE]] || env[Grape::Env::API_FORMAT]
49
+ api_format = env.fetch(Grape::Env::API_FORMAT) { mime_types[headers[Rack::CONTENT_TYPE]] }
57
50
  Grape::Formatter.formatter_for(api_format, options[:formatters])
58
51
  end
59
52
 
@@ -69,34 +62,27 @@ module Grape
69
62
  end
70
63
  end
71
64
 
72
- def request
73
- @request ||= Rack::Request.new(env)
74
- end
75
-
76
- # store read input in env['api.request.input']
77
65
  def read_body_input
78
- return unless
79
- (request.post? || request.put? || request.patch? || request.delete?) &&
80
- (!request.form_data? || !request.media_type) &&
81
- !request.parseable_data? &&
82
- (request.content_length.to_i.positive? || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED)
66
+ input = rack_request.body # reads RACK_INPUT
67
+ return if input.nil?
68
+ return unless read_body_input?
83
69
 
84
- return unless (input = env[Rack::RACK_INPUT])
85
-
86
- rewind_input input
70
+ input.try(:rewind)
87
71
  body = env[Grape::Env::API_REQUEST_INPUT] = input.read
88
72
  begin
89
- read_rack_input(body) if body && !body.empty?
73
+ read_rack_input(body)
90
74
  ensure
91
- rewind_input input
75
+ input.try(:rewind)
92
76
  end
93
77
  end
94
78
 
95
- # store parsed input in env['api.request.body']
96
79
  def read_rack_input(body)
97
- fmt = request.media_type ? mime_types[request.media_type] : options[:default_format]
80
+ return if body.empty?
81
+
82
+ media_type = rack_request.media_type
83
+ fmt = media_type ? mime_types[media_type] : options[:default_format]
98
84
 
99
- throw :error, status: 415, message: "The provided content-type '#{request.media_type}' is not supported." unless content_type_for(fmt)
85
+ throw :error, status: 415, message: "The provided content-type '#{media_type}' is not supported." unless content_type_for(fmt)
100
86
  parser = Grape::Parser.parser_for fmt, options[:parsers]
101
87
  if parser
102
88
  begin
@@ -119,63 +105,43 @@ module Grape
119
105
  end
120
106
  end
121
107
 
108
+ # this middleware will not try to format the following content-types since Rack already handles them
109
+ # when calling Rack's `params` function
110
+ # - application/x-www-form-urlencoded
111
+ # - multipart/form-data
112
+ # - multipart/related
113
+ # - multipart/mixed
114
+ def read_body_input?
115
+ (rack_request.post? || rack_request.put? || rack_request.patch? || rack_request.delete?) &&
116
+ !(rack_request.form_data? && rack_request.content_type) &&
117
+ !rack_request.parseable_data? &&
118
+ (rack_request.content_length.to_i.positive? || rack_request.env['HTTP_TRANSFER_ENCODING'] == 'chunked')
119
+ end
120
+
122
121
  def negotiate_content_type
123
- fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
122
+ fmt = format_from_extension || query_params['format'] || options[:format] || format_from_header || options[:default_format]
124
123
  if content_type_for(fmt)
125
- env[Grape::Env::API_FORMAT] = fmt
124
+ env[Grape::Env::API_FORMAT] = fmt.to_sym
126
125
  else
127
126
  throw :error, status: 406, message: "The requested format '#{fmt}' is not supported."
128
127
  end
129
128
  end
130
129
 
131
130
  def format_from_extension
132
- parts = request.path.split('.')
133
-
134
- if parts.size > 1
135
- extension = parts.last
136
- # avoid symbol memory leak on an unknown format
137
- return extension.to_sym if content_type_for(extension)
138
- end
139
- nil
140
- end
141
-
142
- def format_from_params
143
- fmt = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[FORMAT]
144
- # avoid symbol memory leak on an unknown format
145
- return fmt.to_sym if content_type_for(fmt)
131
+ request_path = rack_request.path.try(:scrub)
132
+ dot_pos = request_path.rindex('.')
133
+ return unless dot_pos
146
134
 
147
- fmt
135
+ extension = request_path[dot_pos + 1..]
136
+ extension if content_type_for(extension)
148
137
  end
149
138
 
150
139
  def format_from_header
151
- mime_array.each do |t|
152
- return mime_types[t] if mime_types.key?(t)
153
- end
154
- nil
155
- end
156
-
157
- def mime_array
158
- accept = env[Grape::Http::Headers::HTTP_ACCEPT]
159
- return [] unless accept
160
-
161
- accept_into_mime_and_quality = %r{
162
- (
163
- \w+/[\w+.-]+) # eg application/vnd.example.myformat+xml
164
- (?:
165
- (?:;[^,]*?)? # optionally multiple formats in a row
166
- ;\s*q=([\w.]+) # optional "quality" preference (eg q=0.5)
167
- )?
168
- }x
169
-
170
- vendor_prefix_pattern = /vnd\.[^+]+\+/
171
-
172
- accept.scan(accept_into_mime_and_quality)
173
- .sort_by { |_, quality_preference| -(quality_preference ? quality_preference.to_f : 1.0) }
174
- .flat_map { |mime, _| [mime, mime.sub(vendor_prefix_pattern, '')] }
175
- end
140
+ accept_header = env['HTTP_ACCEPT'].try(:scrub)
141
+ return if accept_header.blank?
176
142
 
177
- def rewind_input(input)
178
- input.rewind if input.respond_to?(:rewind)
143
+ media_type = Rack::Utils.best_q_match(accept_header, mime_types.keys)
144
+ mime_types[media_type] if media_type
179
145
  end
180
146
  end
181
147
  end
@@ -5,10 +5,11 @@ module Grape
5
5
  # Class to handle the stack of middlewares based on ActionDispatch::MiddlewareStack
6
6
  # It allows to insert and insert after
7
7
  class Stack
8
+ extend Forwardable
8
9
  class Middleware
9
10
  attr_reader :args, :block, :klass
10
11
 
11
- def initialize(klass, *args, &block)
12
+ def initialize(klass, args, block)
12
13
  @klass = klass
13
14
  @args = args
14
15
  @block = block
@@ -31,8 +32,12 @@ module Grape
31
32
  klass.to_s
32
33
  end
33
34
 
34
- def use_in(builder)
35
- builder.use(@klass, *@args, &@block)
35
+ def build(builder)
36
+ # we need to force the ruby2_keywords_hash for middlewares that initialize contains keywords
37
+ # like ActionDispatch::RequestId since middleware arguments are serialized
38
+ # https://rubyapi.org/3.4/o/hash#method-c-ruby2_keywords_hash
39
+ args[-1] = Hash.ruby2_keywords_hash(args[-1]) if args.last.is_a?(Hash) && Hash.respond_to?(:ruby2_keywords_hash)
40
+ builder.use(klass, *args, &block)
36
41
  end
37
42
  end
38
43
 
@@ -40,33 +45,17 @@ module Grape
40
45
 
41
46
  attr_accessor :middlewares, :others
42
47
 
48
+ def_delegators :middlewares, :each, :size, :last, :[]
49
+
43
50
  def initialize
44
51
  @middlewares = []
45
52
  @others = []
46
53
  end
47
54
 
48
- def each(&block)
49
- @middlewares.each(&block)
50
- end
51
-
52
- def size
53
- middlewares.size
54
- end
55
-
56
- def last
57
- middlewares.last
58
- end
59
-
60
- def [](index)
61
- middlewares[index]
62
- end
63
-
64
- def insert(index, *args, &block)
55
+ def insert(index, klass, *args, &block)
65
56
  index = assert_index(index, :before)
66
- middleware = self.class::Middleware.new(*args, &block)
67
- middlewares.insert(index, middleware)
57
+ middlewares.insert(index, self.class::Middleware.new(klass, args, block))
68
58
  end
69
- ruby2_keywords :insert if respond_to?(:ruby2_keywords, true)
70
59
 
71
60
  alias insert_before insert
72
61
 
@@ -74,38 +63,39 @@ module Grape
74
63
  index = assert_index(index, :after)
75
64
  insert(index + 1, *args, &block)
76
65
  end
77
- ruby2_keywords :insert_after if respond_to?(:ruby2_keywords, true)
78
66
 
79
- def use(...)
80
- middleware = self.class::Middleware.new(...)
67
+ def use(klass, *args, &block)
68
+ middleware = self.class::Middleware.new(klass, args, block)
81
69
  middlewares.push(middleware)
82
70
  end
83
71
 
84
72
  def merge_with(middleware_specs)
85
- middleware_specs.each do |operation, *args|
73
+ middleware_specs.each do |operation, klass, *args|
86
74
  if args.last.is_a?(Proc)
87
75
  last_proc = args.pop
88
- public_send(operation, *args, &last_proc)
76
+ public_send(operation, klass, *args, &last_proc)
89
77
  else
90
- public_send(operation, *args)
78
+ public_send(operation, klass, *args)
91
79
  end
92
80
  end
93
81
  end
94
82
 
95
83
  # @return [Rack::Builder] the builder object with our middlewares applied
96
- def build(builder = Rack::Builder.new)
97
- others.shift(others.size).each { |m| merge_with(m) }
98
- middlewares.each do |m|
99
- m.use_in(builder)
84
+ def build
85
+ Rack::Builder.new.tap do |builder|
86
+ others.shift(others.size).each { |m| merge_with(m) }
87
+ middlewares.each do |m|
88
+ m.build(builder)
89
+ end
100
90
  end
101
- builder
102
91
  end
103
92
 
104
93
  # @description Add middlewares with :use operation to the stack. Store others with :insert_* operation for later
105
94
  # @param [Array] other_specs An array of middleware specifications (e.g. [[:use, klass], [:insert_before, *args]])
106
95
  def concat(other_specs)
107
- @others << Array(other_specs).reject { |o| o.first == :use }
108
- merge_with(Array(other_specs).select { |o| o.first == :use })
96
+ use, not_use = other_specs.partition { |o| o.first == :use }
97
+ others << not_use
98
+ merge_with(use)
109
99
  end
110
100
 
111
101
  protected
@@ -17,10 +17,8 @@ module Grape
17
17
  # X-Cascade header to alert Grape::Router to attempt the next matched
18
18
  # route.
19
19
  class AcceptVersionHeader < Base
20
- include VersionerHelpers
21
-
22
20
  def before
23
- potential_version = env[Grape::Http::Headers::HTTP_ACCEPT_VERSION]&.strip
21
+ potential_version = env['HTTP_ACCEPT_VERSION'].try(:scrub)
24
22
  not_acceptable!('Accept-Version header must be set.') if strict? && potential_version.blank?
25
23
 
26
24
  return if potential_version.blank?
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Middleware
5
+ module Versioner
6
+ class Base < Grape::Middleware::Base
7
+ DEFAULT_OPTIONS = {
8
+ pattern: /.*/i.freeze,
9
+ version_options: {
10
+ strict: false,
11
+ cascade: true,
12
+ parameter: 'apiver'
13
+ }.freeze
14
+ }.freeze
15
+
16
+ def self.inherited(klass)
17
+ super
18
+ Versioner.register(klass)
19
+ end
20
+
21
+ def versions
22
+ options[:versions]
23
+ end
24
+
25
+ def prefix
26
+ options[:prefix]
27
+ end
28
+
29
+ def mount_path
30
+ options[:mount_path]
31
+ end
32
+
33
+ def pattern
34
+ options[:pattern]
35
+ end
36
+
37
+ def version_options
38
+ options[:version_options]
39
+ end
40
+
41
+ def strict?
42
+ version_options[:strict]
43
+ end
44
+
45
+ # By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
46
+ # of routes (see Grape::Router) for more information). To prevent
47
+ # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
48
+ def cascade?
49
+ version_options[:cascade]
50
+ end
51
+
52
+ def parameter_key
53
+ version_options[:parameter]
54
+ end
55
+
56
+ def vendor
57
+ version_options[:vendor]
58
+ end
59
+
60
+ def error_headers
61
+ cascade? ? { 'X-Cascade' => 'pass' } : {}
62
+ end
63
+
64
+ def potential_version_match?(potential_version)
65
+ versions.blank? || versions.any? { |v| v.to_s == potential_version }
66
+ end
67
+
68
+ def version_not_found!
69
+ throw :error, status: 404, message: '404 API Version Not Found', headers: { 'X-Cascade' => 'pass' }
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -22,8 +22,6 @@ module Grape
22
22
  # X-Cascade header to alert Grape::Router to attempt the next matched
23
23
  # route.
24
24
  class Header < Base
25
- include VersionerHelpers
26
-
27
25
  def before
28
26
  match_best_quality_media_type! do |media_type|
29
27
  env.update(
@@ -46,16 +44,12 @@ module Grape
46
44
  if media_type
47
45
  yield media_type
48
46
  else
49
- fail!(allowed_methods)
47
+ fail!
50
48
  end
51
49
  end
52
50
 
53
- def allowed_methods
54
- env[Grape::Env::GRAPE_ALLOWED_METHODS]
55
- end
56
-
57
51
  def accept_header
58
- env[Grape::Http::Headers::HTTP_ACCEPT]
52
+ env['HTTP_ACCEPT']
59
53
  end
60
54
 
61
55
  def strict_header_checks!
@@ -93,8 +87,8 @@ module Grape
93
87
  raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
94
88
  end
95
89
 
96
- def fail!(grape_allowed_methods)
97
- return grape_allowed_methods if grape_allowed_methods.present?
90
+ def fail!
91
+ return if env[Grape::Env::GRAPE_ALLOWED_METHODS].present?
98
92
 
99
93
  media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }
100
94
  vendor_not_found!(media_types) || version_not_found!(media_types)
@@ -19,15 +19,12 @@ module Grape
19
19
  #
20
20
  # env['api.version'] => 'v1'
21
21
  class Param < Base
22
- include VersionerHelpers
23
-
24
22
  def before
25
- potential_version = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[parameter_key]
23
+ potential_version = query_params[parameter_key]
26
24
  return if potential_version.blank?
27
25
 
28
26
  version_not_found! unless potential_version_match?(potential_version)
29
- env[Grape::Env::API_VERSION] = potential_version
30
- env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key) if env.key? Rack::RACK_REQUEST_QUERY_HASH
27
+ env[Grape::Env::API_VERSION] = env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key)
31
28
  end
32
29
  end
33
30
  end
@@ -17,8 +17,6 @@ module Grape
17
17
  # env['api.version'] => 'v1'
18
18
  #
19
19
  class Path < Base
20
- include VersionerHelpers
21
-
22
20
  def before
23
21
  path_info = Grape::Router.normalize_path(env[Rack::PATH_INFO])
24
22
  return if path_info == '/'
@@ -11,14 +11,16 @@
11
11
  module Grape
12
12
  module Middleware
13
13
  module Versioner
14
+ extend Grape::Util::Registry
15
+
14
16
  module_function
15
17
 
16
18
  # @param strategy [Symbol] :path, :header, :accept_version_header or :param
17
19
  # @return a middleware class based on strategy
18
20
  def using(strategy)
19
- Grape::Middleware::Versioner.const_get(:"#{strategy.to_s.camelize}")
20
- rescue NameError
21
- raise Grape::Exceptions::InvalidVersionerOption, strategy
21
+ raise Grape::Exceptions::InvalidVersionerOption, strategy unless registry.key?(strategy)
22
+
23
+ registry[strategy]
22
24
  end
23
25
  end
24
26
  end
@@ -12,7 +12,7 @@ module Grape
12
12
  # @option options :requirements [Hash] param-regex pairs, all of which must
13
13
  # be met by a request's params for all endpoints in this namespace, or
14
14
  # validation will fail and return a 422.
15
- def initialize(space, **options)
15
+ def initialize(space, options)
16
16
  @space = space.to_s
17
17
  @options = options
18
18
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ class Base
6
+ class << self
7
+ def call(_params)
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def inherited(klass)
12
+ super
13
+ ParamsBuilder.register(klass)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ class Hash < Base
6
+ def self.call(params)
7
+ params.deep_symbolize_keys
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ class HashWithIndifferentAccess < Base
6
+ def self.call(params)
7
+ params.with_indifferent_access
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ class HashieMash < Base
6
+ def self.call(params)
7
+ ::Hashie::Mash.new(params)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ParamsBuilder
5
+ extend Grape::Util::Registry
6
+
7
+ SHORT_NAME_LOOKUP = {
8
+ 'Grape::Extensions::Hash::ParamBuilder' => :hash,
9
+ 'Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder' => :hash_with_indifferent_access,
10
+ 'Grape::Extensions::Hashie::Mash::ParamBuilder' => :hashie_mash
11
+ }.freeze
12
+
13
+ module_function
14
+
15
+ def params_builder_for(short_name)
16
+ verified_short_name = verify_short_name!(short_name)
17
+
18
+ raise Grape::Exceptions::UnknownParamsBuilder, verified_short_name unless registry.key?(verified_short_name)
19
+
20
+ registry[verified_short_name]
21
+ end
22
+
23
+ def verify_short_name!(short_name)
24
+ return short_name if short_name.is_a?(Symbol)
25
+
26
+ class_name = short_name.name
27
+ SHORT_NAME_LOOKUP[class_name].tap do |real_short_name|
28
+ Grape.deprecator.warn "#{class_name} has been deprecated. Use short name :#{real_short_name} instead."
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Parser
5
+ class Base
6
+ def self.call(_object, _env)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def self.inherited(klass)
11
+ super
12
+ Parser.register(klass)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -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
@@ -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,16 +2,14 @@
2
2
 
3
3
  module Grape
4
4
  module Parser
5
- module_function
5
+ extend Grape::Util::Registry
6
6
 
7
- DEFAULTS = {
8
- json: Grape::Parser::Json,
9
- jsonapi: Grape::Parser::Json,
10
- xml: Grape::Parser::Xml
11
- }.freeze
7
+ module_function
12
8
 
13
9
  def parser_for(format, parsers = nil)
14
- parsers&.key?(format) ? parsers[format] : DEFAULTS[format]
10
+ return parsers[format] if parsers&.key?(format)
11
+
12
+ registry[format]
15
13
  end
16
14
  end
17
15
  end