grape 2.2.0 → 2.3.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 +26 -0
- data/README.md +7 -6
- data/UPGRADING.md +19 -0
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +22 -58
- data/lib/grape/api.rb +2 -11
- data/lib/grape/dsl/desc.rb +27 -24
- data/lib/grape/dsl/inside_route.rb +12 -23
- data/lib/grape/dsl/parameters.rb +2 -2
- data/lib/grape/dsl/routing.rb +5 -12
- data/lib/grape/endpoint.rb +76 -79
- data/lib/grape/error_formatter/base.rb +51 -21
- data/lib/grape/error_formatter/json.rb +7 -24
- data/lib/grape/error_formatter/jsonapi.rb +7 -0
- data/lib/grape/error_formatter/serializable_hash.rb +7 -0
- data/lib/grape/error_formatter/txt.rb +13 -20
- data/lib/grape/error_formatter/xml.rb +3 -13
- data/lib/grape/error_formatter.rb +4 -12
- data/lib/grape/exceptions/base.rb +18 -30
- data/lib/grape/exceptions/validation.rb +5 -4
- data/lib/grape/exceptions/validation_errors.rb +2 -2
- data/lib/grape/formatter/base.rb +16 -0
- data/lib/grape/formatter/json.rb +4 -6
- data/lib/grape/formatter/serializable_hash.rb +1 -1
- data/lib/grape/formatter/txt.rb +3 -5
- data/lib/grape/formatter/xml.rb +4 -6
- data/lib/grape/formatter.rb +4 -12
- data/lib/grape/http/headers.rb +1 -0
- data/lib/grape/middleware/error.rb +2 -0
- data/lib/grape/middleware/formatter.rb +1 -1
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
- data/lib/grape/middleware/versioner/base.rb +82 -0
- data/lib/grape/middleware/versioner/header.rb +3 -9
- data/lib/grape/middleware/versioner/param.rb +0 -2
- data/lib/grape/middleware/versioner/path.rb +0 -2
- data/lib/grape/middleware/versioner.rb +5 -3
- data/lib/grape/namespace.rb +1 -1
- data/lib/grape/parser/base.rb +16 -0
- data/lib/grape/parser/json.rb +6 -8
- data/lib/grape/parser/jsonapi.rb +7 -0
- data/lib/grape/parser/xml.rb +6 -8
- data/lib/grape/parser.rb +5 -7
- data/lib/grape/path.rb +39 -56
- data/lib/grape/request.rb +2 -2
- data/lib/grape/router/base_route.rb +2 -2
- data/lib/grape/router/greedy_route.rb +2 -2
- data/lib/grape/router/pattern.rb +23 -18
- data/lib/grape/router/route.rb +13 -5
- data/lib/grape/router.rb +5 -5
- data/lib/grape/util/registry.rb +27 -0
- data/lib/grape/validations/contract_scope.rb +2 -39
- data/lib/grape/validations/params_scope.rb +7 -11
- data/lib/grape/validations/types/dry_type_coercer.rb +10 -6
- data/lib/grape/validations/validator_factory.rb +2 -2
- data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
- data/lib/grape/validations/validators/base.rb +5 -9
- data/lib/grape/validations/validators/coerce_validator.rb +1 -1
- data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
- data/lib/grape/validations/validators/default_validator.rb +1 -1
- data/lib/grape/validations/validators/except_values_validator.rb +1 -1
- data/lib/grape/validations/validators/length_validator.rb +1 -1
- data/lib/grape/validations/validators/regexp_validator.rb +1 -1
- data/lib/grape/validations/validators/values_validator.rb +15 -57
- data/lib/grape/validations.rb +8 -17
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +1 -1
- metadata +14 -11
- data/lib/grape/middleware/versioner_helpers.rb +0 -75
- data/lib/grape/validations/types/build_coercer.rb +0 -92
data/lib/grape/formatter.rb
CHANGED
@@ -2,24 +2,16 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Formatter
|
5
|
-
|
5
|
+
extend Grape::Util::Registry
|
6
6
|
|
7
|
-
|
8
|
-
json: Grape::Formatter::Json,
|
9
|
-
jsonapi: Grape::Formatter::Json,
|
10
|
-
serializable_hash: Grape::Formatter::SerializableHash,
|
11
|
-
txt: Grape::Formatter::Txt,
|
12
|
-
xml: Grape::Formatter::Xml
|
13
|
-
}.freeze
|
7
|
+
module_function
|
14
8
|
|
15
9
|
DEFAULT_LAMBDA_FORMATTER = ->(obj, _env) { obj }
|
16
10
|
|
17
11
|
def formatter_for(api_format, formatters)
|
18
|
-
|
19
|
-
end
|
12
|
+
return formatters[api_format] if formatters&.key?(api_format)
|
20
13
|
|
21
|
-
|
22
|
-
formatters&.key?(api_format) ? formatters[api_format] : DEFAULTS[api_format]
|
14
|
+
registry[api_format] || DEFAULT_LAMBDA_FORMATTER
|
23
15
|
end
|
24
16
|
end
|
25
17
|
end
|
data/lib/grape/http/headers.rb
CHANGED
@@ -65,6 +65,7 @@ module Grape
|
|
65
65
|
|
66
66
|
def error_response(error = {})
|
67
67
|
status = error[:status] || options[:default_status]
|
68
|
+
env[Grape::Env::API_ENDPOINT].status(status) # error! may not have been called
|
68
69
|
message = error[:message] || options[:default_message]
|
69
70
|
headers = { Rack::CONTENT_TYPE => content_type }.tap do |h|
|
70
71
|
h.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
@@ -130,6 +131,7 @@ module Grape
|
|
130
131
|
end
|
131
132
|
|
132
133
|
def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
|
134
|
+
env[Grape::Env::API_ENDPOINT].status(status) # not error! inside route
|
133
135
|
rack_response(
|
134
136
|
status, headers.reverse_merge(Rack::CONTENT_TYPE => content_type),
|
135
137
|
format_message(message, backtrace, original_exception)
|
@@ -53,7 +53,7 @@ module Grape
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def fetch_formatter(headers, options)
|
56
|
-
api_format = mime_types[headers[Rack::CONTENT_TYPE]]
|
56
|
+
api_format = env.fetch(Grape::Env::API_FORMAT) { mime_types[headers[Rack::CONTENT_TYPE]] }
|
57
57
|
Grape::Formatter.formatter_for(api_format, options[:formatters])
|
58
58
|
end
|
59
59
|
|
@@ -17,10 +17,10 @@ module Grape
|
|
17
17
|
# X-Cascade header to alert Grape::Router to attempt the next matched
|
18
18
|
# route.
|
19
19
|
class AcceptVersionHeader < Base
|
20
|
-
include VersionerHelpers
|
21
|
-
|
22
20
|
def before
|
23
|
-
potential_version = env[Grape::Http::Headers::HTTP_ACCEPT_VERSION]
|
21
|
+
potential_version = env[Grape::Http::Headers::HTTP_ACCEPT_VERSION]
|
22
|
+
potential_version = potential_version.scrub unless potential_version.nil?
|
23
|
+
|
24
24
|
not_acceptable!('Accept-Version header must be set.') if strict? && potential_version.blank?
|
25
25
|
|
26
26
|
return if potential_version.blank?
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
module Versioner
|
6
|
+
class Base < Grape::Middleware::Base
|
7
|
+
DEFAULT_PATTERN = /.*/i.freeze
|
8
|
+
DEFAULT_PARAMETER = 'apiver'
|
9
|
+
|
10
|
+
def self.inherited(klass)
|
11
|
+
super
|
12
|
+
Versioner.register(klass)
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_options
|
16
|
+
{
|
17
|
+
versions: nil,
|
18
|
+
prefix: nil,
|
19
|
+
mount_path: nil,
|
20
|
+
pattern: DEFAULT_PATTERN,
|
21
|
+
version_options: {
|
22
|
+
strict: false,
|
23
|
+
cascade: true,
|
24
|
+
parameter: DEFAULT_PARAMETER
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def versions
|
30
|
+
options[:versions]
|
31
|
+
end
|
32
|
+
|
33
|
+
def prefix
|
34
|
+
options[:prefix]
|
35
|
+
end
|
36
|
+
|
37
|
+
def mount_path
|
38
|
+
options[:mount_path]
|
39
|
+
end
|
40
|
+
|
41
|
+
def pattern
|
42
|
+
options[:pattern]
|
43
|
+
end
|
44
|
+
|
45
|
+
def version_options
|
46
|
+
options[:version_options]
|
47
|
+
end
|
48
|
+
|
49
|
+
def strict?
|
50
|
+
version_options[:strict]
|
51
|
+
end
|
52
|
+
|
53
|
+
# By default those errors contain an `X-Cascade` header set to `pass`, which allows nesting and stacking
|
54
|
+
# of routes (see Grape::Router) for more information). To prevent
|
55
|
+
# this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
|
56
|
+
def cascade?
|
57
|
+
version_options[:cascade]
|
58
|
+
end
|
59
|
+
|
60
|
+
def parameter_key
|
61
|
+
version_options[:parameter]
|
62
|
+
end
|
63
|
+
|
64
|
+
def vendor
|
65
|
+
version_options[:vendor]
|
66
|
+
end
|
67
|
+
|
68
|
+
def error_headers
|
69
|
+
cascade? ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
|
70
|
+
end
|
71
|
+
|
72
|
+
def potential_version_match?(potential_version)
|
73
|
+
versions.blank? || versions.any? { |v| v.to_s == potential_version }
|
74
|
+
end
|
75
|
+
|
76
|
+
def version_not_found!
|
77
|
+
throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -22,8 +22,6 @@ module Grape
|
|
22
22
|
# X-Cascade header to alert Grape::Router to attempt the next matched
|
23
23
|
# route.
|
24
24
|
class Header < Base
|
25
|
-
include VersionerHelpers
|
26
|
-
|
27
25
|
def before
|
28
26
|
match_best_quality_media_type! do |media_type|
|
29
27
|
env.update(
|
@@ -46,14 +44,10 @@ module Grape
|
|
46
44
|
if media_type
|
47
45
|
yield media_type
|
48
46
|
else
|
49
|
-
fail!
|
47
|
+
fail!
|
50
48
|
end
|
51
49
|
end
|
52
50
|
|
53
|
-
def allowed_methods
|
54
|
-
env[Grape::Env::GRAPE_ALLOWED_METHODS]
|
55
|
-
end
|
56
|
-
|
57
51
|
def accept_header
|
58
52
|
env[Grape::Http::Headers::HTTP_ACCEPT]
|
59
53
|
end
|
@@ -93,8 +87,8 @@ module Grape
|
|
93
87
|
raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
|
94
88
|
end
|
95
89
|
|
96
|
-
def fail!
|
97
|
-
return
|
90
|
+
def fail!
|
91
|
+
return if env[Grape::Env::GRAPE_ALLOWED_METHODS].present?
|
98
92
|
|
99
93
|
media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }
|
100
94
|
vendor_not_found!(media_types) || version_not_found!(media_types)
|
@@ -11,14 +11,16 @@
|
|
11
11
|
module Grape
|
12
12
|
module Middleware
|
13
13
|
module Versioner
|
14
|
+
extend Grape::Util::Registry
|
15
|
+
|
14
16
|
module_function
|
15
17
|
|
16
18
|
# @param strategy [Symbol] :path, :header, :accept_version_header or :param
|
17
19
|
# @return a middleware class based on strategy
|
18
20
|
def using(strategy)
|
19
|
-
Grape::
|
20
|
-
|
21
|
-
|
21
|
+
raise Grape::Exceptions::InvalidVersionerOption, strategy unless registry.key?(strategy)
|
22
|
+
|
23
|
+
registry[strategy]
|
22
24
|
end
|
23
25
|
end
|
24
26
|
end
|
data/lib/grape/namespace.rb
CHANGED
@@ -12,7 +12,7 @@ module Grape
|
|
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,
|
15
|
+
def initialize(space, options)
|
16
16
|
@space = space.to_s
|
17
17
|
@options = options
|
18
18
|
end
|
data/lib/grape/parser/json.rb
CHANGED
@@ -2,14 +2,12 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Parser
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
rescue
|
10
|
-
|
11
|
-
raise Grape::Exceptions::InvalidMessageBody.new('application/json')
|
12
|
-
end
|
5
|
+
class Json < Base
|
6
|
+
def self.call(object, _env)
|
7
|
+
::Grape::Json.load(object)
|
8
|
+
rescue ::Grape::Json::ParseError
|
9
|
+
# handle JSON parsing errors via the rescue handlers or provide error message
|
10
|
+
raise Grape::Exceptions::InvalidMessageBody.new('application/json')
|
13
11
|
end
|
14
12
|
end
|
15
13
|
end
|
data/lib/grape/parser/xml.rb
CHANGED
@@ -2,14 +2,12 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Parser
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
rescue
|
10
|
-
|
11
|
-
raise Grape::Exceptions::InvalidMessageBody.new('application/xml')
|
12
|
-
end
|
5
|
+
class Xml < Base
|
6
|
+
def self.call(object, _env)
|
7
|
+
::Grape::Xml.parse(object)
|
8
|
+
rescue ::Grape::Xml::ParseError
|
9
|
+
# handle XML parsing errors via the rescue handlers or provide error message
|
10
|
+
raise Grape::Exceptions::InvalidMessageBody.new('application/xml')
|
13
11
|
end
|
14
12
|
end
|
15
13
|
end
|
data/lib/grape/parser.rb
CHANGED
@@ -2,16 +2,14 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Parser
|
5
|
-
|
5
|
+
extend Grape::Util::Registry
|
6
6
|
|
7
|
-
|
8
|
-
json: Grape::Parser::Json,
|
9
|
-
jsonapi: Grape::Parser::Json,
|
10
|
-
xml: Grape::Parser::Xml
|
11
|
-
}.freeze
|
7
|
+
module_function
|
12
8
|
|
13
9
|
def parser_for(format, parsers = nil)
|
14
|
-
|
10
|
+
return parsers[format] if parsers&.key?(format)
|
11
|
+
|
12
|
+
registry[format]
|
15
13
|
end
|
16
14
|
end
|
17
15
|
end
|
data/lib/grape/path.rb
CHANGED
@@ -3,65 +3,66 @@
|
|
3
3
|
module Grape
|
4
4
|
# Represents a path to an endpoint.
|
5
5
|
class Path
|
6
|
-
|
6
|
+
DEFAULT_FORMAT_SEGMENT = '(/.:format)'
|
7
|
+
NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT = '(.:format)'
|
8
|
+
VERSION_SEGMENT = ':version'
|
7
9
|
|
8
|
-
|
9
|
-
@raw_path = raw_path
|
10
|
-
@namespace = namespace
|
11
|
-
@settings = settings
|
12
|
-
end
|
10
|
+
attr_reader :origin, :suffix
|
13
11
|
|
14
|
-
def
|
15
|
-
settings
|
12
|
+
def initialize(raw_path, raw_namespace, settings)
|
13
|
+
@origin = PartsCache[build_parts(raw_path, raw_namespace, settings)]
|
14
|
+
@suffix = build_suffix(raw_path, raw_namespace, settings)
|
16
15
|
end
|
17
16
|
|
18
|
-
def
|
19
|
-
|
17
|
+
def to_s
|
18
|
+
"#{origin}#{suffix}"
|
20
19
|
end
|
21
20
|
|
22
|
-
|
23
|
-
return false unless settings.key?(:format) && settings.key?(:content_types)
|
21
|
+
private
|
24
22
|
|
25
|
-
|
23
|
+
def build_suffix(raw_path, raw_namespace, settings)
|
24
|
+
if uses_specific_format?(settings)
|
25
|
+
"(.#{settings[:format]})"
|
26
|
+
elsif !uses_path_versioning?(settings) || (valid_part?(raw_namespace) || valid_part?(raw_path))
|
27
|
+
NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT
|
28
|
+
else
|
29
|
+
DEFAULT_FORMAT_SEGMENT
|
30
|
+
end
|
26
31
|
end
|
27
32
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
33
|
+
def build_parts(raw_path, raw_namespace, settings)
|
34
|
+
[].tap do |parts|
|
35
|
+
add_part(parts, settings[:mount_path])
|
36
|
+
add_part(parts, settings[:root_prefix])
|
37
|
+
parts << VERSION_SEGMENT if uses_path_versioning?(settings)
|
38
|
+
add_part(parts, raw_namespace)
|
39
|
+
add_part(parts, raw_path)
|
40
|
+
end
|
32
41
|
end
|
33
42
|
|
34
|
-
def
|
35
|
-
|
43
|
+
def add_part(parts, value)
|
44
|
+
parts << value if value && not_slash?(value)
|
36
45
|
end
|
37
46
|
|
38
|
-
def
|
39
|
-
|
47
|
+
def not_slash?(value)
|
48
|
+
value != '/'
|
40
49
|
end
|
41
50
|
|
42
|
-
def
|
43
|
-
|
44
|
-
"(.#{settings[:format]})"
|
45
|
-
elsif !uses_path_versioning? || (namespace? || path?)
|
46
|
-
'(.:format)'
|
47
|
-
else
|
48
|
-
'(/.:format)'
|
49
|
-
end
|
50
|
-
end
|
51
|
+
def uses_specific_format?(settings)
|
52
|
+
return false unless settings.key?(:format) && settings.key?(:content_types)
|
51
53
|
|
52
|
-
|
53
|
-
PartsCache[parts]
|
54
|
+
settings[:format] && Array(settings[:content_types]).size == 1
|
54
55
|
end
|
55
56
|
|
56
|
-
def
|
57
|
-
|
58
|
-
end
|
57
|
+
def uses_path_versioning?(settings)
|
58
|
+
return false unless settings.key?(:version) && settings[:version_options]&.key?(:using)
|
59
59
|
|
60
|
-
|
61
|
-
path_with_suffix
|
60
|
+
settings[:version] && settings[:version_options][:using] == :path
|
62
61
|
end
|
63
62
|
|
64
|
-
|
63
|
+
def valid_part?(part)
|
64
|
+
part&.match?(/^\S/) && not_slash?(part)
|
65
|
+
end
|
65
66
|
|
66
67
|
class PartsCache < Grape::Util::Cache
|
67
68
|
def initialize
|
@@ -71,23 +72,5 @@ module Grape
|
|
71
72
|
end
|
72
73
|
end
|
73
74
|
end
|
74
|
-
|
75
|
-
def parts
|
76
|
-
[].tap do |parts|
|
77
|
-
add_part(parts, mount_path)
|
78
|
-
add_part(parts, root_prefix)
|
79
|
-
parts << ':version' if uses_path_versioning?
|
80
|
-
add_part(parts, namespace)
|
81
|
-
add_part(parts, raw_path)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def add_part(parts, value)
|
86
|
-
parts << value if value && not_slash?(value)
|
87
|
-
end
|
88
|
-
|
89
|
-
def not_slash?(value)
|
90
|
-
value != '/'
|
91
|
-
end
|
92
75
|
end
|
93
76
|
end
|
data/lib/grape/request.rb
CHANGED
@@ -6,8 +6,8 @@ module Grape
|
|
6
6
|
|
7
7
|
alias rack_params params
|
8
8
|
|
9
|
-
def initialize(env,
|
10
|
-
extend
|
9
|
+
def initialize(env, build_params_with: nil)
|
10
|
+
extend build_params_with || Grape.config.param_builder
|
11
11
|
super(env)
|
12
12
|
end
|
13
13
|
|
@@ -7,8 +7,8 @@ module Grape
|
|
7
7
|
|
8
8
|
attr_reader :index, :pattern, :options
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
@options = ActiveSupport::OrderedOptions.new.update(options)
|
10
|
+
def initialize(options)
|
11
|
+
@options = options.is_a?(ActiveSupport::OrderedOptions) ? options : ActiveSupport::OrderedOptions.new.update(options)
|
12
12
|
end
|
13
13
|
|
14
14
|
alias attributes options
|
@@ -6,9 +6,9 @@
|
|
6
6
|
module Grape
|
7
7
|
class Router
|
8
8
|
class GreedyRoute < BaseRoute
|
9
|
-
def initialize(pattern
|
9
|
+
def initialize(pattern, options)
|
10
10
|
@pattern = pattern
|
11
|
-
super(
|
11
|
+
super(options)
|
12
12
|
end
|
13
13
|
|
14
14
|
# Grape::Router:Route defines params as a function
|
data/lib/grape/router/pattern.rb
CHANGED
@@ -9,14 +9,14 @@ module Grape
|
|
9
9
|
|
10
10
|
attr_reader :origin, :path, :pattern, :to_regexp
|
11
11
|
|
12
|
-
def_delegators :pattern, :
|
12
|
+
def_delegators :pattern, :params
|
13
13
|
def_delegators :to_regexp, :===
|
14
14
|
alias match? ===
|
15
15
|
|
16
|
-
def initialize(
|
17
|
-
@origin =
|
18
|
-
@path = build_path(
|
19
|
-
@pattern = build_pattern(@path, options)
|
16
|
+
def initialize(origin, suffix, options)
|
17
|
+
@origin = origin
|
18
|
+
@path = build_path(origin, options[:anchor], suffix)
|
19
|
+
@pattern = build_pattern(@path, options[:params], options[:format], options[:version], options[:requirements])
|
20
20
|
@to_regexp = @pattern.to_regexp
|
21
21
|
end
|
22
22
|
|
@@ -28,30 +28,31 @@ module Grape
|
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
|
-
def build_pattern(path,
|
31
|
+
def build_pattern(path, params, format, version, requirements)
|
32
32
|
Mustermann::Grape.new(
|
33
33
|
path,
|
34
34
|
uri_decode: true,
|
35
|
-
params:
|
36
|
-
capture: extract_capture(
|
35
|
+
params: params,
|
36
|
+
capture: extract_capture(format, version, requirements)
|
37
37
|
)
|
38
38
|
end
|
39
39
|
|
40
|
-
def build_path(pattern, anchor
|
41
|
-
PatternCache[[build_path_from_pattern(pattern, anchor
|
40
|
+
def build_path(pattern, anchor, suffix)
|
41
|
+
PatternCache[[build_path_from_pattern(pattern, anchor), suffix]]
|
42
42
|
end
|
43
43
|
|
44
|
-
def extract_capture(
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
def extract_capture(format, version, requirements)
|
45
|
+
capture = {}.tap do |h|
|
46
|
+
h[:format] = map_str(format) if format.present?
|
47
|
+
h[:version] = map_str(version) if version.present?
|
48
|
+
end
|
49
|
+
|
50
|
+
return capture if requirements.blank?
|
50
51
|
|
51
|
-
|
52
|
+
requirements.merge(capture)
|
52
53
|
end
|
53
54
|
|
54
|
-
def build_path_from_pattern(pattern, anchor
|
55
|
+
def build_path_from_pattern(pattern, anchor)
|
55
56
|
if pattern.end_with?('*path')
|
56
57
|
pattern.dup.insert(pattern.rindex('/') + 1, '?')
|
57
58
|
elsif anchor
|
@@ -63,6 +64,10 @@ module Grape
|
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
67
|
+
def map_str(value)
|
68
|
+
Array.wrap(value).map(&:to_s)
|
69
|
+
end
|
70
|
+
|
66
71
|
class PatternCache < Grape::Util::Cache
|
67
72
|
def initialize
|
68
73
|
super
|
data/lib/grape/router/route.rb
CHANGED
@@ -5,14 +5,22 @@ module Grape
|
|
5
5
|
class Route < BaseRoute
|
6
6
|
extend Forwardable
|
7
7
|
|
8
|
+
FORWARD_MATCH_METHOD = ->(input, pattern) { input.start_with?(pattern.origin) }
|
9
|
+
NON_FORWARD_MATCH_METHOD = ->(input, pattern) { pattern.match?(input) }
|
10
|
+
|
8
11
|
attr_reader :app, :request_method
|
9
12
|
|
10
13
|
def_delegators :pattern, :path, :origin
|
11
14
|
|
12
|
-
def initialize(method,
|
15
|
+
def initialize(method, origin, path, options)
|
13
16
|
@request_method = upcase_method(method)
|
14
|
-
@pattern = Grape::Router::Pattern.new(
|
15
|
-
|
17
|
+
@pattern = Grape::Router::Pattern.new(origin, path, options)
|
18
|
+
@match_function = options[:forward_match] ? FORWARD_MATCH_METHOD : NON_FORWARD_MATCH_METHOD
|
19
|
+
super(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def convert_to_head_request!
|
23
|
+
@request_method = Rack::HEAD
|
16
24
|
end
|
17
25
|
|
18
26
|
def exec(env)
|
@@ -27,7 +35,7 @@ module Grape
|
|
27
35
|
def match?(input)
|
28
36
|
return false if input.blank?
|
29
37
|
|
30
|
-
|
38
|
+
@match_function.call(input, pattern)
|
31
39
|
end
|
32
40
|
|
33
41
|
def params(input = nil)
|
@@ -42,7 +50,7 @@ module Grape
|
|
42
50
|
private
|
43
51
|
|
44
52
|
def params_without_input
|
45
|
-
pattern.captures_default.merge(attributes.params)
|
53
|
+
@params_without_input ||= pattern.captures_default.merge(attributes.params)
|
46
54
|
end
|
47
55
|
|
48
56
|
def upcase_method(method)
|
data/lib/grape/router.rb
CHANGED
@@ -38,8 +38,8 @@ module Grape
|
|
38
38
|
map[route.request_method] << route
|
39
39
|
end
|
40
40
|
|
41
|
-
def associate_routes(pattern,
|
42
|
-
Grape::Router::GreedyRoute.new(pattern
|
41
|
+
def associate_routes(pattern, options)
|
42
|
+
Grape::Router::GreedyRoute.new(pattern, options).then do |greedy_route|
|
43
43
|
@neutral_regexes << greedy_route.to_regexp(@neutral_map.length)
|
44
44
|
@neutral_map << greedy_route
|
45
45
|
end
|
@@ -107,7 +107,7 @@ module Grape
|
|
107
107
|
|
108
108
|
route = match?(input, '*')
|
109
109
|
|
110
|
-
return last_neighbor_route.endpoint.call(env) if last_neighbor_route && last_response_cascade && route
|
110
|
+
return last_neighbor_route.options[:endpoint].call(env) if last_neighbor_route && last_response_cascade && route
|
111
111
|
|
112
112
|
last_response_cascade = cascade_or_return_response.call(process_route(route, env)) if route
|
113
113
|
|
@@ -152,8 +152,8 @@ module Grape
|
|
152
152
|
|
153
153
|
def call_with_allow_headers(env, route)
|
154
154
|
prepare_env_from_route(env, route)
|
155
|
-
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header
|
156
|
-
route.endpoint.call(env)
|
155
|
+
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.options[:allow_header]
|
156
|
+
route.options[:endpoint].call(env)
|
157
157
|
end
|
158
158
|
|
159
159
|
def prepare_env_from_route(env, route)
|