grape 1.8.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -1
  3. data/README.md +377 -334
  4. data/UPGRADING.md +231 -6
  5. data/grape.gemspec +6 -10
  6. data/lib/grape/api/instance.rb +13 -10
  7. data/lib/grape/api.rb +17 -8
  8. data/lib/grape/content_types.rb +0 -2
  9. data/lib/grape/cookies.rb +2 -1
  10. data/lib/grape/dry_types.rb +0 -2
  11. data/lib/grape/dsl/desc.rb +23 -21
  12. data/lib/grape/dsl/headers.rb +1 -1
  13. data/lib/grape/dsl/inside_route.rb +47 -22
  14. data/lib/grape/dsl/parameters.rb +4 -3
  15. data/lib/grape/dsl/routing.rb +20 -4
  16. data/lib/grape/dsl/validations.rb +13 -0
  17. data/lib/grape/endpoint.rb +15 -10
  18. data/lib/grape/{util/env.rb → env.rb} +0 -5
  19. data/lib/grape/error_formatter/txt.rb +11 -10
  20. data/lib/grape/exceptions/base.rb +3 -3
  21. data/lib/grape/exceptions/missing_group_type.rb +1 -1
  22. data/lib/grape/exceptions/unsupported_group_type.rb +1 -1
  23. data/lib/grape/exceptions/validation.rb +0 -2
  24. data/lib/grape/exceptions/validation_array_errors.rb +1 -0
  25. data/lib/grape/exceptions/validation_errors.rb +1 -3
  26. data/lib/grape/extensions/hash.rb +5 -1
  27. data/lib/grape/http/headers.rb +18 -24
  28. data/lib/grape/{util/json.rb → json.rb} +1 -3
  29. data/lib/grape/locale/en.yml +3 -0
  30. data/lib/grape/middleware/auth/base.rb +0 -2
  31. data/lib/grape/middleware/auth/dsl.rb +0 -2
  32. data/lib/grape/middleware/auth/strategies.rb +1 -2
  33. data/lib/grape/middleware/base.rb +0 -2
  34. data/lib/grape/middleware/error.rb +55 -50
  35. data/lib/grape/middleware/formatter.rb +21 -18
  36. data/lib/grape/middleware/globals.rb +1 -3
  37. data/lib/grape/middleware/stack.rb +2 -3
  38. data/lib/grape/middleware/versioner/accept_version_header.rb +0 -2
  39. data/lib/grape/middleware/versioner/header.rb +17 -163
  40. data/lib/grape/middleware/versioner/param.rb +2 -4
  41. data/lib/grape/middleware/versioner/path.rb +1 -3
  42. data/lib/grape/namespace.rb +3 -4
  43. data/lib/grape/path.rb +24 -29
  44. data/lib/grape/railtie.rb +9 -0
  45. data/lib/grape/request.rb +3 -5
  46. data/lib/grape/router/base_route.rb +39 -0
  47. data/lib/grape/router/greedy_route.rb +20 -0
  48. data/lib/grape/router/pattern.rb +39 -30
  49. data/lib/grape/router/route.rb +22 -59
  50. data/lib/grape/router.rb +30 -36
  51. data/lib/grape/util/accept/header.rb +19 -0
  52. data/lib/grape/util/accept_header_handler.rb +105 -0
  53. data/lib/grape/util/base_inheritable.rb +4 -4
  54. data/lib/grape/util/cache.rb +0 -3
  55. data/lib/grape/util/endpoint_configuration.rb +1 -1
  56. data/lib/grape/util/header.rb +13 -0
  57. data/lib/grape/util/inheritable_values.rb +0 -2
  58. data/lib/grape/util/lazy/block.rb +29 -0
  59. data/lib/grape/util/lazy/object.rb +45 -0
  60. data/lib/grape/util/lazy/value.rb +38 -0
  61. data/lib/grape/util/lazy/value_array.rb +21 -0
  62. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  63. data/lib/grape/util/lazy/value_hash.rb +21 -0
  64. data/lib/grape/util/media_type.rb +70 -0
  65. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  66. data/lib/grape/util/stackable_values.rb +1 -6
  67. data/lib/grape/util/strict_hash_configuration.rb +3 -3
  68. data/lib/grape/validations/attributes_doc.rb +38 -36
  69. data/lib/grape/validations/contract_scope.rb +71 -0
  70. data/lib/grape/validations/params_scope.rb +10 -9
  71. data/lib/grape/validations/types/array_coercer.rb +0 -2
  72. data/lib/grape/validations/types/build_coercer.rb +69 -71
  73. data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
  74. data/lib/grape/validations/types/json.rb +0 -2
  75. data/lib/grape/validations/types/primitive_coercer.rb +0 -2
  76. data/lib/grape/validations/types/set_coercer.rb +0 -3
  77. data/lib/grape/validations/types.rb +0 -3
  78. data/lib/grape/validations/validators/base.rb +2 -1
  79. data/lib/grape/validations/validators/default_validator.rb +5 -1
  80. data/lib/grape/validations/validators/length_validator.rb +42 -0
  81. data/lib/grape/validations/validators/values_validator.rb +8 -3
  82. data/lib/grape/validations.rb +3 -7
  83. data/lib/grape/version.rb +1 -1
  84. data/lib/grape/{util/xml.rb → xml.rb} +1 -1
  85. data/lib/grape.rb +38 -269
  86. metadata +33 -274
  87. data/lib/grape/eager_load.rb +0 -20
  88. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  89. data/lib/grape/router/attribute_translator.rb +0 -63
  90. data/lib/grape/util/lazy_block.rb +0 -27
  91. data/lib/grape/util/lazy_object.rb +0 -43
  92. data/lib/grape/util/lazy_value.rb +0 -91
  93. data/spec/grape/api/custom_validations_spec.rb +0 -213
  94. data/spec/grape/api/deeply_included_options_spec.rb +0 -56
  95. data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
  96. data/spec/grape/api/documentation_spec.rb +0 -59
  97. data/spec/grape/api/inherited_helpers_spec.rb +0 -114
  98. data/spec/grape/api/instance_spec.rb +0 -103
  99. data/spec/grape/api/invalid_format_spec.rb +0 -45
  100. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
  101. data/spec/grape/api/nested_helpers_spec.rb +0 -50
  102. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
  103. data/spec/grape/api/parameters_modification_spec.rb +0 -41
  104. data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
  105. data/spec/grape/api/recognize_path_spec.rb +0 -21
  106. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
  107. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
  108. data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
  109. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
  110. data/spec/grape/api/shared_helpers_spec.rb +0 -36
  111. data/spec/grape/api_remount_spec.rb +0 -509
  112. data/spec/grape/api_spec.rb +0 -4356
  113. data/spec/grape/dsl/callbacks_spec.rb +0 -45
  114. data/spec/grape/dsl/desc_spec.rb +0 -98
  115. data/spec/grape/dsl/headers_spec.rb +0 -62
  116. data/spec/grape/dsl/helpers_spec.rb +0 -100
  117. data/spec/grape/dsl/inside_route_spec.rb +0 -531
  118. data/spec/grape/dsl/logger_spec.rb +0 -24
  119. data/spec/grape/dsl/middleware_spec.rb +0 -60
  120. data/spec/grape/dsl/parameters_spec.rb +0 -180
  121. data/spec/grape/dsl/request_response_spec.rb +0 -225
  122. data/spec/grape/dsl/routing_spec.rb +0 -275
  123. data/spec/grape/dsl/settings_spec.rb +0 -261
  124. data/spec/grape/dsl/validations_spec.rb +0 -55
  125. data/spec/grape/endpoint/declared_spec.rb +0 -846
  126. data/spec/grape/endpoint_spec.rb +0 -1085
  127. data/spec/grape/entity_spec.rb +0 -336
  128. data/spec/grape/exceptions/base_spec.rb +0 -81
  129. data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
  130. data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
  131. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
  132. data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
  133. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
  134. data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
  135. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
  136. data/spec/grape/exceptions/missing_option_spec.rb +0 -15
  137. data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
  138. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
  139. data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
  140. data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
  141. data/spec/grape/exceptions/validation_spec.rb +0 -19
  142. data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
  143. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
  144. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
  145. data/spec/grape/grape_spec.rb +0 -9
  146. data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
  147. data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
  148. data/spec/grape/integration/rack_spec.rb +0 -51
  149. data/spec/grape/loading_spec.rb +0 -44
  150. data/spec/grape/middleware/auth/base_spec.rb +0 -31
  151. data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
  152. data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
  153. data/spec/grape/middleware/base_spec.rb +0 -221
  154. data/spec/grape/middleware/error_spec.rb +0 -85
  155. data/spec/grape/middleware/exception_spec.rb +0 -294
  156. data/spec/grape/middleware/formatter_spec.rb +0 -461
  157. data/spec/grape/middleware/globals_spec.rb +0 -30
  158. data/spec/grape/middleware/stack_spec.rb +0 -155
  159. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
  160. data/spec/grape/middleware/versioner/header_spec.rb +0 -345
  161. data/spec/grape/middleware/versioner/param_spec.rb +0 -171
  162. data/spec/grape/middleware/versioner/path_spec.rb +0 -62
  163. data/spec/grape/middleware/versioner_spec.rb +0 -21
  164. data/spec/grape/named_api_spec.rb +0 -19
  165. data/spec/grape/parser_spec.rb +0 -86
  166. data/spec/grape/path_spec.rb +0 -252
  167. data/spec/grape/presenters/presenter_spec.rb +0 -71
  168. data/spec/grape/request_spec.rb +0 -126
  169. data/spec/grape/util/inheritable_setting_spec.rb +0 -242
  170. data/spec/grape/util/inheritable_values_spec.rb +0 -79
  171. data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
  172. data/spec/grape/util/stackable_values_spec.rb +0 -128
  173. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
  174. data/spec/grape/validations/attributes_doc_spec.rb +0 -153
  175. data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
  176. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
  177. data/spec/grape/validations/params_scope_spec.rb +0 -1420
  178. data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
  179. data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
  180. data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
  181. data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
  182. data/spec/grape/validations/types_spec.rb +0 -111
  183. data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
  184. data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
  185. data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
  186. data/spec/grape/validations/validators/base_spec.rb +0 -38
  187. data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
  188. data/spec/grape/validations/validators/default_spec.rb +0 -463
  189. data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
  190. data/spec/grape/validations/validators/except_values_spec.rb +0 -192
  191. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
  192. data/spec/grape/validations/validators/presence_spec.rb +0 -315
  193. data/spec/grape/validations/validators/regexp_spec.rb +0 -161
  194. data/spec/grape/validations/validators/same_as_spec.rb +0 -57
  195. data/spec/grape/validations/validators/values_spec.rb +0 -733
  196. data/spec/grape/validations/validators/zh-CN.yml +0 -10
  197. data/spec/grape/validations_spec.rb +0 -2030
  198. data/spec/integration/eager_load/eager_load_spec.rb +0 -15
  199. data/spec/integration/multi_json/json_spec.rb +0 -7
  200. data/spec/integration/multi_xml/xml_spec.rb +0 -7
  201. data/spec/shared/deprecated_class_examples.rb +0 -16
  202. data/spec/shared/versioning_examples.rb +0 -215
  203. data/spec/spec_helper.rb +0 -52
  204. data/spec/support/basic_auth_encode_helpers.rb +0 -11
  205. data/spec/support/chunks.rb +0 -14
  206. data/spec/support/content_type_helpers.rb +0 -15
  207. data/spec/support/endpoint_faker.rb +0 -25
  208. data/spec/support/file_streamer.rb +0 -13
  209. data/spec/support/integer_helpers.rb +0 -13
  210. data/spec/support/versioned_helpers.rb +0 -55
