grape 1.2.3 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'
@@ -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 |setup_stage|
90
- instance.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block])
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
- setup_stage = { method: method, args: args, block: block }
116
- @setup << setup_stage
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.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block])
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
@@ -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
- instance_eval(&block) if block_given?
88
- blocks.each { |b| instance_eval(&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
@@ -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
@@ -49,7 +49,8 @@ module Grape
49
49
  #
50
50
  def desc(description, options = {}, &config_block)
51
51
  if block_given?
52
- config_class = desc_container
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
- has_alias = route_setting(:aliased_params) && route_setting(:aliased_params).find { |current| current[declared_param] }
65
- param_alias = has_alias[declared_param] if has_alias
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) || (param_alias && passed_params.key?(param_alias))
67
+ next unless options[:include_missing] || passed_params.key?(declared_param) || (param_renaming && passed_params.key?(param_renaming))
68
68
 
69
- if param_alias
70
- memo[optioned_param_key(param_alias, options)] = passed_params[param_alias]
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
- def error!(message, status = nil, headers = nil)
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
 
@@ -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
- 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_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
- run_filters afters, :after
267
- cookies.write(header)
269
+ # status verifies body presence when DELETE
270
+ @body ||= response_object
268
271
 
269
- # status verifies body presence when DELETE
270
- @body ||= response_object
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
- # The Body commonly is an Array of Strings, the application instance itself, or a File-like object
273
- response_object = file || [body]
274
- [status, header, response_object]
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 remove_aliased_params
324
- return unless route_setting(:aliased_params)
325
- route_setting(:aliased_params).flat_map(&:keys).each do |aliased_param|
326
- @params.delete(aliased_param)
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, :remove_aliased_params
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,6 @@
1
+ module Grape
2
+ module Util
3
+ class EndpointConfiguration < LazyValueHash
4
+ end
5
+ end
6
+ end
@@ -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