grape 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -4
  3. data/Gemfile.lock +13 -13
  4. data/README.md +290 -12
  5. data/UPGRADING.md +68 -1
  6. data/gemfiles/rails_3.gemfile +1 -1
  7. data/lib/grape.rb +8 -2
  8. data/lib/grape/api.rb +40 -34
  9. data/lib/grape/dsl/configuration.rb +2 -115
  10. data/lib/grape/dsl/desc.rb +101 -0
  11. data/lib/grape/dsl/headers.rb +16 -0
  12. data/lib/grape/dsl/helpers.rb +5 -9
  13. data/lib/grape/dsl/inside_route.rb +3 -11
  14. data/lib/grape/dsl/logger.rb +20 -0
  15. data/lib/grape/dsl/parameters.rb +12 -10
  16. data/lib/grape/dsl/request_response.rb +17 -4
  17. data/lib/grape/dsl/routing.rb +24 -7
  18. data/lib/grape/dsl/settings.rb +8 -2
  19. data/lib/grape/endpoint.rb +30 -26
  20. data/lib/grape/error_formatter.rb +31 -0
  21. data/lib/grape/error_formatter/base.rb +0 -28
  22. data/lib/grape/error_formatter/json.rb +13 -2
  23. data/lib/grape/error_formatter/txt.rb +3 -1
  24. data/lib/grape/error_formatter/xml.rb +3 -1
  25. data/lib/grape/exceptions/base.rb +11 -4
  26. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  27. data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
  28. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  29. data/lib/grape/exceptions/invalid_message_body.rb +1 -1
  30. data/lib/grape/exceptions/invalid_version_header.rb +1 -1
  31. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  32. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -1
  33. data/lib/grape/exceptions/method_not_allowed.rb +10 -0
  34. data/lib/grape/exceptions/missing_group_type.rb +1 -1
  35. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  36. data/lib/grape/exceptions/missing_option.rb +1 -1
  37. data/lib/grape/exceptions/missing_vendor_option.rb +1 -1
  38. data/lib/grape/exceptions/unknown_options.rb +1 -1
  39. data/lib/grape/exceptions/unknown_parameter.rb +1 -1
  40. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  41. data/lib/grape/exceptions/unsupported_group_type.rb +1 -1
  42. data/lib/grape/exceptions/validation.rb +2 -1
  43. data/lib/grape/formatter.rb +31 -0
  44. data/lib/grape/middleware/base.rb +28 -2
  45. data/lib/grape/middleware/error.rb +24 -1
  46. data/lib/grape/middleware/formatter.rb +4 -3
  47. data/lib/grape/middleware/versioner/param.rb +13 -2
  48. data/lib/grape/parser.rb +29 -0
  49. data/lib/grape/util/sendfile_response.rb +19 -0
  50. data/lib/grape/util/strict_hash_configuration.rb +1 -1
  51. data/lib/grape/validations/params_scope.rb +39 -9
  52. data/lib/grape/validations/types.rb +16 -0
  53. data/lib/grape/validations/validators/all_or_none.rb +1 -1
  54. data/lib/grape/validations/validators/allow_blank.rb +2 -2
  55. data/lib/grape/validations/validators/at_least_one_of.rb +1 -1
  56. data/lib/grape/validations/validators/base.rb +26 -0
  57. data/lib/grape/validations/validators/coerce.rb +16 -14
  58. data/lib/grape/validations/validators/default.rb +1 -1
  59. data/lib/grape/validations/validators/exactly_one_of.rb +10 -1
  60. data/lib/grape/validations/validators/mutual_exclusion.rb +1 -1
  61. data/lib/grape/validations/validators/presence.rb +1 -1
  62. data/lib/grape/validations/validators/regexp.rb +2 -2
  63. data/lib/grape/validations/validators/values.rb +2 -2
  64. data/lib/grape/version.rb +1 -1
  65. data/spec/grape/api/custom_validations_spec.rb +156 -21
  66. data/spec/grape/api/namespace_parameters_in_route_spec.rb +38 -0
  67. data/spec/grape/api/optional_parameters_in_route_spec.rb +43 -0
  68. data/spec/grape/api/required_parameters_in_route_spec.rb +37 -0
  69. data/spec/grape/api_spec.rb +118 -60
  70. data/spec/grape/dsl/configuration_spec.rb +0 -75
  71. data/spec/grape/dsl/desc_spec.rb +77 -0
  72. data/spec/grape/dsl/headers_spec.rb +32 -0
  73. data/spec/grape/dsl/inside_route_spec.rb +0 -18
  74. data/spec/grape/dsl/logger_spec.rb +26 -0
  75. data/spec/grape/dsl/parameters_spec.rb +13 -7
  76. data/spec/grape/dsl/request_response_spec.rb +17 -3
  77. data/spec/grape/dsl/routing_spec.rb +8 -1
  78. data/spec/grape/dsl/settings_spec.rb +42 -0
  79. data/spec/grape/endpoint_spec.rb +60 -9
  80. data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
  81. data/spec/grape/exceptions/validation_spec.rb +7 -0
  82. data/spec/grape/integration/rack_sendfile_spec.rb +44 -0
  83. data/spec/grape/middleware/base_spec.rb +100 -0
  84. data/spec/grape/middleware/exception_spec.rb +1 -2
  85. data/spec/grape/middleware/formatter_spec.rb +12 -2
  86. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +1 -1
  87. data/spec/grape/middleware/versioner/header_spec.rb +11 -1
  88. data/spec/grape/middleware/versioner/param_spec.rb +105 -1
  89. data/spec/grape/validations/params_scope_spec.rb +77 -0
  90. data/spec/grape/validations/validators/allow_blank_spec.rb +277 -0
  91. data/spec/grape/validations/validators/coerce_spec.rb +91 -0
  92. data/spec/grape/validations/validators/default_spec.rb +6 -0
  93. data/spec/grape/validations/validators/presence_spec.rb +27 -0
  94. data/spec/grape/validations/validators/regexp_spec.rb +36 -0
  95. data/spec/grape/validations/validators/values_spec.rb +44 -0
  96. data/spec/grape/validations_spec.rb +149 -4
  97. data/spec/spec_helper.rb +1 -0
  98. metadata +26 -5
  99. data/lib/grape/formatter/base.rb +0 -31
  100. data/lib/grape/parser/base.rb +0 -29
  101. data/pkg/grape-0.13.0.gem +0 -0