@@ -1,62 +1,71 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
- require 'mustermann/grape'
5
- require 'grape/util/cache'
6
-
7
3
  module Grape
8
4
  class Router
9
5
  class Pattern
10
- DEFAULT_PATTERN_OPTIONS = { uri_decode: true }.freeze
11
- DEFAULT_SUPPORTED_CAPTURE = %i[format version].freeze
6
+ extend Forwardable
7
+
8
+ DEFAULT_CAPTURES = %w[format version].freeze
12
9
 
13
10
  attr_reader :origin, :path, :pattern, :to_regexp
14
11
 
15
- extend Forwardable
16
12
  def_delegators :pattern, :named_captures, :params
17
13
  def_delegators :to_regexp, :===
18
14
  alias match? ===
19
15
 
20
16
  def initialize(pattern, **options)
21
- @origin = pattern
22
- @path = build_path(pattern, **options)
23
- @pattern = Mustermann::Grape.new(@path, **pattern_options(options))
17
+ @origin = pattern
18
+ @path = build_path(pattern, anchor: options[:anchor], suffix: options[:suffix])
19
+ @pattern = build_pattern(@path, options)
24
20
  @to_regexp = @pattern.to_regexp
25
21
  end
26
22
 
23
+ def captures_default
24
+ to_regexp.names
25
+ .delete_if { |n| DEFAULT_CAPTURES.include?(n) }
26
+ .to_h { |k| [k, ''] }
27
+ end
28
+
27
29
  private
