grape 2.0.0 → 2.2.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -1
  3. data/README.md +364 -317
  4. data/UPGRADING.md +205 -7
  5. data/grape.gemspec +7 -7
  6. data/lib/grape/api/instance.rb +14 -11
  7. data/lib/grape/api.rb +19 -10
  8. data/lib/grape/content_types.rb +13 -10
  9. data/lib/grape/cookies.rb +2 -1
  10. data/lib/grape/dry_types.rb +0 -2
  11. data/lib/grape/dsl/desc.rb +22 -20
  12. data/lib/grape/dsl/headers.rb +1 -1
  13. data/lib/grape/dsl/helpers.rb +7 -3
  14. data/lib/grape/dsl/inside_route.rb +51 -15
  15. data/lib/grape/dsl/parameters.rb +5 -4
  16. data/lib/grape/dsl/request_response.rb +14 -18
  17. data/lib/grape/dsl/routing.rb +20 -4
  18. data/lib/grape/dsl/validations.rb +13 -0
  19. data/lib/grape/endpoint.rb +43 -35
  20. data/lib/grape/{util/env.rb → env.rb} +0 -5
  21. data/lib/grape/error_formatter/json.rb +13 -4
  22. data/lib/grape/error_formatter/txt.rb +11 -10
  23. data/lib/grape/error_formatter.rb +13 -25
  24. data/lib/grape/exceptions/base.rb +3 -3
  25. data/lib/grape/exceptions/validation.rb +0 -2
  26. data/lib/grape/exceptions/validation_array_errors.rb +1 -0
  27. data/lib/grape/exceptions/validation_errors.rb +2 -4
  28. data/lib/grape/extensions/hash.rb +5 -1
  29. data/lib/grape/formatter.rb +15 -25
  30. data/lib/grape/http/headers.rb +18 -34
  31. data/lib/grape/{util/json.rb → json.rb} +1 -3
  32. data/lib/grape/locale/en.yml +4 -0
  33. data/lib/grape/middleware/auth/base.rb +0 -2
  34. data/lib/grape/middleware/auth/dsl.rb +0 -2
  35. data/lib/grape/middleware/base.rb +14 -15
  36. data/lib/grape/middleware/error.rb +61 -54
  37. data/lib/grape/middleware/formatter.rb +18 -15
  38. data/lib/grape/middleware/globals.rb +1 -3
  39. data/lib/grape/middleware/stack.rb +4 -5
  40. data/lib/grape/middleware/versioner/accept_version_header.rb +8 -33
  41. data/lib/grape/middleware/versioner/header.rb +62 -123
  42. data/lib/grape/middleware/versioner/param.rb +5 -23
  43. data/lib/grape/middleware/versioner/path.rb +11 -33
  44. data/lib/grape/middleware/versioner.rb +5 -14
  45. data/lib/grape/middleware/versioner_helpers.rb +75 -0
  46. data/lib/grape/namespace.rb +3 -4
  47. data/lib/grape/parser.rb +8 -24
  48. data/lib/grape/path.rb +24 -29
  49. data/lib/grape/request.rb +4 -12
  50. data/lib/grape/router/base_route.rb +39 -0
  51. data/lib/grape/router/greedy_route.rb +20 -0
  52. data/lib/grape/router/pattern.rb +39 -30
  53. data/lib/grape/router/route.rb +22 -59
  54. data/lib/grape/router.rb +32 -37
  55. data/lib/grape/util/base_inheritable.rb +4 -4
  56. data/lib/grape/util/cache.rb +0 -3
  57. data/lib/grape/util/endpoint_configuration.rb +1 -1
  58. data/lib/grape/util/header.rb +13 -0
  59. data/lib/grape/util/inheritable_values.rb +0 -2
  60. data/lib/grape/util/lazy/block.rb +29 -0
  61. data/lib/grape/util/lazy/object.rb +45 -0
  62. data/lib/grape/util/lazy/value.rb +38 -0
  63. data/lib/grape/util/lazy/value_array.rb +21 -0
  64. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  65. data/lib/grape/util/lazy/value_hash.rb +21 -0
  66. data/lib/grape/util/media_type.rb +70 -0
  67. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  68. data/lib/grape/util/stackable_values.rb +1 -6
  69. data/lib/grape/util/strict_hash_configuration.rb +3 -3
  70. data/lib/grape/validations/attributes_doc.rb +38 -36
  71. data/lib/grape/validations/attributes_iterator.rb +1 -0
  72. data/lib/grape/validations/contract_scope.rb +71 -0
  73. data/lib/grape/validations/params_scope.rb +22 -19
  74. data/lib/grape/validations/types/array_coercer.rb +0 -2
  75. data/lib/grape/validations/types/build_coercer.rb +69 -71
  76. data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
  77. data/lib/grape/validations/types/json.rb +0 -2
  78. data/lib/grape/validations/types/primitive_coercer.rb +0 -2
  79. data/lib/grape/validations/types/set_coercer.rb +0 -3
  80. data/lib/grape/validations/types.rb +0 -3
  81. data/lib/grape/validations/validators/base.rb +1 -0
  82. data/lib/grape/validations/validators/default_validator.rb +5 -1
  83. data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
  84. data/lib/grape/validations/validators/length_validator.rb +49 -0
  85. data/lib/grape/validations/validators/values_validator.rb +6 -1
  86. data/lib/grape/validations.rb +3 -7
  87. data/lib/grape/version.rb +1 -1
  88. data/lib/grape/{util/xml.rb → xml.rb} +1 -1
  89. data/lib/grape.rb +30 -274
  90. metadata +31 -38
  91. data/lib/grape/eager_load.rb +0 -20
  92. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  93. data/lib/grape/router/attribute_translator.rb +0 -63
  94. data/lib/grape/util/lazy_block.rb +0 -27
  95. data/lib/grape/util/lazy_object.rb +0 -43
  96. data/lib/grape/util/lazy_value.rb +0 -91
  97. data/lib/grape/util/registrable.rb +0 -15
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/middleware/base'
4
- require 'grape/middleware/versioner/parse_media_type_patch'
5
-
6
3
  module Grape
