grape 1.3.2 → 1.5.2

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -2
  3. data/LICENSE +1 -1
  4. data/README.md +120 -24
  5. data/UPGRADING.md +220 -39
  6. data/lib/grape.rb +3 -2
  7. data/lib/grape/api.rb +3 -3
  8. data/lib/grape/api/instance.rb +22 -25
  9. data/lib/grape/dsl/callbacks.rb +1 -1
  10. data/lib/grape/dsl/helpers.rb +1 -0
  11. data/lib/grape/dsl/inside_route.rb +70 -37
  12. data/lib/grape/dsl/parameters.rb +8 -4
  13. data/lib/grape/dsl/routing.rb +6 -7
  14. data/lib/grape/dsl/validations.rb +18 -1
  15. data/lib/grape/eager_load.rb +1 -1
  16. data/lib/grape/endpoint.rb +8 -6
  17. data/lib/grape/exceptions/validation.rb +1 -1
  18. data/lib/grape/exceptions/validation_errors.rb +1 -1
  19. data/lib/grape/middleware/auth/base.rb +3 -3
  20. data/lib/grape/middleware/base.rb +3 -2
  21. data/lib/grape/middleware/error.rb +11 -13
  22. data/lib/grape/middleware/formatter.rb +3 -3
  23. data/lib/grape/middleware/stack.rb +8 -1
  24. data/lib/grape/request.rb +1 -1
  25. data/lib/grape/router.rb +25 -39
  26. data/lib/grape/router/attribute_translator.rb +26 -5
  27. data/lib/grape/router/route.rb +1 -19
  28. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  29. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  30. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  31. data/lib/grape/util/base_inheritable.rb +2 -2
  32. data/lib/grape/util/lazy_value.rb +1 -0
  33. data/lib/grape/validations/attributes_iterator.rb +8 -0
  34. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  35. data/lib/grape/validations/params_scope.rb +9 -7
  36. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  37. data/lib/grape/validations/types.rb +1 -4
  38. data/lib/grape/validations/types/array_coercer.rb +14 -5
  39. data/lib/grape/validations/types/build_coercer.rb +1 -5
  40. data/lib/grape/validations/types/custom_type_coercer.rb +15 -1
  41. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  42. data/lib/grape/validations/types/invalid_value.rb +24 -0
  43. data/lib/grape/validations/types/primitive_coercer.rb +9 -3
  44. data/lib/grape/validations/types/set_coercer.rb +6 -4
  45. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  46. data/lib/grape/validations/validator_factory.rb +1 -1
  47. data/lib/grape/validations/validators/as.rb +1 -1
  48. data/lib/grape/validations/validators/base.rb +8 -8
  49. data/lib/grape/validations/validators/coerce.rb +8 -14
  50. data/lib/grape/validations/validators/default.rb +3 -5
  51. data/lib/grape/validations/validators/except_values.rb +1 -1
  52. data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
  53. data/lib/grape/validations/validators/values.rb +1 -1
  54. data/lib/grape/version.rb +1 -1
  55. data/spec/grape/api/instance_spec.rb +50 -0
  56. data/spec/grape/api_remount_spec.rb +9 -4
  57. data/spec/grape/api_spec.rb +75 -0
  58. data/spec/grape/dsl/inside_route_spec.rb +182 -33
  59. data/spec/grape/endpoint/declared_spec.rb +601 -0
  60. data/spec/grape/endpoint_spec.rb +0 -521
  61. data/spec/grape/entity_spec.rb +7 -1
  62. data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
  63. data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
  64. data/spec/grape/middleware/error_spec.rb +1 -1
  65. data/spec/grape/middleware/formatter_spec.rb +1 -1
  66. data/spec/grape/middleware/stack_spec.rb +1 -0
  67. data/spec/grape/request_spec.rb +1 -1
  68. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
  69. data/spec/grape/validations/params_scope_spec.rb +26 -0
  70. data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
  71. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  72. data/spec/grape/validations/types/primitive_coercer_spec.rb +65 -5
  73. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  74. data/spec/grape/validations/validators/coerce_spec.rb +223 -25
  75. data/spec/grape/validations/validators/default_spec.rb +170 -0
  76. data/spec/grape/validations/validators/except_values_spec.rb +1 -0
  77. data/spec/grape/validations/validators/values_spec.rb +1 -1
  78. data/spec/grape/validations_spec.rb +290 -18
  79. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  80. data/spec/shared/versioning_examples.rb +20 -20
  81. data/spec/spec_helper.rb +0 -10
  82. data/spec/support/chunks.rb +14 -0
  83. data/spec/support/versioned_helpers.rb +4 -6
  84. metadata +20 -9
