grape 2.3.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +36 -14
  5. data/UPGRADING.md +56 -1
  6. data/grape.gemspec +1 -1
  7. data/lib/grape/api/instance.rb +3 -2
  8. data/lib/grape/api.rb +43 -66
  9. data/lib/grape/cookies.rb +31 -25
  10. data/lib/grape/dsl/api.rb +0 -2
  11. data/lib/grape/dsl/headers.rb +1 -1
  12. data/lib/grape/dsl/helpers.rb +1 -1
  13. data/lib/grape/dsl/inside_route.rb +6 -18
  14. data/lib/grape/dsl/parameters.rb +3 -3
  15. data/lib/grape/dsl/routing.rb +9 -1
  16. data/lib/grape/endpoint.rb +30 -33
  17. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  18. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  19. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  20. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  21. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  22. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
  23. data/lib/grape/extensions/hash.rb +2 -1
  24. data/lib/grape/extensions/hashie/mash.rb +3 -5
  25. data/lib/grape/locale/en.yml +44 -44
  26. data/lib/grape/middleware/auth/base.rb +11 -32
  27. data/lib/grape/middleware/auth/dsl.rb +23 -29
  28. data/lib/grape/middleware/base.rb +30 -11
  29. data/lib/grape/middleware/error.rb +16 -24
  30. data/lib/grape/middleware/formatter.rb +38 -72
  31. data/lib/grape/middleware/stack.rb +26 -36
  32. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -3
  33. data/lib/grape/middleware/versioner/base.rb +10 -18
  34. data/lib/grape/middleware/versioner/header.rb +1 -1
  35. data/lib/grape/middleware/versioner/param.rb +2 -3
  36. data/lib/grape/params_builder/base.rb +18 -0
  37. data/lib/grape/params_builder/hash.rb +11 -0
  38. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  39. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  40. data/lib/grape/params_builder.rb +32 -0
  41. data/lib/grape/request.rb +161 -22
  42. data/lib/grape/router/route.rb +1 -1
  43. data/lib/grape/router.rb +25 -7
  44. data/lib/grape/validations/params_scope.rb +8 -3
  45. data/lib/grape/validations/validators/base.rb +2 -2
  46. data/lib/grape/validations/validators/except_values_validator.rb +1 -1
  47. data/lib/grape/validations/validators/presence_validator.rb +1 -1
  48. data/lib/grape/validations/validators/regexp_validator.rb +1 -1
  49. data/lib/grape/version.rb +1 -1
  50. data/lib/grape.rb +13 -1
  51. metadata +18 -13
  52. data/lib/grape/error_formatter/jsonapi.rb +0 -7
  53. data/lib/grape/http/headers.rb +0 -56
  54. data/lib/grape/middleware/helpers.rb +0 -12
  55. data/lib/grape/parser/jsonapi.rb +0 -7
  56. data/lib/grape/util/lazy/object.rb +0 -45
data/lib/grape/router.rb CHANGED
@@ -4,12 +4,30 @@ module Grape
4
4
  class Router
5
5
  attr_reader :map, :compiled
6
6
 
7
+ # Taken from Rails
8
+ # normalize_path("/foo") # => "/foo"
9
+ # normalize_path("/foo/") # => "/foo"
10
+ # normalize_path("foo") # => "/foo"
11
+ # normalize_path("") # => "/"
12
+ # normalize_path("/%ab") # => "/%AB"
13
+ # https://github.com/rails/rails/blob/00cc4ff0259c0185fe08baadaa40e63ea2534f6e/actionpack/lib/action_dispatch/journey/router/utils.rb#L19
7
14
  def self.normalize_path(path)
