grape 1.2.4 → 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -4
  3. data/README.md +144 -4
  4. data/grape.gemspec +3 -1
  5. data/lib/grape.rb +94 -66
  6. data/lib/grape/api.rb +46 -4
  7. data/lib/grape/api/instance.rb +23 -12
  8. data/lib/grape/dsl/desc.rb +11 -2
  9. data/lib/grape/dsl/validations.rb +4 -3
  10. data/lib/grape/eager_load.rb +18 -0
  11. data/lib/grape/endpoint.rb +3 -3
  12. data/lib/grape/error_formatter.rb +1 -1
  13. data/lib/grape/exceptions/validation_errors.rb +4 -2
  14. data/lib/grape/formatter.rb +1 -1
  15. data/lib/grape/middleware/auth/base.rb +2 -4
  16. data/lib/grape/middleware/base.rb +2 -0
  17. data/lib/grape/middleware/helpers.rb +10 -0
  18. data/lib/grape/parser.rb +1 -1
  19. data/lib/grape/util/base_inheritable.rb +34 -0
  20. data/lib/grape/util/inheritable_values.rb +5 -25
  21. data/lib/grape/util/lazy_block.rb +25 -0
  22. data/lib/grape/util/lazy_value.rb +5 -0
  23. data/lib/grape/util/reverse_stackable_values.rb +7 -36
  24. data/lib/grape/util/stackable_values.rb +19 -22
  25. data/lib/grape/validations/attributes_iterator.rb +5 -3
  26. data/lib/grape/validations/multiple_attributes_iterator.rb +11 -0
  27. data/lib/grape/validations/params_scope.rb +12 -12
  28. data/lib/grape/validations/single_attribute_iterator.rb +13 -0
  29. data/lib/grape/validations/validator_factory.rb +6 -11
  30. data/lib/grape/validations/validators/all_or_none.rb +6 -13
  31. data/lib/grape/validations/validators/at_least_one_of.rb +5 -13
  32. data/lib/grape/validations/validators/base.rb +11 -10
  33. data/lib/grape/validations/validators/coerce.rb +4 -0
  34. data/lib/grape/validations/validators/default.rb +1 -1
  35. data/lib/grape/validations/validators/exactly_one_of.rb +6 -23
  36. data/lib/grape/validations/validators/multiple_params_base.rb +14 -10
  37. data/lib/grape/validations/validators/mutual_exclusion.rb +6 -18
  38. data/lib/grape/version.rb +1 -1
  39. data/spec/grape/api/defines_boolean_in_params_spec.rb +37 -0
  40. data/spec/grape/api_remount_spec.rb +158 -0
  41. data/spec/grape/api_spec.rb +72 -0
  42. data/spec/grape/endpoint_spec.rb +1 -1
  43. data/spec/grape/exceptions/base_spec.rb +4 -0
  44. data/spec/grape/exceptions/validation_errors_spec.rb +6 -4
  45. data/spec/grape/integration/rack_spec.rb +22 -6
  46. data/spec/grape/middleware/base_spec.rb +8 -0
  47. data/spec/grape/middleware/formatter_spec.rb +11 -1
  48. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +29 -0
  49. data/spec/grape/validations/params_scope_spec.rb +13 -0
  50. data/spec/grape/validations/single_attribute_iterator_spec.rb +33 -0
  51. data/spec/grape/validations/validators/all_or_none_spec.rb +138 -30
  52. data/spec/grape/validations/validators/at_least_one_of_spec.rb +173 -29
  53. data/spec/grape/validations/validators/coerce_spec.rb +6 -2
  54. data/spec/grape/validations/validators/exactly_one_of_spec.rb +202 -38
  55. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +184 -27
  56. data/spec/grape/validations_spec.rb +32 -20
  57. metadata +103 -115
  58. data/Appraisals +0 -28
  59. data/Dangerfile +0 -2
  60. data/Gemfile +0 -33
  61. data/Gemfile.lock +0 -231
  62. data/Guardfile +0 -10
  63. data/RELEASING.md +0 -111
  64. data/Rakefile +0 -25
  65. data/benchmark/simple.rb +0 -27
  66. data/benchmark/simple_with_type_coercer.rb +0 -22
  67. data/gemfiles/multi_json.gemfile +0 -35
  68. data/gemfiles/multi_xml.gemfile +0 -35
  69. data/gemfiles/rack_1.5.2.gemfile.lock +0 -232
  70. data/gemfiles/rack_edge.gemfile +0 -35
  71. data/gemfiles/rails_3.gemfile +0 -36
  72. data/gemfiles/rails_3.gemfile.lock +0 -288
  73. data/gemfiles/rails_4.gemfile +0 -35
  74. data/gemfiles/rails_4.gemfile.lock +0 -280
  75. data/gemfiles/rails_5.gemfile +0 -35
  76. data/gemfiles/rails_5.gemfile.lock +0 -312
  77. data/gemfiles/rails_edge.gemfile +0 -35
  78. data/pkg/grape-1.2.0.gem +0 -0
  79. data/pkg/grape-1.2.1.gem +0 -0
  80. data/pkg/grape-1.2.3.gem +0 -0
