grape 1.2.3 → 1.2.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|