grape 0.6.0 → 0.6.1

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 (84) hide show
  1. checksums.yaml +8 -8
  2. data/.rubocop.yml +65 -0
  3. data/.travis.yml +4 -0
  4. data/CHANGELOG.md +17 -1
  5. data/Gemfile +1 -0
  6. data/README.md +16 -8
  7. data/RELEASING.md +105 -0
  8. data/grape.gemspec +1 -1
  9. data/lib/grape.rb +1 -0
  10. data/lib/grape/api.rb +88 -54
  11. data/lib/grape/cookies.rb +4 -6
  12. data/lib/grape/endpoint.rb +81 -69
  13. data/lib/grape/error_formatter/base.rb +5 -4
  14. data/lib/grape/error_formatter/json.rb +3 -3
  15. data/lib/grape/error_formatter/txt.rb +1 -1
  16. data/lib/grape/error_formatter/xml.rb +4 -4
  17. data/lib/grape/exceptions/base.rb +7 -7
  18. data/lib/grape/exceptions/incompatible_option_values.rb +13 -0
  19. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  20. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  21. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -4
  22. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  23. data/lib/grape/exceptions/missing_option.rb +1 -1
  24. data/lib/grape/exceptions/missing_vendor_option.rb +1 -1
  25. data/lib/grape/exceptions/unknown_options.rb +1 -1
  26. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  27. data/lib/grape/exceptions/validation.rb +2 -2
  28. data/lib/grape/exceptions/validation_errors.rb +1 -1
  29. data/lib/grape/formatter/base.rb +5 -4
  30. data/lib/grape/formatter/serializable_hash.rb +7 -6
  31. data/lib/grape/http/request.rb +2 -2
  32. data/lib/grape/locale/en.yml +2 -0
  33. data/lib/grape/middleware/auth/base.rb +3 -3
  34. data/lib/grape/middleware/auth/basic.rb +1 -1
  35. data/lib/grape/middleware/auth/oauth2.rb +18 -20
  36. data/lib/grape/middleware/base.rb +1 -1
  37. data/lib/grape/middleware/error.rb +19 -19
  38. data/lib/grape/middleware/filter.rb +3 -3
  39. data/lib/grape/middleware/formatter.rb +29 -23
  40. data/lib/grape/middleware/versioner.rb +1 -1
  41. data/lib/grape/middleware/versioner/accept_version_header.rb +8 -6
  42. data/lib/grape/middleware/versioner/header.rb +16 -14
  43. data/lib/grape/middleware/versioner/param.rb +7 -7
  44. data/lib/grape/middleware/versioner/path.rb +7 -9
  45. data/lib/grape/parser/base.rb +3 -2
  46. data/lib/grape/path.rb +1 -1
  47. data/lib/grape/route.rb +6 -4
  48. data/lib/grape/util/content_types.rb +2 -1
  49. data/lib/grape/util/deep_merge.rb +5 -5
  50. data/lib/grape/util/hash_stack.rb +2 -2
  51. data/lib/grape/validations.rb +34 -30
  52. data/lib/grape/validations/coerce.rb +6 -5
  53. data/lib/grape/validations/default.rb +0 -1
  54. data/lib/grape/validations/presence.rb +1 -1
  55. data/lib/grape/validations/regexp.rb +2 -2
  56. data/lib/grape/validations/values.rb +16 -0
  57. data/lib/grape/version.rb +1 -1
  58. data/spec/grape/api_spec.rb +229 -210
  59. data/spec/grape/endpoint_spec.rb +56 -54
  60. data/spec/grape/entity_spec.rb +31 -33
  61. data/spec/grape/exceptions/missing_mime_type_spec.rb +3 -9
  62. data/spec/grape/middleware/auth/basic_spec.rb +8 -8
  63. data/spec/grape/middleware/auth/digest_spec.rb +5 -5
  64. data/spec/grape/middleware/auth/oauth2_spec.rb +23 -23
  65. data/spec/grape/middleware/base_spec.rb +6 -6
  66. data/spec/grape/middleware/error_spec.rb +11 -15
  67. data/spec/grape/middleware/exception_spec.rb +45 -25
  68. data/spec/grape/middleware/formatter_spec.rb +56 -45
  69. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +25 -25
  70. data/spec/grape/middleware/versioner/header_spec.rb +54 -54
  71. data/spec/grape/middleware/versioner/param_spec.rb +17 -18
  72. data/spec/grape/middleware/versioner/path_spec.rb +6 -6
  73. data/spec/grape/middleware/versioner_spec.rb +1 -1
  74. data/spec/grape/util/hash_stack_spec.rb +26 -27
  75. data/spec/grape/validations/coerce_spec.rb +39 -34
  76. data/spec/grape/validations/default_spec.rb +12 -13
  77. data/spec/grape/validations/presence_spec.rb +18 -22
  78. data/spec/grape/validations/regexp_spec.rb +9 -9
  79. data/spec/grape/validations/values_spec.rb +64 -0
  80. data/spec/grape/validations_spec.rb +127 -70
  81. data/spec/shared/versioning_examples.rb +5 -5
  82. data/spec/support/basic_auth_encode_helpers.rb +0 -1
  83. data/spec/support/versioned_helpers.rb +5 -6
  84. metadata +10 -4