@@ -0,0 +1,31 @@
1
+ module Grape
2
+ module ErrorFormatter
3
+ class << self
4
+ def builtin_formatters
5
+ {
6
+ serializable_hash: Grape::ErrorFormatter::Json,
7
+ json: Grape::ErrorFormatter::Json,
8
+ jsonapi: Grape::ErrorFormatter::Json,
9
+ txt: Grape::ErrorFormatter::Txt,
10
+ xml: Grape::ErrorFormatter::Xml
11
+ }
12
+ end
13
+
14
+ def formatters(options)
15
+ builtin_formatters.merge(options[:error_formatters] || {})
16
+ end
17
+
18
+ def formatter_for(api_format, options = {})
19
+ spec = formatters(options)[api_format]
20
+ case spec
21
+ when nil
22
+ options[:default_error_formatter] || Grape::ErrorFormatter::Txt
23
+ when Symbol
24
+ method(spec)
25
+ else
26
+ spec
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,34 +1,6 @@
1
1
  module Grape
2
2
  module ErrorFormatter
3
3
  module Base
4
- class << self
5
- FORMATTERS = {
6
- serializable_hash: Grape::ErrorFormatter::Json,
7
- json: Grape::ErrorFormatter::Json,
8
- jsonapi: Grape::ErrorFormatter::Json,
9
- txt: Grape::ErrorFormatter::Txt,
10
- xml: Grape::ErrorFormatter::Xml
11
- }
12
-
13
- def formatters(options)
14
- FORMATTERS.merge(options[:error_formatters] || {})
15
- end
16
-
17
- def formatter_for(api_format, options = {})
18
- spec = formatters(options)[api_format]
19
- case spec
20
- when nil
21
- options[:default_error_formatter] || Grape::ErrorFormatter::Txt
22
- when Symbol
23
- method(spec)
24
- else
25
- spec
26
- end
27
- end
28
- end
29
-
30
- module_function
31
-
32
4
  def present(message, env)