28
30
 
29
- def pattern_options(options)
30
- capture = extract_capture(**options)
31
- options = DEFAULT_PATTERN_OPTIONS.dup
32
- options[:capture] = capture if capture.present?
33
- options
31
+ def build_pattern(path, options)
32
+ Mustermann::Grape.new(
33
+ path,
34
+ uri_decode: true,
35
+ params: options[:params],
36
+ capture: extract_capture(**options)
37
+ )
34
38
  end
35
39
 
36
- def build_path(pattern, anchor: false, suffix: nil, **_options)
37
- unless anchor || pattern.end_with?('*path')
38
- pattern = +pattern
39
- pattern << '/' unless pattern.end_with?('/')
40
- pattern << '*path'
41
- end
40
+ def build_path(pattern, anchor: false, suffix: nil)
41
+ PatternCache[[build_path_from_pattern(pattern, anchor: anchor), suffix]]
42
+ end
42
43
 
43
- pattern = -pattern.split('/').tap do |parts|
44
- parts[parts.length - 1] = "?#{parts.last}"
45
- end.join('/') if pattern.end_with?('*path')
44
+ def extract_capture(**options)
45
+ sliced_options = options
46
+ .slice(:format, :version)
47
+ .delete_if { |_k, v| v.blank? }
48
+ .transform_values { |v| Array.wrap(v).map(&:to_s) }
49
+ return sliced_options if options[:requirements].blank?
46
50
 