7
4
  module Middleware
8
5
  module Versioner
@@ -25,168 +22,110 @@ module Grape
25
22
  # X-Cascade header to alert Grape::Router to attempt the next matched
26
23
  # route.
27
24
  class Header < Base
28
- VENDOR_VERSION_HEADER_REGEX =
29
- /\Avnd\.([a-z0-9.\-_!#{Regexp.last_match(0)}\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z/.freeze
30
-
31
- HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#{Regexp.last_match(0)}\^]+/.freeze
32
- HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#{Regexp.last_match(0)}\^]+?)(?:-([a-z0-9*.]+))+/.freeze
25
+ include VersionerHelpers
33
26
 
34
27
  def before
35
- strict_header_checks if strict?
36
-
37
- if media_type || env[Grape::Env::GRAPE_ALLOWED_METHODS]
38
- media_type_header_handler
39
- elsif headers_contain_wrong_vendor?
40
- fail_with_invalid_accept_header!('API vendor not found.')
41
- elsif headers_contain_wrong_version?
42
- fail_with_invalid_version_header!('API version not found.')
28
+ match_best_quality_media_type! do |media_type|
29
+ env.update(
30
+ Grape::Env::API_TYPE => media_type.type,
31
+ Grape::Env::API_SUBTYPE => media_type.subtype,
32
+ Grape::Env::API_VENDOR => media_type.vendor,
33
+ Grape::Env::API_VERSION => media_type.version,
34
+ Grape::Env::API_FORMAT => media_type.format
35
+ )
43
36
  end
44
37
  end
45
38
 
46
39
  private
47
40
 
48
- def strict_header_checks
49
- strict_accept_header_presence_check
50
- strict_version_vendor_accept_header_presence_check
51
- end
52
-
53
- def strict_accept_header_presence_check
54
- return unless header.qvalues.empty?
55
-
56
- fail_with_invalid_accept_header!('Accept header must be set.')
57
- end
58
-
59
- def strict_version_vendor_accept_header_presence_check
60
- return if versions.blank? || an_accept_header_with_version_and_vendor_is_present?
61
-
62
- fail_with_invalid_accept_header!('API vendor or version not found.')
63
- end
41
+ def match_best_quality_media_type!
42
+ return unless vendor
64
43
 
65
- def an_accept_header_with_version_and_vendor_is_present?
66
- header.qvalues.keys.any? do |h|
67
- VENDOR_VERSION_HEADER_REGEX.match?(h.sub('application/', ''))
44
+ strict_header_checks!
45
+ media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types)
46
+ if media_type
47
+ yield media_type
48
+ else
49
+ fail!(allowed_methods)
68
50
  end
69
51
  end
70
52
 
71
- def header
72
- @header ||= rack_accept_header
53
+ def allowed_methods
54
+ env[Grape::Env::GRAPE_ALLOWED_METHODS]
73
55
  end
74
56
 
75
- def media_type
76
- @media_type ||= header.best_of(available_media_types)
57
+ def accept_header
58
+ env[Grape::Http::Headers::HTTP_ACCEPT]
77
59
  end
78
60
 
79
- def media_type_header_handler
80
- type, subtype = Rack::Accept::Header.parse_media_type(media_type)
81
- env[Grape::Env::API_TYPE] = type
82
- env[Grape::Env::API_SUBTYPE] = subtype
83
-
84
- return unless VENDOR_VERSION_HEADER_REGEX =~ subtype
61
+ def strict_header_checks!
62
+ return unless strict?
85
63
 
86
- env[Grape::Env::API_VENDOR] = Regexp.last_match[1]
87
- env[Grape::Env::API_VERSION] = Regexp.last_match[2]
88
- # weird that Grape::Middleware::Formatter also does this
89
- env[Grape::Env::API_FORMAT] = Regexp.last_match[3]
64
+ accept_header_check!
65
+ version_and_vendor_check!
90
66
  end
91
67
 
92
- def fail_with_invalid_accept_header!(message)
93
- raise Grape::Exceptions::InvalidAcceptHeader
94
- .new(message, error_headers)
95
- end
68
+ def accept_header_check!
69
+ return if accept_header.present?
96
70
 
97
- def fail_with_invalid_version_header!(message)
98
- raise Grape::Exceptions::InvalidVersionHeader
99
- .new(message, error_headers)
71
+ invalid_accept_header!('Accept header must be set.')
100
72
  end
101
73
 
102
- def available_media_types
103
- [].tap do |available_media_types|
104
- content_types.each_key do |extension|
105
- versions.reverse_each do |version|
106
- available_media_types << "application/vnd.#{vendor}-#{version}+#{extension}"
107
- available_media_types << "application/vnd.#{vendor}-#{version}"
108
- end
109
- available_media_types << "application/vnd.#{vendor}+#{extension}"
110
- end
111
-
112
- available_media_types << "application/vnd.#{vendor}"
113
- available_media_types.concat(content_types.values.flatten)
114
- end
115
- end
74
+ def version_and_vendor_check!
75
+ return if versions.blank? || version_and_vendor?
116
76
 
117
- def headers_contain_wrong_vendor?
118
- header.values.all? do |header_value|
119
- vendor?(header_value) && request_vendor(header_value) != vendor
120
- end
77
+ invalid_accept_header!('API vendor or version not found.')
121
78
  end
122
79
 
123
- def headers_contain_wrong_version?
124
- header.values.all? do |header_value|
125
- version?(header_value) && versions.exclude?(request_version(header_value))
126
- end
80
+ def q_values_mime_types
81
+ @q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)
127
82
  end