33
5
  present_options = {}
34
6
  present_options[:with] = message.delete(:with) if message.is_a?(Hash)
@@ -1,16 +1,27 @@
1
1
  module Grape
2
2
  module ErrorFormatter
3
3
  module Json
4
+ extend Base
5
+
4
6
  class << self
5
7
  def call(message, backtrace, options = {}, env = nil)
6
- message = Grape::ErrorFormatter::Base.present(message, env)
8
+ result = wrap_message(present(message, env))
7
9
 
8
- result = message.is_a?(String) ? { error: message } : message
9
10
  if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
10
11
  result = result.merge(backtrace: backtrace)
11
12
  end
12
13
  MultiJson.dump(result)
13
14
  end
15
+
16
+ private
17
+
18
+ def wrap_message(message)
19
+ if message.is_a?(Exceptions::ValidationErrors) || message.is_a?(Hash)
20
+ message
21
+ else
22
+ { error: message }
23
+ end
24
+ end
14
25
  end
15
26
  end
16
27
  end
@@ -1,9 +1,11 @@
1
1
  module Grape
2
2
  module ErrorFormatter
3
3
  module Txt
4
+ extend Base
5
+
4
6
  class << self
5
7
  def call(message, backtrace, options = {}, env = nil)
6
- message = Grape::ErrorFormatter::Base.present(message, env)
8
+ message = present(message, env)
7
9
 
8
10
  result = message.is_a?(Hash) ? MultiJson.dump(message) : message
9
11
  if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
@@ -1,9 +1,11 @@
1
1
  module Grape
2
2
  module ErrorFormatter
3
3
  module Xml
4
+ extend Base
5
+
4
6
  class << self
5
7
  def call(message, backtrace, options = {}, env = nil)
6
- message = Grape::ErrorFormatter::Base.present(message, env)
8
+ message = present(message, env)
7
9
 
8
10
  result = message.is_a?(Hash) ? message : { message: message }
9
11
  if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
@@ -38,15 +38,15 @@ module Grape
38
38
  end
39
39
 
40
40
  def problem(key, attributes)
41
- translate_message("#{key}.problem", attributes)
41
+ translate_message("#{key}.problem".to_sym, attributes)
42
42
  end
43
43
 
44
44
  def summary(key, attributes)
45
- translate_message("#{key}.summary", attributes)
45
+ translate_message("#{key}.summary".to_sym, attributes)
46
46
  end
47
47
 
48
48
  def resolution(key, attributes)
49
- translate_message("#{key}.resolution", attributes)
49
+ translate_message("#{key}.resolution".to_sym, attributes)
50
50
  end
51
51
 
52
52
  def translate_attributes(keys, options = {})
@@ -60,7 +60,14 @@ module Grape
60
60
  end
61
61
 
62
62
  def translate_message(key, options = {})
63
- translate("#{BASE_MESSAGES_KEY}.#{key}", options.reverse_merge(default: ''))
63
+ case key
64
+ when Symbol
65
+ translate("#{BASE_MESSAGES_KEY}.#{key}", options.reverse_merge(default: ''))
66
+ when Proc
67
+ key.call
68
+ else
69
+ key
70
+ end
64
71
  end
65
72
 
66
73
  def translate(key, options = {})
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class IncompatibleOptionValues < Base
5
5
  def initialize(option1, value1, option2, value2)
6
- super(message: compose_message('incompatible_option_values', option1: option1, value1: value1, option2: option2, value2: value2))
6
+ super(message: compose_message(:incompatible_option_values, option1: option1, value1: value1, option2: option2, value2: value2))
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class InvalidAcceptHeader < Base
5
5
  def initialize(message, headers)
6
- super(message: compose_message('invalid_accept_header', message: message), status: 406, headers: headers)
6
+ super(message: compose_message(:invalid_accept_header, message: message), status: 406, headers: headers)
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class InvalidFormatter < Base
5
5
  def initialize(klass, to_format)