47
- PatternCache[[pattern, suffix]]
51
+ options[:requirements].merge(sliced_options)
48
52
  end
49
53
 
50
- def extract_capture(requirements: {}, **options)
51
- requirements = {}.merge(requirements)
52
- DEFAULT_SUPPORTED_CAPTURE.each_with_object(requirements) do |field, capture|
53
- option = Array(options[field])
54
- capture[field] = option.map(&:to_s) if option.present?
54
+ def build_path_from_pattern(pattern, anchor: false)
55
+ if pattern.end_with?('*path')
56
+ pattern.dup.insert(pattern.rindex('/') + 1, '?')
57
+ elsif anchor
58
+ pattern
59
+ elsif pattern.end_with?('/')
60
+ "#{pattern}?*path"
61
+ else
62
+ "#{pattern}/?*path"
55
63
  end
56
64
  end
57
65
 
58
66
  class PatternCache < Grape::Util::Cache
59
67
  def initialize
68
+ super
60
69
  @cache = Hash.new do |h, (pattern, suffix)|
61
70
  h[[pattern, suffix]] = -"#{pattern}#{suffix}"
62
71
  end
@@ -1,57 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/router/pattern'
4
- require 'grape/router/attribute_translator'
5
- require 'forwardable'
6
- require 'pathname'
7
-
8
3
  module Grape