15
+ return +'/' unless path
16
+
17
+ # Fast path for the overwhelming majority of paths that don't need to be normalized
18
+ return path.dup if path == '/' || (path.start_with?('/') && !(path.end_with?('/') || path.match?(%r{%|//})))
19
+
20
+ # Slow path
21
+ encoding = path.encoding
8
22
  path = +"/#{path}"
9
23
  path.squeeze!('/')
10
- path.sub!(%r{/+\Z}, '')
11
- path = '/' if path == ''
12
- path
24
+
25
+ unless path == '/'
26
+ path.delete_suffix!('/')
27
+ path.gsub!(/(%[a-f0-9]{2})/) { ::Regexp.last_match(1).upcase }
28
+ end
29
+
30
+ path.force_encoding(encoding)
13
31
  end
14
32
 
15
33
  def initialize
@@ -24,7 +42,7 @@ module Grape
24
42
 
25
43
  @union = Regexp.union(@neutral_regexes)
26
44
  @neutral_regexes = nil
27
- (Grape::Http::Headers::SUPPORTED_METHODS + ['*']).each do |method|
45
+ (Grape::HTTP_SUPPORTED_METHODS + ['*']).each do |method|
28
46
  next unless map.key?(method)
29
47
 
30
48
  routes = map[method]
@@ -93,7 +111,7 @@ module Grape
93
111
  return response unless cascade
94
112
 
95
113
  # we need to close the body if possible before dismissing
96
- response[2].close if response[2].respond_to?(:close)
114
+ response[2].try(:close)
97
115
  end
98
116
  end
99
117
  end
@@ -138,7 +156,7 @@ module Grape
138
156
  end
139
157
 
140
158
  def default_response
141
- headers = Grape::Util::Header.new.merge(Grape::Http::Headers::X_CASCADE => 'pass')
159
+ headers = Grape::Util::Header.new.merge('X-Cascade' => 'pass')
142
160
  [404, headers, ['404 Not Found']]
143
161
  end
144
162
 
@@ -162,7 +180,7 @@ module Grape
162
180
  end
163
181
 
164
182
  def cascade?(response)
165
- response && response[1][Grape::Http::Headers::X_CASCADE] == 'pass'
183
+ response && response[1]['X-Cascade'] == 'pass'
166
184
  end
167
185
 
168
186
  def string_for(input)
@@ -4,7 +4,7 @@ module Grape
4
4
  module Validations
5
5
  class ParamsScope
6
6
  attr_accessor :element, :parent, :index
7
- attr_reader :type
7
+ attr_reader :type, :params_meeting_dependency
8
8
 
9
9
  include Grape::DSL::Parameters
10
10
 
@@ -67,6 +67,7 @@ module Grape
67
67
  @type = opts[:type]
68
68
  @group = opts[:group]
69
69
  @dependent_on = opts[:dependent_on]
70
+ @params_meeting_dependency = []
70
71
  @declared_params = []
71
72
  @index = nil
72
73
 
@@ -94,7 +95,11 @@ module Grape
94
95
  def meets_dependency?(params, request_params)
95
96
  return true unless @dependent_on
96
97
  return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
97
- return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
98
+
99
+ if params.is_a?(Array)
100
+ @params_meeting_dependency = params.flatten.filter { |param| meets_dependency?(param, request_params) }
101
+ return @params_meeting_dependency.present?
102
+ end
98
103
 
99
104
  meets_hash_dependency?(params)
100
105
  end
@@ -127,7 +132,7 @@ module Grape
127
132
  def full_name(name, index: nil)
128
133
  if nested?
129
134
  # Find our containing element's name, and append ours.
130
- "#{@parent.full_name(@element)}#{brackets(@index || index)}#{brackets(name)}"
135
+ "#{@parent.full_name(@element)}#{brackets(index || @index)}#{brackets(name)}"
131
136
  elsif lateral?
132
137
  # Find the name of the element as if it was at the same nesting level
133
138
  # as our parent. We need to forward our index upward to achieve this.
@@ -49,7 +49,7 @@ module Grape
49
49
  next if !@scope.required? && empty_val
50
50
  next unless @scope.meets_dependency?(val, params)
51
51
 
52
- validate_param!(attr_name, val) if @required || (val.respond_to?(:key?) && val.key?(attr_name))
52
+ validate_param!(attr_name, val) if @required || val.try(:key?, attr_name)
53
53
  rescue Grape::Exceptions::Validation => e
54
54
  array_errors << e
55
55
  end
@@ -69,7 +69,7 @@ module Grape
69
69
 
70
70
  def options_key?(key, options = nil)
71
71
  options = instance_variable_get(:@option) if options.nil?
72
- options.respond_to?(:key?) && options.key?(key) && !options[key].nil?
72
+ options.try(:key?, key) && !options[key].nil?
73
73
  end
74
74
 
75
75
  def fail_fast?
@@ -10,7 +10,7 @@ module Grape
10
10
  end
11
11
 
12
12
  def validate_param!(attr_name, params)
13
- return unless params.respond_to?(:key?) && params.key?(attr_name)
13
+ return unless params.try(:key?, attr_name)
14
14
 
15
15
  excepts = @except.is_a?(Proc) ? @except.call : @except
16
16
  return if excepts.nil?
@@ -5,7 +5,7 @@ module Grape
5
5
  module Validators
6
6
  class PresenceValidator < Base
7
7
  def validate_param!(attr_name, params)
8
- return if params.respond_to?(:key?) && params.key?(attr_name)
8
+ return if params.try(:key?, attr_name)
9
9
 
10
10
  raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:presence))
11
11
  end
@@ -5,7 +5,7 @@ module Grape
5
5
  module Validators
6
6
  class RegexpValidator < Base
7
7
  def validate_param!(attr_name, params)
8
- return unless params.respond_to?(:key?) && params.key?(attr_name)
8
+ return unless params.try(:key?, attr_name)
9
9
  return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.scrub.match?((options_key?(:value) ? @option[:value] : @option)) }
10
10
 
11
11
  raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp))