@@ -17,7 +17,7 @@ module Grape
17
17
  super(**args)
18
18
  end
19
19
 
20
- # remove all the unnecessary stuff from Grape::Exceptions::Base like status
20
+ # Remove all the unnecessary stuff from Grape::Exceptions::Base like status
21
21
  # and headers when converting a validation error to json or string
22
22
  def as_json(*_args)
23
23
  to_s
@@ -39,7 +39,7 @@ module Grape
39
39
  end
40
40
  end
41
41
 
42
- def to_json(**_opts)
42
+ def to_json(*_opts)
43
43
  as_json.to_json
44
44
  end
45
45
 
@@ -10,9 +10,9 @@ module Grape
10
10
 
11
11
  attr_accessor :options, :app, :env
12
12
 
13
- def initialize(app, **options)
13
+ def initialize(app, *options)
14
14
  @app = app
15
- @options = options
15
+ @options = options.shift
16
16
  end
17
17
 
18
18
  def call(env)
@@ -23,7 +23,7 @@ module Grape
23
23
  self.env = env
24
24
 
25
25
  if options.key?(:type)
26
- auth_proc = options[:proc]
26
+ auth_proc = options[:proc]
27
27
  auth_proc_context = context
28
28
 
29
29
  strategy_info = Grape::Middleware::Auth::Strategies[options[:type]]
@@ -8,15 +8,16 @@ module Grape
8
8
  include Helpers
9
9
 
10
10
  attr_reader :app, :env, :options
11
+
11
12
  TEXT_HTML = 'text/html'
12
13
 
13
14
  include Grape::DSL::Headers
14
15
 
15
16
  # @param [Rack Application] app The standard argument for a Rack middleware.
16
17
  # @param [Hash] options A hash of options, simply stored for use by subclasses.
17
- def initialize(app, **options)
18
+ def initialize(app, *options)
18
19
  @app = app
19
- @options = default_options.merge(**options)
20
+ @options = options.any? ? default_options.merge(options.shift) : default_options
20
21
  @app_response = nil
21
22
  end
22
23
 
@@ -19,15 +19,15 @@ module Grape
19
19
  rescue_subclasses: true, # rescue subclasses of exceptions listed
20
20
  rescue_options: {
21
21
  backtrace: false, # true to display backtrace, true to let Grape handle Grape::Exceptions
22
- original_exception: false, # true to display exception
22
+ original_exception: false # true to display exception
23
23
  },
24
24
  rescue_handlers: {}, # rescue handler blocks
25
25
  base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
26
- all_rescue_handler: nil, # rescue handler block to rescue from all exceptions
26
+ all_rescue_handler: nil # rescue handler block to rescue from all exceptions
27
27
  }
28
28
  end
29
29
 
30
- def initialize(app, **options)
30
+ def initialize(app, *options)
31
31
  super
32
32
  self.class.send(:include, @options[:helpers]) if @options[:helpers]
33
33
  end
@@ -38,15 +38,15 @@ module Grape
38
38
  error_response(catch(:error) do
39
39
  return @app.call(@env)
40
40
  end)
41
- rescue Exception => error # rubocop:disable Lint/RescueException
41
+ rescue Exception => e # rubocop:disable Lint/RescueException
42
42
  handler =
43
- rescue_handler_for_base_only_class(error.class) ||
44
- rescue_handler_for_class_or_its_ancestor(error.class) ||
45
- rescue_handler_for_grape_exception(error.class) ||
46
- rescue_handler_for_any_class(error.class) ||
43
+ rescue_handler_for_base_only_class(e.class) ||
44
+ rescue_handler_for_class_or_its_ancestor(e.class) ||
45
+ rescue_handler_for_grape_exception(e.class) ||
46
+ rescue_handler_for_any_class(e.class) ||
47
47
  raise
