grape 0.8.0 → 0.9.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -2
  3. data/.rubocop_todo.yml +80 -0
  4. data/.travis.yml +2 -2
  5. data/CHANGELOG.md +21 -2
  6. data/Gemfile +1 -6
  7. data/Guardfile +1 -5
  8. data/README.md +110 -27
  9. data/Rakefile +1 -1
  10. data/UPGRADING.md +35 -0
  11. data/grape.gemspec +5 -2
  12. data/lib/grape.rb +20 -4
  13. data/lib/grape/api.rb +25 -467
  14. data/lib/grape/api/helpers.rb +7 -0
  15. data/lib/grape/dsl/callbacks.rb +27 -0
  16. data/lib/grape/dsl/configuration.rb +27 -0
  17. data/lib/grape/dsl/helpers.rb +86 -0
  18. data/lib/grape/dsl/inside_route.rb +227 -0
  19. data/lib/grape/dsl/middleware.rb +33 -0
  20. data/lib/grape/dsl/parameters.rb +79 -0
  21. data/lib/grape/dsl/request_response.rb +152 -0
  22. data/lib/grape/dsl/routing.rb +172 -0
  23. data/lib/grape/dsl/validations.rb +29 -0
  24. data/lib/grape/endpoint.rb +6 -226
  25. data/lib/grape/error_formatter/base.rb +28 -0
  26. data/lib/grape/error_formatter/json.rb +2 -0
  27. data/lib/grape/error_formatter/txt.rb +2 -0
  28. data/lib/grape/error_formatter/xml.rb +2 -0
  29. data/lib/grape/exceptions/base.rb +6 -0
  30. data/lib/grape/exceptions/validation.rb +3 -3
  31. data/lib/grape/exceptions/validation_errors.rb +19 -6
  32. data/lib/grape/locale/en.yml +5 -3
  33. data/lib/grape/middleware/auth/base.rb +28 -12
  34. data/lib/grape/middleware/auth/dsl.rb +35 -0
  35. data/lib/grape/middleware/auth/strategies.rb +24 -0
  36. data/lib/grape/middleware/auth/strategy_info.rb +15 -0
  37. data/lib/grape/validations.rb +3 -92
  38. data/lib/grape/validations/at_least_one_of.rb +25 -0
  39. data/lib/grape/validations/coerce.rb +2 -2
  40. data/lib/grape/validations/exactly_one_of.rb +2 -2
  41. data/lib/grape/validations/mutual_exclusion.rb +2 -2
  42. data/lib/grape/validations/presence.rb +1 -1
  43. data/lib/grape/validations/regexp.rb +1 -1
  44. data/lib/grape/validations/values.rb +1 -1
  45. data/lib/grape/version.rb +1 -1
  46. data/spec/grape/api/helpers_spec.rb +36 -0
  47. data/spec/grape/api_spec.rb +72 -19
  48. data/spec/grape/dsl/callbacks_spec.rb +44 -0
  49. data/spec/grape/dsl/configuration_spec.rb +37 -0
  50. data/spec/grape/dsl/helpers_spec.rb +54 -0
  51. data/spec/grape/dsl/inside_route_spec.rb +222 -0
  52. data/spec/grape/dsl/middleware_spec.rb +40 -0
  53. data/spec/grape/dsl/parameters_spec.rb +108 -0
  54. data/spec/grape/dsl/request_response_spec.rb +123 -0
  55. data/spec/grape/dsl/routing_spec.rb +132 -0
  56. data/spec/grape/dsl/validations_spec.rb +55 -0
  57. data/spec/grape/endpoint_spec.rb +60 -11
  58. data/spec/grape/entity_spec.rb +9 -4
  59. data/spec/grape/exceptions/validation_errors_spec.rb +31 -1
  60. data/spec/grape/middleware/auth/base_spec.rb +34 -0
  61. data/spec/grape/middleware/auth/dsl_spec.rb +53 -0
  62. data/spec/grape/middleware/auth/strategies_spec.rb +81 -0
  63. data/spec/grape/middleware/error_spec.rb +33 -1
  64. data/spec/grape/middleware/exception_spec.rb +13 -0
  65. data/spec/grape/validations/at_least_one_of_spec.rb +63 -0
  66. data/spec/grape/validations/exactly_one_of_spec.rb +1 -1
  67. data/spec/grape/validations/presence_spec.rb +159 -122
  68. data/spec/grape/validations/zh-CN.yml +1 -1
  69. data/spec/grape/validations_spec.rb +77 -15
  70. data/spec/spec_helper.rb +1 -0
  71. data/spec/support/endpoint_faker.rb +23 -0
  72. metadata +93 -15
  73. data/lib/grape/middleware/auth/basic.rb +0 -13
  74. data/lib/grape/middleware/auth/digest.rb +0 -13
  75. data/lib/grape/middleware/auth/oauth2.rb +0 -83
  76. data/spec/grape/middleware/auth/basic_spec.rb +0 -31
  77. data/spec/grape/middleware/auth/digest_spec.rb +0 -47
  78. data/spec/grape/middleware/auth/oauth2_spec.rb +0 -135