data/lib/grape/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Grape
4
4
  # The current version of Grape.
5
- VERSION = '2.3.0'
5
+ VERSION = '2.4.0'
6
6
  end
data/lib/grape.rb CHANGED
@@ -20,6 +20,7 @@ require 'active_support/core_ext/hash/slice'
20
20
  require 'active_support/core_ext/module/delegation'
21
21
  require 'active_support/core_ext/object/blank'
22
22
  require 'active_support/core_ext/object/deep_dup'
23
+ require 'active_support/core_ext/object/try'
23
24
  require 'active_support/core_ext/object/duplicable'
24
25
  require 'active_support/core_ext/string/output_safety'
25
26
  require 'active_support/core_ext/string/exclude'
@@ -58,12 +59,23 @@ I18n.load_path << File.expand_path('grape/locale/en.yml', __dir__)
58
59
  module Grape
59
60
  include ActiveSupport::Configurable
60
61
 
62
+ HTTP_SUPPORTED_METHODS = [
63
+ Rack::GET,
64
+ Rack::POST,
65
+ Rack::PUT,
66
+ Rack::PATCH,
67
+ Rack::DELETE,
68
+ Rack::HEAD,
69
+ Rack::OPTIONS
70
+ ].freeze
71
+
61
72
  def self.deprecator
62
73
  @deprecator ||= ActiveSupport::Deprecation.new('2.0', 'Grape')
63
74
  end
64
75
 
65
76
  configure do |config|
66
- config.param_builder = Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
77
+ config.param_builder = :hash_with_indifferent_access
78
+ config.lint = false
67
79
  config.compile_methods!
68
80
  end
69
81
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grape
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-08 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '6'
18
+ version: '6.1'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '6'
25
+ version: '6.1'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: dry-types
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -119,16 +119,17 @@ files:
119
119
  - lib/grape/error_formatter.rb
120
120
  - lib/grape/error_formatter/base.rb
121
121
  - lib/grape/error_formatter/json.rb
122
- - lib/grape/error_formatter/jsonapi.rb
123
122
  - lib/grape/error_formatter/serializable_hash.rb
124
123
  - lib/grape/error_formatter/txt.rb
125
124
  - lib/grape/error_formatter/xml.rb
126
125
  - lib/grape/exceptions/base.rb
126
+ - lib/grape/exceptions/conflicting_types.rb
127
127
  - lib/grape/exceptions/empty_message_body.rb
128
128
  - lib/grape/exceptions/incompatible_option_values.rb
129
129
  - lib/grape/exceptions/invalid_accept_header.rb