@@ -9,7 +9,7 @@
9
9
  module Grape
10
10
  module Middleware
11
11
  module Versioner
12
- extend self
12
+ module_function
13
13
 
14
14
  # @param strategy [Symbol] :path, :header or :param
15
15
  # @return a middleware class based on strategy
@@ -24,21 +24,21 @@ module Grape
24
24
  if strict?
25
25
  # If no Accept-Version header:
26
26
  if potential_version.empty?
27
- throw :error, :status => 406, :headers => error_headers, :message => 'Accept-Version header must be set.'
27
+ throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.'
28
28
  end
29
29
  end
30
30
 
31
31
  unless potential_version.empty?
32
32
  # If the requested version is not supported:
33
- if !versions.any? { |v| v.to_s == potential_version }
34
- throw :error, :status => 406, :headers => error_headers, :message => 'The requested version is not supported.'
33
+ unless versions.any? { |v| v.to_s == potential_version }
34
+ throw :error, status: 406, headers: error_headers, message: 'The requested version is not supported.'
35
35
  end
36
36
 
37
37
  env['api.version'] = potential_version
38
38
  end
39
39
  end
40
40
 
41
- private
41
+ private
42
42
 
43
43
  def versions
44
44
  options[:versions] || []
@@ -52,9 +52,11 @@ module Grape
52
52
  # of routes (see [Rack::Mount](https://github.com/josh/rack-mount) for more information). To prevent
53
53
  # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
54
54
  def cascade?
55
- options[:version_options] && options[:version_options].has_key?(:cascade) ?
56
- !! options[:version_options][:cascade] :
55
+ if options[:version_options] && options[:version_options].has_key?(:cascade)
56
+ !!options[:version_options][:cascade]
57
+ else
57
58
  true
59
+ end
58
60
  end
59
61
 
60
62
  def error_headers
@@ -29,15 +29,15 @@ module Grape
29
29
  if strict?
30
30
  # If no Accept header:
31
31
  if header.qvalues.empty?
32
- throw :error, :status => 406, :headers => error_headers, :message => 'Accept header must be set.'
32
+ throw :error, status: 406, headers: error_headers, message: 'Accept header must be set.'
33
33
  end
34
34
  # Remove any acceptable content types with ranges.
35
- header.qvalues.reject! do |media_type,_|
36
- Rack::Accept::Header.parse_media_type(media_type).find{|s| s == '*'}
35
+ header.qvalues.reject! do |media_type, _|
36
+ Rack::Accept::Header.parse_media_type(media_type).find { |s| s == '*' }
37
37
  end
