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.
@@ -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