130
130
  - lib/grape/exceptions/invalid_formatter.rb
131
131
  - lib/grape/exceptions/invalid_message_body.rb
132
+ - lib/grape/exceptions/invalid_parameters.rb
132
133
  - lib/grape/exceptions/invalid_response.rb
133
134
  - lib/grape/exceptions/invalid_version_header.rb
134
135
  - lib/grape/exceptions/invalid_versioner_option.rb
@@ -138,9 +139,12 @@ files:
138
139
  - lib/grape/exceptions/missing_mime_type.rb
139
140
  - lib/grape/exceptions/missing_option.rb
140
141
  - lib/grape/exceptions/missing_vendor_option.rb
142
+ - lib/grape/exceptions/too_deep_parameters.rb
141
143
  - lib/grape/exceptions/too_many_multipart_files.rb
144
+ - lib/grape/exceptions/unknown_auth_strategy.rb
142
145
  - lib/grape/exceptions/unknown_options.rb
143
146
  - lib/grape/exceptions/unknown_parameter.rb
147
+ - lib/grape/exceptions/unknown_params_builder.rb
144
148
  - lib/grape/exceptions/unknown_validator.rb
145
149
  - lib/grape/exceptions/unsupported_group_type.rb
146
150
  - lib/grape/exceptions/validation.rb
@@ -155,7 +159,6 @@ files:
155
159
  - lib/grape/formatter/serializable_hash.rb
156
160
  - lib/grape/formatter/txt.rb
157
161
  - lib/grape/formatter/xml.rb
158
- - lib/grape/http/headers.rb
159
162
  - lib/grape/json.rb
160
163
  - lib/grape/locale/en.yml
161
164
  - lib/grape/middleware/auth/base.rb
@@ -167,7 +170,6 @@ files:
167
170
  - lib/grape/middleware/filter.rb
168
171
  - lib/grape/middleware/formatter.rb
169
172
  - lib/grape/middleware/globals.rb
170
- - lib/grape/middleware/helpers.rb
171
173
  - lib/grape/middleware/stack.rb
172
174
  - lib/grape/middleware/versioner.rb
173
175
  - lib/grape/middleware/versioner/accept_version_header.rb
@@ -176,10 +178,14 @@ files:
176
178
  - lib/grape/middleware/versioner/param.rb
177
179
  - lib/grape/middleware/versioner/path.rb
178
180
  - lib/grape/namespace.rb
181
+ - lib/grape/params_builder.rb
182
+ - lib/grape/params_builder/base.rb
183
+ - lib/grape/params_builder/hash.rb
184
+ - lib/grape/params_builder/hash_with_indifferent_access.rb
185
+ - lib/grape/params_builder/hashie_mash.rb
179
186
  - lib/grape/parser.rb
180
187
  - lib/grape/parser/base.rb
181
188
  - lib/grape/parser/json.rb
182
- - lib/grape/parser/jsonapi.rb
183
189
  - lib/grape/parser/xml.rb
184
190
  - lib/grape/path.rb
185
191
  - lib/grape/presenters/presenter.rb
@@ -201,7 +207,6 @@ files:
201
207
  - lib/grape/util/inheritable_setting.rb
202
208
  - lib/grape/util/inheritable_values.rb
203
209
  - lib/grape/util/lazy/block.rb
204
- - lib/grape/util/lazy/object.rb
205
210
  - lib/grape/util/lazy/value.rb
206
211
  - lib/grape/util/lazy/value_array.rb
207
212
  - lib/grape/util/lazy/value_enumerable.rb
@@ -255,9 +260,9 @@ licenses:
255
260
  - MIT
256
261
  metadata:
257
262
  bug_tracker_uri: https://github.com/ruby-grape/grape/issues
258
- changelog_uri: https://github.com/ruby-grape/grape/blob/v2.3.0/CHANGELOG.md
259
- documentation_uri: https://www.rubydoc.info/gems/grape/2.3.0
260
- source_code_uri: https://github.com/ruby-grape/grape/tree/v2.3.0
263
+ changelog_uri: https://github.com/ruby-grape/grape/blob/v2.4.0/CHANGELOG.md
264
+ documentation_uri: https://www.rubydoc.info/gems/grape/2.4.0
265
+ source_code_uri: https://github.com/ruby-grape/grape/tree/v2.4.0
261
266
  rubygems_mfa_required: 'true'