@@ -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! configuration]).freeze
9
+ NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile!]).freeze
10
10
 
11
11
  class << self
12
12
  attr_accessor :base_instance, :instances
@@ -42,13 +42,28 @@ module Grape
42
42
  end
43
43
  end
44
44
 
45
+ # Configure an API from the outside. If a block is given, it'll pass a
46
+ # configuration hash to the block which you can use to configure your
47
+ # API. If no block is given, returns the configuration hash.
48
+ # The configuration set here is accessible from inside an API with
49
+ # `configuration` as normal.
50
+ def configure
51
+ config = @base_instance.configuration
52
+ if block_given?
53
+ yield config
54
+ self
55
+ else
56
+ config
57
+ end
58
+ end
59
+
45
60
  # This is the interface point between Rack and Grape; it accepts a request
46
61
  # from Rack and ultimately returns an array of three values: the status,
47
62
  # the headers, and the body. See [the rack specification]
48
63
  # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
49
64
  # NOTE: This will only be called on an API directly mounted on RACK
50
65
  def call(*args, &block)
51
- base_instance.call(*args, &block)
66
+ instance_for_rack.call(*args, &block)
52
67
  end
53
68
 
54
69
  # Allows an API to itself be inheritable:
@@ -106,8 +121,21 @@ module Grape
106
121
  end
107
122
  end
108
123
 
124
+ def compile!
125
+ require 'grape/eager_load'
126
+ instance_for_rack.compile! # See API::Instance.compile!
127
+ end
128
+
109
129
  private
110
130
 
131
+ def instance_for_rack
132
+ if never_mounted?
133
+ base_instance
134
+ else
135
+ mounted_instances.first
136
+ end
137
+ end
138
+
111
139
  # Adds a new stage to the set up require to get a Grape::API up and running
112
140
  def add_setup(method, *args, &block)
113
141
  setup_step = { method: method, args: args, block: block }
@@ -121,7 +149,13 @@ module Grape
121
149
 
122
150
  def replay_step_on(instance, setup_step)
123
151
  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])
152
+ args = evaluate_arguments(instance.configuration, *setup_step[:args])
153
+ response = instance.send(setup_step[:method], *args, &setup_step[:block])
154
+ if skip_immediate_run?(instance, [response])
155
+ response
156
+ else
157
+ evaluate_arguments(instance.configuration, response).first
158
+ end
125
159
  end
126
160
 
127
161
  # Skips steps that contain arguments to be lazily executed (on re-mount time)
@@ -137,7 +171,7 @@ module Grape
137
171
  def evaluate_arguments(configuration, *args)
138
172
  args.map do |argument|
139
173
  if argument.respond_to?(:lazy?) && argument.lazy?
140
- configuration.fetch(argument.access_keys).evaluate
174
+ argument.evaluate_from(configuration)
141
175
  elsif argument.is_a?(Hash)