6
- super(message: compose_message('invalid_formatter', klass: klass, to_format: to_format))
6
+ super(message: compose_message(:invalid_formatter, klass: klass, to_format: to_format))
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class InvalidMessageBody < Base
5
5
  def initialize(body_format)
6
- super(message: compose_message('invalid_message_body', body_format: body_format), status: 400)
6
+ super(message: compose_message(:invalid_message_body, body_format: body_format), status: 400)
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class InvalidVersionHeader < Base
5
5
  def initialize(message, headers)
6
- super(message: compose_message('invalid_version_header', message: message), status: 406, headers: headers)
6
+ super(message: compose_message(:invalid_version_header, message: message), status: 406, headers: headers)
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class InvalidVersionerOption < Base
5
5
  def initialize(strategy)
6
- super(message: compose_message('invalid_versioner_option', strategy: strategy))
6
+ super(message: compose_message(:invalid_versioner_option, strategy: strategy))
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class InvalidWithOptionForRepresent < Base
5
5
  def initialize
6
- super(message: compose_message('invalid_with_option_for_represent'))
6
+ super(message: compose_message(:invalid_with_option_for_represent))
7
7
  end
8
8
  end
9
9
  end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ module Grape
3
+ module Exceptions
4
+ class MethodNotAllowed < Base
5
+ def initialize(headers)
6
+ super(message: '405 Not Allowed', status: 405, headers: headers)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class MissingGroupTypeError < Base
5
5
  def initialize
6
- super(message: compose_message('missing_group_type'))
6
+ super(message: compose_message(:missing_group_type))
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class MissingMimeType < Base
5
5
  def initialize(new_format)
6
- super(message: compose_message('missing_mime_type', new_format: new_format))
6
+ super(message: compose_message(:missing_mime_type, new_format: new_format))
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class MissingOption < Base
5
5
  def initialize(option)
6
- super(message: compose_message('missing_option', option: option))
6
+ super(message: compose_message(:missing_option, option: option))
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class MissingVendorOption < Base
5
5
  def initialize
6
- super(message: compose_message('missing_vendor_option'))
6
+ super(message: compose_message(:missing_vendor_option))
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class UnknownOptions < Base
5
5
  def initialize(options)
6
- super(message: compose_message('unknown_options', options: options))
6
+ super(message: compose_message(:unknown_options, options: options))
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class UnknownParameter < Base
5
5
  def initialize(param)
6
- super(message: compose_message('unknown_parameter', param: param))
6
+ super(message: compose_message(:unknown_parameter, param: param))
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class UnknownValidator < Base
5
5
  def initialize(validator_type)
6
- super(message: compose_message('unknown_validator', validator_type: validator_type))
6
+ super(message: compose_message(:unknown_validator, validator_type: validator_type))
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Grape
3
3
  module Exceptions
4
4
  class UnsupportedGroupTypeError < Base
5
5
  def initialize
6
- super(message: compose_message('unsupported_group_type'))
6
+ super(message: compose_message(:unsupported_group_type))
7
7
  end
8
8
  end
9
9
  end
@@ -4,11 +4,12 @@ module Grape
4
4
  module Exceptions
5
5
  class Validation < Grape::Exceptions::Base
6
6
  attr_accessor :params
7
+ attr_accessor :message_key
7
8
 
8
9
  def initialize(args = {})
9
10
  fail 'Params are missing:' unless args.key? :params
10
11
  @params = args[:params]
11
- args[:message] = translate_message(args[:message_key]) if args.key? :message_key
12
+ args[:message] = translate_message(args[:message]) if args.key? :message
12
13
  super
13
14
  end
14
15
 
@@ -0,0 +1,31 @@
1
+ module Grape
2
+ module Formatter
3
+ class << self
4
+ def builtin_formmaters
5
+ {
6
+ json: Grape::Formatter::Json,
7
+ jsonapi: Grape::Formatter::Json,
8
+ serializable_hash: Grape::Formatter::SerializableHash,
9
+ txt: Grape::Formatter::Txt,
10
+ xml: Grape::Formatter::Xml
11
+ }
12
+ end
13
+
14
+ def formatters(options)
15
+ builtin_formmaters.merge(options[:formatters] || {})
16
+ end
17
+
18
+ def formatter_for(api_format, options = {})
19
+ spec = formatters(options)[api_format]
20
+ case spec
21
+ when nil
22
+ ->(obj, _env) { obj }
23
+ when Symbol
24
+ method(spec)
25
+ else
26
+ spec
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,9 +1,13 @@
1
+ require 'grape/dsl/headers'
2
+
1
3
  module Grape