@@ -0,0 +1,7 @@
1
+ module Grape
2
+ class API
3
+ module Helpers
4
+ include Grape::DSL::Helpers::BaseHelper
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_support/concern'
2
+
3
+ module Grape
4
+ module DSL
5
+ module Callbacks
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ def before(&block)
10
+ imbue(:befores, [block])
11
+ end
12
+
13
+ def before_validation(&block)
14
+ imbue(:before_validations, [block])
15
+ end
16
+
17
+ def after_validation(&block)
18
+ imbue(:after_validations, [block])
19
+ end
20
+
21
+ def after(&block)
22
+ imbue(:afters, [block])
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_support/concern'
2
+
3
+ module Grape
4
+ module DSL
5
+ module Configuration
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ attr_writer :logger
10
+ attr_reader :settings
11
+
12
+ def logger(logger = nil)
13
+ if logger
14
+ @logger = logger
15
+ else
16
+ @logger ||= Logger.new($stdout)
17
+ end
18
+ end
19
+
20
+ # Add a description to the next namespace or function.
21
+ def desc(description, options = {})
22
+ @last_description = options.merge(description: description)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,86 @@
1
+ require 'active_support/concern'
2
+
3
+ module Grape
4
+ module DSL
5
+ module Helpers
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ # Add helper methods that will be accessible from any
10
+ # endpoint within this namespace (and child namespaces).
11
+ #
12
+ # When called without a block, all known helpers within this scope
13
+ # are included.
14
+ #
15
+ # @param [Module] new_mod optional module of methods to include
16
+ # @param [Block] block optional block of methods to include
17
+ #
18
+ # @example Define some helpers.
19
+ #
20
+ # class ExampleAPI < Grape::API
21
+ # helpers do
22
+ # def current_user
23
+ # User.find_by_id(params[:token])
24
+ # end
25
+ # end
26
+ # end
27
+ #
28
+ def helpers(new_mod = nil, &block)
29
+ if block_given? || new_mod
30
+ mod = settings.peek[:helpers] || Module.new
31
+ if new_mod
32
+ inject_api_helpers_to_mod(new_mod) if new_mod.is_a?(BaseHelper)
33
+ mod.class_eval do
34
+ include new_mod
35
+ end
36
+ end
37
+ if block_given?
38
+ inject_api_helpers_to_mod(mod) do
39
+ mod.class_eval(&block)
40
+ end
41
+ end
42
+ set(:helpers, mod)
43
+ else
44
+ mod = Module.new
45
+ settings.stack.each do |s|
46
+ mod.send :include, s[:helpers] if s[:helpers]
47
+ end
48
+ change!
49
+ mod
50
+ end
51
+ end
52
+
53
+ protected
54
+
55
+ def inject_api_helpers_to_mod(mod, &block)
56
+ mod.extend(BaseHelper)
57
+ yield if block_given?
58
+ mod.api_changed(self)
59
+ end
60
+ end
61
+
62
+ # This module extends user defined helpers
63
+ # to provide some API-specific functionality
64
+ module BaseHelper
65
+ attr_accessor :api
66
+ def params(name, &block)
67
+ @named_params ||= {}
68
+ @named_params.merge! name => block
69
+ end
70
+
71
+ def api_changed(new_api)
72
+ @api = new_api
73
+ process_named_params
74
+ end
75
+
76
+ protected
77
+
78
+ def process_named_params
79
+ if @named_params && @named_params.any?
80
+ api.imbue(:named_params, @named_params)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,227 @@
1
+ require 'active_support/concern'
2
+
3
+ module Grape
4
+ module DSL
5
+ module InsideRoute
6
+ extend ActiveSupport::Concern
7
+
8
+ # A filtering method that will return a hash
9
+ # consisting only of keys that have been declared by a
10
+ # `params` statement against the current/target endpoint or parent
11
+ # namespaces
12
+ #
13
+ # @param params [Hash] The initial hash to filter. Usually this will just be `params`
14
+ # @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
15
+ # options. `:include_parent_namespaces` defaults to true, hence must be set to false if
16
+ # you want only to return params declared against the current/target endpoint
17
+ def declared(params, options = {}, declared_params = nil)
18
+ options[:include_missing] = true unless options.key?(:include_missing)
19
+ options[:include_parent_namespaces] = true unless options.key?(:include_parent_namespaces)
20
+ if declared_params.nil?
21
+ declared_params = !options[:include_parent_namespaces] ? settings[:declared_params] :
22
+ settings.gather(:declared_params)
23
+ end
24
+
25
+ unless declared_params
26
+ raise ArgumentError, "Tried to filter for declared parameters but none exist."
27
+ end
28
+
29
+ if params.is_a? Array
30
+ params.map do |param|
31
+ declared(param || {}, options, declared_params)
32
+ end
33
+ else
34
+ declared_params.inject({}) do |hash, key|
35
+ key = { key => nil } unless key.is_a? Hash
36
+
37
+ key.each_pair do |parent, children|
38
+ output_key = options[:stringify] ? parent.to_s : parent.to_sym
39
+ if params.key?(parent) || options[:include_missing]
40
+ hash[output_key] = if children
41
+ declared(params[parent] || {}, options, Array(children))
42
+ else
43
+ params[parent]
44
+ end
45
+ end
46
+ end
47
+
48
+ hash
49
+ end
50
+ end
51
+ end
52
+
53
+ # The API version as specified in the URL.
54
+ def version
55
+ env['api.version']
56
+ end
57
+
58
+ # End the request and display an error to the
59
+ # end user with the specified message.
60
+ #
61
+ # @param message [String] The message to display.
62
+ # @param status [Integer] the HTTP Status Code. Defaults to default_error_status, 500 if not set.
63
+ def error!(message, status = nil, headers = nil)
64
+ self.status(status || settings[:default_error_status])
65
+ throw :error, message: message, status: self.status, headers: headers
66
+ end
67
+
68
+ # Redirect to a new url.
69
+ #
70
+ # @param url [String] The url to be redirect.
71
+ # @param options [Hash] The options used when redirect.
72
+ # :permanent, default false.
73
+ def redirect(url, options = {})
74
+ merged_options = { permanent: false }.merge(options)
75
+ if merged_options[:permanent]
76
+ status 301
77
+ else
78
+ if env['HTTP_VERSION'] == 'HTTP/1.1' && request.request_method.to_s.upcase != "GET"
79
+ status 303
80
+ else
81
+ status 302
82
+ end
83
+ end
84
+ header "Location", url
85
+ body ""
86
+ end
87
+
88
+ # Set or retrieve the HTTP status code.
89
+ #
90
+ # @param status [Integer] The HTTP Status Code to return for this request.
91
+ def status(status = nil)
92
+ if status
93
+ @status = status
94
+ else
95
+ return @status if @status
96
+ case request.request_method.to_s.upcase
97
+ when 'POST'
98
+ 201
99
+ else
100
+ 200
101
+ end
102
+ end
103
+ end
104
+
105
+ # Set an individual header or retrieve
106
+ # all headers that have been set.
107
+ def header(key = nil, val = nil)
108
+ if key
109
+ val ? @header[key.to_s] = val : @header.delete(key.to_s)
110
+ else
111
+ @header
112
+ end
113
+ end
114
+
115
+ # Set response content-type
116
+ def content_type(val = nil)
117
+ if val
118
+ header('Content-Type', val)
119
+ else
120
+ header['Content-Type']
121
+ end
122
+ end
123
+
124
+ # Set or get a cookie
125
+ #
126
+ # @example
127
+ # cookies[:mycookie] = 'mycookie val'
128
+ # cookies['mycookie-string'] = 'mycookie string val'
129
+ # cookies[:more] = { value: '123', expires: Time.at(0) }
130
+ # cookies.delete :more
131
+ #
132
+ def cookies
133
+ @cookies ||= Cookies.new
134
+ end
135
+
136
+ # Allows you to define the response body as something other than the
137
+ # return value.
138
+ #
139
+ # @example
140
+ # get '/body' do
141
+ # body "Body"
142
+ # "Not the Body"
143
+ # end
144
+ #
145
+ # GET /body # => "Body"
146
+ def body(value = nil)
147
+ if value
148
+ @body = value
149
+ else
150
+ @body
151
+ end
152
+ end
153
+
154
+ # Allows you to make use of Grape Entities by setting
155
+ # the response body to the serializable hash of the
156
+ # entity provided in the `:with` option. This has the
157
+ # added benefit of automatically passing along environment
158
+ # and version information to the serialization, making it
159
+ # very easy to do conditional exposures. See Entity docs
160
+ # for more info.
161
+ #
162
+ # @example
163
+ #
164
+ # get '/users/:id' do
165
+ # present User.find(params[:id]),
166
+ # with: API::Entities::User,
167
+ # admin: current_user.admin?
168
+ # end
169
+ def present(*args)
170
+ options = args.count > 1 ? args.extract_options! : {}
171
+ key, object = if args.count == 2 && args.first.is_a?(Symbol)
172
+ args
173
+ else
174
+ [nil, args.first]
175
+ end
176
+ entity_class = entity_class_for_obj(object, options)
177
+
178
+ root = options.delete(:root)
179
+
180
+ representation = if entity_class
181
+ embeds = { env: env }
182
+ embeds[:version] = env['api.version'] if env['api.version']
183
+ entity_class.represent(object, embeds.merge(options))
184
+ else
185
+ object
186
+ end
187
+
188
+ representation = { root => representation } if root
189
+ representation = (@body || {}).merge(key => representation) if key
190
+ body representation
191
+ end
192
+
193
+ # Returns route information for the current request.
194
+ #
195
+ # @example
196
+ #
197
+ # desc "Returns the route description."
198
+ # get '/' do
199
+ # route.route_description
200
+ # end
201
+ def route
202
+ env["rack.routing_args"][:route_info]
203
+ end
204
+
205
+ def entity_class_for_obj(object, options)
206
+ entity_class = options.delete(:with)
207
+
208
+ if entity_class.nil?
209
+ # entity class not explicitely defined, auto-detect from relation#klass or first object in the collection
210
+ object_class = if object.respond_to?(:klass)
211
+ object.klass
212
+ else
213
+ object.respond_to?(:first) ? object.first.class : object.class
214
+ end
215
+
216
+ object_class.ancestors.each do |potential|
217
+ entity_class ||= (settings[:representations] || {})[potential]
218
+ end
219
+
220
+ entity_class ||= object_class.const_get(:Entity) if object_class.const_defined?(:Entity) && object_class.const_get(:Entity).respond_to?(:represent)
221
+ end
222
+
223
+ entity_class
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,33 @@
1
+ require 'active_support/concern'
2
+
3
+ module Grape
4
+ module DSL
5
+ module Middleware
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ # Apply a custom middleware to the API. Applies
10
+ # to the current namespace and any children, but
11
+ # not parents.
12
+ #
13
+ # @param middleware_class [Class] The class of the middleware you'd like
14
+ # to inject.
15
+ def use(middleware_class, *args, &block)
16
+ arr = [middleware_class, *args]
17
+ arr << block if block_given?
18
+ imbue(:middleware, [arr])
19
+ end
20
+
21
+ # Retrieve an array of the middleware classes
22
+ # and arguments that are currently applied to the
23
+ # application.
24
+ def middleware
25
+ settings.stack.inject([]) do |a, s|
26
+ a += s[:middleware] if s[:middleware]
27
+ a
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,79 @@
1
+ require 'active_support/concern'
2
+
3
+ module Grape
4
+ module DSL
5
+ module Parameters
6
+ extend ActiveSupport::Concern
7
+
8
+ def use(*names)
9
+ named_params = @api.settings[:named_params] || {}
10
+ options = names.last.is_a?(Hash) ? names.pop : {}
11
+ names.each do |name|
12
+ params_block = named_params.fetch(name) do
13
+ raise "Params :#{name} not found!"
14
+ end
15
+ instance_exec(options, &params_block)
16
+ end
17
+ end
18
+ alias_method :use_scope, :use
19
+ alias_method :includes, :use
20
+
21
+ def requires(*attrs, &block)
22
+ orig_attrs = attrs.clone
23
+
24
+ opts = attrs.last.is_a?(Hash) ? attrs.pop : nil
25
+
26
+ if opts && opts[:using]
27
+ require_required_and_optional_fields(attrs.first, opts)
28
+ else
29
+ validate_attributes(attrs, opts, &block)
30
+
31
+ block_given? ? new_scope(orig_attrs, &block) :
32
+ push_declared_params(attrs)
33
+ end
34
+ end
35
+
36
+ def optional(*attrs, &block)
37
+ orig_attrs = attrs
38
+
39
+ validations = {}
40
+ validations.merge!(attrs.pop) if attrs.last.is_a?(Hash)
41
+ validations[:type] ||= Array if block_given?
42
+ validates(attrs, validations)
43
+
44
+ block_given? ? new_scope(orig_attrs, true, &block) :
45
+ push_declared_params(attrs)
46
+ end
47
+
48
+ def mutually_exclusive(*attrs)
49
+ validates(attrs, mutual_exclusion: true)
50
+ end
51
+
52
+ def exactly_one_of(*attrs)
53
+ validates(attrs, exactly_one_of: true)
54
+ end
55
+
56
+ def at_least_one_of(*attrs)
57
+ validates(attrs, at_least_one_of: true)
58
+ end
59
+
60
+ def group(*attrs, &block)
61
+ requires(*attrs, &block)
62
+ end
63
+
64
+ def params(params)
65
+ params = @parent.params(params) if @parent
66
+ if @element
67
+ if params.is_a?(Array)
68
+ params = params.flat_map { |el| el[@element] || {} }
69
+ elsif params.is_a?(Hash)
70
+ params = params[@element] || {}
71
+ else
72
+ params = {}
73
+ end
74
+ end
75
+ params
76
+ end
77
+ end
78
+ end
79
+ end