128
83
 
129
- def rack_accept_header
130
- Rack::Accept::MediaType.new env[Grape::Http::Headers::HTTP_ACCEPT]
131
- rescue RuntimeError => e
132
- fail_with_invalid_accept_header!(e.message)
84
+ def version_and_vendor?
85
+ q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }
133
86
  end
134
87
 
135
- def versions
136
- options[:versions] || []
88
+ def invalid_accept_header!(message)
89
+ raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)
137
90
  end
138
91
 
139
- def vendor
140
- version_options && version_options[:vendor]
92
+ def invalid_version_header!(message)
93
+ raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
141
94
  end
142
95
 
143
- def strict?
144
- version_options && version_options[:strict]
145
- end
96
+ def fail!(grape_allowed_methods)
97
+ return grape_allowed_methods if grape_allowed_methods.present?
146
98
 
147
- def version_options
148
- options[:version_options]
99
+ media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }
100
+ vendor_not_found!(media_types) || version_not_found!(media_types)
149
101
  end
150
102
 
151
- # By default those errors contain an `X-Cascade` header set to `pass`,
152
- # which allows nesting and stacking of routes
153
- # (see Grape::Router for more
154
- # information). To prevent # this behavior, and not add the `X-Cascade`
155
- # header, one can set the `:cascade` option to `false`.
156
- def cascade?
157
- if version_options&.key?(:cascade)
158
- version_options[:cascade]
159
- else
160
- true
161
- end
162
- end
103
+ def vendor_not_found!(media_types)
104
+ return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }
163
105
 