142
176
  argument.map { |key, value| [key, evaluate_arguments(configuration, value).first] }.to_h
143
177
  elsif argument.is_a?(Array)
@@ -147,6 +181,14 @@ module Grape
147
181
  end
148
182
  end
149
183
  end
184
+
185
+ def never_mounted?
186
+ mounted_instances.empty?
187
+ end
188
+
189
+ def mounted_instances
190
+ instances - [base_instance]
191
+ end
150
192
  end
151
193
  end
152
194
  end
@@ -13,12 +13,11 @@ module Grape
13
13
  attr_accessor :configuration
14
14
 
15
15
  def given(conditional_option, &block)
16
- evaluate_as_instance_with_configuration(block) if conditional_option && block_given?
16
+ evaluate_as_instance_with_configuration(block, lazy: true) if conditional_option && block_given?
17
17
  end
18
18
 
19
19
  def mounted(&block)
20
- return if base_instance?
21
- evaluate_as_instance_with_configuration(block)
20
+ evaluate_as_instance_with_configuration(block, lazy: true)
22
21
  end
23
22
 
24
23
  def base=(grape_api)
@@ -61,7 +60,7 @@ module Grape
61
60
  # the headers, and the body. See [the rack specification]
62
61
  # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
63
62
  def call(env)
64
- LOCK.synchronize { compile } unless instance
63
+ compile!
65
64
  call!(env)
66
65
  end
67
66
 
@@ -79,9 +78,14 @@ module Grape
79
78
  end
80
79
  end
81
80
 
81
+ def compile!
82
+ return if instance
83
+ LOCK.synchronize { compile unless instance }
84
+ end
85
+
82
86
  # see Grape::Router#recognize_path
83
87
  def recognize_path(path)
84
- LOCK.synchronize { compile } unless instance
88
+ compile!
85
89
  instance.router.recognize_path(path)
86
90
  end
87
91
 
@@ -105,14 +109,21 @@ module Grape
105
109
  end
106
110
  end
107
111
 
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
+ def evaluate_as_instance_with_configuration(block, lazy: false)
113
+ lazy_block = Grape::Util::LazyBlock.new do |configuration|
114
+ value_for_configuration = configuration
115
+ if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
116
+ self.configuration = value_for_configuration.evaluate
117
+ end
118
+ response = instance_eval(&block)
119
+ self.configuration = value_for_configuration
120
+ response
121
+ end
122
+ if base_instance? && lazy
123
+ lazy_block
124
+ else
125
+ lazy_block.evaluate_from(configuration)
112
126
  end
113
- response = instance_eval(&block)
114
- self.configuration = value_for_configuration
115
- response
116
127
  end
117
128
 
118
129
  def inherited(subclass)
@@ -49,8 +49,17 @@ module Grape
49
49
  #
50
50
  def desc(description, options = {}, &config_block)
51
51
  if block_given?
52
- configuration = defined?(configuration) && configuration.respond_to?(:evaluate) ? configuration.evaluate : {}
53
- config_class = desc_container(configuration)
52
+ endpoint_configuration = if defined?(configuration)
53
+ # When the instance is mounted - the configuration is executed on mount time
54
+ if configuration.respond_to?(:evaluate)
55
+ configuration.evaluate
56
+ # Within `given` or `mounted blocks` the configuration is already evaluated
57
+ elsif configuration.is_a?(Hash)
58
+ configuration
59
+ end
60
+ end
61
+ endpoint_configuration ||= {}
62
+ config_class = desc_container(endpoint_configuration)
54
63
 
55
64
  config_class.configure do
56
65
  description description
@@ -27,10 +27,11 @@ module Grape
27
27
  setting = description_field(:params)
28
28
  setting ||= description_field(:params, {})
29
29
  Array(names).each do |name|
30
- setting[name[:full_name].to_s] ||= {}
31
- setting[name[:full_name].to_s].merge!(opts)
30
+ full_name = name[:full_name].to_s
31
+ setting[full_name] ||= {}
32
+ setting[full_name].merge!(opts)
32
33
 