9
4
  class Router
10
- class Route
11
- ROUTE_ATTRIBUTE_REGEXP = /route_([_a-zA-Z]\w*)/.freeze
12
- SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/.freeze
13
- FIXED_NAMED_CAPTURES = %w[format version].freeze
14
-
15
- attr_accessor :pattern, :translator, :app, :index, :options
16
-
17
- alias attributes translator
18
-
5
+ class Route < BaseRoute
19
6
  extend Forwardable
20
- def_delegators :pattern, :path, :origin
21
- delegate Grape::Router::AttributeTranslator::ROUTE_ATTRIBUTES => :attributes
22
7
 
23
- def method_missing(method_id, *arguments)
24
- match = ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s)
25
- if match
26
- method_name = match.captures.last.to_sym
27
- warn_route_methods(method_name, caller(1).shift)
28
- @options[method_name]
29
- else
30
- super
31
- end
32
- end
33
-
34
- def respond_to_missing?(method_id, _)
35
- ROUTE_ATTRIBUTE_REGEXP.match?(method_id.to_s)
36
- end
37
-
38
- def route_method
39
- warn_route_methods(:method, caller(1).shift, :request_method)
40
- request_method
41
- end
8
+ attr_reader :app, :request_method
42
9
 
43
- def route_path
44
- warn_route_methods(:path, caller(1).shift)
45
- pattern.path
46
- end
10
+ def_delegators :pattern, :path, :origin
47
11
 
48
12
  def initialize(method, pattern, **options)
49
- method_s = method.to_s
50
- method_upcase = Grape::Http::Headers.find_supported_method(method_s) || method_s.upcase
51
-
52
- @options = options.merge(method: method_upcase)
53
- @pattern = Pattern.new(pattern, **options)
54
- @translator = AttributeTranslator.new(**options, request_method: method_upcase)
13
+ @request_method = upcase_method(method)
14
+ @pattern = Grape::Router::Pattern.new(pattern, **options)
15
+ super(**options)
55
16
  end
56
17
 
57
18
  def exec(env)
@@ -64,27 +25,29 @@ module Grape
64
25
  end
65
26
 
66
27
  def match?(input)
67
- translator.respond_to?(:forward_match) && translator.forward_match ? input.start_with?(pattern.origin) : pattern.match?(input)
28
+ return false if input.blank?
29
+
30
+ options[:forward_match] ? input.start_with?(pattern.origin) : pattern.match?(input)
68
31
  end
69
32
 
70
33
  def params(input = nil)
71
- if input.nil?
72
- pattern.named_captures.keys.each_with_object(translator.params) do |(key), defaults|
73
- defaults[key] ||= '' unless FIXED_NAMED_CAPTURES.include?(key) || defaults.key?(key)
74
- end
75
- else
76
- parsed = pattern.params(input)
77
- parsed ? parsed.delete_if { |_, value| value.nil? }.symbolize_keys : {}
78
- end
34
+ return params_without_input if input.blank?
35
+
36
+ parsed = pattern.params(input)
37
+ return {} unless parsed
38
+
39
+ parsed.compact.symbolize_keys
79
40
  end
80
41
 
81
42
  private
82
43
 
83
- def warn_route_methods(name, location, expected = nil)
84
- path, line = *location.scan(SOURCE_LOCATION_REGEXP).first
85
- path = File.realpath(path) if Pathname.new(path).relative?
86
- expected ||= name
87
- ActiveSupport::Deprecation.warn("#{path}:#{line}: The route_xxx methods such as route_#{name} have been deprecated, please use #{expected}.")
44
+ def params_without_input
45
+ pattern.captures_default.merge(attributes.params)
46
+ end
47
+
48
+ def upcase_method(method)
49
+ method_s = method.to_s
50
+ Grape::Http::Headers::SUPPORTED_METHODS.detect { |m| m.casecmp(method_s).zero? } || method_s.upcase
88
51
  end
