grape 2.0.0 → 2.1.1

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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -1
  3. data/README.md +362 -316
  4. data/UPGRADING.md +197 -7
  5. data/grape.gemspec +5 -6
  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 +22 -20
  12. data/lib/grape/dsl/headers.rb +1 -1
  13. data/lib/grape/dsl/inside_route.rb +42 -13
  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 +12 -15
  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/validation.rb +0 -2
  22. data/lib/grape/exceptions/validation_array_errors.rb +1 -0
  23. data/lib/grape/exceptions/validation_errors.rb +1 -3
  24. data/lib/grape/extensions/hash.rb +5 -1
  25. data/lib/grape/http/headers.rb +18 -34
  26. data/lib/grape/{util/json.rb → json.rb} +1 -3
  27. data/lib/grape/locale/en.yml +3 -0
  28. data/lib/grape/middleware/auth/base.rb +0 -2
  29. data/lib/grape/middleware/auth/dsl.rb +0 -2
  30. data/lib/grape/middleware/base.rb +0 -2
  31. data/lib/grape/middleware/error.rb +55 -50
  32. data/lib/grape/middleware/formatter.rb +16 -13
  33. data/lib/grape/middleware/globals.rb +1 -3
  34. data/lib/grape/middleware/stack.rb +2 -3
  35. data/lib/grape/middleware/versioner/accept_version_header.rb +0 -2
  36. data/lib/grape/middleware/versioner/header.rb +17 -163
  37. data/lib/grape/middleware/versioner/param.rb +2 -4
  38. data/lib/grape/middleware/versioner/path.rb +1 -3
  39. data/lib/grape/namespace.rb +3 -4
  40. data/lib/grape/path.rb +24 -29
  41. data/lib/grape/request.rb +4 -12
  42. data/lib/grape/router/base_route.rb +39 -0
  43. data/lib/grape/router/greedy_route.rb +20 -0
  44. data/lib/grape/router/pattern.rb +39 -30
  45. data/lib/grape/router/route.rb +22 -59
  46. data/lib/grape/router.rb +32 -37
  47. data/lib/grape/util/accept/header.rb +19 -0
  48. data/lib/grape/util/accept_header_handler.rb +105 -0
  49. data/lib/grape/util/base_inheritable.rb +4 -4
  50. data/lib/grape/util/cache.rb +0 -3
  51. data/lib/grape/util/endpoint_configuration.rb +1 -1
  52. data/lib/grape/util/header.rb +13 -0
  53. data/lib/grape/util/inheritable_values.rb +0 -2
  54. data/lib/grape/util/lazy/block.rb +29 -0
  55. data/lib/grape/util/lazy/object.rb +45 -0
  56. data/lib/grape/util/lazy/value.rb +38 -0
  57. data/lib/grape/util/lazy/value_array.rb +21 -0
  58. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  59. data/lib/grape/util/lazy/value_hash.rb +21 -0
  60. data/lib/grape/util/media_type.rb +70 -0
  61. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  62. data/lib/grape/util/stackable_values.rb +1 -6
  63. data/lib/grape/util/strict_hash_configuration.rb +3 -3
  64. data/lib/grape/validations/attributes_doc.rb +38 -36
  65. data/lib/grape/validations/contract_scope.rb +71 -0
  66. data/lib/grape/validations/params_scope.rb +10 -9
  67. data/lib/grape/validations/types/array_coercer.rb +0 -2
  68. data/lib/grape/validations/types/build_coercer.rb +69 -71
  69. data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
  70. data/lib/grape/validations/types/json.rb +0 -2
  71. data/lib/grape/validations/types/primitive_coercer.rb +0 -2
  72. data/lib/grape/validations/types/set_coercer.rb +0 -3
  73. data/lib/grape/validations/types.rb +0 -3
  74. data/lib/grape/validations/validators/base.rb +1 -0
  75. data/lib/grape/validations/validators/default_validator.rb +5 -1
  76. data/lib/grape/validations/validators/length_validator.rb +42 -0
  77. data/lib/grape/validations/validators/values_validator.rb +6 -1
  78. data/lib/grape/validations.rb +3 -7
  79. data/lib/grape/version.rb +1 -1
  80. data/lib/grape/{util/xml.rb → xml.rb} +1 -1
  81. data/lib/grape.rb +30 -274
  82. metadata +31 -37
  83. data/lib/grape/eager_load.rb +0 -20
  84. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  85. data/lib/grape/router/attribute_translator.rb +0 -63
  86. data/lib/grape/util/lazy_block.rb +0 -27
  87. data/lib/grape/util/lazy_object.rb +0 -43
  88. data/lib/grape/util/lazy_value.rb +0 -91
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
 
@@ -137,22 +138,16 @@ module Grape
137
138
  end
138
139
 
139
140
  def default_response
140
- [404, { Grape::Http::Headers::X_CASCADE => 'pass' }, ['404 Not Found']]
141
+ headers = Grape::Util::Header.new.merge(Grape::Http::Headers::X_CASCADE => 'pass')
142
+ [404, headers, ['404 Not Found']]
141
143
  end
142
144
 
143
145
  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}"] }
146
+ @optimized_map[method].match(input) { |m| @map[method].detect { |route| m[route.regexp_capture_index] } }
149
147
  end
150
148
 
151
149
  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}"] }
150
+ @union.match(input) { |m| @neutral_map.detect { |route| m[route.regexp_capture_index] } }
156
151
  end
