grape 1.2.4 → 1.2.5

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