33
- namespace_stackable(:params, name[:full_name].to_s => opts)
34
+ namespace_stackable(:params, full_name => opts)
34
35
  end
35
36
  end
36
37
  end
@@ -0,0 +1,18 @@
1
+ Grape.eager_load!
2
+ Grape::Http.eager_load!
3
+ Grape::Exceptions.eager_load!
4
+ Grape::Extensions.eager_load!
5
+ Grape::Extensions::ActiveSupport.eager_load!
6
+ Grape::Extensions::Hashie.eager_load!
7
+ Grape::Middleware.eager_load!
8
+ Grape::Middleware::Auth.eager_load!
9
+ Grape::Middleware::Versioner.eager_load!
10
+ Grape::Util.eager_load!
11
+ Grape::ErrorFormatter.eager_load!
12
+ Grape::Formatter.eager_load!
13
+ Grape::Parser.eager_load!
14
+ Grape::DSL.eager_load!
15
+ Grape::API.eager_load!
16
+ Grape::Presenters.eager_load!
17
+ Grape::ServeFile.eager_load!
18
+ Rack::Head # AutoLoads the Rack::Head
@@ -200,7 +200,7 @@ module Grape
200
200
  end
201
201
 
202
202
  def merge_route_options(**default)
203
- options[:route_options].clone.merge(**default)
203
+ options[:route_options].clone.merge!(**default)
204
204
  end
205
205
 
206
206
  def map_routes
@@ -353,7 +353,7 @@ module Grape
353
353
  def run_validators(validator_factories, request)
354
354
  validation_errors = []
355
355
 
356
- validators = validator_factories.map(&:create_validator)
356
+ validators = validator_factories.map { |options| Grape::Validations::ValidatorFactory.create_validator(options) }
357
357
 
358
358
  ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do
359
359
  validators.each do |validator|
@@ -363,7 +363,7 @@ module Grape
363
363
  validation_errors << e
364
364
  break if validator.fail_fast?
365
365
  rescue Grape::Exceptions::ValidationArrayErrors => e
366
- validation_errors += e.errors
366
+ validation_errors.concat e.errors
367
367
  break if validator.fail_fast?
368
368
  end
369
369
  end
@@ -14,7 +14,7 @@ module Grape
14
14
  end
15
15
 
16
16
  def formatters(options)
17
- builtin_formatters.merge(default_elements).merge(options[:error_formatters] || {})
17
+ builtin_formatters.merge(default_elements).merge!(options[:error_formatters] || {})
18
18
  end
19
19
 
20
20
  def formatter_for(api_format, **options)
@@ -39,14 +39,16 @@ module Grape
39
39
  end
40
40
 
41
41
  def full_messages
42
- map { |attributes, error| full_message(attributes, error) }.uniq
42
+ messages = map { |attributes, error| full_message(attributes, error) }
43
+ messages.uniq!
44
+ messages
43
45
  end
44
46
 
45
47
  private
46
48
 
47
49
  def full_message(attributes, error)