157
152
 
158
153
  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
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Lazy
6
+ class ValueEnumerable < Value
7
+ def [](key)
8
+ if @value_hash[key].nil?
9
+ Value.new(nil).reached_by(access_keys, key)
10
+ else
11
+ @value_hash[key].reached_by(access_keys, key)
12
+ end
13
+ end
14
+
15
+ def fetch(access_keys)
16
+ fetched_keys = access_keys.dup
17
+ value = self[fetched_keys.shift]
18
+ fetched_keys.any? ? value.fetch(fetched_keys) : value
19
+ end
20
+
21
+ def []=(key, value)
22
+ @value_hash[key] = case value
23
+ when Hash
24
+ ValueHash.new(value)
25
+ when Array
26
+ ValueArray.new(value)
27
+ else
28
+ Value.new(value)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Lazy
6
+ class ValueHash < ValueEnumerable
7
+ def initialize(hash)
8
+ super
9
+ @value_hash = ActiveSupport::HashWithIndifferentAccess.new
10
+ hash.each do |key, value|
11
+ self[key] = value
12
+ end
13
+ end
14
+
15
+ def evaluate
16
+ @value_hash.transform_values(&:evaluate)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ class MediaType
6
+ attr_reader :type, :subtype, :vendor, :version, :format
7
+
8
+ # based on the HTTP Accept header with the pattern:
9
+ # application/vnd.:vendor-:version+:format
10
+ VENDOR_VERSION_HEADER_REGEX = /\Avnd\.(?<vendor>[a-z0-9.\-_!^]+?)(?:-(?<version>[a-z0-9*.]+))?(?:\+(?<format>[a-z0-9*\-.]+))?\z/.freeze
11
+
12
+ def initialize(type:, subtype:)
13
+ @type = type
14
+ @subtype = subtype
15
+ VENDOR_VERSION_HEADER_REGEX.match(subtype) do |m|
16
+ @vendor = m[:vendor]
17
+ @version = m[:version]
18
+ @format = m[:format]
19
+ end
20
+ end
21
+
22
+ def ==(other)
23
+ eql?(other)
24
+ end
25
+
26
+ def eql?(other)
27
+ self.class == other.class &&
28
+ other.type == type &&
29
+ other.subtype == subtype &&
30
+ other.vendor == vendor &&
31
+ other.version == version &&
32
+ other.format == format
33
+ end
34
+
35
+ def hash
36
+ [self.class, type, subtype, vendor, version, format].hash
37
+ end
38
+
39
+ class << self
40
+ def best_quality(header, available_media_types)
41
+ parse(best_quality_media_type(header, available_media_types))
42
+ end
43
+
44
+ def parse(media_type)
45
+ return if media_type.blank?
46
+
47
+ type, subtype = media_type.split('/', 2)
48
+ return if type.blank? || subtype.blank?
49
+
50
+ new(type: type, subtype: subtype)
51
+ end
52
+
53
+ def match?(media_type)
54
+ return false if media_type.blank?
55
+
56
+ subtype = media_type.split('/', 2).last
57
+ return false if subtype.blank?
58
+
59
+ VENDOR_VERSION_HEADER_REGEX.match?(subtype)
60
+ end
61
+
62
+ def best_quality_media_type(header, available_media_types)
63
+ header.blank? ? available_media_types.first : Rack::Utils.best_q_match(header, available_media_types)
64
+ end
65
+ end
66
+
67
+ private_class_method :best_quality_media_type
68
+ end
69
+ end
70
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'stackable_values'
4
-
5
3
  module Grape
6
4
  module Util
7
5
  class ReverseStackableValues < StackableValues
@@ -10,10 +8,7 @@ module Grape
10
8
  def concat_values(inherited_value, new_value)
11
9
  return inherited_value unless new_value
12
10
 
13
- [].tap do |value|
14
- value.concat(new_value)
15
- value.concat(inherited_value)
16
- end
11
+ new_value + inherited_value
17
12
  end
18
13
  end
19
14
  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 StackableValues < BaseInheritable
@@ -31,10 +29,7 @@ module Grape
31
29
  def concat_values(inherited_value, new_value)
32
30
  return inherited_value unless new_value
33
31
 
34
- [].tap do |value|
35
- value.concat(inherited_value)
36
- value.concat(new_value)
37
- end
32
+ inherited_value + new_value
38
33
  end
39
34
  end
40
35
  end
@@ -56,19 +56,19 @@ module Grape
56
56
  def self.nested_settings_methods(setting_name, new_config_class)
57
57
  new_config_class.class_eval do
58
58
  setting_name.each_pair do |key, value|
59
- define_method "#{key}_context" do
59
+ define_method :"#{key}_context" do
60
60
  @contexts[key] ||= Grape::Util::StrictHashConfiguration.config_class(*value).new
61
61
  end
62
62
 
63
63
  define_method key do |&block|
64
- send("#{key}_context").instance_exec(&block)
64
+ send(:"#{key}_context").instance_exec(&block)
65
65
  end
66
66
  end
67
67
 
68
68
  define_method :to_hash do
69
69
  @settings.to_hash.merge(
70
70
  setting_name.each_key.with_object({}) do |k, merge_hash|
71
- merge_hash[k] = send("#{k}_context").to_hash
71
+ merge_hash[k] = send(:"#{k}_context").to_hash
72
72
  end
73
73
  )
74
74
  end