48
48
 
49
- run_rescue_handler(handler, error)
49
+ run_rescue_handler(handler, e)
50
50
  end
51
51
  end
52
52
 
@@ -65,15 +65,13 @@ module Grape
65
65
  message = error[:message] || options[:default_message]
66
66
  headers = { Grape::Http::Headers::CONTENT_TYPE => content_type }
67
67
  headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
68
- backtrace = error[:backtrace] || error[:original_exception] && error[:original_exception].backtrace || []
68
+ backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
69
69
  original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
70
70
  rack_response(format_message(message, backtrace, original_exception), status, headers)
71
71
  end
72
72
 
73
73
  def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type })
74
- if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
75
- message = ERB::Util.html_escape(message)
76
- end
74
+ message = ERB::Util.html_escape(message) if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
77
75
  Rack::Response.new([message], status, headers)
78
76
  end
79
77
 
@@ -36,9 +36,9 @@ module Grape
36
36
  def build_formatted_response(status, headers, bodies)
37
37
  headers = ensure_content_type(headers)
38
38
 
39
- if bodies.is_a?(Grape::ServeFile::FileResponse)
40
- Grape::ServeFile::SendfileResponse.new([], status, headers) do |resp|
41
- resp.body = bodies.file
39
+ if bodies.is_a?(Grape::ServeStream::StreamResponse)
40
+ Grape::ServeStream::SendfileResponse.new([], status, headers) do |resp|
41
+ resp.body = bodies.stream
42
42
  end
43
43
  else
44
44
  # Allow content-type to be explicitly overwritten
@@ -30,6 +30,10 @@ module Grape
30
30
  def inspect
31
31
  klass.to_s
32
32
  end
33
+
34
+ def use_in(builder)
35
+ builder.use(@klass, *@args, &@block)
36
+ end
33
37
  end
34
38
 
35
39
  include Enumerable
@@ -62,6 +66,7 @@ module Grape
62
66
  middleware = self.class::Middleware.new(*args, &block)
63
67
  middlewares.insert(index, middleware)
64
68
  end
69
+ ruby2_keywords :insert if respond_to?(:ruby2_keywords, true)
65
70
 
66
71
  alias insert_before insert
67
72
 
@@ -69,11 +74,13 @@ module Grape
69
74
  index = assert_index(index, :after)
70
75
  insert(index + 1, *args, &block)
71
76
  end
77
+ ruby2_keywords :insert_after if respond_to?(:ruby2_keywords, true)
72
78
 
73
79
  def use(*args, &block)
74
80
  middleware = self.class::Middleware.new(*args, &block)
75
81
  middlewares.push(middleware)
76
82
  end
83
+ ruby2_keywords :use if respond_to?(:ruby2_keywords, true)
77
84
 
78
85
  def merge_with(middleware_specs)
79
86
  middleware_specs.each do |operation, *args|
@@ -90,7 +97,7 @@ module Grape
90
97
  def build(builder = Rack::Builder.new)
91
98
  others.shift(others.size).each(&method(:merge_with))
92
99
  middlewares.each do |m|
93
- m.block ? builder.use(m.klass, *m.args, &m.block) : builder.use(m.klass, *m.args)
100
+ m.use_in(builder)
94
101
  end
95
102
  builder
96
103
  end
data/lib/grape/request.rb CHANGED
@@ -8,7 +8,7 @@ module Grape
8
8
 
9
9
  alias rack_params params
10
10
 
11
- def initialize(env, options = {})
11
+ def initialize(env, **options)
12
12
  extend options[:build_params_with] || Grape.config.param_builder
13
13
  super(env)
14
14
  end
data/lib/grape/router.rb CHANGED
@@ -7,29 +7,12 @@ module Grape
7
7
  class Router
8
8
  attr_reader :map, :compiled
9
9
 
