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,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
|