grape 2.4.0 → 4.0.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 +351 -0
- data/CONTRIBUTING.md +18 -10
- data/README.md +457 -228
- data/UPGRADING.md +1240 -1
- data/grape.gemspec +7 -7
- data/lib/grape/api/instance.rb +65 -116
- data/lib/grape/api.rb +53 -62
- data/lib/grape/content_types.rb +58 -3
- data/lib/grape/cookies.rb +2 -6
- data/lib/grape/declared_params_handler.rb +116 -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 +24 -67
- data/lib/grape/dsl/entity.rb +106 -0
- data/lib/grape/dsl/helpers.rb +80 -65
- data/lib/grape/dsl/inside_route.rb +56 -296
- data/lib/grape/dsl/logger.rb +4 -9
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +86 -93
- data/lib/grape/dsl/request_response.rb +139 -139
- data/lib/grape/dsl/rescue_options.rb +25 -0
- data/lib/grape/dsl/routing.rb +328 -203
- data/lib/grape/dsl/settings.rb +29 -135
- data/lib/grape/dsl/validations.rb +38 -44
- data/lib/grape/dsl/version_options.rb +23 -0
- data/lib/grape/endpoint/options.rb +25 -0
- data/lib/grape/endpoint.rb +285 -245
- data/lib/grape/env.rb +8 -3
- data/lib/grape/error_formatter/base.rb +27 -21
- data/lib/grape/error_formatter/json.rb +6 -0
- data/lib/grape/error_formatter/txt.rb +10 -10
- data/lib/grape/error_formatter.rb +6 -2
- data/lib/grape/exceptions/base.rb +20 -44
- data/lib/grape/exceptions/error_response.rb +48 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/internal_server_error.rb +16 -0
- 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_error_formatter.rb +11 -0
- 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 +25 -7
- data/lib/grape/exceptions/validation_array_errors.rb +4 -0
- data/lib/grape/exceptions/validation_errors.rb +21 -23
- data/lib/grape/formatter/json.rb +1 -0
- data/lib/grape/formatter/serializable_hash.rb +6 -9
- data/lib/grape/json.rb +38 -2
- data/lib/grape/locale/en.yml +3 -5
- data/lib/grape/middleware/auth/base.rb +4 -3
- data/lib/grape/middleware/auth/dsl.rb +9 -23
- data/lib/grape/middleware/base.rb +41 -36
- data/lib/grape/middleware/error.rb +271 -73
- data/lib/grape/middleware/formatter.rb +152 -63
- data/lib/grape/middleware/precomputed_content_types.rb +51 -0
- data/lib/grape/middleware/stack.rb +20 -11
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
- data/lib/grape/middleware/versioner/base.rb +40 -48
- data/lib/grape/middleware/versioner/header.rb +5 -23
- data/lib/grape/middleware/versioner/param.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +54 -7
- data/lib/grape/mountable.rb +22 -0
- data/lib/grape/namespace.rb +19 -11
- data/lib/grape/params_builder/base.rb +2 -0
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +1 -1
- data/lib/grape/params_builder.rb +2 -19
- data/lib/grape/parser/json.rb +1 -1
- data/lib/grape/path.rb +8 -72
- data/lib/grape/precompiled_json.rb +50 -0
- data/lib/grape/request.rb +57 -20
- data/lib/grape/router/base_route.rb +53 -7
- data/lib/grape/router/greedy_route.rb +16 -6
- data/lib/grape/router/mustermann_pattern.rb +44 -0
- data/lib/grape/router/pattern/path.rb +78 -0
- data/lib/grape/router/pattern.rb +65 -37
- data/lib/grape/router/route.rb +66 -18
- data/lib/grape/router.rb +126 -115
- data/lib/grape/serve_stream/file_body.rb +7 -0
- data/lib/grape/serve_stream/sendfile_response.rb +3 -5
- data/lib/grape/serve_stream/stream_response.rb +7 -0
- data/lib/grape/testing.rb +33 -0
- data/lib/grape/util/api_description.rb +67 -0
- data/lib/grape/util/cache.rb +22 -2
- data/lib/grape/util/deep_freeze.rb +34 -0
- data/lib/grape/util/freeze_on_new.rb +20 -0
- data/lib/grape/util/inheritable_setting.rb +777 -39
- data/lib/grape/util/lazy/base.rb +16 -0
- data/lib/grape/util/lazy/block.rb +2 -9
- data/lib/grape/util/lazy/value.rb +2 -9
- data/lib/grape/util/lazy/value_enumerable.rb +13 -16
- data/lib/grape/util/media_type.rb +13 -9
- data/lib/grape/util/path_normalizer.rb +37 -0
- data/lib/grape/util/registry.rb +13 -3
- data/lib/grape/util/shadowed_rescue_handlers.rb +49 -0
- data/lib/grape/util/stackable_values.rb +39 -10
- data/lib/grape/util/translation.rb +42 -0
- data/lib/grape/validations/attributes_iterator.rb +61 -29
- data/lib/grape/validations/coerce_options.rb +21 -0
- data/lib/grape/validations/contract_scope.rb +8 -13
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/oneof_collector.rb +35 -0
- data/lib/grape/validations/param_scope_tracker.rb +62 -0
- data/lib/grape/validations/params_documentation.rb +52 -0
- data/lib/grape/validations/params_scope.rb +217 -304
- data/lib/grape/validations/shared_options.rb +19 -0
- data/lib/grape/validations/single_attribute_iterator.rb +6 -4
- data/lib/grape/validations/types/array_coercer.rb +7 -10
- data/lib/grape/validations/types/custom_type_coercer.rb +47 -85
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +10 -15
- data/lib/grape/validations/types/json.rb +1 -3
- data/lib/grape/validations/types/multiple_type_coercer.rb +5 -3
- data/lib/grape/validations/types/primitive_coercer.rb +14 -33
- data/lib/grape/validations/types/set_coercer.rb +1 -1
- data/lib/grape/validations/types/variant_collection_coercer.rb +16 -3
- data/lib/grape/validations/types.rb +29 -51
- data/lib/grape/validations/validations_spec.rb +164 -0
- 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 +118 -34
- data/lib/grape/validations/validators/coerce_validator.rb +25 -37
- data/lib/grape/validations/validators/contract_scope_validator.rb +10 -5
- data/lib/grape/validations/validators/default_validator.rb +13 -20
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
- data/lib/grape/validations/validators/except_values_validator.rb +14 -4
- data/lib/grape/validations/validators/length_validator.rb +23 -22
- data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
- data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
- data/lib/grape/validations/validators/oneof_validator.rb +51 -0
- 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 +7 -15
- data/lib/grape/validations/validators/values_validator.rb +34 -26
- data/lib/grape/version.rb +1 -2
- data/lib/grape/xml.rb +8 -1
- data/lib/grape.rb +58 -31
- metadata +62 -39
- 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/middleware/globals.rb +0 -14
- data/lib/grape/types/invalid_value.rb +0 -8
- data/lib/grape/util/base_inheritable.rb +0 -43
- data/lib/grape/util/inheritable_values.rb +0 -29
- data/lib/grape/util/reverse_stackable_values.rb +0 -15
- 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
data/grape.gemspec
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require 'grape/version'
|
|
3
|
+
require_relative 'lib/grape/version'
|
|
5
4
|
|
|
6
5
|
Gem::Specification.new do |s|
|
|
7
6
|
s.name = 'grape'
|
|
@@ -21,13 +20,14 @@ Gem::Specification.new do |s|
|
|
|
21
20
|
'rubygems_mfa_required' => 'true'
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
s.add_dependency 'activesupport', '>=
|
|
23
|
+
s.add_dependency 'activesupport', '>= 7.2'
|
|
24
|
+
s.add_dependency 'dry-configurable', '>= 1.0'
|
|
25
25
|
s.add_dependency 'dry-types', '>= 1.1'
|
|
26
|
-
s.add_dependency 'mustermann
|
|
27
|
-
s.add_dependency 'rack', '>= 2'
|
|
28
|
-
s.add_dependency 'zeitwerk'
|
|
26
|
+
s.add_dependency 'mustermann', '>= 4.0'
|
|
27
|
+
s.add_dependency 'rack', '>= 2.2.4'
|
|
28
|
+
s.add_dependency 'zeitwerk', '>= 2.6'
|
|
29
29
|
|
|
30
30
|
s.files = Dir['lib/**/*', 'CHANGELOG.md', 'CONTRIBUTING.md', 'README.md', 'grape.png', 'UPGRADING.md', 'LICENSE', 'grape.gemspec']
|
|
31
31
|
s.require_paths = ['lib']
|
|
32
|
-
s.required_ruby_version = '>=
|
|
32
|
+
s.required_ruby_version = '>= 3.3'
|
|
33
33
|
end
|
data/lib/grape/api/instance.rb
CHANGED
|
@@ -5,32 +5,37 @@ module Grape
|
|
|
5
5
|
# The API Instance class, is the engine behind Grape::API. Each class that inherits
|
|
6
6
|
# from this will represent a different API instance
|
|
7
7
|
class Instance
|
|
8
|
+
# Marks this and every mounted instance as a mountable Grape app (see Grape::Mountable).
|
|
9
|
+
extend Grape::Mountable
|
|
10
|
+
extend Grape::DSL::Settings
|
|
11
|
+
extend Grape::DSL::Desc
|
|
12
|
+
extend Grape::DSL::Validations
|
|
13
|
+
extend Grape::DSL::Callbacks
|
|
14
|
+
extend Grape::DSL::Logger
|
|
15
|
+
extend Grape::DSL::Middleware
|
|
16
|
+
extend Grape::DSL::RequestResponse
|
|
17
|
+
extend Grape::DSL::Routing
|
|
18
|
+
extend Grape::DSL::Helpers
|
|
8
19
|
extend Grape::Middleware::Auth::DSL
|
|
9
|
-
|
|
20
|
+
|
|
21
|
+
Boolean = Grape::API::Boolean
|
|
10
22
|
|
|
11
23
|
class << self
|
|
12
|
-
attr_reader :instance, :base
|
|
13
24
|
attr_accessor :configuration
|
|
14
25
|
|
|
15
|
-
def
|
|
16
|
-
|
|
26
|
+
def to_s
|
|
27
|
+
@base&.to_s || super
|
|
17
28
|
end
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
evaluate_as_instance_with_configuration(block, lazy: true)
|
|
21
|
-
end
|
|
30
|
+
attr_reader :base
|
|
22
31
|
|
|
23
32
|
def base=(grape_api)
|
|
24
33
|
@base = grape_api
|
|
25
34
|
grape_api.instances << self
|
|
26
35
|
end
|
|
27
36
|
|
|
28
|
-
def to_s
|
|
29
|
-
base&.to_s || super
|
|
30
|
-
end
|
|
31
|
-
|
|
32
37
|
def base_instance?
|
|
33
|
-
self == base.base_instance
|
|
38
|
+
self == @base.base_instance
|
|
34
39
|
end
|
|
35
40
|
|
|
36
41
|
# A class-level lock to ensure the API is not compiled by multiple
|
|
@@ -44,92 +49,32 @@ module Grape
|
|
|
44
49
|
reset_validations!
|
|
45
50
|
end
|
|
46
51
|
|
|
47
|
-
# Parses the API's definition and compiles it into an instance of
|
|
48
|
-
# Grape::API.
|
|
49
|
-
def compile
|
|
50
|
-
@instance ||= new # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Wipe the compiled API so we can recompile after changes were made.
|
|
54
|
-
def change!
|
|
55
|
-
@instance = nil
|
|
56
|
-
end
|
|
57
|
-
|
|
58
52
|
# This is the interface point between Rack and Grape; it accepts a request
|
|
59
53
|
# from Rack and ultimately returns an array of three values: the status,
|
|
60
54
|
# the headers, and the body. See [the rack specification]
|
|
61
55
|
# (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
|
|
62
56
|
def call(env)
|
|
63
|
-
compile
|
|
64
|
-
call!(env)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# A non-synchronized version of ::call.
|
|
68
|
-
def call!(env)
|
|
69
|
-
instance.call(env)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# (see #cascade?)
|
|
73
|
-
def cascade(value = nil)
|
|
74
|
-
if value.nil?
|
|
75
|
-
inheritable_setting.namespace_inheritable.key?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
|
|
76
|
-
else
|
|
77
|
-
namespace_inheritable(:cascade, value)
|
|
78
|
-
end
|
|
57
|
+
compile!.call(env)
|
|
79
58
|
end
|
|
80
59
|
|
|
60
|
+
# Returns the compiled instance, so callers serve the one they
|
|
61
|
+
# compiled rather than re-reading @instance — +change!+ can nil it
|
|
62
|
+
# between the two reads (see #call / #recognize_path).
|
|
81
63
|
def compile!
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
LOCK.synchronize { compile unless instance }
|
|
64
|
+
@instance || LOCK.synchronize { @instance ||= new }
|
|
85
65
|
end
|
|
86
66
|
|
|
87
67
|
# see Grape::Router#recognize_path
|
|
88
68
|
def recognize_path(path)
|
|
89
|
-
compile
|
|
90
|
-
instance.router.recognize_path(path)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
protected
|
|
94
|
-
|
|
95
|
-
def prepare_routes
|
|
96
|
-
endpoints.map(&:routes).flatten
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Execute first the provided block, then each of the
|
|
100
|
-
# block passed in. Allows for simple 'before' setups
|
|
101
|
-
# of settings stack pushes.
|
|
102
|
-
def nest(*blocks, &block)
|
|
103
|
-
blocks.compact!
|
|
104
|
-
if blocks.any?
|
|
105
|
-
evaluate_as_instance_with_configuration(block) if block
|
|
106
|
-
blocks.each { |b| evaluate_as_instance_with_configuration(b) }
|
|
107
|
-
reset_validations!
|
|
108
|
-
else
|
|
109
|
-
instance_eval(&block)
|
|
110
|
-
end
|
|
69
|
+
compile!.router.recognize_path(path)
|
|
111
70
|
end
|
|
112
71
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
self.configuration = value_for_configuration.evaluate if value_for_configuration.try(:lazy?)
|
|
117
|
-
response = instance_eval(&block)
|
|
118
|
-
self.configuration = value_for_configuration
|
|
119
|
-
response
|
|
120
|
-
end
|
|
121
|
-
if base && base_instance? && lazy
|
|
122
|
-
lazy_block
|
|
123
|
-
else
|
|
124
|
-
lazy_block.evaluate_from(configuration)
|
|
125
|
-
end
|
|
72
|
+
# Wipe the compiled API so we can recompile after changes were made.
|
|
73
|
+
def change!
|
|
74
|
+
@instance = nil
|
|
126
75
|
end
|
|
127
76
|
|
|
128
|
-
|
|
129
|
-
super
|
|
130
|
-
subclass.reset!
|
|
131
|
-
subclass.logger = logger.clone
|
|
132
|
-
end
|
|
77
|
+
protected
|
|
133
78
|
|
|
134
79
|
def inherit_settings(other_settings)
|
|
135
80
|
top_level_setting.inherit_from other_settings.point_in_time_copy
|
|
@@ -137,12 +82,20 @@ module Grape
|
|
|
137
82
|
# Propagate any inherited params down to our endpoints, and reset any
|
|
138
83
|
# compiled routes.
|
|
139
84
|
endpoints.each do |e|
|
|
140
|
-
e.inherit_settings(top_level_setting
|
|
85
|
+
e.inherit_settings(top_level_setting)
|
|
141
86
|
e.reset_routes!
|
|
142
87
|
end
|
|
143
88
|
|
|
144
89
|
reset_routes!
|
|
145
90
|
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def inherited(subclass)
|
|
95
|
+
super
|
|
96
|
+
subclass.reset!
|
|
97
|
+
subclass.logger logger.clone
|
|
98
|
+
end
|
|
146
99
|
end
|
|
147
100
|
|
|
148
101
|
attr_reader :router
|
|
@@ -158,13 +111,19 @@ module Grape
|
|
|
158
111
|
|
|
159
112
|
@router.compile!
|
|
160
113
|
@router.freeze
|
|
114
|
+
@cascade = resolve_cascade
|
|
161
115
|
end
|
|
162
116
|
|
|
163
117
|
# Handle a request. See Rack documentation for what `env` is.
|
|
164
118
|
def call(env)
|
|
165
119
|
status, headers, response = @router.call(env)
|
|
166
|
-
unless cascade
|
|
167
|
-
|
|
120
|
+
unless @cascade
|
|
121
|
+
# +merge!+, not +merge+: the latter is a `dup` plus a `merge!`, so the
|
|
122
|
+
# Header built on this line would be allocated only to be discarded.
|
|
123
|
+
# The copy stays because +headers+ can come from a mounted Rack app,
|
|
124
|
+
# which is free to hand back a frozen or shared Hash that the delete
|
|
125
|
+
# below must not reach into.
|
|
126
|
+
headers = Grape::Util::Header.new.merge!(headers)
|
|
168
127
|
headers.delete('X-Cascade')
|
|
169
128
|
end
|
|
170
129
|
|
|
@@ -179,11 +138,13 @@ module Grape
|
|
|
179
138
|
# In some applications (e.g. mounting grape on rails), one might need to trap
|
|
180
139
|
# errors from reaching upstream. This is effectivelly done by unsetting
|
|
181
140
|
# X-Cascade. Default :cascade is true.
|
|
141
|
+
#
|
|
142
|
+
# Resolved in the constructor rather than per request: answering it walks
|
|
143
|
+
# the whole scope chain twice, and it reads the same settings the routes
|
|
144
|
+
# were compiled and frozen from -- changing those has to go through
|
|
145
|
+
# +change!+, which discards this instance.
|
|
182
146
|
def cascade?
|
|
183
|
-
|
|
184
|
-
return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options)&.key?(:cascade)
|
|
185
|
-
|
|
186
|
-
true
|
|
147
|
+
@cascade
|
|
187
148
|
end
|
|
188
149
|
|
|
189
150
|
reset!
|
|
@@ -197,50 +158,38 @@ module Grape
|
|
|
197
158
|
def add_head_not_allowed_methods_and_options_methods
|
|
198
159
|
# The paths we collected are prepared (cf. Path#prepare), so they
|
|
199
160
|
# contain already versioning information when using path versioning.
|
|
200
|
-
all_routes = self.class.endpoints.
|
|
161
|
+
all_routes = self.class.endpoints.flat_map(&:routes)
|
|
201
162
|
|
|
202
|
-
|
|
203
|
-
# informations again.
|
|
204
|
-
without_root_prefix_and_versioning { collect_route_config_per_pattern(all_routes) }
|
|
163
|
+
collect_route_config_per_pattern(all_routes)
|
|
205
164
|
end
|
|
206
165
|
|
|
207
166
|
def collect_route_config_per_pattern(all_routes)
|
|
167
|
+
setting = self.class.inheritable_setting
|
|
208
168
|
routes_by_regexp = all_routes.group_by(&:pattern_regexp)
|
|
209
169
|
|
|
210
170
|
# Build the configuration based on the first endpoint and the collection of methods supported.
|
|
211
171
|
routes_by_regexp.each_value do |routes|
|
|
212
|
-
last_route = routes.last # Most of the configuration is taken from the last endpoint
|
|
213
172
|
next if routes.any? { |route| route.request_method == '*' }
|
|
214
173
|
|
|
174
|
+
last_route = routes.last # Most of the configuration is taken from the last endpoint
|
|
215
175
|
allowed_methods = routes.map(&:request_method)
|
|
216
|
-
allowed_methods |= [Rack::HEAD] if !
|
|
176
|
+
allowed_methods |= [Rack::HEAD] if !setting.do_not_route_head? && allowed_methods.include?(Rack::GET)
|
|
217
177
|
|
|
218
|
-
allow_header =
|
|
219
|
-
last_route.app.
|
|
178
|
+
allow_header = setting.do_not_route_options? ? allowed_methods : [Rack::OPTIONS] | allowed_methods
|
|
179
|
+
last_route.app.options_route_enabled = true unless setting.do_not_route_options? || allowed_methods.include?(Rack::OPTIONS)
|
|
220
180
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
allow_header: allow_header
|
|
224
|
-
})
|
|
181
|
+
greedy_route = Grape::Router::GreedyRoute.new(last_route.pattern, endpoint: last_route.app, allow_header:)
|
|
182
|
+
@router.associate_routes(greedy_route)
|
|
225
183
|
end
|
|
226
184
|
end
|
|
227
185
|
|
|
228
|
-
#
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
old_root_prefix = self.class.namespace_inheritable(:root_prefix)
|
|
186
|
+
# Backs {#cascade?}; called once, from the constructor.
|
|
187
|
+
def resolve_cascade
|
|
188
|
+
setting = self.class.inheritable_setting
|
|
189
|
+
return setting.cascade if setting.cascade_defined?
|
|
190
|
+
return setting.version_options.cascade if setting.version_options
|
|
234
191
|
|
|
235
|
-
|
|
236
|
-
self.class.namespace_inheritable_to_nil(:version_options)
|
|
237
|
-
self.class.namespace_inheritable_to_nil(:root_prefix)
|
|
238
|
-
|
|
239
|
-
yield
|
|
240
|
-
|
|
241
|
-
self.class.namespace_inheritable(:version, old_version)
|
|
242
|
-
self.class.namespace_inheritable(:version_options, old_version_options)
|
|
243
|
-
self.class.namespace_inheritable(:root_prefix, old_root_prefix)
|
|
192
|
+
true
|
|
244
193
|
end
|
|
245
194
|
end
|
|
246
195
|
end
|
data/lib/grape/api.rb
CHANGED
|
@@ -4,43 +4,42 @@ module Grape
|
|
|
4
4
|
# The API class is the primary entry point for creating Grape APIs. Users
|
|
5
5
|
# should subclass this class in order to build an API.
|
|
6
6
|
class API
|
|
7
|
+
# Marks this and every subclass as a mountable Grape app (see Grape::Mountable).
|
|
8
|
+
extend Grape::Mountable
|
|
9
|
+
|
|
7
10
|
# Class methods that we want to call on the API rather than on the API object
|
|
8
|
-
NON_OVERRIDABLE = %i[call
|
|
11
|
+
NON_OVERRIDABLE = %i[base base= base_instance? call change! configuration compile! inherit_settings recognize_path reset! routes top_level_setting= top_level_setting].freeze
|
|
12
|
+
|
|
13
|
+
Helpers = Grape::DSL::Helpers::BaseHelper
|
|
9
14
|
|
|
10
15
|
class Boolean
|
|
16
|
+
VALUES = [true, false].freeze
|
|
17
|
+
private_constant :VALUES
|
|
18
|
+
|
|
11
19
|
def self.build(val)
|
|
12
|
-
return
|
|
20
|
+
return unless VALUES.include?(val)
|
|
13
21
|
|
|
14
22
|
new
|
|
15
23
|
end
|
|
16
24
|
end
|
|
17
25
|
|
|
18
|
-
class Instance
|
|
19
|
-
Boolean = Grape::API::Boolean
|
|
20
|
-
end
|
|
21
|
-
|
|
22
26
|
class << self
|
|
23
27
|
extend Forwardable
|
|
28
|
+
|
|
24
29
|
attr_accessor :base_instance, :instances
|
|
25
30
|
|
|
26
31
|
delegate_missing_to :base_instance
|
|
27
|
-
def_delegators :base_instance, :new, :configuration
|
|
28
32
|
|
|
29
33
|
# This is the interface point between Rack and Grape; it accepts a request
|
|
30
34
|
# from Rack and ultimately returns an array of three values: the status,
|
|
31
35
|
# the headers, and the body. See [the rack specification]
|
|
32
|
-
# (
|
|
36
|
+
# (https://github.com/rack/rack/blob/main/SPEC.rdoc) for more.
|
|
33
37
|
# NOTE: This will only be called on an API directly mounted on RACK
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
super
|
|
40
|
-
|
|
41
|
-
api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)
|
|
42
|
-
api.override_all_methods!
|
|
43
|
-
end
|
|
38
|
+
#
|
|
39
|
+
# +inherit_settings+ is left out: it is protected on the base instance, so
|
|
40
|
+
# a delegator would raise NoMethodError just as the missing one does.
|
|
41
|
+
def_delegators :base_instance, :new, :configuration, :call, :change!, :compile!, :recognize_path, :routes,
|
|
42
|
+
:base, :base=, :base_instance?, :reset!, :top_level_setting
|
|
44
43
|
|
|
45
44
|
# Initialize the instance variables on the remountable class, and the base_instance
|
|
46
45
|
# an instance that will be used to create the set up but will not be mounted
|
|
@@ -54,8 +53,8 @@ module Grape
|
|
|
54
53
|
# Redefines all methods so that are forwarded to add_setup and be recorded
|
|
55
54
|
def override_all_methods!
|
|
56
55
|
(base_instance.methods - Class.methods - NON_OVERRIDABLE).each do |method_override|
|
|
57
|
-
define_singleton_method(method_override) do |*args, &block|
|
|
58
|
-
add_setup(method: method_override, args
|
|
56
|
+
define_singleton_method(method_override) do |*args, **kwargs, &block|
|
|
57
|
+
add_setup(method: method_override, args:, kwargs:, block:)
|
|
59
58
|
end
|
|
60
59
|
end
|
|
61
60
|
end
|
|
@@ -67,12 +66,10 @@ module Grape
|
|
|
67
66
|
# `configuration` as normal.
|
|
68
67
|
def configure
|
|
69
68
|
config = @base_instance.configuration
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
config
|
|
75
|
-
end
|
|
69
|
+
return config unless block_given?
|
|
70
|
+
|
|
71
|
+
yield config
|
|
72
|
+
self
|
|
76
73
|
end
|
|
77
74
|
|
|
78
75
|
# The remountable class can have a configuration hash to provide some dynamic class-level variables.
|
|
@@ -80,15 +77,24 @@ module Grape
|
|
|
80
77
|
# depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
|
|
81
78
|
# too much, you may actually want to provide a new API rather than remount it.
|
|
82
79
|
def mount_instance(configuration: nil)
|
|
83
|
-
Class.new(@base_parent)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
instance = Class.new(@base_parent)
|
|
81
|
+
instance.configuration = Grape::Util::EndpointConfiguration.new(configuration || {})
|
|
82
|
+
instance.base = self
|
|
83
|
+
replay_setup_on(instance)
|
|
84
|
+
instance
|
|
88
85
|
end
|
|
89
86
|
|
|
90
87
|
private
|
|
91
88
|
|
|
89
|
+
# When inherited, will create a list of all instances (times the API was mounted)
|
|
90
|
+
# It will listen to the setup required to mount that endpoint, and replicate it on any new instance
|
|
91
|
+
def inherited(api)
|
|
92
|
+
super
|
|
93
|
+
|
|
94
|
+
api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)
|
|
95
|
+
api.override_all_methods!
|
|
96
|
+
end
|
|
97
|
+
|
|
92
98
|
# Replays the set up to produce an API as defined in this class, can be called
|
|
93
99
|
# on classes that inherit from Grape::API
|
|
94
100
|
def replay_setup_on(instance)
|
|
@@ -97,16 +103,8 @@ module Grape
|
|
|
97
103
|
end
|
|
98
104
|
end
|
|
99
105
|
|
|
100
|
-
def instance_for_rack
|
|
101
|
-
if never_mounted?
|
|
102
|
-
base_instance
|
|
103
|
-
else
|
|
104
|
-
mounted_instances.first
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
106
|
# Adds a new stage to the set up require to get a Grape::API up and running
|
|
109
|
-
def add_setup(step)
|
|
107
|
+
def add_setup(**step)
|
|
110
108
|
@setup << step
|
|
111
109
|
last_response = nil
|
|
112
110
|
@instances.each do |instance|
|
|
@@ -130,49 +128,42 @@ module Grape
|
|
|
130
128
|
end
|
|
131
129
|
end
|
|
132
130
|
|
|
133
|
-
def replay_step_on(instance, method:, args:, block:)
|
|
134
|
-
return if skip_immediate_run?(instance, args)
|
|
131
|
+
def replay_step_on(instance, method:, args:, kwargs:, block:)
|
|
132
|
+
return if skip_immediate_run?(instance, args, kwargs)
|
|
135
133
|
|
|
136
134
|
eval_args = evaluate_arguments(instance.configuration, *args)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
135
|
+
eval_kwargs = kwargs.deep_transform_values { |v| evaluate_arguments(instance.configuration, v).first }
|
|
136
|
+
response = instance.__send__(method, *eval_args, **eval_kwargs, &block)
|
|
137
|
+
|
|
138
|
+
return response if skip_immediate_run?(instance, [response], kwargs)
|
|
139
|
+
|
|
140
|
+
evaluate_arguments(instance.configuration, response).first
|
|
143
141
|
end
|
|
144
142
|
|
|
145
143
|
# Skips steps that contain arguments to be lazily executed (on re-mount time)
|
|
146
|
-
def skip_immediate_run?(instance, args)
|
|
144
|
+
def skip_immediate_run?(instance, args, kwargs)
|
|
147
145
|
instance.base_instance? &&
|
|
148
|
-
(any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) })
|
|
146
|
+
(any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) } || any_lazy?(kwargs.values))
|
|
149
147
|
end
|
|
150
148
|
|
|
151
149
|
def any_lazy?(args)
|
|
152
|
-
args.any?
|
|
150
|
+
args.any?(Grape::Util::Lazy::Base)
|
|
153
151
|
end
|
|
154
152
|
|
|
155
153
|
def evaluate_arguments(configuration, *args)
|
|
156
154
|
args.map do |argument|
|
|
157
|
-
|
|
155
|
+
case argument
|
|
156
|
+
when Grape::Util::Lazy::Base
|
|
158
157
|
argument.evaluate_from(configuration)
|
|
159
|
-
|
|
158
|
+
when Hash
|
|
160
159
|
argument.transform_values { |value| evaluate_arguments(configuration, value).first }
|
|
161
|
-
|
|
160
|
+
when Array
|
|
162
161
|
evaluate_arguments(configuration, *argument)
|
|
163
162
|
else
|
|
164
163
|
argument
|
|
165
164
|
end
|
|
166
165
|
end
|
|
167
166
|
end
|
|
168
|
-
|
|
169
|
-
def never_mounted?
|
|
170
|
-
mounted_instances.empty?
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def mounted_instances
|
|
174
|
-
instances - [base_instance]
|
|
175
|
-
end
|
|
176
167
|
end
|
|
177
168
|
end
|
|
178
169
|
end
|
data/lib/grape/content_types.rb
CHANGED
|
@@ -22,9 +22,64 @@ module Grape
|
|
|
22
22
|
def mime_types_for(from_settings)
|
|
23
23
|
return MIME_TYPES if from_settings == Grape::ContentTypes::DEFAULTS
|
|
24
24
|
|
|
25
|
-
from_settings
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
MimeTypesCache[from_settings]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Every format under both spellings in one plain Hash, so a lookup is a
|
|
29
|
+
# single +Hash#[]+. +HashWithIndifferentAccess+ converted the key on every
|
|
30
|
+
# read instead, and this is read two or three times per request — to
|
|
31
|
+
# negotiate the format, and again to set the response content type.
|
|
32
|
+
#
|
|
33
|
+
# Keys arrive as Symbols: the +content_type+ DSL symbolizes what it is
|
|
34
|
+
# given and the defaults are Symbols. The key is stored as it came too,
|
|
35
|
+
# so a middleware constructed directly with String keys still answers to
|
|
36
|
+
# either spelling, as the indifferent hash did.
|
|
37
|
+
def lookup_for(from_settings)
|
|
38
|
+
LookupCache[from_settings]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The media type of a content-type header: the part before any `;`
|
|
42
|
+
# parameters, with surrounding whitespace removed
|
|
43
|
+
# (e.g. `'text/html'` for `'text/html; charset=utf-8'`). Returns nil for a
|
|
44
|
+
# nil content type. Skips the split (and its allocation) when there are no
|
|
45
|
+
# parameters, which is the common case.
|
|
46
|
+
def media_type(content_type)
|
|
47
|
+
return if content_type.nil?
|
|
48
|
+
|
|
49
|
+
base = content_type.include?(';') ? content_type.split(';', 2).first : content_type
|
|
50
|
+
base.strip
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Both tables below are derived from nothing but the content-type registry,
|
|
54
|
+
# and one content-type-aware middleware is built per API instance — so an
|
|
55
|
+
# app mounting N APIs held N copies of tables it only ever reads. Keying
|
|
56
|
+
# the cache on the registry itself collapses them: Hash keys compare by
|
|
57
|
+
# value, so every API that registers the same content types shares one
|
|
58
|
+
# table.
|
|
59
|
+
#
|
|
60
|
+
# Both the key and the table are frozen: the caller's registry stays
|
|
61
|
+
# reachable (through +middleware.options[:content_types]+, among others)
|
|
62
|
+
# and mutating a live key would corrupt a cache that is now shared
|
|
63
|
+
# process-wide.
|
|
64
|
+
|
|
65
|
+
class MimeTypesCache < Grape::Util::Cache
|
|
66
|
+
def initialize
|
|
67
|
+
super
|
|
68
|
+
@cache = Hash.new do |h, from_settings|
|
|
69
|
+
h[from_settings.dup.freeze] = from_settings.invert.transform_keys! { |mime_type| Grape::ContentTypes.media_type(mime_type) }.freeze
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class LookupCache < Grape::Util::Cache
|
|
75
|
+
def initialize
|
|
76
|
+
super
|
|
77
|
+
@cache = Hash.new do |h, from_settings|
|
|
78
|
+
h[from_settings.dup.freeze] = from_settings.each_with_object({}) do |(format, media_type), lookup|
|
|
79
|
+
lookup[format] = media_type
|
|
80
|
+
lookup[format.is_a?(String) ? format.to_sym : format.to_s] = media_type
|
|
81
|
+
end.freeze
|
|
82
|
+
end
|
|
28
83
|
end
|
|
29
84
|
end
|
|
30
85
|
end
|
data/lib/grape/cookies.rb
CHANGED
|
@@ -13,7 +13,7 @@ module Grape
|
|
|
13
13
|
def_delegators :cookies, :[], :each
|
|
14
14
|
|
|
15
15
|
def initialize(rack_cookies)
|
|
16
|
-
@cookies = rack_cookies
|
|
16
|
+
@cookies = ActiveSupport::HashWithIndifferentAccess.new(rack_cookies)
|
|
17
17
|
@send_cookies = nil
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -37,11 +37,7 @@ module Grape
|
|
|
37
37
|
|
|
38
38
|
private
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
return @cookies unless @cookies.is_a?(Proc)
|
|
42
|
-
|
|
43
|
-
@cookies = @cookies.call.with_indifferent_access
|
|
44
|
-
end
|
|
40
|
+
attr_reader :cookies
|
|
45
41
|
|
|
46
42
|
def send_cookies
|
|
47
43
|
@send_cookies ||= Set.new
|