grape 1.3.0 → 1.3.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 +22 -0
- data/README.md +3 -4
- data/lib/grape.rb +2 -3
- data/lib/grape/api.rb +2 -2
- data/lib/grape/api/instance.rb +4 -4
- data/lib/grape/content_types.rb +34 -0
- data/lib/grape/dsl/helpers.rb +1 -1
- data/lib/grape/dsl/inside_route.rb +10 -9
- data/lib/grape/dsl/parameters.rb +4 -4
- data/lib/grape/dsl/routing.rb +6 -4
- data/lib/grape/exceptions/base.rb +0 -4
- data/lib/grape/exceptions/validation_errors.rb +11 -12
- data/lib/grape/http/headers.rb +25 -0
- data/lib/grape/middleware/base.rb +1 -3
- data/lib/grape/middleware/stack.rb +2 -1
- data/lib/grape/middleware/versioner/header.rb +3 -3
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +12 -2
- data/lib/grape/path.rb +11 -1
- data/lib/grape/request.rb +12 -7
- data/lib/grape/router.rb +16 -7
- data/lib/grape/router/pattern.rb +17 -16
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/util/base_inheritable.rb +4 -0
- data/lib/grape/util/cache.rb +20 -0
- data/lib/grape/util/lazy_object.rb +43 -0
- data/lib/grape/util/reverse_stackable_values.rb +1 -1
- data/lib/grape/util/stackable_values.rb +6 -21
- data/lib/grape/validations/params_scope.rb +1 -1
- data/lib/grape/validations/types/file.rb +1 -0
- data/lib/grape/validations/types/primitive_coercer.rb +7 -4
- data/lib/grape/validations/validators/coerce.rb +1 -1
- data/lib/grape/validations/validators/exactly_one_of.rb +4 -2
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +7 -6
- data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
- data/spec/grape/middleware/formatter_spec.rb +2 -2
- data/spec/grape/middleware/stack_spec.rb +9 -0
- data/spec/grape/validations/instance_behaivour_spec.rb +1 -1
- data/spec/grape/validations/types/primitive_coercer_spec.rb +75 -0
- data/spec/grape/validations/validators/coerce_spec.rb +15 -51
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +12 -12
- data/spec/grape/validations_spec.rb +8 -12
- data/spec/spec_helper.rb +3 -0
- data/spec/support/eager_load.rb +19 -0
- metadata +12 -6
- data/lib/grape/util/content_types.rb +0 -28
data/lib/grape/namespace.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'grape/util/cache'
|
4
|
+
|
3
5
|
module Grape
|
4
6
|
# A container for endpoints or other namespaces, which allows for both
|
5
7
|
# logical grouping of endpoints as well as sharing common configuration.
|
@@ -25,13 +27,21 @@ module Grape
|
|
25
27
|
|
26
28
|
# (see ::joined_space_path)
|
27
29
|
def self.joined_space(settings)
|
28
|
-
|
30
|
+
settings&.map(&:space)
|
29
31
|
end
|
30
32
|
|
31
33
|
# Join the namespaces from a list of settings to create a path prefix.
|
32
34
|
# @param settings [Array] list of Grape::Util::InheritableSettings.
|
33
35
|
def self.joined_space_path(settings)
|
34
|
-
Grape::Router.normalize_path(joined_space(settings))
|
36
|
+
Grape::Router.normalize_path(JoinedSpaceCache[joined_space(settings)])
|
37
|
+
end
|
38
|
+
|
39
|
+
class JoinedSpaceCache < Grape::Util::Cache
|
40
|
+
def initialize
|
41
|
+
@cache = Hash.new do |h, joined_space|
|
42
|
+
h[joined_space] = -joined_space.join('/')
|
43
|
+
end
|
44
|
+
end
|
35
45
|
end
|
36
46
|
end
|
37
47
|
end
|
data/lib/grape/path.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'grape/util/cache'
|
4
|
+
|
3
5
|
module Grape
|
4
6
|
# Represents a path to an endpoint.
|
5
7
|
class Path
|
@@ -58,7 +60,7 @@ module Grape
|
|
58
60
|
end
|
59
61
|
|
60
62
|
def path
|
61
|
-
Grape::Router.normalize_path(parts
|
63
|
+
Grape::Router.normalize_path(PartsCache[parts])
|
62
64
|
end
|
63
65
|
|
64
66
|
def path_with_suffix
|
@@ -71,6 +73,14 @@ module Grape
|
|
71
73
|
|
72
74
|
private
|
73
75
|
|
76
|
+
class PartsCache < Grape::Util::Cache
|
77
|
+
def initialize
|
78
|
+
@cache = Hash.new do |h, parts|
|
79
|
+
h[parts] = -parts.join('/')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
74
84
|
def parts
|
75
85
|
parts = [mount_path, root_prefix].compact
|
76
86
|
parts << ':version' if uses_path_versioning?
|
data/lib/grape/request.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'grape/util/lazy_object'
|
4
|
+
|
3
5
|
module Grape
|
4
6
|
class Request < Rack::Request
|
5
7
|
HTTP_PREFIX = 'HTTP_'
|
@@ -30,14 +32,17 @@ module Grape
|
|
30
32
|
end
|
31
33
|
|
32
34
|
def build_headers
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
Grape::Util::LazyObject.new do
|
36
|
+
env.each_pair.with_object({}) do |(k, v), headers|
|
37
|
+
next unless k.to_s.start_with? HTTP_PREFIX
|
38
|
+
transformed_header = Grape::Http::Headers::HTTP_HEADERS[k] || transform_header(k)
|
39
|
+
headers[transformed_header] = v
|
40
|
+
end
|
39
41
|
end
|
40
|
-
|
42
|
+
end
|
43
|
+
|
44
|
+
def transform_header(header)
|
45
|
+
-header[5..-1].split('_').each(&:capitalize!).join('-')
|
41
46
|
end
|
42
47
|
end
|
43
48
|
end
|
data/lib/grape/router.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'grape/router/route'
|
4
|
+
require 'grape/util/cache'
|
4
5
|
|
5
6
|
module Grape
|
6
7
|
class Router
|
@@ -16,12 +17,20 @@ module Grape
|
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
20
|
+
class NormalizePathCache < Grape::Util::Cache
|
21
|
+
def initialize
|
22
|
+
@cache = Hash.new do |h, path|
|
23
|
+
normalized_path = +"/#{path}"
|
24
|
+
normalized_path.squeeze!('/')
|
25
|
+
normalized_path.sub!(%r{/+\Z}, '')
|
26
|
+
normalized_path = '/' if normalized_path.empty?
|
27
|
+
h[path] = -normalized_path
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
19
32
|
def self.normalize_path(path)
|
20
|
-
path
|
21
|
-
path.squeeze!('/')
|
22
|
-
path.sub!(%r{/+\Z}, '')
|
23
|
-
path = '/' if path == ''
|
24
|
-
path
|
33
|
+
NormalizePathCache[path]
|
25
34
|
end
|
26
35
|
|
27
36
|
def self.supported_methods
|
@@ -106,7 +115,7 @@ module Grape
|
|
106
115
|
env,
|
107
116
|
neighbor.allow_header,
|
108
117
|
neighbor.endpoint
|
109
|
-
) if neighbor && method ==
|
118
|
+
) if neighbor && method == Grape::Http::Headers::OPTIONS && !cascade
|
110
119
|
|
111
120
|
route = match?(input, '*')
|
112
121
|
return neighbor.endpoint.call(env) if neighbor && cascade && route
|
@@ -160,7 +169,7 @@ module Grape
|
|
160
169
|
end
|
161
170
|
|
162
171
|
def call_with_allow_headers(env, methods, endpoint)
|
163
|
-
env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods
|
172
|
+
env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods.join(', ').freeze
|
164
173
|
endpoint.call(env)
|
165
174
|
end
|
166
175
|
|
data/lib/grape/router/pattern.rb
CHANGED
@@ -2,35 +2,32 @@
|
|
2
2
|
|
3
3
|
require 'forwardable'
|
4
4
|
require 'mustermann/grape'
|
5
|
+
require 'grape/util/cache'
|
5
6
|
|
6
7
|
module Grape
|
7
8
|
class Router
|
8
9
|
class Pattern
|
9
|
-
DEFAULT_PATTERN_OPTIONS = { uri_decode: true
|
10
|
+
DEFAULT_PATTERN_OPTIONS = { uri_decode: true }.freeze
|
10
11
|
DEFAULT_SUPPORTED_CAPTURE = %i[format version].freeze
|
11
12
|
|
12
|
-
attr_reader :origin, :path, :
|
13
|
+
attr_reader :origin, :path, :pattern, :to_regexp
|
13
14
|
|
14
15
|
extend Forwardable
|
15
16
|
def_delegators :pattern, :named_captures, :params
|
16
|
-
def_delegators
|
17
|
+
def_delegators :to_regexp, :===
|
17
18
|
alias match? ===
|
18
19
|
|
19
20
|
def initialize(pattern, **options)
|
20
21
|
@origin = pattern
|
21
22
|
@path = build_path(pattern, **options)
|
22
|
-
@
|
23
|
-
@
|
24
|
-
@regexp = to_regexp
|
25
|
-
end
|
26
|
-
|
27
|
-
def to_regexp
|
28
|
-
@to_regexp ||= @pattern.to_regexp
|
23
|
+
@pattern = Mustermann::Grape.new(@path, **pattern_options(options))
|
24
|
+
@to_regexp = @pattern.to_regexp
|
29
25
|
end
|
30
26
|
|
31
27
|
private
|
32
28
|
|
33
|
-
def pattern_options
|
29
|
+
def pattern_options(options)
|
30
|
+
capture = extract_capture(**options)
|
34
31
|
options = DEFAULT_PATTERN_OPTIONS.dup
|
35
32
|
options[:capture] = capture if capture.present?
|
36
33
|
options
|
@@ -43,23 +40,27 @@ module Grape
|
|
43
40
|
pattern << '*path'
|
44
41
|
end
|
45
42
|
|
46
|
-
pattern = pattern.split('/').tap do |parts|
|
43
|
+
pattern = -pattern.split('/').tap do |parts|
|
47
44
|
parts[parts.length - 1] = '?' + parts.last
|
48
45
|
end.join('/') if pattern.end_with?('*path')
|
49
46
|
|
50
|
-
|
47
|
+
PatternCache[[pattern, suffix]]
|
51
48
|
end
|
52
49
|
|
53
50
|
def extract_capture(requirements: {}, **options)
|
54
51
|
requirements = {}.merge(requirements)
|
55
|
-
|
52
|
+
DEFAULT_SUPPORTED_CAPTURE.each_with_object(requirements) do |field, capture|
|
56
53
|
option = Array(options[field])
|
57
54
|
capture[field] = option.map(&:to_s) if option.present?
|
58
55
|
end
|
59
56
|
end
|
60
57
|
|
61
|
-
|
62
|
-
|
58
|
+
class PatternCache < Grape::Util::Cache
|
59
|
+
def initialize
|
60
|
+
@cache = Hash.new do |h, (pattern, suffix)|
|
61
|
+
h[[pattern, suffix]] = -"#{pattern}#{suffix}"
|
62
|
+
end
|
63
|
+
end
|
63
64
|
end
|
64
65
|
end
|
65
66
|
end
|
data/lib/grape/router/route.rb
CHANGED
@@ -8,8 +8,8 @@ require 'pathname'
|
|
8
8
|
module Grape
|
9
9
|
class Router
|
10
10
|
class Route
|
11
|
-
ROUTE_ATTRIBUTE_REGEXP = /route_([_a-zA-Z]\w*)
|
12
|
-
SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')
|
11
|
+
ROUTE_ATTRIBUTE_REGEXP = /route_([_a-zA-Z]\w*)/.freeze
|
12
|
+
SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/.freeze
|
13
13
|
FIXED_NAMED_CAPTURES = %w[format version].freeze
|
14
14
|
|
15
15
|
attr_accessor :pattern, :translator, :app, :index, :regexp, :options
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_String_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require 'forwardable'
|
5
|
+
|
6
|
+
module Grape
|
7
|
+
module Util
|
8
|
+
class Cache
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
attr_reader :cache
|
12
|
+
|
13
|
+
class << self
|
14
|
+
extend Forwardable
|
15
|
+
def_delegators :cache, :[]
|
16
|
+
def_delegators :instance, :cache
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Based on https://github.com/HornsAndHooves/lazy_object
|
4
|
+
|
5
|
+
module Grape
|
6
|
+
module Util
|
7
|
+
class LazyObject < BasicObject
|
8
|
+
attr_reader :callable
|
9
|
+
|
10
|
+
def initialize(&callable)
|
11
|
+
@callable = callable
|
12
|
+
end
|
13
|
+
|
14
|
+
def __target_object__
|
15
|
+
@__target_object__ ||= callable.call
|
16
|
+
end
|
17
|
+
|
18
|
+
def ==(other)
|
19
|
+
__target_object__ == other
|
20
|
+
end
|
21
|
+
|
22
|
+
def !=(other)
|
23
|
+
__target_object__ != other
|
24
|
+
end
|
25
|
+
|
26
|
+
def !
|
27
|
+
!__target_object__
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing(method_name, *args, &block)
|
31
|
+
if __target_object__.respond_to?(method_name)
|
32
|
+
__target_object__.send(method_name, *args, &block)
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def respond_to_missing?(method_name, include_priv = false)
|
39
|
+
__target_object__.respond_to?(method_name, include_priv)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -5,30 +5,19 @@ require_relative 'base_inheritable'
|
|
5
5
|
module Grape
|
6
6
|
module Util
|
7
7
|
class StackableValues < BaseInheritable
|
8
|
-
attr_reader :frozen_values
|
9
|
-
|
10
|
-
def initialize(*_args)
|
11
|
-
super
|
12
|
-
|
13
|
-
@frozen_values = {}
|
14
|
-
end
|
15
|
-
|
16
8
|
# Even if there is no value, an empty array will be returned.
|
17
9
|
def [](name)
|
18
|
-
|
10
|
+
inherited_value = inherited_values[name]
|
11
|
+
new_value = new_values[name]
|
19
12
|
|
20
|
-
|
21
|
-
new_value = @new_values[name] || []
|
22
|
-
|
23
|
-
return new_value unless inherited_value
|
13
|
+
return new_value || [] unless inherited_value
|
24
14
|
|
25
15
|
concat_values(inherited_value, new_value)
|
26
16
|
end
|
27
17
|
|
28
18
|
def []=(name, value)
|
29
|
-
|
30
|
-
|
31
|
-
@new_values[name].push value
|
19
|
+
new_values[name] ||= []
|
20
|
+
new_values[name].push value
|
32
21
|
end
|
33
22
|
|
34
23
|
def to_hash
|
@@ -37,16 +26,12 @@ module Grape
|
|
37
26
|
end
|
38
27
|
end
|
39
28
|
|
40
|
-
def freeze_value(key)
|
41
|
-
@frozen_values[key] = self[key].freeze
|
42
|
-
end
|
43
|
-
|
44
29
|
protected
|
45
30
|
|
46
31
|
def concat_values(inherited_value, new_value)
|
47
32
|
[].tap do |value|
|
48
33
|
value.concat(inherited_value)
|
49
|
-
value.concat(new_value)
|
34
|
+
value.concat(new_value) if new_value
|
50
35
|
end
|
51
36
|
end
|
52
37
|
end
|
@@ -6,17 +6,20 @@ module Grape
|
|
6
6
|
module Validations
|
7
7
|
module Types
|
8
8
|
# Coerces the given value to a type defined via a +type+ argument during
|
9
|
-
# initialization.
|
9
|
+
# initialization. When +strict+ is true, it doesn't coerce a value but check
|
10
|
+
# that it has the proper type.
|
10
11
|
class PrimitiveCoercer < DryTypeCoercer
|
11
12
|
MAPPING = {
|
12
13
|
Grape::API::Boolean => DryTypes::Params::Bool,
|
13
14
|
|
14
|
-
#
|
15
|
-
String => DryTypes::Coercible::String
|
15
|
+
# unfortunately, a +Params+ scope doesn't contain String
|
16
|
+
String => DryTypes::Coercible::String,
|
17
|
+
BigDecimal => DryTypes::Coercible::Decimal
|
16
18
|
}.freeze
|
17
19
|
|
18
20
|
STRICT_MAPPING = {
|
19
|
-
Grape::API::Boolean => DryTypes::Strict::Bool
|
21
|
+
Grape::API::Boolean => DryTypes::Strict::Bool,
|
22
|
+
BigDecimal => DryTypes::Strict::Decimal
|
20
23
|
}.freeze
|
21
24
|
|
22
25
|
def initialize(type, strict = false)
|
@@ -6,8 +6,10 @@ module Grape
|
|
6
6
|
module Validations
|
7
7
|
class ExactlyOneOfValidator < MultipleParamsBase
|
8
8
|
def validate_params!(params)
|
9
|
-
|
10
|
-
|
9
|
+
keys = keys_in_common(params)
|
10
|
+
return if keys.length == 1
|
11
|
+
raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.length.zero?
|
12
|
+
raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion))
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
data/lib/grape/version.rb
CHANGED