164
- def error_headers
165
- cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
106
+ invalid_accept_header!('API vendor not found.')
166
107
  end
167
108
 
168
- # @param [String] media_type a content type
169
- # @return [Boolean] whether the content type sets a vendor
170
- def vendor?(media_type)
171
- _, subtype = Rack::Accept::Header.parse_media_type(media_type)
172
- subtype.present? && subtype[HAS_VENDOR_REGEX]
173
- end
109
+ def version_not_found!(media_types)
110
+ return unless media_types.all? { |media_type| media_type&.version && versions&.exclude?(media_type.version) }
174
111
 
175
- def request_vendor(media_type)
176
- _, subtype = Rack::Accept::Header.parse_media_type(media_type)
177
- subtype.match(VENDOR_VERSION_HEADER_REGEX)[1]
112
+ invalid_version_header!('API version not found.')
178
113
  end
179
114
 
180
- def request_version(media_type)
181
- _, subtype = Rack::Accept::Header.parse_media_type(media_type)
182
- subtype.match(VENDOR_VERSION_HEADER_REGEX)[2]
183
- end
115
+ def available_media_types
116
+ [].tap do |available_media_types|
117
+ base_media_type = "application/vnd.#{vendor}"
118
+ content_types.each_key do |extension|
119
+ versions&.reverse_each do |version|
120
+ available_media_types << "#{base_media_type}-#{version}+#{extension}"
121
+ available_media_types << "#{base_media_type}-#{version}"
122
+ end
123
+ available_media_types << "#{base_media_type}+#{extension}"
124
+ end
184
125
 
185
- # @param [String] media_type a content type
186
- # @return [Boolean] whether the content type sets an API version
187
- def version?(media_type)
188
- _, subtype = Rack::Accept::Header.parse_media_type(media_type)
189
- subtype.present? && subtype[HAS_VERSION_REGEX]
126
+ available_media_types << base_media_type
127
+ available_media_types.concat(content_types.values.flatten)
128
+ end
190
129
  end
191
130
  end
192
131
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/middleware/base'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  module Versioner
@@ -21,31 +19,15 @@ module Grape
21
19
  #
22
20
  # env['api.version'] => 'v1'
23
21
  class Param < Base
24
- def default_options
25
- {
26
- version_options: {
27
- parameter: 'apiver'
28
- }
29
- }
30
- end
22
+ include VersionerHelpers
31
23
 
32
24
  def before
33
- potential_version = Rack::Utils.parse_nested_query(env[Grape::Http::Headers::QUERY_STRING])[paramkey]
34
- return if potential_version.nil?
25
+ potential_version = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[parameter_key]
26
+ return if potential_version.blank?
35
27
 
36
- throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' } if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
28
+ version_not_found! unless potential_version_match?(potential_version)
37
29
  env[Grape::Env::API_VERSION] = potential_version
