fragrant 0.0.1
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.
- data/LICENSE +22 -0
- data/README.md +6 -0
- data/bin/fragrant +14 -0
- data/lib/fragrant/address_manager.rb +80 -0
- data/lib/fragrant/vagrantfile_generator.rb +64 -0
- data/lib/fragrant.rb +323 -0
- data/spec/fragrant/environments_spec.rb +18 -0
- data/spec/fragrant/vagrantfile_generator_spec.rb +40 -0
- data/spec/fragrant/vms_spec.rb +26 -0
- data/spec/spec_helper.rb +15 -0
- data/vendor/grape/LICENSE +20 -0
- data/vendor/grape/lib/grape/api.rb +420 -0
- data/vendor/grape/lib/grape/cookies.rb +41 -0
- data/vendor/grape/lib/grape/endpoint.rb +377 -0
- data/vendor/grape/lib/grape/entity.rb +378 -0
- data/vendor/grape/lib/grape/exceptions/base.rb +17 -0
- data/vendor/grape/lib/grape/exceptions/validation_error.rb +10 -0
- data/vendor/grape/lib/grape/middleware/auth/basic.rb +30 -0
- data/vendor/grape/lib/grape/middleware/auth/digest.rb +30 -0
- data/vendor/grape/lib/grape/middleware/auth/oauth2.rb +72 -0
- data/vendor/grape/lib/grape/middleware/base.rb +154 -0
- data/vendor/grape/lib/grape/middleware/error.rb +87 -0
- data/vendor/grape/lib/grape/middleware/filter.rb +17 -0
- data/vendor/grape/lib/grape/middleware/formatter.rb +81 -0
- data/vendor/grape/lib/grape/middleware/prefixer.rb +21 -0
- data/vendor/grape/lib/grape/middleware/versioner/header.rb +59 -0
- data/vendor/grape/lib/grape/middleware/versioner/param.rb +44 -0
- data/vendor/grape/lib/grape/middleware/versioner/path.rb +42 -0
- data/vendor/grape/lib/grape/middleware/versioner.rb +29 -0
- data/vendor/grape/lib/grape/route.rb +23 -0
- data/vendor/grape/lib/grape/util/deep_merge.rb +23 -0
- data/vendor/grape/lib/grape/util/hash_stack.rb +100 -0
- data/vendor/grape/lib/grape/validations/coerce.rb +61 -0
- data/vendor/grape/lib/grape/validations/presence.rb +11 -0
- data/vendor/grape/lib/grape/validations/regexp.rb +13 -0
- data/vendor/grape/lib/grape/validations.rb +192 -0
- data/vendor/grape/lib/grape/version.rb +3 -0
- data/vendor/grape/lib/grape.rb +44 -0
- metadata +216 -0
@@ -0,0 +1,377 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'grape'
|
3
|
+
require 'hashie'
|
4
|
+
|
5
|
+
module Grape
|
6
|
+
# An Endpoint is the proxy scope in which all routing
|
7
|
+
# blocks are executed. In other words, any methods
|
8
|
+
# on the instance level of this class may be called
|
9
|
+
# from inside a `get`, `post`, etc. block.
|
10
|
+
class Endpoint
|
11
|
+
attr_accessor :block, :options, :settings
|
12
|
+
attr_reader :env, :request
|
13
|
+
|
14
|
+
def initialize(settings, options = {}, &block)
|
15
|
+
@settings = settings
|
16
|
+
@block = block
|
17
|
+
@options = options
|
18
|
+
|
19
|
+
raise ArgumentError, "Must specify :path option." unless options.key?(:path)
|
20
|
+
options[:path] = Array(options[:path])
|
21
|
+
options[:path] = ['/'] if options[:path].empty?
|
22
|
+
|
23
|
+
raise ArgumentError, "Must specify :method option." unless options.key?(:method)
|
24
|
+
options[:method] = Array(options[:method])
|
25
|
+
|
26
|
+
options[:route_options] ||= {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def routes
|
30
|
+
@routes ||= prepare_routes
|
31
|
+
end
|
32
|
+
|
33
|
+
def mount_in(route_set)
|
34
|
+
if options[:app] && options[:app].respond_to?(:endpoints)
|
35
|
+
options[:app].endpoints.each{|e| e.mount_in(route_set)}
|
36
|
+
else
|
37
|
+
routes.each do |route|
|
38
|
+
route_set.add_route(self, {
|
39
|
+
:path_info => route.route_compiled,
|
40
|
+
:request_method => route.route_method,
|
41
|
+
}, { :route_info => route })
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def prepare_routes
|
47
|
+
routes = []
|
48
|
+
options[:method].each do |method|
|
49
|
+
options[:path].each do |path|
|
50
|
+
prepared_path = prepare_path(path)
|
51
|
+
|
52
|
+
anchor = options[:route_options][:anchor]
|
53
|
+
anchor = anchor.nil? ? true : anchor
|
54
|
+
|
55
|
+
requirements = options[:route_options][:requirements] || {}
|
56
|
+
|
57
|
+
path = compile_path(prepared_path, anchor && !options[:app], requirements)
|
58
|
+
regex = Rack::Mount::RegexpWithNamedGroups.new(path)
|
59
|
+
path_params = {}
|
60
|
+
# named parameters in the api path
|
61
|
+
named_params = regex.named_captures.map { |nc| nc[0] } - [ 'version', 'format' ]
|
62
|
+
named_params.each { |named_param| path_params[named_param] = "" }
|
63
|
+
# route parameters declared via desc or appended to the api declaration
|
64
|
+
route_params = (options[:route_options][:params] || {})
|
65
|
+
path_params.merge!(route_params)
|
66
|
+
request_method = (method.to_s.upcase unless method == :any)
|
67
|
+
routes << Route.new(options[:route_options].clone.merge({
|
68
|
+
:prefix => settings[:root_prefix],
|
69
|
+
:version => settings[:version] ? settings[:version].join('|') : nil,
|
70
|
+
:namespace => namespace,
|
71
|
+
:method => request_method,
|
72
|
+
:path => prepared_path,
|
73
|
+
:params => path_params,
|
74
|
+
:compiled => path,
|
75
|
+
})
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
routes
|
80
|
+
end
|
81
|
+
|
82
|
+
def prepare_path(path)
|
83
|
+
parts = []
|
84
|
+
parts << settings[:root_prefix] if settings[:root_prefix]
|
85
|
+
parts << ':version' if settings[:version] && settings[:version_options][:using] == :path
|
86
|
+
parts << namespace.to_s if namespace
|
87
|
+
parts << path.to_s if path && '/' != path
|
88
|
+
Rack::Mount::Utils.normalize_path(parts.join('/') + '(.:format)')
|
89
|
+
end
|
90
|
+
|
91
|
+
def namespace
|
92
|
+
Rack::Mount::Utils.normalize_path(settings.stack.map{|s| s[:namespace]}.join('/'))
|
93
|
+
end
|
94
|
+
|
95
|
+
def compile_path(prepared_path, anchor = true, requirements = {})
|
96
|
+
endpoint_options = {}
|
97
|
+
endpoint_options[:version] = /#{settings[:version].join('|')}/ if settings[:version]
|
98
|
+
endpoint_options.merge!(requirements)
|
99
|
+
Rack::Mount::Strexp.compile(prepared_path, endpoint_options, %w( / . ? ), anchor)
|
100
|
+
end
|
101
|
+
|
102
|
+
def call(env)
|
103
|
+
dup.call!(env)
|
104
|
+
end
|
105
|
+
|
106
|
+
def call!(env)
|
107
|
+
env['api.endpoint'] = self
|
108
|
+
if options[:app]
|
109
|
+
options[:app].call(env)
|
110
|
+
else
|
111
|
+
builder = build_middleware
|
112
|
+
builder.run options[:app] || lambda{|env| self.run(env) }
|
113
|
+
builder.call(env)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# The parameters passed into the request as
|
118
|
+
# well as parsed from URL segments.
|
119
|
+
def params
|
120
|
+
@params ||= Hashie::Mash.new.
|
121
|
+
deep_merge(request.params).
|
122
|
+
deep_merge(env['rack.routing_args'] || {}).
|
123
|
+
deep_merge(self.body_params)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Pull out request body params if the content type matches and we're on a POST or PUT
|
127
|
+
def body_params
|
128
|
+
if ['POST', 'PUT'].include?(request.request_method.to_s.upcase) && request.content_length.to_i > 0
|
129
|
+
return case env['CONTENT_TYPE']
|
130
|
+
when 'application/json'
|
131
|
+
MultiJson.decode(request.body.read)
|
132
|
+
when 'application/xml'
|
133
|
+
MultiXml.parse(request.body.read)
|
134
|
+
else
|
135
|
+
{}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
{}
|
140
|
+
end
|
141
|
+
|
142
|
+
# The API version as specified in the URL.
|
143
|
+
def version; env['api.version'] end
|
144
|
+
|
145
|
+
# End the request and display an error to the
|
146
|
+
# end user with the specified message.
|
147
|
+
#
|
148
|
+
# @param message [String] The message to display.
|
149
|
+
# @param status [Integer] the HTTP Status Code. Defaults to 403.
|
150
|
+
def error!(message, status=403)
|
151
|
+
throw :error, :message => message, :status => status
|
152
|
+
end
|
153
|
+
|
154
|
+
# Redirect to a new url.
|
155
|
+
#
|
156
|
+
# @param url [String] The url to be redirect.
|
157
|
+
# @param options [Hash] The options used when redirect.
|
158
|
+
# :permanent, default true.
|
159
|
+
def redirect(url, options = {})
|
160
|
+
merged_options = {:permanent => false }.merge(options)
|
161
|
+
if merged_options[:permanent]
|
162
|
+
status 301
|
163
|
+
else
|
164
|
+
if env['HTTP_VERSION'] == 'HTTP/1.1' && request.request_method.to_s.upcase != "GET"
|
165
|
+
status 303
|
166
|
+
else
|
167
|
+
status 302
|
168
|
+
end
|
169
|
+
end
|
170
|
+
header "Location", url
|
171
|
+
body ""
|
172
|
+
end
|
173
|
+
|
174
|
+
# Set or retrieve the HTTP status code.
|
175
|
+
#
|
176
|
+
# @param status [Integer] The HTTP Status Code to return for this request.
|
177
|
+
def status(status = nil)
|
178
|
+
if status
|
179
|
+
@status = status
|
180
|
+
else
|
181
|
+
return @status if @status
|
182
|
+
case request.request_method.to_s.upcase
|
183
|
+
when 'POST'
|
184
|
+
201
|
185
|
+
else
|
186
|
+
200
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Set an individual header or retrieve
|
192
|
+
# all headers that have been set.
|
193
|
+
def header(key = nil, val = nil)
|
194
|
+
if key
|
195
|
+
val ? @header[key.to_s] = val : @header.delete(key.to_s)
|
196
|
+
else
|
197
|
+
@header
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Set response content-type
|
202
|
+
def content_type(val)
|
203
|
+
header('Content-Type', val)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Set or get a cookie
|
207
|
+
#
|
208
|
+
# @example
|
209
|
+
# cookies[:mycookie] = 'mycookie val'
|
210
|
+
# cookies['mycookie-string'] = 'mycookie string val'
|
211
|
+
# cookies[:more] = { :value => '123', :expires => Time.at(0) }
|
212
|
+
# cookies.delete :more
|
213
|
+
#
|
214
|
+
def cookies
|
215
|
+
@cookies ||= Cookies.new
|
216
|
+
end
|
217
|
+
|
218
|
+
# Allows you to define the response body as something other than the
|
219
|
+
# return value.
|
220
|
+
#
|
221
|
+
# @example
|
222
|
+
# get '/body' do
|
223
|
+
# body "Body"
|
224
|
+
# "Not the Body"
|
225
|
+
# end
|
226
|
+
#
|
227
|
+
# GET /body # => "Body"
|
228
|
+
def body(value = nil)
|
229
|
+
if value
|
230
|
+
@body = value
|
231
|
+
else
|
232
|
+
@body
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Allows you to make use of Grape Entities by setting
|
237
|
+
# the response body to the serializable hash of the
|
238
|
+
# entity provided in the `:with` option. This has the
|
239
|
+
# added benefit of automatically passing along environment
|
240
|
+
# and version information to the serialization, making it
|
241
|
+
# very easy to do conditional exposures. See Entity docs
|
242
|
+
# for more info.
|
243
|
+
#
|
244
|
+
# @example
|
245
|
+
#
|
246
|
+
# get '/users/:id' do
|
247
|
+
# present User.find(params[:id]),
|
248
|
+
# :with => API::Entities::User,
|
249
|
+
# :admin => current_user.admin?
|
250
|
+
# end
|
251
|
+
def present(object, options = {})
|
252
|
+
entity_class = options.delete(:with)
|
253
|
+
|
254
|
+
object.class.ancestors.each do |potential|
|
255
|
+
entity_class ||= (settings[:representations] || {})[potential]
|
256
|
+
end
|
257
|
+
|
258
|
+
entity_class ||= object.class.const_get(:Entity) if object.class.const_defined?(:Entity)
|
259
|
+
|
260
|
+
root = options.delete(:root)
|
261
|
+
|
262
|
+
representation = if entity_class
|
263
|
+
embeds = {:env => env}
|
264
|
+
embeds[:version] = env['api.version'] if env['api.version']
|
265
|
+
entity_class.represent(object, embeds.merge(options))
|
266
|
+
else
|
267
|
+
object
|
268
|
+
end
|
269
|
+
|
270
|
+
representation = { root => representation } if root
|
271
|
+
body representation
|
272
|
+
end
|
273
|
+
|
274
|
+
# Returns route information for the current request.
|
275
|
+
#
|
276
|
+
# @example
|
277
|
+
#
|
278
|
+
# desc "Returns the route description."
|
279
|
+
# get '/' do
|
280
|
+
# route.route_description
|
281
|
+
# end
|
282
|
+
def route
|
283
|
+
env["rack.routing_args"][:route_info]
|
284
|
+
end
|
285
|
+
|
286
|
+
protected
|
287
|
+
|
288
|
+
def run(env)
|
289
|
+
@env = env
|
290
|
+
@header = {}
|
291
|
+
@request = Rack::Request.new(@env)
|
292
|
+
|
293
|
+
self.extend helpers
|
294
|
+
cookies.read(@request)
|
295
|
+
|
296
|
+
run_filters befores
|
297
|
+
|
298
|
+
Array(settings[:validations]).each do |validator|
|
299
|
+
validator.validate!(params)
|
300
|
+
end
|
301
|
+
|
302
|
+
response_text = instance_eval &self.block
|
303
|
+
run_filters afters
|
304
|
+
cookies.write(header)
|
305
|
+
|
306
|
+
[status, header, [body || response_text]]
|
307
|
+
end
|
308
|
+
|
309
|
+
def build_middleware
|
310
|
+
b = Rack::Builder.new
|
311
|
+
|
312
|
+
b.use Rack::Head
|
313
|
+
b.use Grape::Middleware::Error,
|
314
|
+
:default_status => settings[:default_error_status] || 403,
|
315
|
+
:rescue_all => settings[:rescue_all],
|
316
|
+
:rescued_errors => aggregate_setting(:rescued_errors),
|
317
|
+
:format => settings[:error_format] || :txt,
|
318
|
+
:rescue_options => settings[:rescue_options],
|
319
|
+
:rescue_handlers => merged_setting(:rescue_handlers)
|
320
|
+
|
321
|
+
b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic
|
322
|
+
b.use Rack::Auth::Digest::MD5, settings[:auth][:realm], settings[:auth][:opaque], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_digest
|
323
|
+
b.use Grape::Middleware::Prefixer, :prefix => settings[:root_prefix] if settings[:root_prefix]
|
324
|
+
|
325
|
+
if settings[:version]
|
326
|
+
b.use Grape::Middleware::Versioner.using(settings[:version_options][:using]), {
|
327
|
+
:versions => settings[:version],
|
328
|
+
:version_options => settings[:version_options]
|
329
|
+
}
|
330
|
+
end
|
331
|
+
|
332
|
+
b.use Grape::Middleware::Formatter,
|
333
|
+
:format => settings[:format],
|
334
|
+
:default_format => settings[:default_format] || :txt,
|
335
|
+
:content_types => settings[:content_types]
|
336
|
+
|
337
|
+
aggregate_setting(:middleware).each do |m|
|
338
|
+
m = m.dup
|
339
|
+
block = m.pop if m.last.is_a?(Proc)
|
340
|
+
if block
|
341
|
+
b.use *m, &block
|
342
|
+
else
|
343
|
+
b.use *m
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
b
|
348
|
+
end
|
349
|
+
|
350
|
+
def helpers
|
351
|
+
m = Module.new
|
352
|
+
settings.stack.each{|frame| m.send :include, frame[:helpers] if frame[:helpers]}
|
353
|
+
m
|
354
|
+
end
|
355
|
+
|
356
|
+
def aggregate_setting(key)
|
357
|
+
settings.stack.inject([]) do |aggregate, frame|
|
358
|
+
aggregate += (frame[key] || [])
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def merged_setting(key)
|
363
|
+
settings.stack.inject({}) do |merged, frame|
|
364
|
+
merged.merge(frame[key] || {})
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
def run_filters(filters)
|
369
|
+
(filters || []).each do |filter|
|
370
|
+
instance_eval &filter
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
def befores; aggregate_setting(:befores) end
|
375
|
+
def afters; aggregate_setting(:afters) end
|
376
|
+
end
|
377
|
+
end
|