38
38
  # If all Accept headers included a range:
39
39
  if header.qvalues.empty?
40
- throw :error, :status => 406, :headers => error_headers, :message => 'Accept header must not contain ranges ("*").'
40
+ throw :error, status: 406, headers: error_headers, message: 'Accept header must not contain ranges ("*").'
41
41
  end
42
42
  end
43
43
 
@@ -55,19 +55,19 @@ module Grape
55
55
  end
56
56
  # If none of the available content types are acceptable:
57
57
  elsif strict?
58
- throw :error, :status => 406, :headers => error_headers, :message => '406 Not Acceptable'
58
+ throw :error, status: 406, headers: error_headers, message: '406 Not Acceptable'
59
59
  # If all acceptable content types specify a vendor or version that doesn't exist:
60
- elsif header.values.all?{ |media_type| has_vendor?(media_type) || has_version?(media_type)}
61
- throw :error, :status => 406, :headers => error_headers, :message => 'API vendor or version not found.'
60
+ elsif header.values.all? { |header_value| has_vendor?(header_value) || has_version?(header_value) }
61
+ throw :error, status: 406, headers: error_headers, message: 'API vendor or version not found.'
62
62
  end
63
63
  end
64
64
 
65
- private
65
+ private
66
66
 
67
67
  def available_media_types
68
68
  available_media_types = []
69
69
 
70
- content_types.each do |extension,media_type|
70
+ content_types.each do |extension, media_type|
71
71
  versions.reverse.each do |version|
72
72
  available_media_types += ["application/vnd.#{vendor}-#{version}+#{extension}", "application/vnd.#{vendor}-#{version}"]
73
73
  end
@@ -76,7 +76,7 @@ module Grape
76
76
 
77
77
  available_media_types << "application/vnd.#{vendor}"
78
78
 
79
- content_types.each do |_,media_type|
79
+ content_types.each do |_, media_type|
80
80
  available_media_types << media_type
81
81
  end
82
82
 
@@ -99,9 +99,11 @@ module Grape
99
99
  # of routes (see [Rack::Mount](https://github.com/josh/rack-mount) for more information). To prevent
100
100
  # this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
101
101
  def cascade?
102
- options[:version_options] && options[:version_options].has_key?(:cascade) ?
103
- !! options[:version_options][:cascade] :
102
+ if options[:version_options] && options[:version_options].has_key?(:cascade)
103
+ !!options[:version_options][:cascade]
104
+ else
104
105
  true
106
+ end
105
107
  end
106
108
 
107
109
  def error_headers
@@ -111,14 +113,14 @@ module Grape
111
113
  # @param [String] media_type a content type
112
114
  # @return [Boolean] whether the content type sets a vendor
113
115
  def has_vendor?(media_type)
114
- type, subtype = Rack::Accept::Header.parse_media_type media_type
116
+ _, subtype = Rack::Accept::Header.parse_media_type media_type
115
117
  subtype[/\Avnd\.[a-z0-9*.]+/]
116
118
  end
117
119
 
118
120
  # @param [String] media_type a content type
119
121
  # @return [Boolean] whether the content type sets an API version
120
122
  def has_version?(media_type)
121
- type, subtype = Rack::Accept::Header.parse_media_type media_type
123
+ _, subtype = Rack::Accept::Header.parse_media_type media_type
122
124
  subtype[/\Avnd\.[a-z0-9*.]+-[a-z0-9*\-.]+/]
123
125
  end
124
126
 
@@ -4,24 +4,24 @@ module Grape
4
4
  module Middleware
5
5
  module Versioner
6
6
  # This middleware sets various version related rack environment variables
7
- # based on the request parameters and removes that parameter from the
7
+ # based on the request parameters and removes that parameter from the
8
8
  # request parameters for subsequent middleware and API.
9
9
  # If the version substring does not match any potential initialized
10
10
  # versions, a 404 error is thrown.
11
11
  # If the version substring is not passed the version (highest mounted)