89
52
  end
90
53
  end
data/lib/grape/router.rb CHANGED
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/router/route'
4
- require 'grape/util/cache'
5
-
6
3
  module Grape
7
4
  class Router
8
5
  attr_reader :map, :compiled
@@ -15,10 +12,6 @@ module Grape
15
12
  path
16
13
  end
17
14
 
18
- def self.supported_methods
19
- @supported_methods ||= Grape::Http::Headers::SUPPORTED_METHODS + ['*']
20
- end
21
-
22
15
  def initialize
23
16
  @neutral_map = []
24
17
  @neutral_regexes = []
@@ -31,13 +24,12 @@ module Grape
31
24
 
32
25
  @union = Regexp.union(@neutral_regexes)
33
26
  @neutral_regexes = nil
34
- self.class.supported_methods.each do |method|
27
+ (Grape::Http::Headers::SUPPORTED_METHODS + ['*']).each do |method|
28
+ next unless map.key?(method)
29
+
35
30
  routes = map[method]
36
- @optimized_map[method] = routes.map.with_index do |route, index|
37
- route.index = index
38
- Regexp.new("(?<_#{index}>#{route.pattern.to_regexp})")
39
- end
40
- @optimized_map[method] = Regexp.union(@optimized_map[method])
31
+ optimized_map = routes.map.with_index { |route, index| route.to_regexp(index) }
32
+ @optimized_map[method] = Regexp.union(optimized_map)
41
33
  end
42
34
  @compiled = true
43
35
  end
@@ -47,8 +39,10 @@ module Grape
47
39
  end
48
40
 
49
41
  def associate_routes(pattern, **options)
50
- @neutral_regexes << Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}")
51
- @neutral_map << Grape::Router::AttributeTranslator.new(**options, pattern: pattern, index: @neutral_map.length)
42
+ Grape::Router::GreedyRoute.new(pattern: pattern, **options).then do |greedy_route|
43
+ @neutral_regexes << greedy_route.to_regexp(@neutral_map.length)
44
+ @neutral_map << greedy_route
45
+ end
52
46
  end
53
47
 
54
48
  def call(env)
@@ -91,26 +85,33 @@ module Grape
91
85
 
92
86
  def transaction(env)
93
87
  input, method = *extract_input_and_method(env)
94
- response = yield(input, method)
95
88
 
96
- return response if response && !(cascade = cascade?(response))
89
+ # using a Proc is important since `return` will exit the enclosing function
90
+ cascade_or_return_response = proc do |response|
91
+ if response
92
+ cascade?(response).tap do |cascade|
93
+ return response unless cascade
97
94
 
95
+ # we need to close the body if possible before dismissing
96
+ response[2].close if response[2].respond_to?(:close)
97
+ end
98
+ end
99
+ end
100
+
101
+ last_response_cascade = cascade_or_return_response.call(yield(input, method))
98
102
  last_neighbor_route = greedy_match?(input)
99
103
 
100
104
  # If last_neighbor_route exists and request method is OPTIONS,
101
105
  # return response by using #call_with_allow_headers.
102
- return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Grape::Http::Headers::OPTIONS && !cascade
106
+ return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Rack::OPTIONS && !last_response_cascade
103
107
 
104
108
  route = match?(input, '*')
105
109
 
106
- return last_neighbor_route.endpoint.call(env) if last_neighbor_route && cascade && route
110
+ return last_neighbor_route.endpoint.call(env) if last_neighbor_route && last_response_cascade && route
107
111
 
108
- if route
109
- response = process_route(route, env)
110
- return response if response && !(cascade = cascade?(response))
111
- end
112
+ last_response_cascade = cascade_or_return_response.call(process_route(route, env)) if route
112
113
 
113
- return call_with_allow_headers(env, last_neighbor_route) if !cascade && last_neighbor_route
114
+ return call_with_allow_headers(env, last_neighbor_route) if !last_response_cascade && last_neighbor_route
114
115
 
