fragrant 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,87 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
module Grape
|
5
|
+
module Middleware
|
6
|
+
class Error < Base
|
7
|
+
include Formats
|
8
|
+
|
9
|
+
def default_options
|
10
|
+
{
|
11
|
+
:default_status => 403, # default status returned on error
|
12
|
+
:default_message => "",
|
13
|
+
:format => :txt,
|
14
|
+
:formatters => {},
|
15
|
+
:rescue_all => false, # true to rescue all exceptions
|
16
|
+
:rescue_options => { :backtrace => false }, # true to display backtrace
|
17
|
+
:rescue_handlers => {}, # rescue handler blocks
|
18
|
+
:rescued_errors => []
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def encode_json(message, backtrace)
|
23
|
+
result = message.is_a?(Hash) ? message : { :error => message }
|
24
|
+
if (options[:rescue_options] || {})[:backtrace] && backtrace && ! backtrace.empty?
|
25
|
+
result = result.merge({ :backtrace => backtrace })
|
26
|
+
end
|
27
|
+
MultiJson.dump(result)
|
28
|
+
end
|
29
|
+
|
30
|
+
def encode_txt(message, backtrace)
|
31
|
+
result = message.is_a?(Hash) ? MultiJson.dump(message) : message
|
32
|
+
if (options[:rescue_options] || {})[:backtrace] && backtrace && ! backtrace.empty?
|
33
|
+
result += "\r\n "
|
34
|
+
result += backtrace.join("\r\n ")
|
35
|
+
end
|
36
|
+
result
|
37
|
+
end
|
38
|
+
|
39
|
+
def call!(env)
|
40
|
+
@env = env
|
41
|
+
|
42
|
+
begin
|
43
|
+
error_response(catch(:error){
|
44
|
+
return @app.call(@env)
|
45
|
+
})
|
46
|
+
rescue Exception => e
|
47
|
+
is_rescuable = rescuable?(e.class)
|
48
|
+
if e.is_a?(Grape::Exceptions::Base) && !is_rescuable
|
49
|
+
handler = lambda {|e| error_response(e) }
|
50
|
+
else
|
51
|
+
raise unless is_rescuable
|
52
|
+
handler = options[:rescue_handlers][e.class] || options[:rescue_handlers][:all]
|
53
|
+
end
|
54
|
+
handler.nil? ? handle_error(e) : self.instance_exec(e, &handler)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def rescuable?(klass)
|
59
|
+
options[:rescue_all] || (options[:rescued_errors] || []).include?(klass)
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_error(e)
|
63
|
+
error_response({ :message => e.message, :backtrace => e.backtrace })
|
64
|
+
end
|
65
|
+
|
66
|
+
def error_response(error = {})
|
67
|
+
status = error[:status] || options[:default_status]
|
68
|
+
message = error[:message] || options[:default_message]
|
69
|
+
headers = {'Content-Type' => content_type}
|
70
|
+
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
71
|
+
backtrace = error[:backtrace] || []
|
72
|
+
rack_response(format_message(message, backtrace, status), status, headers)
|
73
|
+
end
|
74
|
+
|
75
|
+
def rack_response(message, status = options[:default_status], headers = { 'Content-Type' => content_type })
|
76
|
+
Rack::Response.new([ message ], status, headers).finish
|
77
|
+
end
|
78
|
+
|
79
|
+
def format_message(message, backtrace, status)
|
80
|
+
formatter = formatter_for(options[:format])
|
81
|
+
throw :error, :status => 406, :message => "The requested format #{options[:format]} is not supported." unless formatter
|
82
|
+
formatter.call(message, backtrace)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Grape
|
2
|
+
module Middleware
|
3
|
+
# This is a simple middleware for adding before and after filters
|
4
|
+
# to Grape APIs. It is used like so:
|
5
|
+
#
|
6
|
+
# use Grape::Middleware::Filter, :before => lambda{ do_something }, after: => lambda{ do_something }
|
7
|
+
class Filter < Base
|
8
|
+
def before
|
9
|
+
app.instance_eval &options[:before] if options[:before]
|
10
|
+
end
|
11
|
+
|
12
|
+
def after
|
13
|
+
app.instance_eval &options[:after] if options[:after]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
class Formatter < Base
|
6
|
+
include Formats
|
7
|
+
|
8
|
+
def default_options
|
9
|
+
{
|
10
|
+
:default_format => :txt,
|
11
|
+
:formatters => {},
|
12
|
+
:content_types => {},
|
13
|
+
:parsers => {}
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def headers
|
18
|
+
env.dup.inject({}){|h,(k,v)| h[k.to_s.downcase[5..-1]] = v if k.to_s.downcase.start_with?('http_'); h}
|
19
|
+
end
|
20
|
+
|
21
|
+
def before
|
22
|
+
fmt = format_from_extension || options[:format] || format_from_header || options[:default_format]
|
23
|
+
if content_types.key?(fmt)
|
24
|
+
if !env['rack.input'].nil? and (body = env['rack.input'].read).strip.length != 0
|
25
|
+
parser = parser_for fmt
|
26
|
+
unless parser.nil?
|
27
|
+
begin
|
28
|
+
body = parser.call(body)
|
29
|
+
env['rack.request.form_hash'] = !env['rack.request.form_hash'].nil? ? env['rack.request.form_hash'].merge(body) : body
|
30
|
+
env['rack.request.form_input'] = env['rack.input']
|
31
|
+
rescue
|
32
|
+
# It's possible that it's just regular POST content -- just back off
|
33
|
+
end
|
34
|
+
end
|
35
|
+
env['rack.input'].rewind
|
36
|
+
end
|
37
|
+
env['api.format'] = fmt
|
38
|
+
else
|
39
|
+
throw :error, :status => 406, :message => 'The requested format is not supported.'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def format_from_extension
|
44
|
+
parts = request.path.split('.')
|
45
|
+
extension = parts.last.to_sym
|
46
|
+
|
47
|
+
if parts.size > 1 && content_types.key?(extension)
|
48
|
+
return extension
|
49
|
+
end
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def format_from_header
|
54
|
+
mime_array.each do |t|
|
55
|
+
if mime_types.key?(t)
|
56
|
+
return mime_types[t]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def mime_array
|
63
|
+
accept = headers['accept'] or return []
|
64
|
+
|
65
|
+
accept.gsub(/\b/,'').scan(%r((\w+/[\w+.-]+)(?:(?:;[^,]*?)?;\s*q=([\d.]+))?)).sort_by { |_, q| -q.to_f }.map {|mime, _|
|
66
|
+
mime.sub(%r(vnd\.[^+]+\+), '')
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def after
|
71
|
+
status, headers, bodies = *@app_response
|
72
|
+
formatter = formatter_for env['api.format']
|
73
|
+
bodymap = bodies.collect do |body|
|
74
|
+
formatter.call(body)
|
75
|
+
end
|
76
|
+
headers['Content-Type'] = content_types[env['api.format']] unless headers['Content-Type']
|
77
|
+
Rack::Response.new(bodymap, status, headers).to_a
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rack/mount/utils'
|
2
|
+
require 'grape'
|
3
|
+
|
4
|
+
module Grape
|
5
|
+
module Middleware
|
6
|
+
class Prefixer < Base
|
7
|
+
def prefix
|
8
|
+
prefix = options[:prefix] || ""
|
9
|
+
prefix = Rack::Mount::Utils.normalize_path(prefix)
|
10
|
+
prefix
|
11
|
+
end
|
12
|
+
|
13
|
+
def before
|
14
|
+
if env['PATH_INFO'].index(prefix) == 0
|
15
|
+
env['PATH_INFO'].sub!(prefix, '')
|
16
|
+
env['PATH_INFO'] = Rack::Mount::Utils.normalize_path(env['PATH_INFO'])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
module Versioner
|
6
|
+
# This middleware sets various version related rack environment variables
|
7
|
+
# based on the HTTP Accept header with the pattern:
|
8
|
+
# application/vnd.:vendor-:version+:format
|
9
|
+
#
|
10
|
+
# Example: For request header
|
11
|
+
# Accept: application/vnd.mycompany-v1+json
|
12
|
+
#
|
13
|
+
# The following rack env variables are set:
|
14
|
+
#
|
15
|
+
# env['api.type'] => 'application'
|
16
|
+
# env['api.subtype'] => 'vnd.mycompany-v1+json'
|
17
|
+
# env['api.vendor] => 'mycompany'
|
18
|
+
# env['api.version] => 'v1'
|
19
|
+
# env['api.format] => 'format'
|
20
|
+
#
|
21
|
+
# If version does not match this route, then a 406 is throw with
|
22
|
+
# X-Cascade header to alert Rack::Mount to attempt the next matched
|
23
|
+
# route.
|
24
|
+
class Header < Base
|
25
|
+
def before
|
26
|
+
accept = env['HTTP_ACCEPT'] || ""
|
27
|
+
|
28
|
+
if options[:version_options] && options[:version_options].keys.include?(:strict) && options[:version_options][:strict]
|
29
|
+
if (is_accept_header_valid?(accept)) && options[:version_options][:using] == :header
|
30
|
+
throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => "406 API Version Not Found"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
accept.strip.scan(/^(.+?)\/(.+?)$/) do |type, subtype|
|
34
|
+
env['api.type'] = type
|
35
|
+
env['api.subtype'] = subtype
|
36
|
+
|
37
|
+
subtype.scan(/vnd\.(.+)?-(.+)?\+(.*)?/) do |vendor, version, format|
|
38
|
+
is_vendored = options[:version_options] && options[:version_options][:vendor]
|
39
|
+
is_vendored_match = is_vendored ? options[:version_options][:vendor] == vendor : true
|
40
|
+
|
41
|
+
if (options[:versions] && !options[:versions].include?(version)) || !is_vendored_match
|
42
|
+
throw :error, :status => 406, :headers => {'X-Cascade' => 'pass'}, :message => "406 API Version Not Found"
|
43
|
+
end
|
44
|
+
|
45
|
+
env['api.version'] = version
|
46
|
+
env['api.vendor'] = vendor
|
47
|
+
env['api.format'] = format # weird that Grape::Middleware::Formatter also does this
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
def is_accept_header_valid?(header)
|
54
|
+
(header.strip =~ /application\/vnd\.(.+?)-(.+?)\+(.+?)/).nil?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
module Versioner
|
6
|
+
# This middleware sets various version related rack environment variables
|
7
|
+
# based on the request parameters and removes that parameter from the
|
8
|
+
# request parameters for subsequent middleware and API.
|
9
|
+
# If the version substring does not match any potential initialized
|
10
|
+
# versions, a 404 error is thrown.
|
11
|
+
# If the version substring is not passed the version (highest mounted)
|
12
|
+
# version will be used.
|
13
|
+
#
|
14
|
+
# Example: For a uri path
|
15
|
+
# /resource?apiver=v1
|
16
|
+
#
|
17
|
+
# The following rack env variables are set and path is rewritten to
|
18
|
+
# '/resource':
|
19
|
+
#
|
20
|
+
# env['api.version'] => 'v1'
|
21
|
+
class Param < Base
|
22
|
+
def default_options
|
23
|
+
{
|
24
|
+
:parameter => "apiver"
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def before
|
29
|
+
paramkey = options[:parameter]
|
30
|
+
potential_version = request.params[paramkey]
|
31
|
+
|
32
|
+
unless potential_version.nil?
|
33
|
+
if options[:versions] && !options[:versions].include?(potential_version)
|
34
|
+
throw :error, :status => 404, :message => "404 API Version Not Found", :headers => {'X-Cascade' => 'pass'}
|
35
|
+
end
|
36
|
+
env['api.version'] = potential_version
|
37
|
+
env['rack.request.query_hash'].delete(paramkey)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'grape/middleware/base'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Middleware
|
5
|
+
module Versioner
|
6
|
+
# This middleware sets various version related rack environment variables
|
7
|
+
# based on the uri path and removes the version substring from the uri
|
8
|
+
# path. If the version substring does not match any potential initialized
|
9
|
+
# versions, a 404 error is thrown.
|
10
|
+
#
|
11
|
+
# Example: For a uri path
|
12
|
+
# /v1/resource
|
13
|
+
#
|
14
|
+
# The following rack env variables are set and path is rewritten to
|
15
|
+
# '/resource':
|
16
|
+
#
|
17
|
+
# env['api.version'] => 'v1'
|
18
|
+
#
|
19
|
+
class Path < Base
|
20
|
+
def default_options
|
21
|
+
{
|
22
|
+
:pattern => /.*/i
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def before
|
27
|
+
pieces = env['PATH_INFO'].split('/')
|
28
|
+
potential_version = pieces[1]
|
29
|
+
if potential_version =~ options[:pattern]
|
30
|
+
if options[:versions] && !options[:versions].include?(potential_version)
|
31
|
+
throw :error, :status => 404, :message => "404 API Version Not Found"
|
32
|
+
end
|
33
|
+
|
34
|
+
truncated_path = "/#{pieces[2..-1].join('/')}"
|
35
|
+
env['api.version'] = potential_version
|
36
|
+
env['PATH_INFO'] = truncated_path
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Versioners set env['api.version'] when a version is defined on an API and
|
2
|
+
# on the requests. The current methods for determining version are:
|
3
|
+
#
|
4
|
+
# :header - version from HTTP Accept header.
|
5
|
+
# :path - version from uri. e.g. /v1/resource
|
6
|
+
#
|
7
|
+
# See individual classes for details.
|
8
|
+
module Grape
|
9
|
+
module Middleware
|
10
|
+
module Versioner
|
11
|
+
extend self
|
12
|
+
|
13
|
+
# @param strategy [Symbol] :path or :header
|
14
|
+
# @return a middleware class based on strategy
|
15
|
+
def using(strategy)
|
16
|
+
case strategy
|
17
|
+
when :path
|
18
|
+
Path
|
19
|
+
when :header
|
20
|
+
Header
|
21
|
+
when :param
|
22
|
+
Param
|
23
|
+
else
|
24
|
+
raise ArgumentError.new("Unknown :using for versioner: #{strategy}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Grape
|
2
|
+
|
3
|
+
# A compiled route for inspection.
|
4
|
+
class Route
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@options = options || {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(method_id, *arguments)
|
11
|
+
if match = /route_([_a-zA-Z]\w*)/.match(method_id.to_s)
|
12
|
+
@options[match.captures.last.to_sym]
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"version=#{route_version}, method=#{route_method}, path=#{route_path}"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Hash
|
2
|
+
# deep_merge from rails
|
3
|
+
# activesupport/lib/active_support/core_ext/hash/deep_merge.rb
|
4
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
5
|
+
#
|
6
|
+
# h1 = {:x => {:y => [4,5,6]}, :z => [7,8,9]}
|
7
|
+
# h2 = {:x => {:y => [7,8,9]}, :z => "xyz"}
|
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] }
|
11
|
+
def deep_merge(other_hash)
|
12
|
+
dup.deep_merge!(other_hash)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Same as +deep_merge+, but modifies +self+.
|
16
|
+
def deep_merge!(other_hash)
|
17
|
+
other_hash.each_pair do |k,v|
|
18
|
+
tv = self[k]
|
19
|
+
self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
|
20
|
+
end
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Grape
|
2
|
+
module Util
|
3
|
+
# HashStack is a stack of hashes. When retrieving a value, keys of the top
|
4
|
+
# hash on the stack take precendent over the lower keys.
|
5
|
+
class HashStack
|
6
|
+
# Unmerged array of hashes to represent the stack.
|
7
|
+
# The top of the stack is the last element.
|
8
|
+
attr_reader :stack
|
9
|
+
|
10
|
+
# TODO: handle aggregates
|
11
|
+
def initialize
|
12
|
+
@stack = [{}]
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the top hash on the stack
|
16
|
+
def peek
|
17
|
+
@stack.last
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add a new hash to the top of the stack.
|
21
|
+
#
|
22
|
+
# @param hash [Hash] optional hash to be pushed. Defaults to empty hash
|
23
|
+
# @return [HashStack]
|
24
|
+
def push(hash = {})
|
25
|
+
@stack.push(hash)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def pop
|
30
|
+
@stack.pop
|
31
|
+
end
|
32
|
+
|
33
|
+
# Looks through the stack for the first frame that matches :key
|
34
|
+
#
|
35
|
+
# @param key [Symbol] key to look for in hash frames
|
36
|
+
# @return value of given key after merging the stack
|
37
|
+
def get(key)
|
38
|
+
(@stack.length - 1).downto(0).each do |i|
|
39
|
+
return @stack[i][key] if @stack[i].key? key
|
40
|
+
end
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
alias_method :[], :get
|
44
|
+
|
45
|
+
# Replace a value on the top hash of the stack.
|
46
|
+
#
|
47
|
+
# @param key [Symbol] The key to set.
|
48
|
+
# @param value [Object] The value to set.
|
49
|
+
def set(key, value)
|
50
|
+
peek[key.to_sym] = value
|
51
|
+
end
|
52
|
+
alias_method :[]=, :set
|
53
|
+
|
54
|
+
# Replace multiple values on the top hash of the stack.
|
55
|
+
#
|
56
|
+
# @param hash [Hash] Hash of values to be merged in.
|
57
|
+
def update(hash)
|
58
|
+
peek.merge!(hash)
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
# Adds addition value into the top hash of the stack
|
63
|
+
def imbue(key, value)
|
64
|
+
current = peek[key.to_sym]
|
65
|
+
if current.is_a?(Array)
|
66
|
+
current.concat(value)
|
67
|
+
elsif current.is_a?(Hash)
|
68
|
+
current.merge!(value)
|
69
|
+
else
|
70
|
+
set(key, value)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Prepend another HashStack's to self
|
75
|
+
def prepend(hash_stack)
|
76
|
+
@stack.unshift *hash_stack.stack
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
# Concatenate another HashStack's to self
|
81
|
+
def concat(hash_stack)
|
82
|
+
@stack.concat hash_stack.stack
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_s
|
87
|
+
@stack.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
def clone
|
91
|
+
new_stack = HashStack.new
|
92
|
+
stack.each do |frame|
|
93
|
+
new_stack.push frame.clone
|
94
|
+
end
|
95
|
+
new_stack.stack.shift
|
96
|
+
new_stack
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Grape
|
2
|
+
|
3
|
+
class API
|
4
|
+
Boolean = Virtus::Attribute::Boolean
|
5
|
+
end
|
6
|
+
|
7
|
+
module Validations
|
8
|
+
|
9
|
+
class CoerceValidator < SingleOptionValidator
|
10
|
+
def validate_param!(attr_name, params)
|
11
|
+
new_value = coerce_value(@option, params[attr_name])
|
12
|
+
if valid_type?(new_value)
|
13
|
+
params[attr_name] = new_value
|
14
|
+
else
|
15
|
+
raise ValidationError, :status => 400, :param => attr_name, :message => "invalid parameter: #{attr_name}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class InvalidValue; end
|
20
|
+
private
|
21
|
+
|
22
|
+
def _valid_array_type?(type, values)
|
23
|
+
values.all? do |val|
|
24
|
+
_valid_single_type?(type, val)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def _valid_single_type?(klass, val)
|
29
|
+
# allow nil, to ignore when a parameter is absent
|
30
|
+
return true if val.nil?
|
31
|
+
if klass == Virtus::Attribute::Boolean
|
32
|
+
val.is_a?(TrueClass) || val.is_a?(FalseClass)
|
33
|
+
elsif klass == Rack::Multipart::UploadedFile
|
34
|
+
val.is_a?(Hashie::Mash) && val.key?(:tempfile)
|
35
|
+
else
|
36
|
+
val.is_a?(klass)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid_type?(val)
|
41
|
+
if @option.is_a?(Array)
|
42
|
+
_valid_array_type?(@option[0], val)
|
43
|
+
else
|
44
|
+
_valid_single_type?(@option, val)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def coerce_value(type, val)
|
49
|
+
converter = Virtus::Attribute.build(:a, type)
|
50
|
+
converter.coerce(val)
|
51
|
+
|
52
|
+
# not the prettiest but some invalid coercion can currently trigger
|
53
|
+
# errors in Virtus (see coerce_spec.rb)
|
54
|
+
rescue
|
55
|
+
InvalidValue.new
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
class PresenceValidator < Validator
|
4
|
+
def validate_param!(attr_name, params)
|
5
|
+
unless params.has_key?(attr_name)
|
6
|
+
raise ValidationError, :status => 400, :param => attr_name, :message => "missing parameter: #{attr_name}"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
|
4
|
+
class RegexpValidator < SingleOptionValidator
|
5
|
+
def validate_param!(attr_name, params)
|
6
|
+
if params[attr_name] && !( params[attr_name].to_s =~ @option )
|
7
|
+
raise ValidationError, :status => 400, :param => attr_name, :message => "invalid parameter: #{attr_name}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|