48
50
  I18n.t(
49
- 'grape.errors.format'.to_sym,
51
+ 'grape.errors.format',
50
52
  default: '%{attributes} %{message}',
51
53
  attributes: attributes.count == 1 ? translate_attribute(attributes.first) : translate_attributes(attributes),
52
54
  message: error.message
@@ -14,7 +14,7 @@ module Grape
14
14
  end
15
15
 
16
16
  def formatters(options)
17
- builtin_formmaters.merge(default_elements).merge(options[:formatters] || {})
17
+ builtin_formmaters.merge(default_elements).merge!(options[:formatters] || {})
18
18
  end
19
19
 
20
20
  def formatter_for(api_format, **options)
@@ -4,6 +4,8 @@ module Grape
4
4
  module Middleware
5
5
  module Auth
6
6
  class Base
7
+ include Helpers
8
+
7
9
  attr_accessor :options, :app, :env
8
10
 
9
11
  def initialize(app, **options)
@@ -11,10 +13,6 @@ module Grape
11
13
  @options = options
12
14
  end
13
15
 
14
- def context
15
- env[Grape::Env::API_ENDPOINT]
16
- end
17
-
18
16
  def call(env)
19
17
  dup._call(env)
20
18
  end
@@ -3,6 +3,8 @@ require 'grape/dsl/headers'
3
3
  module Grape
4
4
  module Middleware
5
5
  class Base
6
+ include Helpers
7
+
6
8
  attr_reader :app, :env, :options
7
9
  TEXT_HTML = 'text/html'.freeze
8
10
 
@@ -0,0 +1,10 @@
1
+ module Grape
2
+ module Middleware
3
+ # Common methods for all types of Grape middleware
4
+ module Helpers
5
+ def context
6
+ env[Grape::Env::API_ENDPOINT]
7
+ end
8
+ end
9
+ end
10
+ end
@@ -12,7 +12,7 @@ module Grape
12
12
  end
13
13
 
14
14
  def parsers(options)
15
- builtin_parsers.merge(default_elements).merge(options[:parsers] || {})
15
+ builtin_parsers.merge(default_elements).merge!(options[:parsers] || {})
16
16
  end
17
17
 
18
18
  def parser_for(api_format, **options)
@@ -0,0 +1,34 @@
1
+ module Grape
2
+ module Util
3
+ # Base for classes which need to operate with own values kept
4
+ # in the hash and inherited values kept in a Hash-like object.
5
+ class BaseInheritable
6
+ attr_accessor :inherited_values
7
+ attr_accessor :new_values
8
+
9
+ # @param inherited_values [Object] An object implementing an interface
10
+ # of the Hash class.
11
+ def initialize(inherited_values = {})
12
+ @inherited_values = inherited_values
13
+ @new_values = {}
14
+ end
15
+
16
+ def delete(key)
17
+ new_values.delete key
18
+ end
19
+
20
+ def initialize_copy(other)
21
+ super
22
+ self.inherited_values = other.inherited_values
23
+ self.new_values = other.new_values.dup
24
+ end
25
+
26
+ def keys
27
+ combined = inherited_values.keys
28
+ combined.concat(new_values.keys)
29
+ combined.uniq!
30
+ combined
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,14 +1,8 @@
1
+ require_relative 'base_inheritable'
2
+
1
3
  module Grape
2
4
  module Util
3
- class InheritableValues
4
- attr_accessor :inherited_values
5
- attr_accessor :new_values
6
-
7
- def initialize(inherited_values = {})
8
- self.inherited_values = inherited_values
9
- self.new_values = {}
10
- end
11
-
5
+ class InheritableValues < BaseInheritable
12
6
  def [](name)
13
7
  values[name]
14
8
  end
@@ -17,26 +11,12 @@ module Grape
17
11
  new_values[name] = value
18
12
  end
19
13
 
20
- def delete(key)
21
- new_values.delete key
22
- end
23
-
24
14
  def merge(new_hash)
25
- values.merge(new_hash)
26
- end
27
-
28
- def keys
29
- (new_values.keys + inherited_values.keys).sort.uniq
15
+ values.merge!(new_hash)
30
16
  end
31
17
 
32
18
  def to_hash
33
- values.clone
34
- end
35
-
36
- def initialize_copy(other)
37
- super
38
- self.inherited_values = other.inherited_values
39
- self.new_values = other.new_values.dup
19
+ values
40
20
  end
41
21
 
42
22
  protected
@@ -0,0 +1,25 @@
1
+ module Grape
2
+ module Util
3
+ class LazyBlock
4
+ def initialize(&new_block)
5
+ @block = new_block
6
+ end
7
+
8
+ def evaluate_from(configuration)
9
+ @block.call(configuration)
10
+ end
11
+
12
+ def evaluate
13
+ @block.call({})
14
+ end
15
+
16
+ def lazy?
17
+ true
18
+ end
19
+
20
+ def to_s
21
+ evaluate.to_s
22
+ end
23
+ end
24
+ end
25
+ end