10
- class Any < AttributeTranslator
11
- attr_reader :pattern, :index
12
- def initialize(pattern, index, **attributes)
13
- @pattern = pattern
14
- @index = index
15
- super(attributes)
16
- end
17
- end
18
-
19
- class NormalizePathCache < Grape::Util::Cache
20
- def initialize
21
- @cache = Hash.new do |h, path|
22
- normalized_path = +"/#{path}"
23
- normalized_path.squeeze!('/')
24
- normalized_path.sub!(%r{/+\Z}, '')
25
- normalized_path = '/' if normalized_path.empty?
26
- h[path] = -normalized_path
27
- end
28
- end
29
- end
30
-
31
10
  def self.normalize_path(path)
32
- NormalizePathCache[path]
11
+ path = +"/#{path}"
12
+ path.squeeze!('/')
13
+ path.sub!(%r{/+\Z}, '')
14
+ path = '/' if path == ''
15
+ path
33
16
  end
34
17
 
35
18
  def self.supported_methods
@@ -64,7 +47,7 @@ module Grape
64
47
 
65
48
  def associate_routes(pattern, **options)
66
49
  @neutral_regexes << Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}")
67
- @neutral_map << Any.new(pattern, @neutral_map.length, **options)
50
+ @neutral_map << Grape::Router::AttributeTranslator.new(**options, pattern: pattern, index: @neutral_map.length)
68
51
  end
69
52
 
70
53
  def call(env)
@@ -108,37 +91,34 @@ module Grape
108
91
  response = yield(input, method)
109
92
 
110
93
  return response if response && !(cascade = cascade?(response))
111
- neighbor = greedy_match?(input)
94
+ last_neighbor_route = greedy_match?(input)
112
95
 
113
- # If neighbor exists and request method is OPTIONS,
96
+ # If last_neighbor_route exists and request method is OPTIONS,
114
97
  # return response by using #call_with_allow_headers.
115
- return call_with_allow_headers(
116
- env,
117
- neighbor.allow_header,
118
- neighbor.endpoint
119
- ) if neighbor && method == Grape::Http::Headers::OPTIONS && !cascade
98
+ return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Grape::Http::Headers::OPTIONS && !cascade
120
99
 
121
100
  route = match?(input, '*')
122
- return neighbor.endpoint.call(env) if neighbor && cascade && route
101
+
102
+ return last_neighbor_route.endpoint.call(env) if last_neighbor_route && cascade && route
123
103
 
124
104
  if route
125
105
  response = process_route(route, env)
126
106
  return response if response && !(cascade = cascade?(response))
127
107
  end
128
108
 
129
- !cascade && neighbor ? call_with_allow_headers(env, neighbor.allow_header, neighbor.endpoint) : nil
109
+ return call_with_allow_headers(env, last_neighbor_route) if !cascade && last_neighbor_route
110
+
111
+ nil
130
112
  end
131
113
 
132
114
  def process_route(route, env)
133
- input, = *extract_input_and_method(env)
134
- routing_args = env[Grape::Env::GRAPE_ROUTING_ARGS]
135
- env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(routing_args, route, input)
115
+ prepare_env_from_route(env, route)
136
116
  route.exec(env)
137
117
  end
138
118
 
139
119
  def make_routing_args(default_args, route, input)
140
120
  args = default_args || { route_info: route }
141
- args.merge(route.params(input))
121
+ args.merge(route.params(input) || {})
142
122
  end
143
123
 
144
124
  def extract_input_and_method(env)
@@ -169,9 +149,15 @@ module Grape
169
149
  @neutral_map.detect { |route| last_match["_#{route.index}"] }
170
150
  end
171
151
 
172
- def call_with_allow_headers(env, methods, endpoint)
173
- env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods.join(', ').freeze
174
- endpoint.call(env)
152
+ def call_with_allow_headers(env, route)
153
+ prepare_env_from_route(env, route)
154
+ env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header.join(', ').freeze
155
+ route.endpoint.call(env)
156
+ end
157
+
158
+ def prepare_env_from_route(env, route)
159
+ input, = *extract_input_and_method(env)
160
+ env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Env::GRAPE_ROUTING_ARGS], route, input)
175
161
  end
176
162
 
177
163
  def cascade?(response)
@@ -4,19 +4,40 @@ module Grape
4
4
  class Router
5
5
  # this could be an OpenStruct, but doesn't work in Ruby 2.3.0, see https://bugs.ruby-lang.org/issues/12251
6
6
  class AttributeTranslator