262
267
  rdoc_options: []
263
268
  require_paths:
@@ -273,7 +278,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
273
278
  - !ruby/object:Gem::Version
274
279
  version: '0'
275
280
  requirements: []
276
- rubygems_version: 3.6.2
281
+ rubygems_version: 3.6.9
277
282
  specification_version: 4
278
283
  summary: A simple Ruby framework for building REST-like APIs.
279
284
  test_files: []
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grape
4
- module ErrorFormatter
5
- class Jsonapi < Json; end
6
- end
7
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grape
4
- module Http
5
- module Headers
6
- HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION'
7
- HTTP_ACCEPT = 'HTTP_ACCEPT'
8
- HTTP_TRANSFER_ENCODING = 'HTTP_TRANSFER_ENCODING'
9
- HTTP_VERSION = 'HTTP_VERSION'
10
-
11
- ALLOW = 'Allow'
12
- LOCATION = 'Location'
13
- X_CASCADE = 'X-Cascade'
14
- TRANSFER_ENCODING = 'Transfer-Encoding'
15
-
16
- SUPPORTED_METHODS = [
17
- Rack::GET,
18
- Rack::POST,
19
- Rack::PUT,
20
- Rack::PATCH,
21
- Rack::DELETE,
22
- Rack::HEAD,
23
- Rack::OPTIONS
24
- ].freeze
25
-
26
- SUPPORTED_METHODS_WITHOUT_OPTIONS = (SUPPORTED_METHODS - [Rack::OPTIONS]).freeze
27
-
28
- HTTP_HEADERS = Grape::Util::Lazy::Object.new do
29
- common_http_headers = %w[
30
- Version
31
- Host
32
- Connection
33
- Cache-Control
34
- Dnt
35
- Upgrade-Insecure-Requests
36
- User-Agent
37
- Sec-Fetch-Dest
38
- Accept
39
- Sec-Fetch-Site
40
- Sec-Fetch-Mode
41
- Sec-Fetch-User
42
- Accept-Encoding
43
- Accept-Language
44
- Cookie
45
- ].freeze
46
- common_http_headers.each_with_object({}) do |header, response|
47
- response["HTTP_#{header.upcase.tr('-', '_')}"] = header
48
- end.freeze
49
- end
50
-
51
- def self.find_supported_method(route_method)
52
- Grape::Http::Headers::SUPPORTED_METHODS.detect { |supported_method| supported_method.casecmp(route_method).zero? }
53
- end
54
- end
55
- end
56
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grape
4
- module Middleware
5
- # Common methods for all types of Grape middleware
6
- module Helpers
7
- def context
8
- env[Grape::Env::API_ENDPOINT]
9
- end
10
- end
11
- end
12
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grape
4
- module Parser
5
- class Jsonapi < Json; end
6
- end
7
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Based on https://github.com/HornsAndHooves/lazy_object
4
-
5
- module Grape
6
- module Util
7
- module Lazy
8
- class Object < BasicObject
9
- attr_reader :callable
10
-
11
- def initialize(&callable)
12
- @callable = callable
13
- end
14
-
15
- def __target_object__
16
- @__target_object__ ||= callable.call
17
- end
18
-
19
- def ==(other)
20
- __target_object__ == other
21
- end
22
-
23
- def !=(other)
24
- __target_object__ != other
25
- end
26
-
27
- def !
28
- !__target_object__
29
- end
30
-
31
- def method_missing(method_name, *args, &block)
32
- if __target_object__.respond_to?(method_name)
33
- __target_object__.send(method_name, *args, &block)
34
- else
35
- super
36
- end
37
- end
38
-
39
- def respond_to_missing?(method_name, include_priv = false)
40
- __target_object__.respond_to?(method_name, include_priv)
41
- end
42
- end
43
- end
44
- end
45
- end