2
4
  module Middleware
3
5
  class Base
4
6
  attr_reader :app, :env, :options
5
7
  TEXT_HTML = 'text/html'.freeze
6
8
 
9
+ include Grape::DSL::Headers
10
+
7
11
  # @param [Rack Application] app The standard argument for a Rack middleware.
8
12
  # @param [Hash] options A hash of options, simply stored for use by subclasses.
9
13
  def initialize(app, options = {})
@@ -22,8 +26,20 @@ module Grape
22
26
  def call!(env)
23
27
  @env = env
24
28
  before
25
- @app_response = @app.call(@env)
26
- after || @app_response
29
+ begin
30
+ @app_response = @app.call(@env)
31
+ ensure
32
+ begin
33
+ after_response = after
34
+ rescue StandardError => e
35
+ warn "caught error of type #{e.class} in after callback inside #{self.class.name} : #{e.message}"
36
+ raise e
37
+ end
38
+ end
39
+
40
+ response = after_response || @app_response
41
+ merge_headers response
42
+ response
27
43
  end
28
44
 
29
45
  # @abstract
@@ -61,6 +77,16 @@ module Grape
61
77
  end
62
78
  types_without_params
63
79
  end
80
+
81
+ private
82
+
83
+ def merge_headers(response)
84
+ return unless headers.is_a?(Hash)
85
+ case response
86
+ when Rack::Response then response.headers.merge!(headers)
87
+ when Array then response[1].merge!(headers)
88
+ end
89
+ end
64
90
  end
65
91
  end
66
92
  end
@@ -22,6 +22,8 @@ module Grape
22
22
  def call!(env)
23
23
  @env = env
24
24
 
25
+ inject_helpers!
26
+
25
27
  begin
26
28
  error_response(catch(:error) do
27
29
  return @app.call(@env)
@@ -43,6 +45,14 @@ module Grape
43
45
  handler = options[:rescue_handlers].find(-> { [] }) { |error, _| klass <= error }[1]
44
46
  handler ||= options[:base_only_rescue_handlers][klass]
45
47
  handler ||= options[:all_rescue_handler]
48
+ with_option = options[:rescue_options][:with]
49
+ if with_option.instance_of?(Symbol)
50
+ if respond_to?(with_option)
51
+ handler ||= self.class.instance_method(with_option).bind(self)
52
+ else
53
+ fail NoMethodError, "undefined method `#{with_option}'"
54
+ end
55
+ end
46
56
  handler
47
57
  end
48
58
 
@@ -59,6 +69,19 @@ module Grape
59
69
  end
60
70
  end
61
71
 
72
+ def inject_helpers!
73
+ return if helpers_available?
74
+ endpoint = @env['api.endpoint']
75
+ self.class.instance_eval do
76
+ include endpoint.send(:helpers)
77
+ end if endpoint.is_a?(Grape::Endpoint)
78
+ @helpers = true
79
+ end
80
+
81
+ def helpers_available?
82
+ @helpers
83
+ end
84
+
62
85
  def error!(message, status = options[:default_status], headers = {}, backtrace = [])
63
86
  headers = headers.reverse_merge(Grape::Http::Headers::CONTENT_TYPE => content_type)
64
87
  rack_response(format_message(message, backtrace), status, headers)
@@ -84,7 +107,7 @@ module Grape
84
107
 
85
108
  def format_message(message, backtrace)
86
109
  format = env[Grape::Env::API_FORMAT] || options[:format]
87
- formatter = Grape::ErrorFormatter::Base.formatter_for(format, options)
110
+ formatter = Grape::ErrorFormatter.formatter_for(format, options)
88
111
  throw :error, status: 406, message: "The requested format '#{format}' is not supported." unless formatter
89
112
  formatter.call(message, backtrace, options, env)
90
113
  end