7
- attr_reader :attributes, :request_method, :requirements
7
+ attr_reader :attributes
8
8
 
9
- def initialize(attributes = {})
9
+ ROUTE_ATTRIBUTES = %i[
10
+ prefix
11
+ version
12
+ settings
13
+ format
14
+ description
15
+ http_codes
16
+ headers
17
+ entity
18
+ details
19
+ requirements
20
+ request_method
21
+ namespace
22
+ ].freeze
23
+
24
+ ROUTER_ATTRIBUTES = %i[pattern index].freeze
25
+
26
+ def initialize(**attributes)
10
27
  @attributes = attributes
11
- @request_method = attributes[:request_method]
12
- @requirements = attributes[:requirements]
28
+ end
29
+
30
+ (ROUTER_ATTRIBUTES + ROUTE_ATTRIBUTES).each do |attr|
31
+ define_method attr do
32
+ attributes[attr]
33
+ end
13
34
  end
14
35
 
15
36
  def to_h
16
37
  attributes
17
38
  end
18
39
 
19
- def method_missing(method_name, *args) # rubocop:disable Style/MethodMissing
40
+ def method_missing(method_name, *args)
20
41
  if setter?(method_name[-1])
21
42
  attributes[method_name[0..-1]] = *args
22
43
  else
@@ -18,6 +18,7 @@ module Grape
18
18
 
19
19
  extend Forwardable
20
20
  def_delegators :pattern, :path, :origin
21
+ delegate Grape::Router::AttributeTranslator::ROUTE_ATTRIBUTES => :attributes
21
22
 
22
23
  def method_missing(method_id, *arguments)
23
24
  match = ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s)
@@ -34,25 +35,6 @@ module Grape
34
35
  ROUTE_ATTRIBUTE_REGEXP.match?(method_id.to_s)
35
36
  end
36
37
 
37
- %i[
38
- prefix
39
- version
40
- settings
41
- format
42
- description
43
- http_codes
44
- headers
45
- entity
46
- details
47
- requirements
48
- request_method
49
- namespace
50
- ].each do |method_name|
51
- define_method method_name do
52
- attributes.public_send method_name
53
- end
54
- end
55
-
56
38
  def route_method
57
39
  warn_route_methods(:method, caller(1).shift, :request_method)
58
40
  request_method
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grape
4
- module ServeFile
4
+ module ServeStream
5
5
  CHUNK_SIZE = 16_384
6
6
 
7
7
  # Class helps send file through API
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grape
4
- module ServeFile
4
+ module ServeStream
5
5
  # Response should respond to to_path method
6
6
  # for using Rack::SendFile middleware
7
7
  class SendfileResponse < Rack::Response
@@ -1,22 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grape
4
- module ServeFile
5
- # A simple class used to identify responses which represent files and do not
4
+ module ServeStream
5
+ # A simple class used to identify responses which represent streams (or files) and do not
6
6
  # need to be formatted or pre-read by Rack::Response
7
- class FileResponse
8
- attr_reader :file
7
+ class StreamResponse
8
+ attr_reader :stream
9
9
 
10
- # @param file [Object]
11
- def initialize(file)
12
- @file = file
10
+ # @param stream [Object]
11
+ def initialize(stream)
12
+ @stream = stream
13
13
  end
14
14
 
15
15
  # Equality provided mostly for tests.
16
16
  #
17
17
  # @return [Boolean]
18
18
  def ==(other)
19
- file == other.file
19
+ stream == other.stream
20
20
  end
21
21
  end
22
22
  end
@@ -9,8 +9,8 @@ module Grape
9
9
 
10
10
  # @param inherited_values [Object] An object implementing an interface
11
11
  # of the Hash class.
12
- def initialize(inherited_values = {})
13
- @inherited_values = inherited_values
12
+ def initialize(inherited_values = nil)
13
+ @inherited_values = inherited_values || {}
14
14
  @new_values = {}
15
15
  end
16
16
 
@@ -4,6 +4,7 @@ module Grape
4
4
  module Util
5
5
  class LazyValue
6
6
  attr_reader :access_keys
7
+
7
8
  def initialize(value, access_keys = [])
8
9
  @value = value
9
10
  @access_keys = access_keys