grape 2.0.0 → 2.2.0

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