12
12
  # version will be used.
13
- #
13
+ #
14
14
  # Example: For a uri path
15
15
  # /resource?apiver=v1
16
- #
16
+ #
17
17
  # The following rack env variables are set and path is rewritten to
18
18
  # '/resource':
19
- #
19
+ #
20
20
  # env['api.version'] => 'v1'
21
21
  class Param < Base
22
22
  def default_options
23
23
  {
24
- :parameter => "apiver"
24
+ parameter: "apiver"
25
25
  }
26
26
  end
27
27
 
@@ -30,8 +30,8 @@ module Grape
30
30
  potential_version = request.params[paramkey]
31
31
 
32
32
  unless potential_version.nil?
33
- if options[:versions] && ! options[:versions].find { |v| v.to_s == potential_version }
34
- throw :error, :status => 404, :message => "404 API Version Not Found", :headers => {'X-Cascade' => 'pass'}
33
+ if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
34
+ throw :error, status: 404, message: "404 API Version Not Found", headers: { 'X-Cascade' => 'pass' }
35
35
  end
36
36
  env['api.version'] = potential_version
37
37
  env['rack.request.query_hash'].delete(paramkey)
@@ -7,19 +7,19 @@ module Grape
7
7
  # based on the uri path and removes the version substring from the uri
8
8
  # path. If the version substring does not match any potential initialized
9
9
  # versions, a 404 error is thrown.
10
- #
10
+ #
11
11
  # Example: For a uri path
12
12
  # /v1/resource
13
- #
13
+ #
14
14
  # The following rack env variables are set and path is rewritten to
15
15
  # '/resource':
16
- #
16
+ #
17
17
  # env['api.version'] => 'v1'
18
- #
18
+ #
19
19
  class Path < Base
20
20
  def default_options
21
21
  {
22
- :pattern => /.*/i
22
+ pattern: /.*/i
23
23
  }
24
24
  end
25
25
 
@@ -34,11 +34,9 @@ module Grape
34
34
  pieces = path.split('/')
35
35
  potential_version = pieces[1]
36
36
  if potential_version =~ options[:pattern]
37
- if options[:versions] && ! options[:versions].find { |v| v.to_s == potential_version }
38
- throw :error, :status => 404, :message => "404 API Version Not Found"
37
+ if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
38
+ throw :error, status: 404, message: "404 API Version Not Found"
39
39
  end
40
-
41
- truncated_path = "/#{pieces[2..-1].join('/')}"
42
40
  env['api.version'] = potential_version
43
41
  end
44
42
  end
@@ -5,8 +5,9 @@ module Grape
5
5
  class << self
6
6
 
7
7
  PARSERS = {
8
- :json => Grape::Parser::Json,
9
- :xml => Grape::Parser::Xml
8
+ json: Grape::Parser::Json,
9
+ jsonapi: Grape::Parser::Json,
10
+ xml: Grape::Parser::Xml
10
11
  }
11
12
 
12
13
  def parsers(options)
data/lib/grape/path.rb CHANGED
@@ -62,7 +62,7 @@ module Grape
62
62
  parts << raw_path.to_s
63
63
  parts.flatten.reject { |part| part == '/' }
64
64
  end
65
-
65
+
66
66
  def split_setting(key, delimiter)
67
67
  return if settings[key].nil?
68
68
  settings[key].to_s.split("/")
data/lib/grape/route.rb CHANGED
@@ -2,24 +2,26 @@ module Grape
2
2
 
3
3
  # A compiled route for inspection.
4
4
  class Route
5
-
5
+
6
6
  def initialize(options = {})
7
7
  @options = options || {}
8
8
  end
9
-
9
+
10
10
  def method_missing(method_id, *arguments)
11
- if match = /route_([_a-zA-Z]\w*)/.match(method_id.to_s)
11
+ match = /route_([_a-zA-Z]\w*)/.match(method_id.to_s)
12
+ if match
12
13
  @options[match.captures.last.to_sym]
