grape 1.8.0 → 2.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +65 -1
- data/README.md +377 -334
- data/UPGRADING.md +231 -6
- data/grape.gemspec +6 -10
- data/lib/grape/api/instance.rb +13 -10
- data/lib/grape/api.rb +17 -8
- data/lib/grape/content_types.rb +0 -2
- data/lib/grape/cookies.rb +2 -1
- data/lib/grape/dry_types.rb +0 -2
- data/lib/grape/dsl/desc.rb +23 -21
- data/lib/grape/dsl/headers.rb +1 -1
- data/lib/grape/dsl/inside_route.rb +47 -22
- data/lib/grape/dsl/parameters.rb +4 -3
- data/lib/grape/dsl/routing.rb +20 -4
- data/lib/grape/dsl/validations.rb +13 -0
- data/lib/grape/endpoint.rb +15 -10
- data/lib/grape/{util/env.rb → env.rb} +0 -5
- data/lib/grape/error_formatter/txt.rb +11 -10
- data/lib/grape/exceptions/base.rb +3 -3
- data/lib/grape/exceptions/missing_group_type.rb +1 -1
- data/lib/grape/exceptions/unsupported_group_type.rb +1 -1
- data/lib/grape/exceptions/validation.rb +0 -2
- data/lib/grape/exceptions/validation_array_errors.rb +1 -0
- data/lib/grape/exceptions/validation_errors.rb +1 -3
- data/lib/grape/extensions/hash.rb +5 -1
- data/lib/grape/http/headers.rb +18 -24
- data/lib/grape/{util/json.rb → json.rb} +1 -3
- data/lib/grape/locale/en.yml +3 -0
- data/lib/grape/middleware/auth/base.rb +0 -2
- data/lib/grape/middleware/auth/dsl.rb +0 -2
- data/lib/grape/middleware/auth/strategies.rb +1 -2
- data/lib/grape/middleware/base.rb +0 -2
- data/lib/grape/middleware/error.rb +55 -50
- data/lib/grape/middleware/formatter.rb +21 -18
- data/lib/grape/middleware/globals.rb +1 -3
- data/lib/grape/middleware/stack.rb +2 -3
- data/lib/grape/middleware/versioner/accept_version_header.rb +0 -2
- data/lib/grape/middleware/versioner/header.rb +17 -163
- data/lib/grape/middleware/versioner/param.rb +2 -4
- data/lib/grape/middleware/versioner/path.rb +1 -3
- data/lib/grape/namespace.rb +3 -4
- data/lib/grape/path.rb +24 -29
- data/lib/grape/railtie.rb +9 -0
- data/lib/grape/request.rb +3 -5
- data/lib/grape/router/base_route.rb +39 -0
- data/lib/grape/router/greedy_route.rb +20 -0
- data/lib/grape/router/pattern.rb +39 -30
- data/lib/grape/router/route.rb +22 -59
- data/lib/grape/router.rb +30 -36
- data/lib/grape/util/accept/header.rb +19 -0
- data/lib/grape/util/accept_header_handler.rb +105 -0
- data/lib/grape/util/base_inheritable.rb +4 -4
- data/lib/grape/util/cache.rb +0 -3
- data/lib/grape/util/endpoint_configuration.rb +1 -1
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_values.rb +0 -2
- data/lib/grape/util/lazy/block.rb +29 -0
- data/lib/grape/util/lazy/object.rb +45 -0
- data/lib/grape/util/lazy/value.rb +38 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +34 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +70 -0
- data/lib/grape/util/reverse_stackable_values.rb +1 -6
- data/lib/grape/util/stackable_values.rb +1 -6
- data/lib/grape/util/strict_hash_configuration.rb +3 -3
- data/lib/grape/validations/attributes_doc.rb +38 -36
- data/lib/grape/validations/contract_scope.rb +71 -0
- data/lib/grape/validations/params_scope.rb +10 -9
- data/lib/grape/validations/types/array_coercer.rb +0 -2
- data/lib/grape/validations/types/build_coercer.rb +69 -71
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
- data/lib/grape/validations/types/json.rb +0 -2
- data/lib/grape/validations/types/primitive_coercer.rb +0 -2
- data/lib/grape/validations/types/set_coercer.rb +0 -3
- data/lib/grape/validations/types.rb +0 -3
- data/lib/grape/validations/validators/base.rb +2 -1
- data/lib/grape/validations/validators/default_validator.rb +5 -1
- data/lib/grape/validations/validators/length_validator.rb +42 -0
- data/lib/grape/validations/validators/values_validator.rb +8 -3
- data/lib/grape/validations.rb +3 -7
- data/lib/grape/version.rb +1 -1
- data/lib/grape/{util/xml.rb → xml.rb} +1 -1
- data/lib/grape.rb +38 -269
- metadata +33 -274
- data/lib/grape/eager_load.rb +0 -20
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
- data/lib/grape/router/attribute_translator.rb +0 -63
- data/lib/grape/util/lazy_block.rb +0 -27
- data/lib/grape/util/lazy_object.rb +0 -43
- data/lib/grape/util/lazy_value.rb +0 -91
- data/spec/grape/api/custom_validations_spec.rb +0 -213
- data/spec/grape/api/deeply_included_options_spec.rb +0 -56
- data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
- data/spec/grape/api/documentation_spec.rb +0 -59
- data/spec/grape/api/inherited_helpers_spec.rb +0 -114
- data/spec/grape/api/instance_spec.rb +0 -103
- data/spec/grape/api/invalid_format_spec.rb +0 -45
- data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
- data/spec/grape/api/nested_helpers_spec.rb +0 -50
- data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
- data/spec/grape/api/parameters_modification_spec.rb +0 -41
- data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
- data/spec/grape/api/recognize_path_spec.rb +0 -21
- data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
- data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
- data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
- data/spec/grape/api/shared_helpers_spec.rb +0 -36
- data/spec/grape/api_remount_spec.rb +0 -509
- data/spec/grape/api_spec.rb +0 -4356
- data/spec/grape/dsl/callbacks_spec.rb +0 -45
- data/spec/grape/dsl/desc_spec.rb +0 -98
- data/spec/grape/dsl/headers_spec.rb +0 -62
- data/spec/grape/dsl/helpers_spec.rb +0 -100
- data/spec/grape/dsl/inside_route_spec.rb +0 -531
- data/spec/grape/dsl/logger_spec.rb +0 -24
- data/spec/grape/dsl/middleware_spec.rb +0 -60
- data/spec/grape/dsl/parameters_spec.rb +0 -180
- data/spec/grape/dsl/request_response_spec.rb +0 -225
- data/spec/grape/dsl/routing_spec.rb +0 -275
- data/spec/grape/dsl/settings_spec.rb +0 -261
- data/spec/grape/dsl/validations_spec.rb +0 -55
- data/spec/grape/endpoint/declared_spec.rb +0 -846
- data/spec/grape/endpoint_spec.rb +0 -1085
- data/spec/grape/entity_spec.rb +0 -336
- data/spec/grape/exceptions/base_spec.rb +0 -81
- data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
- data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
- data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
- data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
- data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
- data/spec/grape/exceptions/missing_option_spec.rb +0 -15
- data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
- data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
- data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
- data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
- data/spec/grape/exceptions/validation_spec.rb +0 -19
- data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
- data/spec/grape/grape_spec.rb +0 -9
- data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
- data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
- data/spec/grape/integration/rack_spec.rb +0 -51
- data/spec/grape/loading_spec.rb +0 -44
- data/spec/grape/middleware/auth/base_spec.rb +0 -31
- data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
- data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
- data/spec/grape/middleware/base_spec.rb +0 -221
- data/spec/grape/middleware/error_spec.rb +0 -85
- data/spec/grape/middleware/exception_spec.rb +0 -294
- data/spec/grape/middleware/formatter_spec.rb +0 -461
- data/spec/grape/middleware/globals_spec.rb +0 -30
- data/spec/grape/middleware/stack_spec.rb +0 -155
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
- data/spec/grape/middleware/versioner/header_spec.rb +0 -345
- data/spec/grape/middleware/versioner/param_spec.rb +0 -171
- data/spec/grape/middleware/versioner/path_spec.rb +0 -62
- data/spec/grape/middleware/versioner_spec.rb +0 -21
- data/spec/grape/named_api_spec.rb +0 -19
- data/spec/grape/parser_spec.rb +0 -86
- data/spec/grape/path_spec.rb +0 -252
- data/spec/grape/presenters/presenter_spec.rb +0 -71
- data/spec/grape/request_spec.rb +0 -126
- data/spec/grape/util/inheritable_setting_spec.rb +0 -242
- data/spec/grape/util/inheritable_values_spec.rb +0 -79
- data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
- data/spec/grape/util/stackable_values_spec.rb +0 -128
- data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
- data/spec/grape/validations/attributes_doc_spec.rb +0 -153
- data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
- data/spec/grape/validations/params_scope_spec.rb +0 -1420
- data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
- data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
- data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
- data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
- data/spec/grape/validations/types_spec.rb +0 -111
- data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
- data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
- data/spec/grape/validations/validators/base_spec.rb +0 -38
- data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
- data/spec/grape/validations/validators/default_spec.rb +0 -463
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
- data/spec/grape/validations/validators/except_values_spec.rb +0 -192
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
- data/spec/grape/validations/validators/presence_spec.rb +0 -315
- data/spec/grape/validations/validators/regexp_spec.rb +0 -161
- data/spec/grape/validations/validators/same_as_spec.rb +0 -57
- data/spec/grape/validations/validators/values_spec.rb +0 -733
- data/spec/grape/validations/validators/zh-CN.yml +0 -10
- data/spec/grape/validations_spec.rb +0 -2030
- data/spec/integration/eager_load/eager_load_spec.rb +0 -15
- data/spec/integration/multi_json/json_spec.rb +0 -7
- data/spec/integration/multi_xml/xml_spec.rb +0 -7
- data/spec/shared/deprecated_class_examples.rb +0 -16
- data/spec/shared/versioning_examples.rb +0 -215
- data/spec/spec_helper.rb +0 -52
- data/spec/support/basic_auth_encode_helpers.rb +0 -11
- data/spec/support/chunks.rb +0 -14
- data/spec/support/content_type_helpers.rb +0 -15
- data/spec/support/endpoint_faker.rb +0 -25
- data/spec/support/file_streamer.rb +0 -13
- data/spec/support/integer_helpers.rb +0 -13
- data/spec/support/versioned_helpers.rb +0 -55
data/lib/grape/router/pattern.rb
CHANGED
|
@@ -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
|
-
|
|
11
|
-
|
|
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
|
|
22
|
-
@path
|
|
23
|
-
@pattern =
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
51
|
+
options[:requirements].merge(sliced_options)
|
|
48
52
|
end
|
|
49
53
|
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
data/lib/grape/router/route.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
27
|
+
(Grape::Http::Headers::SUPPORTED_METHODS + ['*']).each do |method|
|
|
28
|
+
next unless map.key?(method)
|
|
29
|
+
|
|
35
30
|
routes = map[method]
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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 ==
|
|
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 &&
|
|
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 !
|
|
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[
|
|
130
|
-
method = env[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
data/lib/grape/util/cache.rb
CHANGED
|
@@ -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
|
|
@@ -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
|