38
- env[Grape::Env::RACK_REQUEST_QUERY_HASH].delete(paramkey) if env.key? Grape::Env::RACK_REQUEST_QUERY_HASH
39
- end
40
-
41
- private
42
-
43
- def paramkey
44
- version_options[:parameter] || default_options[:version_options][:parameter]
45
- end
46
-
47
- def version_options
48
- options[:version_options]
30
+ env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter_key) if env.key? Rack::RACK_REQUEST_QUERY_HASH
49
31
  end
50
32
  end
51
33
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/middleware/base'
4
-
5
3
  module Grape
6
4
  module Middleware
7
5
  module Versioner
@@ -19,44 +17,24 @@ module Grape
19
17
  # env['api.version'] => 'v1'
20
18
  #
21
19
  class Path < Base
22
- def default_options
23
- {
24
- pattern: /.*/i
25
- }
26
- end
20
+ include VersionerHelpers
27
21
 
28
22
  def before
29
- path = env[Grape::Http::Headers::PATH_INFO].dup
30
- path.sub!(mount_path, '') if mounted_path?(path)
23
+ path_info = Grape::Router.normalize_path(env[Rack::PATH_INFO])
24
+ return if path_info == '/'
31
25
 
32
- if prefix && path.index(prefix) == 0 # rubocop:disable all
33
- path.sub!(prefix, '')
34
- path = Grape::Router.normalize_path(path)
26
+ [mount_path, Grape::Router.normalize_path(prefix)].each do |path|
27
+ path_info.delete_prefix!(path) if path.present? && path != '/' && path_info.start_with?(path)
35
28
  end
36
29
 
37
- pieces = path.split('/')
38
- potential_version = pieces[1]
39
- return unless potential_version&.match?(options[:pattern])
40
-
41
- throw :error, status: 404, message: '404 API Version Not Found' if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
42
- env[Grape::Env::API_VERSION] = potential_version
43
- end
44
-
45
- private
30
+ slash_position = path_info.index('/', 1) # omit the first one
31
+ return unless slash_position
46
32
 
47
- def mounted_path?(path)
48
- return false unless mount_path && path.start_with?(mount_path)
33
+ potential_version = path_info[1..slash_position - 1]
34
+ return unless potential_version.match?(pattern)
49
35
 
50
- rest = path.slice(mount_path.length..-1)
51
- rest.start_with?('/') || rest.empty?
52
- end
53
-
54
- def mount_path
55
- @mount_path ||= options[:mount_path] && options[:mount_path] != '/' ? options[:mount_path] : ''
56
- end
57
-
58
- def prefix
59
- Grape::Router.normalize_path(options[:prefix].to_s) if options[:prefix]
36
+ version_not_found! unless potential_version_match?(potential_version)
37
+ env[Grape::Env::API_VERSION] = potential_version
60
38
  end
61
39
  end
62
40
  end
@@ -4,30 +4,21 @@
4
4
  # on the requests. The current methods for determining version are:
5
5
  #
6
6
  # :header - version from HTTP Accept header.
7
+ # :accept_version_header - version from HTTP Accept-Version header
7
8
  # :path - version from uri. e.g. /v1/resource
8
9
  # :param - version from uri query string, e.g. /v1/resource?apiver=v1
9
- #
10
10
  # See individual classes for details.
11
11
  module Grape
12
12
  module Middleware
13
13
  module Versioner
14
14
  module_function
15
15
 
16
- # @param strategy [Symbol] :path, :header or :param
16
+ # @param strategy [Symbol] :path, :header, :accept_version_header or :param
17
17
  # @return a middleware class based on strategy
18
18
  def using(strategy)
19
- case strategy
20
- when :path
21
- Path
22
- when :header
23
- Header
24
- when :param
25
- Param
26
- when :accept_version_header
27
- AcceptVersionHeader
28
- else
29
- raise Grape::Exceptions::InvalidVersionerOption.new(strategy)
30
- end
19
+ Grape::Middleware::Versioner.const_get(:"#{strategy.to_s.camelize}")
20
+ rescue NameError
21
+ raise Grape::Exceptions::InvalidVersionerOption, strategy
31
22
  end