115
116
  nil
116
117
  end
@@ -122,12 +123,12 @@ module Grape
122
123
 
123
124
  def make_routing_args(default_args, route, input)
124
125
  args = default_args || { route_info: route }
125
- args.merge(route.params(input) || {})
126
+ args.merge(route.params(input))
126
127
  end
127
128
 
128
129
  def extract_input_and_method(env)
129
- input = string_for(env[Grape::Http::Headers::PATH_INFO])
130
- method = env[Grape::Http::Headers::REQUEST_METHOD]
130
+ input = string_for(env[Rack::PATH_INFO])
131
+ method = env[Rack::REQUEST_METHOD]
131
132
  [input, method]
132
133
  end
133
134
 
@@ -141,18 +142,11 @@ module Grape
141
142
  end
142
143
 
143
144
  def match?(input, method)
144
- current_regexp = @optimized_map[method]
145
- return unless current_regexp.match(input)
146
-
147
- last_match = Regexp.last_match
148
- @map[method].detect { |route| last_match["_#{route.index}"] }
145
+ @optimized_map[method].match(input) { |m| @map[method].detect { |route| m[route.regexp_capture_index] } }
149
146
  end
150
147
 
151
148
  def greedy_match?(input)
152
- return unless @union.match(input)
153
-
154
- last_match = Regexp.last_match
155
- @neutral_map.detect { |route| last_match["_#{route.index}"] }
149
+ @union.match(input) { |m| @neutral_map.detect { |route| m[route.regexp_capture_index] } }
156
150
  end
157
151
 
158
152
  def call_with_allow_headers(env, route)
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Accept
6
+ module Header
7
+ ALLOWED_CHARACTERS = %r{^([a-z*]+)/([a-z0-9*&\^\-_#{$ERROR_INFO}.+]+)(?:;([a-z0-9=;]+))?$}.freeze
8
+ class << self
9
+ # Corrected version of https://github.com/mjackson/rack-accept/blob/master/lib/rack/accept/header.rb#L40-L44
10
+ def parse_media_type(media_type)
11
+ # see http://tools.ietf.org/html/rfc6838#section-4.2 for allowed characters in media type names
12
+ m = media_type&.match(ALLOWED_CHARACTERS)
13
+ m ? [m[1], m[2], m[3] || ''] : []
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ class AcceptHeaderHandler
6
+ attr_reader :accept_header, :versions, :vendor, :strict, :cascade
7
+
8
+ def initialize(accept_header:, versions:, **options)
9
+ @accept_header = accept_header
10
+ @versions = versions
11
+ @vendor = options.fetch(:vendor, nil)
12
+ @strict = options.fetch(:strict, false)
13
+ @cascade = options.fetch(:cascade, true)
14
+ end
15
+
16
+ def match_best_quality_media_type!(content_types: Grape::ContentTypes::CONTENT_TYPES, allowed_methods: nil)
17
+ return unless vendor
18
+
19
+ strict_header_checks!
20
+ media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types(content_types))
21
+ if media_type
22
+ yield media_type
23
+ else
24
+ fail!(allowed_methods)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def strict_header_checks!
31
+ return unless strict
32
+
33
+ accept_header_check!
34
+ version_and_vendor_check!
35
+ end
36
+
37
+ def accept_header_check!
38
+ return if accept_header.present?
39
+
40
+ invalid_accept_header!('Accept header must be set.')
41
+ end
42
+
43
+ def version_and_vendor_check!
44
+ return if versions.blank? || version_and_vendor?
45
+
46
+ invalid_accept_header!('API vendor or version not found.')
47
+ end
48
+
49
+ def q_values_mime_types
50
+ @q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)
51
+ end
52
+
53
+ def version_and_vendor?
54
+ q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }
55
+ end
56
+
57
+ def invalid_accept_header!(message)
58
+ raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)
59
+ end
60
+
61
+ def invalid_version_header!(message)
62
+ raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
63
+ end
64
+
65
+ def fail!(grape_allowed_methods)
66
+ return grape_allowed_methods if grape_allowed_methods.present?
67
+
68
+ media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }
69
+ vendor_not_found!(media_types) || version_not_found!(media_types)
70
+ end
71
+
72
+ def vendor_not_found!(media_types)
73
+ return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }
74
+
75
+ invalid_accept_header!('API vendor not found.')
76
+ end
77
+
78
+ def version_not_found!(media_types)
79
+ return unless media_types.all? { |media_type| media_type&.version && versions.exclude?(media_type.version) }
80
+
81
+ invalid_version_header!('API version not found.')
82
+ end
83
+
84
+ def error_headers
85
+ cascade ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
86
+ end
87
+
88
+ def available_media_types(content_types)
89
+ [].tap do |available_media_types|
90
+ base_media_type = "application/vnd.#{vendor}"
91
+ content_types.each_key do |extension|
92
+ versions&.reverse_each do |version|
93
+ available_media_types << "#{base_media_type}-#{version}+#{extension}"
94
+ available_media_types << "#{base_media_type}-#{version}"
95
+ end
96
+ available_media_types << "#{base_media_type}+#{extension}"
97
+ end
98
+
99
+ available_media_types << base_media_type
100
+ available_media_types.concat(content_types.values.flatten)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -26,10 +26,10 @@ module Grape
26
26
 
