grape 2.4.0 → 3.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +109 -0
- data/CONTRIBUTING.md +2 -10
- data/README.md +137 -181
- data/UPGRADING.md +154 -0
- data/grape.gemspec +4 -4
- data/lib/grape/api/instance.rb +51 -104
- data/lib/grape/api.rb +29 -42
- data/lib/grape/content_types.rb +1 -4
- data/lib/grape/declared_params_handler.rb +118 -0
- data/lib/grape/dry_types.rb +48 -4
- data/lib/grape/dsl/callbacks.rb +8 -58
- data/lib/grape/dsl/declared.rb +35 -0
- data/lib/grape/dsl/desc.rb +8 -67
- data/lib/grape/dsl/helpers.rb +59 -64
- data/lib/grape/dsl/inside_route.rb +28 -189
- data/lib/grape/dsl/logger.rb +3 -6
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +24 -51
- data/lib/grape/dsl/request_response.rb +136 -139
- data/lib/grape/dsl/routing.rb +240 -200
- data/lib/grape/dsl/settings.rb +23 -135
- data/lib/grape/dsl/validations.rb +38 -44
- data/lib/grape/endpoint.rb +169 -205
- data/lib/grape/error_formatter/base.rb +4 -2
- data/lib/grape/exceptions/base.rb +19 -45
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_message_body.rb +1 -1
- data/lib/grape/exceptions/invalid_version_header.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/method_not_allowed.rb +1 -1
- data/lib/grape/exceptions/missing_group_type.rb +0 -2
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/request_error.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +1 -1
- data/lib/grape/exceptions/unknown_parameter.rb +1 -1
- data/lib/grape/exceptions/unknown_params_builder.rb +1 -1
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
- data/lib/grape/exceptions/validation.rb +7 -4
- data/lib/grape/exceptions/validation_errors.rb +13 -7
- data/lib/grape/locale/en.yml +0 -5
- data/lib/grape/middleware/auth/base.rb +2 -0
- data/lib/grape/middleware/auth/dsl.rb +9 -10
- data/lib/grape/middleware/base.rb +6 -4
- data/lib/grape/middleware/error.rb +4 -14
- data/lib/grape/middleware/formatter.rb +11 -7
- data/lib/grape/middleware/stack.rb +3 -2
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
- data/lib/grape/middleware/versioner/base.rb +42 -40
- data/lib/grape/middleware/versioner/header.rb +2 -18
- data/lib/grape/middleware/versioner/param.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +2 -2
- data/lib/grape/namespace.rb +15 -8
- data/lib/grape/params_builder/base.rb +2 -0
- data/lib/grape/params_builder.rb +2 -19
- data/lib/grape/request.rb +2 -10
- data/lib/grape/router/base_route.rb +14 -5
- data/lib/grape/router/greedy_route.rb +11 -5
- data/lib/grape/router/pattern.rb +6 -20
- data/lib/grape/router/route.rb +7 -11
- data/lib/grape/router.rb +42 -65
- data/lib/grape/util/api_description.rb +58 -0
- data/lib/grape/util/base_inheritable.rb +5 -2
- data/lib/grape/util/cache.rb +1 -0
- data/lib/grape/util/deep_freeze.rb +35 -0
- data/lib/grape/util/inheritable_setting.rb +8 -1
- data/lib/grape/util/media_type.rb +2 -2
- data/lib/grape/util/registry.rb +1 -1
- data/lib/grape/util/translation.rb +42 -0
- data/lib/grape/validations/attributes_iterator.rb +35 -20
- data/lib/grape/validations/contract_scope.rb +2 -8
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/param_scope_tracker.rb +57 -0
- data/lib/grape/validations/params_documentation.rb +50 -0
- data/lib/grape/validations/params_scope.rb +147 -156
- data/lib/grape/validations/single_attribute_iterator.rb +2 -2
- data/lib/grape/validations/types/array_coercer.rb +2 -3
- data/lib/grape/validations/types/dry_type_coercer.rb +4 -11
- data/lib/grape/validations/types/primitive_coercer.rb +1 -28
- data/lib/grape/validations/types.rb +10 -25
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
- data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
- data/lib/grape/validations/validators/base.rb +95 -25
- data/lib/grape/validations/validators/coerce_validator.rb +15 -35
- data/lib/grape/validations/validators/contract_scope_validator.rb +9 -5
- data/lib/grape/validations/validators/default_validator.rb +12 -18
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
- data/lib/grape/validations/validators/except_values_validator.rb +13 -4
- data/lib/grape/validations/validators/length_validator.rb +21 -22
- data/lib/grape/validations/validators/multiple_params_base.rb +5 -5
- data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
- data/lib/grape/validations/validators/presence_validator.rb +4 -2
- data/lib/grape/validations/validators/regexp_validator.rb +11 -3
- data/lib/grape/validations/validators/same_as_validator.rb +6 -15
- data/lib/grape/validations/validators/values_validator.rb +29 -21
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +29 -23
- metadata +31 -26
- data/lib/grape/api/helpers.rb +0 -9
- data/lib/grape/dsl/api.rb +0 -17
- data/lib/grape/dsl/configuration.rb +0 -15
- data/lib/grape/exceptions/conflicting_types.rb +0 -11
- data/lib/grape/exceptions/empty_message_body.rb +0 -11
- data/lib/grape/exceptions/invalid_parameters.rb +0 -11
- data/lib/grape/exceptions/missing_option.rb +0 -11
- data/lib/grape/exceptions/too_deep_parameters.rb +0 -11
- data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
- data/lib/grape/exceptions/unknown_options.rb +0 -11
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -24
- data/lib/grape/extensions/hash.rb +0 -27
- data/lib/grape/extensions/hashie/mash.rb +0 -24
- data/lib/grape/types/invalid_value.rb +0 -8
- data/lib/grape/util/strict_hash_configuration.rb +0 -108
- data/lib/grape/validations/attributes_doc.rb +0 -60
- data/lib/grape/validations/validator_factory.rb +0 -15
|
@@ -5,68 +5,70 @@ module Grape
|
|
|
5
5
|
module Versioner
|
|
6
6
|
class Base < Grape::Middleware::Base
|
|
7
7
|
DEFAULT_OPTIONS = {
|
|
8
|
-
pattern: /.*/i
|
|
8
|
+
pattern: /.*/i,
|
|
9
|
+
prefix: nil,
|
|
10
|
+
mount_path: nil,
|
|
9
11
|
version_options: {
|
|
10
12
|
strict: false,
|
|
11
13
|
cascade: true,
|
|
12
|
-
parameter: 'apiver'
|
|
14
|
+
parameter: 'apiver',
|
|
15
|
+
vendor: nil
|
|
13
16
|
}.freeze
|
|
14
17
|
}.freeze
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
super
|
|
18
|
-
Versioner.register(klass)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def versions
|
|
22
|
-
options[:versions]
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def prefix
|
|
26
|
-
options[:prefix]
|
|
27
|
-
end
|
|
19
|
+
CASCADE_PASS_HEADER = { 'X-Cascade' => 'pass' }.freeze
|
|
28
20
|
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
DEFAULT_OPTIONS.each_key do |key|
|
|
22
|
+
define_method key do
|
|
23
|
+
options[key]
|
|
24
|
+
end
|
|
31
25
|
end
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
DEFAULT_OPTIONS[:version_options].each_key do |key|
|
|
28
|
+
define_method key do
|
|
29
|
+
options[:version_options][key]
|
|
30
|
+
end
|
|
35
31
|
end
|
|
36
32
|
|
|
37
|
-
def
|
|
38
|
-
|
|
33
|
+
def self.inherited(klass)
|
|
34
|
+
super
|
|
35
|
+
Versioner.register(klass)
|
|
39
36
|
end
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
version_options[:strict]
|
|
43
|
-
end
|
|
38
|
+
attr_reader :error_headers, :versions
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
version_options[:cascade]
|
|
40
|
+
def initialize(app, **options)
|
|
41
|
+
super
|
|
42
|
+
@error_headers = cascade ? CASCADE_PASS_HEADER : {}
|
|
43
|
+
@versions = options[:versions]&.map(&:to_s) # making sure versions are strings to ease potential match
|
|
50
44
|
end
|
|
51
45
|
|
|
52
|
-
def
|
|
53
|
-
|
|
46
|
+
def potential_version_match?(potential_version)
|
|
47
|
+
versions.blank? || versions.include?(potential_version)
|
|
54
48
|
end
|
|
55
49
|
|
|
56
|
-
def
|
|
57
|
-
|
|
50
|
+
def version_not_found!
|
|
51
|
+
throw :error, status: 404, message: '404 API Version Not Found', headers: CASCADE_PASS_HEADER
|
|
58
52
|
end
|
|
59
53
|
|
|
60
|
-
|
|
61
|
-
cascade? ? { 'X-Cascade' => 'pass' } : {}
|
|
62
|
-
end
|
|
54
|
+
private
|
|
63
55
|
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
def available_media_types
|
|
57
|
+
@available_media_types ||= begin
|
|
58
|
+
media_types = []
|
|
59
|
+
base_media_type = "application/vnd.#{vendor}"
|
|
60
|
+
content_types.each_key do |extension|
|
|
61
|
+
versions&.reverse_each do |version|
|
|
62
|
+
media_types << "#{base_media_type}-#{version}+#{extension}"
|
|
63
|
+
media_types << "#{base_media_type}-#{version}"
|
|
64
|
+
end
|
|
65
|
+
media_types << "#{base_media_type}+#{extension}"
|
|
66
|
+
end
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
media_types << base_media_type
|
|
69
|
+
media_types.concat(content_types.values.flatten)
|
|
70
|
+
media_types
|
|
71
|
+
end
|
|
70
72
|
end
|
|
71
73
|
end
|
|
72
74
|
end
|
|
@@ -53,7 +53,7 @@ module Grape
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def strict_header_checks!
|
|
56
|
-
return unless strict
|
|
56
|
+
return unless strict
|
|
57
57
|
|
|
58
58
|
accept_header_check!
|
|
59
59
|
version_and_vendor_check!
|
|
@@ -101,26 +101,10 @@ module Grape
|
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
def version_not_found!(media_types)
|
|
104
|
-
return unless media_types.all? { |media_type| media_type&.version && versions
|
|
104
|
+
return unless media_types.all? { |media_type| media_type&.version && versions && !versions.include?(media_type.version) }
|
|
105
105
|
|
|
106
106
|
invalid_version_header!('API version not found.')
|
|
107
107
|
end
|
|
108
|
-
|
|
109
|
-
def available_media_types
|
|
110
|
-
[].tap do |available_media_types|
|
|
111
|
-
base_media_type = "application/vnd.#{vendor}"
|
|
112
|
-
content_types.each_key do |extension|
|
|
113
|
-
versions&.reverse_each do |version|
|
|
114
|
-
available_media_types << "#{base_media_type}-#{version}+#{extension}"
|
|
115
|
-
available_media_types << "#{base_media_type}-#{version}"
|
|
116
|
-
end
|
|
117
|
-
available_media_types << "#{base_media_type}+#{extension}"
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
available_media_types << base_media_type
|
|
121
|
-
available_media_types.concat(content_types.values.flatten)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
108
|
end
|
|
125
109
|
end
|
|
126
110
|
end
|
|
@@ -20,11 +20,11 @@ module Grape
|
|
|
20
20
|
# env['api.version'] => 'v1'
|
|
21
21
|
class Param < Base
|
|
22
22
|
def before
|
|
23
|
-
potential_version = query_params[
|
|
23
|
+
potential_version = query_params[parameter]
|
|
24
24
|
return if potential_version.blank?
|
|
25
25
|
|
|
26
26
|
version_not_found! unless potential_version_match?(potential_version)
|
|
27
|
-
env[Grape::Env::API_VERSION] = env[Rack::RACK_REQUEST_QUERY_HASH].delete(
|
|
27
|
+
env[Grape::Env::API_VERSION] = env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter)
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -22,13 +22,13 @@ module Grape
|
|
|
22
22
|
return if path_info == '/'
|
|
23
23
|
|
|
24
24
|
[mount_path, Grape::Router.normalize_path(prefix)].each do |path|
|
|
25
|
-
path_info.delete_prefix
|
|
25
|
+
path_info = path_info.delete_prefix(path) if path.present? && path != '/' && path_info.start_with?(path)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
slash_position = path_info.index('/', 1) # omit the first one
|
|
29
29
|
return unless slash_position
|
|
30
30
|
|
|
31
|
-
potential_version = path_info[1..slash_position - 1]
|
|
31
|
+
potential_version = path_info[1..(slash_position - 1)]
|
|
32
32
|
return unless potential_version.match?(pattern)
|
|
33
33
|
|
|
34
34
|
version_not_found! unless potential_version_match?(potential_version)
|
data/lib/grape/namespace.rb
CHANGED
|
@@ -5,29 +5,36 @@ module Grape
|
|
|
5
5
|
# logical grouping of endpoints as well as sharing common configuration.
|
|
6
6
|
# May also be referred to as group, segment, or resource.
|
|
7
7
|
class Namespace
|
|
8
|
-
attr_reader :space, :options
|
|
8
|
+
attr_reader :space, :requirements, :options
|
|
9
9
|
|
|
10
10
|
# @param space [String] the name of this namespace
|
|
11
11
|
# @param options [Hash] options hash
|
|
12
12
|
# @option options :requirements [Hash] param-regex pairs, all of which must
|
|
13
13
|
# be met by a request's params for all endpoints in this namespace, or
|
|
14
14
|
# validation will fail and return a 422.
|
|
15
|
-
def initialize(space, options)
|
|
15
|
+
def initialize(space, requirements: nil, **options)
|
|
16
16
|
@space = space.to_s
|
|
17
|
+
@requirements = requirements
|
|
17
18
|
@options = options
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
# Retrieves the requirements from the options hash, if given.
|
|
21
|
-
# @return [Hash]
|
|
22
|
-
def requirements
|
|
23
|
-
options[:requirements] || {}
|
|
24
|
-
end
|
|
25
|
-
|
|
26
21
|
# (see ::joined_space_path)
|
|
27
22
|
def self.joined_space(settings)
|
|
28
23
|
settings&.map(&:space)
|
|
29
24
|
end
|
|
30
25
|
|
|
26
|
+
def eql?(other)
|
|
27
|
+
other.class == self.class &&
|
|
28
|
+
other.space == space &&
|
|
29
|
+
other.requirements == requirements &&
|
|
30
|
+
other.options == options
|
|
31
|
+
end
|
|
32
|
+
alias == eql?
|
|
33
|
+
|
|
34
|
+
def hash
|
|
35
|
+
[self.class, space, requirements, options].hash
|
|
36
|
+
end
|
|
37
|
+
|
|
31
38
|
# Join the namespaces from a list of settings to create a path prefix.
|
|
32
39
|
# @param settings [Array] list of Grape::Util::InheritableSettings.
|
|
33
40
|
def self.joined_space_path(settings)
|
data/lib/grape/params_builder.rb
CHANGED
|
@@ -4,29 +4,12 @@ module Grape
|
|
|
4
4
|
module ParamsBuilder
|
|
5
5
|
extend Grape::Util::Registry
|
|
6
6
|
|
|
7
|
-
SHORT_NAME_LOOKUP = {
|
|
8
|
-
'Grape::Extensions::Hash::ParamBuilder' => :hash,
|
|
9
|
-
'Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder' => :hash_with_indifferent_access,
|
|
10
|
-
'Grape::Extensions::Hashie::Mash::ParamBuilder' => :hashie_mash
|
|
11
|
-
}.freeze
|
|
12
|
-
|
|
13
7
|
module_function
|
|
14
8
|
|
|
15
9
|
def params_builder_for(short_name)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
raise Grape::Exceptions::UnknownParamsBuilder, verified_short_name unless registry.key?(verified_short_name)
|
|
19
|
-
|
|
20
|
-
registry[verified_short_name]
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def verify_short_name!(short_name)
|
|
24
|
-
return short_name if short_name.is_a?(Symbol)
|
|
10
|
+
raise Grape::Exceptions::UnknownParamsBuilder, short_name unless registry.key?(short_name)
|
|
25
11
|
|
|
26
|
-
|
|
27
|
-
SHORT_NAME_LOOKUP[class_name].tap do |real_short_name|
|
|
28
|
-
Grape.deprecator.warn "#{class_name} has been deprecated. Use short name :#{real_short_name} instead."
|
|
29
|
-
end
|
|
12
|
+
registry[short_name]
|
|
30
13
|
end
|
|
31
14
|
end
|
|
32
15
|
end
|
data/lib/grape/request.rb
CHANGED
|
@@ -166,16 +166,8 @@ module Grape
|
|
|
166
166
|
|
|
167
167
|
def make_params
|
|
168
168
|
@params_builder.call(rack_params).deep_merge!(grape_routing_args)
|
|
169
|
-
rescue
|
|
170
|
-
raise Grape::Exceptions::
|
|
171
|
-
rescue Rack::Multipart::MultipartPartLimitError, Rack::Multipart::MultipartTotalPartLimitError
|
|
172
|
-
raise Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit)
|
|
173
|
-
rescue Rack::QueryParser::ParamsTooDeepError
|
|
174
|
-
raise Grape::Exceptions::TooDeepParameters.new(Rack::Utils.param_depth_limit)
|
|
175
|
-
rescue Rack::Utils::ParameterTypeError
|
|
176
|
-
raise Grape::Exceptions::ConflictingTypes
|
|
177
|
-
rescue Rack::Utils::InvalidParameterError
|
|
178
|
-
raise Grape::Exceptions::InvalidParameters
|
|
169
|
+
rescue *Grape::RACK_ERRORS
|
|
170
|
+
raise Grape::Exceptions::RequestError
|
|
179
171
|
end
|
|
180
172
|
|
|
181
173
|
def build_headers
|
|
@@ -3,22 +3,31 @@
|
|
|
3
3
|
module Grape
|
|
4
4
|
class Router
|
|
5
5
|
class BaseRoute
|
|
6
|
+
extend Forwardable
|
|
7
|
+
|
|
6
8
|
delegate_missing_to :@options
|
|
7
9
|
|
|
8
|
-
attr_reader :
|
|
10
|
+
attr_reader :options, :pattern
|
|
11
|
+
|
|
12
|
+
def_delegators :@pattern, :path, :origin
|
|
13
|
+
def_delegators :@options, :description, :version, :requirements, :prefix, :anchor, :settings, :forward_match, *Grape::Util::ApiDescription::DSL_METHODS
|
|
9
14
|
|
|
10
|
-
def initialize(options)
|
|
15
|
+
def initialize(pattern, options = {})
|
|
16
|
+
@pattern = pattern
|
|
11
17
|
@options = options.is_a?(ActiveSupport::OrderedOptions) ? options : ActiveSupport::OrderedOptions.new.update(options)
|
|
12
18
|
end
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
# see https://github.com/ruby-grape/grape/issues/1348
|
|
21
|
+
def namespace
|
|
22
|
+
@namespace ||= @options[:namespace]
|
|
23
|
+
end
|
|
15
24
|
|
|
16
25
|
def regexp_capture_index
|
|
17
|
-
CaptureIndexCache[index]
|
|
26
|
+
@regexp_capture_index ||= CaptureIndexCache[@index]
|
|
18
27
|
end
|
|
19
28
|
|
|
20
29
|
def pattern_regexp
|
|
21
|
-
pattern.to_regexp
|
|
30
|
+
@pattern.to_regexp
|
|
22
31
|
end
|
|
23
32
|
|
|
24
33
|
def to_regexp(index)
|
|
@@ -6,14 +6,20 @@
|
|
|
6
6
|
module Grape
|
|
7
7
|
class Router
|
|
8
8
|
class GreedyRoute < BaseRoute
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
extend Forwardable
|
|
10
|
+
|
|
11
|
+
def_delegators :@endpoint, :call
|
|
12
|
+
|
|
13
|
+
attr_reader :endpoint, :allow_header
|
|
14
|
+
|
|
15
|
+
def initialize(pattern, endpoint:, allow_header:)
|
|
16
|
+
super(pattern)
|
|
17
|
+
@endpoint = endpoint
|
|
18
|
+
@allow_header = allow_header
|
|
12
19
|
end
|
|
13
20
|
|
|
14
|
-
# Grape::Router:Route defines params as a function
|
|
15
21
|
def params(_input = nil)
|
|
16
|
-
|
|
22
|
+
nil
|
|
17
23
|
end
|
|
18
24
|
end
|
|
19
25
|
end
|
data/lib/grape/router/pattern.rb
CHANGED
|
@@ -13,10 +13,10 @@ module Grape
|
|
|
13
13
|
def_delegators :to_regexp, :===
|
|
14
14
|
alias match? ===
|
|
15
15
|
|
|
16
|
-
def initialize(origin
|
|
16
|
+
def initialize(origin:, suffix:, anchor:, params:, format:, version:, requirements:)
|
|
17
17
|
@origin = origin
|
|
18
|
-
@path =
|
|
19
|
-
@pattern =
|
|
18
|
+
@path = PatternCache[[build_path_from_pattern(@origin, anchor), suffix]]
|
|
19
|
+
@pattern = Mustermann::Grape.new(@path, uri_decode: true, params:, capture: extract_capture(format, version, requirements))
|
|
20
20
|
@to_regexp = @pattern.to_regexp
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -28,24 +28,10 @@ module Grape
|
|
|
28
28
|
|
|
29
29
|
private
|
|
30
30
|
|
|
31
|
-
def build_pattern(path, params, format, version, requirements)
|
|
32
|
-
Mustermann::Grape.new(
|
|
33
|
-
path,
|
|
34
|
-
uri_decode: true,
|
|
35
|
-
params: params,
|
|
36
|
-
capture: extract_capture(format, version, requirements)
|
|
37
|
-
)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def build_path(pattern, anchor, suffix)
|
|
41
|
-
PatternCache[[build_path_from_pattern(pattern, anchor), suffix]]
|
|
42
|
-
end
|
|
43
|
-
|
|
44
31
|
def extract_capture(format, version, requirements)
|
|
45
|
-
capture = {}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
end
|
|
32
|
+
capture = {}
|
|
33
|
+
capture[:format] = map_str(format) if format.present?
|
|
34
|
+
capture[:version] = map_str(version) if version.present?
|
|
49
35
|
|
|
50
36
|
return capture if requirements.blank?
|
|
51
37
|
|
data/lib/grape/router/route.rb
CHANGED
|
@@ -8,25 +8,21 @@ module Grape
|
|
|
8
8
|
FORWARD_MATCH_METHOD = ->(input, pattern) { input.start_with?(pattern.origin) }
|
|
9
9
|
NON_FORWARD_MATCH_METHOD = ->(input, pattern) { pattern.match?(input) }
|
|
10
10
|
|
|
11
|
-
attr_reader :app, :request_method
|
|
11
|
+
attr_reader :app, :request_method, :index
|
|
12
12
|
|
|
13
|
-
def_delegators
|
|
13
|
+
def_delegators :@app, :call
|
|
14
14
|
|
|
15
|
-
def initialize(
|
|
15
|
+
def initialize(endpoint, method, pattern, options)
|
|
16
|
+
super(pattern, options)
|
|
17
|
+
@app = endpoint
|
|
16
18
|
@request_method = upcase_method(method)
|
|
17
|
-
@pattern = Grape::Router::Pattern.new(origin, path, options)
|
|
18
19
|
@match_function = options[:forward_match] ? FORWARD_MATCH_METHOD : NON_FORWARD_MATCH_METHOD
|
|
19
|
-
super(options)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def convert_to_head_request!
|
|
23
23
|
@request_method = Rack::HEAD
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def exec(env)
|
|
27
|
-
@app.call(env)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
26
|
def apply(app)
|
|
31
27
|
@app = app
|
|
32
28
|
self
|
|
@@ -42,7 +38,7 @@ module Grape
|
|
|
42
38
|
return params_without_input if input.blank?
|
|
43
39
|
|
|
44
40
|
parsed = pattern.params(input)
|
|
45
|
-
return
|
|
41
|
+
return unless parsed
|
|
46
42
|
|
|
47
43
|
parsed.compact.symbolize_keys
|
|
48
44
|
end
|
|
@@ -50,7 +46,7 @@ module Grape
|
|
|
50
46
|
private
|
|
51
47
|
|
|
52
48
|
def params_without_input
|
|
53
|
-
@params_without_input ||= pattern.captures_default.merge(
|
|
49
|
+
@params_without_input ||= pattern.captures_default.merge(options[:params])
|
|
54
50
|
end
|
|
55
51
|
|
|
56
52
|
def upcase_method(method)
|
data/lib/grape/router.rb
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module Grape
|
|
4
4
|
class Router
|
|
5
|
-
attr_reader :map, :compiled
|
|
6
|
-
|
|
7
5
|
# Taken from Rails
|
|
8
6
|
# normalize_path("/foo") # => "/foo"
|
|
9
7
|
# normalize_path("/foo/") # => "/foo"
|
|
@@ -12,14 +10,15 @@ module Grape
|
|
|
12
10
|
# normalize_path("/%ab") # => "/%AB"
|
|
13
11
|
# https://github.com/rails/rails/blob/00cc4ff0259c0185fe08baadaa40e63ea2534f6e/actionpack/lib/action_dispatch/journey/router/utils.rb#L19
|
|
14
12
|
def self.normalize_path(path)
|
|
15
|
-
return
|
|
13
|
+
return '/' unless path
|
|
14
|
+
return path if path == '/'
|
|
16
15
|
|
|
17
16
|
# Fast path for the overwhelming majority of paths that don't need to be normalized
|
|
18
|
-
return path
|
|
17
|
+
return path if path.start_with?('/') && !(path.end_with?('/') || path.match?(%r{%|//}))
|
|
19
18
|
|
|
20
19
|
# Slow path
|
|
21
20
|
encoding = path.encoding
|
|
22
|
-
path =
|
|
21
|
+
path = "/#{path}"
|
|
23
22
|
path.squeeze!('/')
|
|
24
23
|
|
|
25
24
|
unless path == '/'
|
|
@@ -38,14 +37,14 @@ module Grape
|
|
|
38
37
|
end
|
|
39
38
|
|
|
40
39
|
def compile!
|
|
41
|
-
return if compiled
|
|
40
|
+
return if @compiled
|
|
42
41
|
|
|
43
42
|
@union = Regexp.union(@neutral_regexes)
|
|
44
43
|
@neutral_regexes = nil
|
|
45
44
|
(Grape::HTTP_SUPPORTED_METHODS + ['*']).each do |method|
|
|
46
|
-
next unless map.key?(method)
|
|
45
|
+
next unless @map.key?(method)
|
|
47
46
|
|
|
48
|
-
routes = map[method]
|
|
47
|
+
routes = @map[method]
|
|
49
48
|
optimized_map = routes.map.with_index { |route, index| route.to_regexp(index) }
|
|
50
49
|
@optimized_map[method] = Regexp.union(optimized_map)
|
|
51
50
|
end
|
|
@@ -53,20 +52,20 @@ module Grape
|
|
|
53
52
|
end
|
|
54
53
|
|
|
55
54
|
def append(route)
|
|
56
|
-
map[route.request_method] << route
|
|
55
|
+
@map[route.request_method] << route
|
|
57
56
|
end
|
|
58
57
|
|
|
59
|
-
def associate_routes(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@neutral_map << greedy_route
|
|
63
|
-
end
|
|
58
|
+
def associate_routes(greedy_route)
|
|
59
|
+
@neutral_regexes << greedy_route.to_regexp(@neutral_map.length)
|
|
60
|
+
@neutral_map << greedy_route
|
|
64
61
|
end
|
|
65
62
|
|
|
66
63
|
def call(env)
|
|
67
64
|
with_optimization do
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
input = Router.normalize_path(env[Rack::PATH_INFO])
|
|
66
|
+
method = env[Rack::REQUEST_METHOD]
|
|
67
|
+
response, route = identity(input, method, env)
|
|
68
|
+
response || rotation(input, method, env, route)
|
|
70
69
|
end
|
|
71
70
|
end
|
|
72
71
|
|
|
@@ -77,33 +76,33 @@ module Grape
|
|
|
77
76
|
any.endpoint
|
|
78
77
|
end
|
|
79
78
|
|
|
79
|
+
DEFAULT_RESPONSE_HEADERS = Grape::Util::Header.new.merge('X-Cascade' => 'pass').freeze
|
|
80
|
+
DEFAULT_RESPONSE_BODY = ['404 Not Found'].freeze
|
|
81
|
+
|
|
80
82
|
private
|
|
81
83
|
|
|
82
|
-
def identity(env)
|
|
84
|
+
def identity(input, method, env)
|
|
83
85
|
route = nil
|
|
84
|
-
response = transaction(env) do
|
|
86
|
+
response = transaction(input, method, env) do
|
|
85
87
|
route = match?(input, method)
|
|
86
|
-
process_route(route, env) if route
|
|
88
|
+
process_route(route, input, env) if route
|
|
87
89
|
end
|
|
88
90
|
[response, route]
|
|
89
91
|
end
|
|
90
92
|
|
|
91
|
-
def rotation(env, exact_route
|
|
93
|
+
def rotation(input, method, env, exact_route)
|
|
92
94
|
response = nil
|
|
93
|
-
|
|
94
|
-
map[method].each do |route|
|
|
95
|
+
@map[method].each do |route|
|
|
95
96
|
next if exact_route == route
|
|
96
97
|
next unless route.match?(input)
|
|
97
98
|
|
|
98
|
-
response = process_route(route, env)
|
|
99
|
+
response = process_route(route, input, env)
|
|
99
100
|
break unless cascade?(response)
|
|
100
101
|
end
|
|
101
102
|
response
|
|
102
103
|
end
|
|
103
104
|
|
|
104
|
-
def transaction(env)
|
|
105
|
-
input, method = *extract_input_and_method(env)
|
|
106
|
-
|
|
105
|
+
def transaction(input, method, env)
|
|
107
106
|
# using a Proc is important since `return` will exit the enclosing function
|
|
108
107
|
cascade_or_return_response = proc do |response|
|
|
109
108
|
if response
|
|
@@ -111,53 +110,46 @@ module Grape
|
|
|
111
110
|
return response unless cascade
|
|
112
111
|
|
|
113
112
|
# we need to close the body if possible before dismissing
|
|
114
|
-
response[2].
|
|
113
|
+
response[2].close if response[2].respond_to?(:close)
|
|
115
114
|
end
|
|
116
115
|
end
|
|
117
116
|
end
|
|
118
117
|
|
|
119
|
-
|
|
118
|
+
response = yield
|
|
119
|
+
last_response_cascade = cascade_or_return_response.call(response)
|
|
120
120
|
last_neighbor_route = greedy_match?(input)
|
|
121
121
|
|
|
122
122
|
# If last_neighbor_route exists and request method is OPTIONS,
|
|
123
|
-
# return response by using #
|
|
124
|
-
return
|
|
123
|
+
# return response by using #include_allow_header.
|
|
124
|
+
return process_route(last_neighbor_route, input, env, include_allow_header: true) if !last_response_cascade && method == Rack::OPTIONS && last_neighbor_route
|
|
125
125
|
|
|
126
126
|
route = match?(input, '*')
|
|
127
127
|
|
|
128
|
-
return last_neighbor_route.
|
|
128
|
+
return last_neighbor_route.call(env) if last_neighbor_route && last_response_cascade && route
|
|
129
129
|
|
|
130
|
-
last_response_cascade = cascade_or_return_response.call(process_route(route, env)) if route
|
|
130
|
+
last_response_cascade = cascade_or_return_response.call(process_route(route, input, env)) if route
|
|
131
131
|
|
|
132
|
-
return
|
|
132
|
+
return process_route(last_neighbor_route, input, env, include_allow_header: true) if !last_response_cascade && last_neighbor_route
|
|
133
133
|
|
|
134
134
|
nil
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
-
def process_route(route, env)
|
|
138
|
-
|
|
139
|
-
route.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
args.merge(route.params(input))
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def extract_input_and_method(env)
|
|
148
|
-
input = string_for(env[Rack::PATH_INFO])
|
|
149
|
-
method = env[Rack::REQUEST_METHOD]
|
|
150
|
-
[input, method]
|
|
137
|
+
def process_route(route, input, env, include_allow_header: false)
|
|
138
|
+
args = env[Grape::Env::GRAPE_ROUTING_ARGS] || { route_info: route }
|
|
139
|
+
route_params = route.params(input)
|
|
140
|
+
routing_args = args.merge(route_params || {})
|
|
141
|
+
env[Grape::Env::GRAPE_ROUTING_ARGS] = routing_args
|
|
142
|
+
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header if include_allow_header
|
|
143
|
+
route.call(env)
|
|
151
144
|
end
|
|
152
145
|
|
|
153
146
|
def with_optimization
|
|
154
|
-
compile!
|
|
147
|
+
compile!
|
|
155
148
|
yield || default_response
|
|
156
149
|
end
|
|
157
150
|
|
|
158
151
|
def default_response
|
|
159
|
-
|
|
160
|
-
[404, headers, ['404 Not Found']]
|
|
152
|
+
[404, DEFAULT_RESPONSE_HEADERS.dup, DEFAULT_RESPONSE_BODY.dup]
|
|
161
153
|
end
|
|
162
154
|
|
|
163
155
|
def match?(input, method)
|
|
@@ -168,23 +160,8 @@ module Grape
|
|
|
168
160
|
@union.match(input) { |m| @neutral_map.detect { |route| m[route.regexp_capture_index] } }
|
|
169
161
|
end
|
|
170
162
|
|
|
171
|
-
def call_with_allow_headers(env, route)
|
|
172
|
-
prepare_env_from_route(env, route)
|
|
173
|
-
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.options[:allow_header]
|
|
174
|
-
route.options[:endpoint].call(env)
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def prepare_env_from_route(env, route)
|
|
178
|
-
input, = *extract_input_and_method(env)
|
|
179
|
-
env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Env::GRAPE_ROUTING_ARGS], route, input)
|
|
180
|
-
end
|
|
181
|
-
|
|
182
163
|
def cascade?(response)
|
|
183
164
|
response && response[1]['X-Cascade'] == 'pass'
|
|
184
165
|
end
|
|
185
|
-
|
|
186
|
-
def string_for(input)
|
|
187
|
-
self.class.normalize_path(input)
|
|
188
|
-
end
|
|
189
166
|
end
|
|
190
167
|
end
|