13
14
  else
14
15
  super
15
16
  end
16
17
  end
17
-
18
+
18
19
  def to_s
19
20
  "version=#{route_version}, method=#{route_method}, path=#{route_path}"
20
21
  end
21
22
 
22
23
  private
24
+
23
25
  def to_ary
24
26
  nil
25
27
  end
@@ -5,10 +5,11 @@ module Grape
5
5
  :xml, 'application/xml',
6
6
  :serializable_hash, 'application/json',
7
7
  :json, 'application/json',
8
+ :jsonapi, 'application/vnd.api+json',
8
9
  :atom, 'application/atom+xml',
9
10
  :rss, 'application/rss+xml',
10
11
  :txt, 'text/plain',
11
- ]
12
+ ]
12
13
 
13
14
  def self.content_types_for(from_settings)
14
15
  from_settings || Grape::ContentTypes::CONTENT_TYPES
@@ -3,18 +3,18 @@ class Hash
3
3
  # activesupport/lib/active_support/core_ext/hash/deep_merge.rb
4
4
  # Returns a new hash with +self+ and +other_hash+ merged recursively.
5
5
  #
6
- # h1 = {:x => {:y => [4,5,6]}, :z => [7,8,9]}
7
- # h2 = {:x => {:y => [7,8,9]}, :z => "xyz"}
6
+ # h1 = {x: {y: [4,5,6]}, z: [7,8,9]}
7
+ # h2 = {x: {y: [7,8,9]}, z: "xyz"}
8
8
  #
9
- # h1.deep_merge(h2) #=> { :x => {:y => [7, 8, 9]}, :z => "xyz" }
10
- # h2.deep_merge(h1) #=> { :x => {:y => [4, 5, 6]}, :z => [7, 8, 9] }
9
+ # h1.deep_merge(h2) #=> { x: {y: [7, 8, 9]}, z: "xyz" }
10
+ # h2.deep_merge(h1) #=> { x: {y: [4, 5, 6]}, z: [7, 8, 9] }
11
11
  def deep_merge(other_hash)
12
12
  dup.deep_merge!(other_hash)
13
13
  end
14
14
 
15
15
  # Same as +deep_merge+, but modifies +self+.
16
16
  def deep_merge!(other_hash)
17
- other_hash.each_pair do |k,v|
17
+ other_hash.each_pair do |k, v|
18
18
  tv = self[k]
19
19
  self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
20
20
  end
@@ -84,7 +84,7 @@ module Grape
84
84
 
85
85
  # Prepend another HashStack's to self
86
86
  def prepend(hash_stack)
87
- @stack.unshift *hash_stack.stack
87
+ @stack.unshift(*hash_stack.stack)
88
88
  self
89
89
  end
90
90
 
@@ -100,7 +100,7 @@ module Grape
100
100
  # @param key [Symbol] The key to gather
101
101
  # @return [Array]
102
102
  def gather(key)
103
- stack.map{|s| s[key] }.flatten.compact.uniq
103
+ stack.map { |s| s[key] }.flatten.compact.uniq
104
104
  end
105
105
 
106
106
  def to_s
@@ -45,14 +45,14 @@ module Grape
45
45
  end
46
46
  end
47
47
 
48
- private
48
+ private
49
49
 
50
50
  def self.convert_to_short_name(klass)
51
- ret = klass.name.gsub(/::/, '/').
52
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
53
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
54
- tr("-", "_").
55
- downcase
51
+ ret = klass.name.gsub(/::/, '/')
52
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
53
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
54
+ .tr("-", "_")
55
+ .downcase
56
56
  File.basename(ret, '_validator')
57
57
  end
58
58
  end
@@ -72,7 +72,7 @@ module Grape
72
72
  class Validator
73
73
  def self.inherited(klass)
74
74
  short_name = convert_to_short_name(klass)
75
- Validations::register_validator(short_name, klass)
75
+ Validations.register_validator(short_name, klass)
76
76
  end