27
27
  def keys
28
28
  if new_values.any?
29
- combined = inherited_values.keys
30
- combined.concat(new_values.keys)
31
- combined.uniq!
32
- combined
29
+ inherited_values.keys.tap do |combined|
30
+ combined.concat(new_values.keys)
31
+ combined.uniq!
32
+ end
33
33
  else
34
34
  inherited_values.keys
35
35
  end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'singleton'
4
- require 'forwardable'
5
-
6
3
  module Grape
7
4
  module Util
8
5
  class Cache
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Grape
4
4
  module Util
5
- class EndpointConfiguration < LazyValueHash
5
+ class EndpointConfiguration < Lazy::ValueHash
6
6
  end
7
7
  end
8
8
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ if Gem::Version.new(Rack.release) >= Gem::Version.new('3')
6
+ require 'rack/headers'
7
+ Header = Rack::Headers
8
+ else
9
+ require 'rack/utils'
10
+ Header = Rack::Utils::HeaderHash
11
+ end
12
+ end
13
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base_inheritable'
4
-
5
3
  module Grape
6
4
  module Util
7
5
  class InheritableValues < BaseInheritable
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Lazy
6
+ class Block
7
+ def initialize(&new_block)
8
+ @block = new_block
9
+ end
10
+
11
+ def evaluate_from(configuration)
12
+ @block.call(configuration)
13
+ end
14
+
15
+ def evaluate
16
+ @block.call({})
17
+ end
18
+
19
+ def lazy?
20
+ true
21
+ end
22
+
23
+ def to_s
24
+ evaluate.to_s
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,45 @@
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
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Lazy
6
+ class Value
7
+ attr_reader :access_keys
8
+
9
+ def initialize(value, access_keys = [])
10
+ @value = value
11
+ @access_keys = access_keys
12
+ end
13
+
14
+ def evaluate_from(configuration)
15
+ matching_lazy_value = configuration.fetch(@access_keys)
16
+ matching_lazy_value.evaluate
17
+ end
18
+
19
+ def evaluate
20
+ @value
21
+ end
22
+
23
+ def lazy?
24
+ true
25
+ end
26
+
27
+ def reached_by(parent_access_keys, access_key)
28
+ @access_keys = parent_access_keys + [access_key]
29
+ self
30
+ end
31
+
32
+ def to_s
33
+ evaluate.to_s
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Lazy
6
+ class ValueArray < ValueEnumerable
7
+ def initialize(array)
8
+ super
9
+ @value_hash = []
10
+ array.each_with_index do |value, index|
11
+ self[index] = value
12
+ end
13
+ end
14
+
15
+ def evaluate
16
+ @value_hash.map(&:evaluate)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end