32
23
  end
33
24
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Middleware
5
+ module VersionerHelpers
6
+ DEFAULT_PATTERN = /.*/i.freeze
7
+ DEFAULT_PARAMETER = 'apiver'
8
+
9
+ def default_options
10
+ {
11
+ versions: nil,
12
+ prefix: nil,
13
+ mount_path: nil,
14
+ pattern: DEFAULT_PATTERN,
15
+ version_options: {
16
+ strict: false,
17
+ cascade: true,
18
+ parameter: DEFAULT_PARAMETER
19
+ }
20
+ }
21
+ end
22
+
23
+ def versions
24
+ options[:versions]
25
+ end
26
+
27
+ def prefix
28
+ options[:prefix]
29
+ end
30
+
31
+ def mount_path
32
+ options[:mount_path]
33
+ end
34
+
35
+ def pattern
36
+ options[:pattern]
37
+ end
38
+
39
+ def version_options
40
+ options[:version_options]
41
+ end
42
+
43
+ def strict?
44
+ version_options[:strict]
45
+ end
46
+
47
+ # By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
48
+ # of routes (see Grape::Router) for more information). To prevent
49
+ # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
50
+ def cascade?
51
+ version_options[:cascade]
52
+ end
53
+
54
+ def parameter_key
55
+ version_options[:parameter]
56
+ end
57
+
58
+ def vendor
59
+ version_options[:vendor]
60
+ end
61
+
62
+ def error_headers
63
+ cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
64
+ end
65
+
66
+ def potential_version_match?(potential_version)
67
+ versions.blank? || versions.any? { |v| v.to_s == potential_version }
68
+ end
69
+
70
+ def version_not_found!
71
+ throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' }
72
+ end
73
+ end
74
+ end
75
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/util/cache'
4
-
5
3
  module Grape
6
4
  # A container for endpoints or other namespaces, which allows for both
7
5
  # logical grouping of endpoints as well as sharing common configuration.
@@ -33,13 +31,14 @@ module Grape
33
31
  # Join the namespaces from a list of settings to create a path prefix.
34
32
  # @param settings [Array] list of Grape::Util::InheritableSettings.
35
33
  def self.joined_space_path(settings)
36
- Grape::Router.normalize_path(JoinedSpaceCache[joined_space(settings)])
34
+ JoinedSpaceCache[joined_space(settings)]
37
35
  end
38
36
 
39
37
  class JoinedSpaceCache < Grape::Util::Cache
40
38
  def initialize
39
+ super
41
40
  @cache = Hash.new do |h, joined_space|
42
- h[joined_space] = -joined_space.join('/')
41
+ h[joined_space] = Grape::Router.normalize_path(joined_space.join('/'))
43
42
  end
44
43
  end
45
44
  end
data/lib/grape/parser.rb CHANGED
@@ -2,32 +2,16 @@
2
2
 
3
3
  module Grape
4
4
  module Parser
5
- extend Util::Registrable
5
+ module_function
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
+ DEFAULTS = {
8
+ json: Grape::Parser::Json,
9
+ jsonapi: Grape::Parser::Json,
10
+ xml: Grape::Parser::Xml
11
+ }.freeze
15
12
 
16
- def parsers(**options)
17
- builtin_parsers.merge(default_elements).merge!(options[:parsers] || {})
18
- end
19
-
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
13
+ def parser_for(format, parsers = nil)
14
+ parsers&.key?(format) ? parsers[format] : DEFAULTS[format]
31
15
  end
32
16
  end
33
17
  end
data/lib/grape/path.rb CHANGED
@@ -1,14 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/util/cache'
4
-
5
3
  module Grape
6
4
  # Represents a path to an endpoint.
7
5
  class Path
8
- def self.prepare(raw_path, namespace, settings)
9
- Path.new(raw_path, namespace, settings)
10
- end
11
-
12
6
  attr_reader :raw_path, :namespace, :settings
13
7
 
14
8
  def initialize(raw_path, namespace, settings)
@@ -22,31 +16,27 @@ module Grape
22
16
  end
