grape 1.2.3 → 1.2.4
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 +14 -1
- data/Gemfile.lock +12 -12
- data/LICENSE +1 -1
- data/README.md +103 -23
- data/UPGRADING.md +32 -0
- data/lib/grape.rb +2 -0
- data/lib/grape/api.rb +36 -9
- data/lib/grape/api/instance.rb +25 -2
- data/lib/grape/dsl/callbacks.rb +20 -0
- data/lib/grape/dsl/desc.rb +6 -2
- data/lib/grape/dsl/inside_route.rb +12 -6
- data/lib/grape/endpoint.rb +36 -28
- data/lib/grape/middleware/error.rb +1 -2
- data/lib/grape/util/endpoint_configuration.rb +6 -0
- data/lib/grape/util/lazy_value.rb +90 -0
- data/lib/grape/validations/params_scope.rb +7 -2
- data/lib/grape/validations/validators/as.rb +2 -2
- data/lib/grape/version.rb +1 -1
- data/pkg/grape-1.2.3.gem +0 -0
- data/spec/grape/api_remount_spec.rb +235 -12
- data/spec/grape/api_spec.rb +210 -0
- data/spec/grape/endpoint_spec.rb +39 -3
- data/spec/grape/validations/params_scope_spec.rb +32 -12
- metadata +5 -3
- data/gemfiles/rails_edge.gemfile.lock +0 -335
data/lib/grape.rb
CHANGED
@@ -195,6 +195,8 @@ end
|
|
195
195
|
|
196
196
|
require 'grape/config'
|
197
197
|
require 'grape/util/content_types'
|
198
|
+
require 'grape/util/lazy_value'
|
199
|
+
require 'grape/util/endpoint_configuration'
|
198
200
|
|
199
201
|
require 'grape/validations/validators/base'
|
200
202
|
require 'grape/validations/attributes_iterator'
|
data/lib/grape/api.rb
CHANGED
@@ -6,7 +6,7 @@ module Grape
|
|
6
6
|
# should subclass this class in order to build an API.
|
7
7
|
class API
|
8
8
|
# Class methods that we want to call on the API rather than on the API object
|
9
|
-
NON_OVERRIDABLE = (Class.new.methods + %i[call call!]).freeze
|
9
|
+
NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration]).freeze
|
10
10
|
|
11
11
|
class << self
|
12
12
|
attr_accessor :base_instance, :instances
|
@@ -64,8 +64,6 @@ module Grape
|
|
64
64
|
def const_missing(*args)
|
65
65
|
if base_instance.const_defined?(*args)
|
66
66
|
base_instance.const_get(*args)
|
67
|
-
elsif parent && parent.const_defined?(*args)
|
68
|
-
parent.const_get(*args)
|
69
67
|
else
|
70
68
|
super
|
71
69
|
end
|
@@ -77,7 +75,7 @@ module Grape
|
|
77
75
|
# too much, you may actually want to provide a new API rather than remount it.
|
78
76
|
def mount_instance(opts = {})
|
79
77
|
instance = Class.new(@base_parent)
|
80
|
-
instance.configuration = opts[:configuration] || {}
|
78
|
+
instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {})
|
81
79
|
instance.base = self
|
82
80
|
replay_setup_on(instance)
|
83
81
|
instance
|
@@ -86,8 +84,8 @@ module Grape
|
|
86
84
|
# Replays the set up to produce an API as defined in this class, can be called
|
87
85
|
# on classes that inherit from Grape::API
|
88
86
|
def replay_setup_on(instance)
|
89
|
-
@setup.each do |
|
90
|
-
instance
|
87
|
+
@setup.each do |setup_step|
|
88
|
+
replay_step_on(instance, setup_step)
|
91
89
|
end
|
92
90
|
end
|
93
91
|
|
@@ -112,14 +110,43 @@ module Grape
|
|
112
110
|
|
113
111
|
# Adds a new stage to the set up require to get a Grape::API up and running
|
114
112
|
def add_setup(method, *args, &block)
|
115
|
-
|
116
|
-
@setup <<
|
113
|
+
setup_step = { method: method, args: args, block: block }
|
114
|
+
@setup << setup_step
|
117
115
|
last_response = nil
|
118
116
|
@instances.each do |instance|
|
119
|
-
last_response = instance
|
117
|
+
last_response = replay_step_on(instance, setup_step)
|
120
118
|
end
|
121
119
|
last_response
|
122
120
|
end
|
121
|
+
|
122
|
+
def replay_step_on(instance, setup_step)
|
123
|
+
return if skip_immediate_run?(instance, setup_step[:args])
|
124
|
+
instance.send(setup_step[:method], *evaluate_arguments(instance.configuration, *setup_step[:args]), &setup_step[:block])
|
125
|
+
end
|
126
|
+
|
127
|
+
# Skips steps that contain arguments to be lazily executed (on re-mount time)
|
128
|
+
def skip_immediate_run?(instance, args)
|
129
|
+
instance.base_instance? &&
|
130
|
+
(any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) })
|
131
|
+
end
|
132
|
+
|
133
|
+
def any_lazy?(args)
|
134
|
+
args.any? { |argument| argument.respond_to?(:lazy?) && argument.lazy? }
|
135
|
+
end
|
136
|
+
|
137
|
+
def evaluate_arguments(configuration, *args)
|
138
|
+
args.map do |argument|
|
139
|
+
if argument.respond_to?(:lazy?) && argument.lazy?
|
140
|
+
configuration.fetch(argument.access_keys).evaluate
|
141
|
+
elsif argument.is_a?(Hash)
|
142
|
+
argument.map { |key, value| [key, evaluate_arguments(configuration, value).first] }.to_h
|
143
|
+
elsif argument.is_a?(Array)
|
144
|
+
evaluate_arguments(configuration, *argument)
|
145
|
+
else
|
146
|
+
argument
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
123
150
|
end
|
124
151
|
end
|
125
152
|
end
|
data/lib/grape/api/instance.rb
CHANGED
@@ -12,6 +12,15 @@ module Grape
|
|
12
12
|
attr_reader :base
|
13
13
|
attr_accessor :configuration
|
14
14
|
|
15
|
+
def given(conditional_option, &block)
|
16
|
+
evaluate_as_instance_with_configuration(block) if conditional_option && block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def mounted(&block)
|
20
|
+
return if base_instance?
|
21
|
+
evaluate_as_instance_with_configuration(block)
|
22
|
+
end
|
23
|
+
|
15
24
|
def base=(grape_api)
|
16
25
|
@base = grape_api
|
17
26
|
grape_api.instances << self
|
@@ -21,6 +30,10 @@ module Grape
|
|
21
30
|
(base && base.to_s) || super
|
22
31
|
end
|
23
32
|
|
33
|
+
def base_instance?
|
34
|
+
self == base.base_instance
|
35
|
+
end
|
36
|
+
|
24
37
|
# A class-level lock to ensure the API is not compiled by multiple
|
25
38
|
# threads simultaneously within the same process.
|
26
39
|
LOCK = Mutex.new
|
@@ -84,14 +97,24 @@ module Grape
|
|
84
97
|
def nest(*blocks, &block)
|
85
98
|
blocks.reject!(&:nil?)
|
86
99
|
if blocks.any?
|
87
|
-
|
88
|
-
blocks.each { |b|
|
100
|
+
evaluate_as_instance_with_configuration(block) if block_given?
|
101
|
+
blocks.each { |b| evaluate_as_instance_with_configuration(b) }
|
89
102
|
reset_validations!
|
90
103
|
else
|
91
104
|
instance_eval(&block)
|
92
105
|
end
|
93
106
|
end
|
94
107
|
|
108
|
+
def evaluate_as_instance_with_configuration(block)
|
109
|
+
value_for_configuration = configuration
|
110
|
+
if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
|
111
|
+
self.configuration = value_for_configuration.evaluate
|
112
|
+
end
|
113
|
+
response = instance_eval(&block)
|
114
|
+
self.configuration = value_for_configuration
|
115
|
+
response
|
116
|
+
end
|
117
|
+
|
95
118
|
def inherited(subclass)
|
96
119
|
subclass.reset!
|
97
120
|
subclass.logger = logger.clone
|
data/lib/grape/dsl/callbacks.rb
CHANGED
@@ -43,6 +43,26 @@ module Grape
|
|
43
43
|
def after(&block)
|
44
44
|
namespace_stackable(:afters, block)
|
45
45
|
end
|
46
|
+
|
47
|
+
# Allows you to specify a something that will always be executed after a call
|
48
|
+
# API call. Unlike the `after` block, this code will run even on
|
49
|
+
# unsuccesful requests.
|
50
|
+
# @example
|
51
|
+
# class ExampleAPI < Grape::API
|
52
|
+
# before do
|
53
|
+
# ApiLogger.start
|
54
|
+
# end
|
55
|
+
# finally do
|
56
|
+
# ApiLogger.close
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# This will make sure that the ApiLogger is opened and close around every
|
61
|
+
# request
|
62
|
+
# @param ensured_block [Proc] The block to be executed after every api_call
|
63
|
+
def finally(&block)
|
64
|
+
namespace_stackable(:finallies, block)
|
65
|
+
end
|
46
66
|
end
|
47
67
|
end
|
48
68
|
end
|
data/lib/grape/dsl/desc.rb
CHANGED
@@ -49,7 +49,8 @@ module Grape
|
|
49
49
|
#
|
50
50
|
def desc(description, options = {}, &config_block)
|
51
51
|
if block_given?
|
52
|
-
|
52
|
+
configuration = defined?(configuration) && configuration.respond_to?(:evaluate) ? configuration.evaluate : {}
|
53
|
+
config_class = desc_container(configuration)
|
53
54
|
|
54
55
|
config_class.configure do
|
55
56
|
description description
|
@@ -84,7 +85,7 @@ module Grape
|
|
84
85
|
end
|
85
86
|
|
86
87
|
# Returns an object which configures itself via an instance-context DSL.
|
87
|
-
def desc_container
|
88
|
+
def desc_container(endpoint_configuration)
|
88
89
|
Module.new do
|
89
90
|
include Grape::Util::StrictHashConfiguration.module(
|
90
91
|
:summary,
|
@@ -105,6 +106,9 @@ module Grape
|
|
105
106
|
:security,
|
106
107
|
:tags
|
107
108
|
)
|
109
|
+
config_context.define_singleton_method(:configuration) do
|
110
|
+
endpoint_configuration
|
111
|
+
end
|
108
112
|
|
109
113
|
def config_context.success(*args)
|
110
114
|
entity(*args)
|
@@ -61,13 +61,13 @@ module Grape
|
|
61
61
|
else
|
62
62
|
# If it is not a Hash then it does not have children.
|
63
63
|
# Find its value or set it to nil.
|
64
|
-
|
65
|
-
|
64
|
+
has_renaming = route_setting(:renamed_params) && route_setting(:renamed_params).find { |current| current[declared_param] }
|
65
|
+
param_renaming = has_renaming[declared_param] if has_renaming
|
66
66
|
|
67
|
-
next unless options[:include_missing] || passed_params.key?(declared_param) || (
|
67
|
+
next unless options[:include_missing] || passed_params.key?(declared_param) || (param_renaming && passed_params.key?(param_renaming))
|
68
68
|
|
69
|
-
if
|
70
|
-
memo[optioned_param_key(
|
69
|
+
if param_renaming
|
70
|
+
memo[optioned_param_key(param_renaming, options)] = passed_params[param_renaming]
|
71
71
|
else
|
72
72
|
memo[optioned_param_key(declared_param, options)] = passed_params[declared_param]
|
73
73
|
end
|
@@ -129,13 +129,19 @@ module Grape
|
|
129
129
|
env[Grape::Env::API_VERSION]
|
130
130
|
end
|
131
131
|
|
132
|
+
def configuration
|
133
|
+
options[:for].configuration.evaluate
|
134
|
+
end
|
135
|
+
|
132
136
|
# End the request and display an error to the
|
133
137
|
# end user with the specified message.
|
134
138
|
#
|
135
139
|
# @param message [String] The message to display.
|
136
140
|
# @param status [Integer] the HTTP Status Code. Defaults to default_error_status, 500 if not set.
|
137
|
-
|
141
|
+
# @param additional_headers [Hash] Addtional headers for the response.
|
142
|
+
def error!(message, status = nil, additional_headers = nil)
|
138
143
|
self.status(status || namespace_inheritable(:default_error_status))
|
144
|
+
headers = additional_headers.present? ? header.merge(additional_headers) : header
|
139
145
|
throw :error, message: message, status: self.status, headers: headers
|
140
146
|
end
|
141
147
|
|
data/lib/grape/endpoint.rb
CHANGED
@@ -245,33 +245,37 @@ module Grape
|
|
245
245
|
@request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with))
|
246
246
|
@params = @request.params
|
247
247
|
@headers = @request.headers
|
248
|
+
begin
|
249
|
+
cookies.read(@request)
|
250
|
+
self.class.run_before_each(self)
|
251
|
+
run_filters befores, :before
|
252
|
+
|
253
|
+
if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
|
254
|
+
raise Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => allowed_methods) unless options?
|
255
|
+
header 'Allow', allowed_methods
|
256
|
+
response_object = ''
|
257
|
+
status 204
|
258
|
+
else
|
259
|
+
run_filters before_validations, :before_validation
|
260
|
+
run_validators validations, request
|
261
|
+
remove_renamed_params
|
262
|
+
run_filters after_validations, :after_validation
|
263
|
+
response_object = @block ? @block.call(self) : nil
|
264
|
+
end
|
248
265
|
|
249
|
-
|
250
|
-
|
251
|
-
run_filters befores, :before
|
252
|
-
|
253
|
-
if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
|
254
|
-
raise Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => allowed_methods) unless options?
|
255
|
-
header 'Allow', allowed_methods
|
256
|
-
response_object = ''
|
257
|
-
status 204
|
258
|
-
else
|
259
|
-
run_filters before_validations, :before_validation
|
260
|
-
run_validators validations, request
|
261
|
-
remove_aliased_params
|
262
|
-
run_filters after_validations, :after_validation
|
263
|
-
response_object = @block ? @block.call(self) : nil
|
264
|
-
end
|
266
|
+
run_filters afters, :after
|
267
|
+
cookies.write(header)
|
265
268
|
|
266
|
-
|
267
|
-
|
269
|
+
# status verifies body presence when DELETE
|
270
|
+
@body ||= response_object
|
268
271
|
|
269
|
-
|
270
|
-
|
272
|
+
# The body commonly is an Array of Strings, the application instance itself, or a File-like object
|
273
|
+
response_object = file || [body]
|
271
274
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
+
[status, header, response_object]
|
276
|
+
ensure
|
277
|
+
run_filters finallies, :finally
|
278
|
+
end
|
275
279
|
end
|
276
280
|
end
|
277
281
|
|
@@ -320,14 +324,14 @@ module Grape
|
|
320
324
|
Module.new { helpers.each { |mod_to_include| include mod_to_include } }
|
321
325
|
end
|
322
326
|
|
323
|
-
def
|
324
|
-
return unless route_setting(:
|
325
|
-
route_setting(:
|
326
|
-
@params.delete(
|
327
|
+
def remove_renamed_params
|
328
|
+
return unless route_setting(:renamed_params)
|
329
|
+
route_setting(:renamed_params).flat_map(&:keys).each do |renamed_param|
|
330
|
+
@params.delete(renamed_param)
|
327
331
|
end
|
328
332
|
end
|
329
333
|
|
330
|
-
private :build_stack, :build_helpers, :
|
334
|
+
private :build_stack, :build_helpers, :remove_renamed_params
|
331
335
|
|
332
336
|
def helpers
|
333
337
|
lazy_initialize! && @helpers
|
@@ -392,6 +396,10 @@ module Grape
|
|
392
396
|
namespace_stackable(:afters) || []
|
393
397
|
end
|
394
398
|
|
399
|
+
def finallies
|
400
|
+
namespace_stackable(:finallies) || []
|
401
|
+
end
|
402
|
+
|
395
403
|
def validations
|
396
404
|
route_setting(:saved_validations) || []
|
397
405
|
end
|
@@ -21,7 +21,7 @@ module Grape
|
|
21
21
|
},
|
22
22
|
rescue_handlers: {}, # rescue handler blocks
|
23
23
|
base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
|
24
|
-
all_rescue_handler: nil # rescue handler block to rescue from all exceptions
|
24
|
+
all_rescue_handler: nil, # rescue handler block to rescue from all exceptions
|
25
25
|
}
|
26
26
|
end
|
27
27
|
|
@@ -32,7 +32,6 @@ module Grape
|
|
32
32
|
|
33
33
|
def call!(env)
|
34
34
|
@env = env
|
35
|
-
|
36
35
|
begin
|
37
36
|
error_response(catch(:error) do
|
38
37
|
return @app.call(@env)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Grape
|
2
|
+
module Util
|
3
|
+
class LazyValue
|
4
|
+
attr_reader :access_keys
|
5
|
+
def initialize(value, access_keys = [])
|
6
|
+
@value = value
|
7
|
+
@access_keys = access_keys
|
8
|
+
end
|
9
|
+
|
10
|
+
def evaluate
|
11
|
+
@value
|
12
|
+
end
|
13
|
+
|
14
|
+
def lazy?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def reached_by(parent_access_keys, access_key)
|
19
|
+
@access_keys = parent_access_keys + [access_key]
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
evaluate.to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class LazyValueEnumerable < LazyValue
|
29
|
+
def [](key)
|
30
|
+
if @value_hash[key].nil?
|
31
|
+
LazyValue.new(nil).reached_by(access_keys, key)
|
32
|
+
else
|
33
|
+
@value_hash[key].reached_by(access_keys, key)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def fetch(access_keys)
|
38
|
+
fetched_keys = access_keys.dup
|
39
|
+
value = self[fetched_keys.shift]
|
40
|
+
fetched_keys.any? ? value.fetch(fetched_keys) : value
|
41
|
+
end
|
42
|
+
|
43
|
+
def []=(key, value)
|
44
|
+
@value_hash[key] = if value.is_a?(Hash)
|
45
|
+
LazyValueHash.new(value)
|
46
|
+
elsif value.is_a?(Array)
|
47
|
+
LazyValueArray.new(value)
|
48
|
+
else
|
49
|
+
LazyValue.new(value)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class LazyValueArray < LazyValueEnumerable
|
55
|
+
def initialize(array)
|
56
|
+
super
|
57
|
+
@value_hash = []
|
58
|
+
array.each_with_index do |value, index|
|
59
|
+
self[index] = value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def evaluate
|
64
|
+
evaluated = []
|
65
|
+
@value_hash.each_with_index do |value, index|
|
66
|
+
evaluated[index] = value.evaluate
|
67
|
+
end
|
68
|
+
evaluated
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class LazyValueHash < LazyValueEnumerable
|
73
|
+
def initialize(hash)
|
74
|
+
super
|
75
|
+
@value_hash = {}.with_indifferent_access
|
76
|
+
hash.each do |key, value|
|
77
|
+
self[key] = value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def evaluate
|
82
|
+
evaluated = {}.with_indifferent_access
|
83
|
+
@value_hash.each do |key, value|
|
84
|
+
evaluated[key] = value.evaluate
|
85
|
+
end
|
86
|
+
evaluated
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|