77
77
  end
78
78
 
@@ -110,10 +110,8 @@ module Grape
110
110
  def requires(*attrs, &block)
111
111
  return new_scope(attrs, &block) if block_given?
112
112
 
113
- validations = {:presence => true}
114
- if attrs.last.is_a?(Hash)
115
- validations.merge!(attrs.pop)
116
- end
113
+ validations = { presence: true }
114
+ validations.merge!(attrs.pop) if attrs.last.is_a?(Hash)
117
115
 
118
116
  push_declared_params(attrs)
119
117
  validates(attrs, validations)
@@ -123,9 +121,7 @@ module Grape
123
121
  return new_scope(attrs, true, &block) if block_given?
124
122
 
125
123
  validations = {}
126
- if attrs.last.is_a?(Hash)
127
- validations.merge!(attrs.pop)
128
- end
124
+ validations.merge!(attrs.pop) if attrs.last.is_a?(Hash)
129
125
 
130
126
  push_declared_params(attrs)
131
127
  validates(attrs, validations)
@@ -146,15 +142,15 @@ module Grape
146
142
  name.to_s
147
143
  end
148
144
 
149
- protected
145
+ protected
150
146
 
151
147
  def push_declared_params(attrs)
152
148
  @declared_params.concat attrs
153
149
  end
154
150
 
155
- private
151
+ private
156
152
 
157
- def new_scope(attrs, optional=false, &block)
153
+ def new_scope(attrs, optional = false, &block)
158
154
  raise ArgumentError unless attrs.size == 1
159
155
  ParamsScope.new(api: @api, element: attrs.first, parent: self, optional: optional, &block)
160
156
  end
@@ -170,26 +166,34 @@ module Grape
170
166
  end
171
167
 
172
168
  def validates(attrs, validations)
173
- doc_attrs = { :required => validations.keys.include?(:presence) }
169
+ doc_attrs = { required: validations.keys.include?(:presence) }
174
170
 
175
171
  # special case (type = coerce)
176
- if validations[:type]
177
- validations[:coerce] = validations.delete(:type)
178
- end
172
+ validations[:coerce] = validations.delete(:type) if validations.key?(:type)
179
173
 
180
- if coerce_type = validations[:coerce]
181
- doc_attrs[:type] = coerce_type.to_s
182
- end
174
+ coerce_type = validations[:coerce]
175
+ doc_attrs[:type] = coerce_type.to_s if coerce_type
176
+
177
+ desc = validations.delete(:desc)
178
+ doc_attrs[:desc] = desc if desc
179
+
180
+ default = validations[:default]
181
+ doc_attrs[:default] = default if default
182
+
183
+ values = validations[:values]
184
+ doc_attrs[:values] = values if values
183
185
 
184
- if desc = validations.delete(:desc)
185
- doc_attrs[:desc] = desc
186
+ # default value should be present in values array, if both exist
187
+ if default && values && !values.include?(default)
188
+ raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values)
186
189
  end
187
190
 
188
- if default = validations[:default]
189
- doc_attrs[:default] = default
191
+ # type should be compatible with values array, if both exist
192
+ if coerce_type && values && values.any? { |v| !v.instance_of?(coerce_type) }
193
+ raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
190
194
  end
191
195
 
192
- full_attrs = attrs.collect{ |name| { :name => name, :full_name => full_name(name)} }
196
+ full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
193
197
  @api.document_attribute(full_attrs, doc_attrs)
194
198
 
195
199
  # Validate for presence before any other validators
@@ -212,7 +216,7 @@ module Grape
212
216
  end
213
217
 
214
218
  def validate(type, options, attrs, doc_attrs)
215
- validator_class = Validations::validators[type.to_s]
219
+ validator_class = Validations.validators[type.to_s]
216
220
 
217
221
  if validator_class
218
222
  (@api.settings.peek[:validations] ||= []) << validator_class.new(attrs, options, doc_attrs[:required], self)