23
17
 
24
18
  def root_prefix
25
- split_setting(:root_prefix)
19
+ settings[:root_prefix]
26
20
  end
27
21
 
28
22
  def uses_specific_format?
29
- if settings.key?(:format) && settings.key?(:content_types)
30
- (settings[:format] && Array(settings[:content_types]).size == 1)
31
- else
32
- false
33
- end
23
+ return false unless settings.key?(:format) && settings.key?(:content_types)
24
+
25
+ settings[:format] && Array(settings[:content_types]).size == 1
34
26
  end
35
27
 
36
28
  def uses_path_versioning?
37
- if settings.key?(:version) && settings[:version_options] && settings[:version_options].key?(:using)
38
- (settings[:version] && settings[:version_options][:using] == :path)
39
- else
40
- false
41
- end
29
+ return false unless settings.key?(:version) && settings[:version_options]&.key?(:using)
30
+
31
+ settings[:version] && settings[:version_options][:using] == :path
42
32
  end
43
33
 
44
34
  def namespace?
45
- namespace&.match?(/^\S/) && namespace != '/'
35
+ namespace&.match?(/^\S/) && not_slash?(namespace)
46
36
  end
47
37
 
48
38
  def path?
49
- raw_path&.match?(/^\S/) && raw_path != '/'
39
+ raw_path&.match?(/^\S/) && not_slash?(raw_path)
50
40
  end
51
41
 
52
42
  def suffix
@@ -60,7 +50,7 @@ module Grape
60
50
  end
61
51
 
62
52
  def path
63
- Grape::Router.normalize_path(PartsCache[parts])
53
+ PartsCache[parts]
64
54
  end
65
55
 
66
56
  def path_with_suffix
@@ -75,24 +65,29 @@ module Grape
75
65
 
76
66
  class PartsCache < Grape::Util::Cache
77
67
  def initialize
68
+ super
78
69
  @cache = Hash.new do |h, parts|
79
- h[parts] = -parts.join('/')
70
+ h[parts] = Grape::Router.normalize_path(parts.join('/'))
80
71
  end
81
72
  end
82
73
  end
83
74
 
84
75
  def parts
85
- parts = [mount_path, root_prefix].compact
86
- parts << ':version' if uses_path_versioning?
87
- parts << namespace.to_s
88
- parts << raw_path.to_s
89
- parts.flatten.reject { |part| part == '/' }
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
90
83
  end
91
84
 
92
- def split_setting(key)
93
- return if settings[key].nil?
85
+ def add_part(parts, value)
86
+ parts << value if value && not_slash?(value)
87
+ end
94
88
 
95
- settings[key].to_s.split('/')
89
+ def not_slash?(value)
90
+ value != '/'
96
91
  end
97
92
  end
98
93
  end
data/lib/grape/request.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/util/lazy_object'
4
-
5
3
  module Grape
6
4
  class Request < Rack::Request
7
5
  HTTP_PREFIX = 'HTTP_'
@@ -36,8 +34,8 @@ module Grape
36
34
  end
37
35
 
38
36
  def build_headers
39
- Grape::Util::LazyObject.new do
40
- env.each_pair.with_object({}) do |(k, v), headers|
37
+ Grape::Util::Lazy::Object.new do
38
+ env.each_pair.with_object(Grape::Util::Header.new) do |(k, v), headers|
41
39
  next unless k.to_s.start_with? HTTP_PREFIX
42
40
 
43
41
  transformed_header = Grape::Http::Headers::HTTP_HEADERS[k] || transform_header(k)
@@ -46,14 +44,8 @@ module Grape
46
44
  end
47
45
  end
48
46
 
49
- if Grape.lowercase_headers?
50
- def transform_header(header)
51
- -header[5..].tr('_', '-').downcase
52
- end
53
- else
54
- def transform_header(header)
55
- -header[5..].split('_').map(&:capitalize).join('-')
56
- end
47
+ def transform_header(header)
48
+ -header[5..].tr('_', '-').downcase
57
49
  end
58
50
  end
59
51
  end