goliath 0.9.4 → 1.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of goliath might be problematic. Click here for more details.
- data/.gitignore +3 -0
- data/Guardfile +8 -0
- data/HISTORY.md +10 -0
- data/LICENSE +1 -1
- data/README.md +28 -29
- data/Rakefile +10 -2
- data/examples/activerecord/config/srv.rb +2 -1
- data/examples/activerecord/srv.rb +14 -5
- data/examples/api_proxy.rb +3 -7
- data/examples/around.rb +38 -0
- data/examples/async_aroundware_demo.rb +2 -2
- data/examples/async_upload.rb +1 -1
- data/examples/auth_and_rate_limit.rb +1 -1
- data/examples/clone.rb +26 -0
- data/examples/config/websocket.rb +1 -0
- data/examples/custom_logs.rb +21 -0
- data/examples/custom_server.rb +6 -44
- data/examples/early_abort.rb +6 -3
- data/examples/echo.rb +1 -1
- data/examples/fiber_pool.rb +35 -0
- data/examples/grape/config/apiserver.rb +8 -0
- data/examples/grape/server.rb +67 -0
- data/examples/gziped.rb +1 -1
- data/examples/params.rb +36 -0
- data/examples/rasterize/rasterize.rb +1 -2
- data/examples/router.rb +15 -0
- data/examples/template.rb +14 -7
- data/examples/test.rb +31 -0
- data/examples/upload.rb +17 -0
- data/examples/views/joke.markdown +4 -4
- data/examples/websocket.rb +39 -0
- data/examples/ws/favicon.ico +0 -0
- data/examples/ws/index.erb +50 -0
- data/goliath.gemspec +24 -6
- data/lib/goliath/api.rb +15 -82
- data/lib/goliath/application.rb +3 -17
- data/lib/goliath/connection.rb +16 -9
- data/lib/goliath/constants.rb +2 -0
- data/lib/goliath/env.rb +2 -3
- data/lib/goliath/goliath.rb +24 -34
- data/lib/goliath/plugins/latency.rb +7 -3
- data/lib/goliath/rack/builder.rb +1 -37
- data/lib/goliath/rack/favicon.rb +31 -0
- data/lib/goliath/rack/formatters/json.rb +1 -1
- data/lib/goliath/rack/heartbeat.rb +1 -0
- data/lib/goliath/rack/jsonp.rb +1 -0
- data/lib/goliath/rack/params.rb +2 -17
- data/lib/goliath/rack/render.rb +0 -1
- data/lib/goliath/rack/templates.rb +18 -7
- data/lib/goliath/rack/types/base.rb +24 -0
- data/lib/goliath/rack/types/boolean.rb +18 -0
- data/lib/goliath/rack/types/core.rb +19 -0
- data/lib/goliath/rack/types/symbol.rb +16 -0
- data/lib/goliath/rack/types.rb +10 -0
- data/lib/goliath/rack/validation/coerce.rb +48 -0
- data/lib/goliath/rack/validation/param.rb +113 -0
- data/lib/goliath/rack/validation/request_method.rb +1 -1
- data/lib/goliath/rack/validation/required.rb +47 -0
- data/lib/goliath/rack/validation/required_param.rb +42 -17
- data/lib/goliath/rack/validation.rb +3 -0
- data/lib/goliath/rack.rb +2 -2
- data/lib/goliath/request.rb +41 -9
- data/lib/goliath/response.rb +0 -2
- data/lib/goliath/runner.rb +55 -4
- data/lib/goliath/server.rb +7 -3
- data/lib/goliath/test_helper.rb +70 -16
- data/lib/goliath/test_helper_ws.rb +42 -0
- data/lib/goliath/version.rb +1 -1
- data/lib/goliath/websocket.rb +80 -0
- data/pkg/goliath-0.9.4.gem +0 -0
- data/pkg/goliath-1.0.0.beta.1.gem +0 -0
- data/spec/integration/async_request_processing.rb +1 -1
- data/spec/integration/early_abort_spec.rb +3 -10
- data/spec/integration/echo_spec.rb +8 -8
- data/spec/integration/jsonp_spec.rb +31 -0
- data/spec/integration/keepalive_spec.rb +2 -2
- data/spec/integration/template_spec.rb +10 -5
- data/spec/integration/test_helper_spec.rb +33 -0
- data/spec/integration/valid_spec.rb +35 -5
- data/spec/integration/websocket_spec.rb +44 -0
- data/spec/unit/api_spec.rb +2 -19
- data/spec/unit/connection_spec.rb +3 -0
- data/spec/unit/rack/formatters/json_spec.rb +3 -3
- data/spec/unit/rack/heartbeat_spec.rb +13 -0
- data/spec/unit/rack/params_spec.rb +2 -8
- data/spec/unit/rack/validation/param_spec.rb +443 -0
- data/spec/unit/rack/validation/request_method_spec.rb +5 -0
- data/spec/unit/rack/validation/required_param_spec.rb +71 -1
- data/spec/unit/runner_spec.rb +21 -7
- data/test/echo_test.rb +25 -0
- data/test/test_helper.rb +5 -0
- metadata +316 -78
- data/examples/env_use_statements.rb +0 -20
- data/examples/favicon.rb +0 -40
- data/examples/rack_routes.rb +0 -125
- data/examples/rasterize/thumb/f7ad4cb03e5bfd0e2c43db8e598fb3cd.png +0 -0
- data/examples/valid.rb +0 -17
- data/lib/goliath/deprecated/async_aroundware.rb +0 -133
- data/lib/goliath/deprecated/mongo_receiver.rb +0 -84
- data/lib/goliath/deprecated/response_receiver.rb +0 -97
- data/spec/integration/rack_routes_spec.rb +0 -169
- data/spec/unit/rack/builder_spec.rb +0 -40
data/lib/goliath/rack/builder.rb
CHANGED
@@ -1,22 +1,9 @@
|
|
1
|
-
require 'http_router'
|
2
|
-
|
3
|
-
class HttpRouter::Route
|
4
|
-
attr_accessor :api_class
|
5
|
-
end
|
6
|
-
|
7
1
|
module Goliath
|
8
2
|
module Rack
|
9
3
|
class Builder < ::Rack::Builder
|
10
4
|
attr_accessor :params
|
11
|
-
attr_reader :inner_app
|
12
5
|
include Params::Parser
|
13
6
|
|
14
|
-
alias_method :original_run, :run
|
15
|
-
def run(app)
|
16
|
-
@inner_app = app
|
17
|
-
original_run(app)
|
18
|
-
end
|
19
|
-
|
20
7
|
# Builds the rack middleware chain for the given API
|
21
8
|
#
|
22
9
|
# @param klass [Class] The API class to build the middlewares for
|
@@ -27,30 +14,7 @@ module Goliath
|
|
27
14
|
klass.middlewares.each do |mw_klass, args, blk|
|
28
15
|
use(mw_klass, *args, &blk)
|
29
16
|
end
|
30
|
-
|
31
|
-
klass.maps.each do |path, route_klass, opts, blk|
|
32
|
-
route = klass.router.add(path, opts.dup)
|
33
|
-
route.api_class = route_klass
|
34
|
-
route.to {|env|
|
35
|
-
builder = Builder.new
|
36
|
-
env['params'] ||= {}
|
37
|
-
env['params'].merge!(env['router.params']) if env['router.params']
|
38
|
-
builder.params = builder.retrieve_params(env)
|
39
|
-
builder.instance_eval(&blk) if blk
|
40
|
-
route_klass.middlewares.each do |mw|
|
41
|
-
builder.instance_eval { use mw[0], *mw[1], &mw[2] }
|
42
|
-
end if route_klass
|
43
|
-
if route_klass or blk.nil?
|
44
|
-
raise "You cannot use `run' and supply a routing class at the same time" if builder.inner_app
|
45
|
-
builder.instance_eval { run env.event_handler }
|
46
|
-
end
|
47
|
-
builder.to_app.call(env)
|
48
|
-
}
|
49
|
-
end
|
50
|
-
run klass.router
|
51
|
-
else
|
52
|
-
run api
|
53
|
-
end
|
17
|
+
run api
|
54
18
|
end
|
55
19
|
end
|
56
20
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
# Reads a favicon.ico statically at load time, renders it on any request for
|
4
|
+
# '/favicon.ico', and sends every other request on downstream.
|
5
|
+
#
|
6
|
+
# Rack::Static is a better option if you're serving several static assets.
|
7
|
+
#
|
8
|
+
module Goliath
|
9
|
+
module Rack
|
10
|
+
class Favicon
|
11
|
+
def initialize(app, filename)
|
12
|
+
@app = app
|
13
|
+
@favicon = File.read(File.join(filename))
|
14
|
+
@expires = Time.at(Time.now + (60 * 60 * 24 * 7)).utc.rfc822.to_s
|
15
|
+
@last_modified = File.mtime(filename).utc.rfc822.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
if env['REQUEST_PATH'] == '/favicon.ico'
|
20
|
+
env.logger.info('Serving favicon.ico')
|
21
|
+
|
22
|
+
[200, {'Last-Modified' => @last_modified,
|
23
|
+
'Expires' => @expires,
|
24
|
+
'Content-Type' => "image/vnd.microsoft.icon"}, @favicon]
|
25
|
+
else
|
26
|
+
@app.call(env)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/goliath/rack/jsonp.rb
CHANGED
data/lib/goliath/rack/params.rb
CHANGED
@@ -31,7 +31,7 @@ module Goliath
|
|
31
31
|
when URL_ENCODED then
|
32
32
|
::Rack::Utils.parse_nested_query(body)
|
33
33
|
when JSON_ENCODED then
|
34
|
-
MultiJson.
|
34
|
+
MultiJson.load(body)
|
35
35
|
else
|
36
36
|
{}
|
37
37
|
end
|
@@ -42,24 +42,9 @@ module Goliath
|
|
42
42
|
post_params = {}
|
43
43
|
end
|
44
44
|
end
|
45
|
-
|
46
45
|
params.merge!(post_params)
|
47
46
|
end
|
48
|
-
|
49
|
-
indifferent_params(params)
|
50
|
-
end
|
51
|
-
|
52
|
-
def indifferent_params(params)
|
53
|
-
params = indifferent_hash.merge(params)
|
54
|
-
params.each do |key, value|
|
55
|
-
next unless value.is_a?(Hash)
|
56
|
-
params[key] = indifferent_params(value)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
# Creates a Hash with indifferent access.
|
61
|
-
def indifferent_hash
|
62
|
-
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
47
|
+
params
|
63
48
|
end
|
64
49
|
end
|
65
50
|
|
data/lib/goliath/rack/render.rb
CHANGED
@@ -321,14 +321,25 @@ module Goliath
|
|
321
321
|
Tilt.new(layout_filename, nil, options)
|
322
322
|
end
|
323
323
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
324
|
+
# mimic sinatra behavior, if a string is given consider it as a template source
|
325
|
+
# otherwise a symbol is expected to be a template path
|
326
|
+
if data.is_a?(Symbol)
|
327
|
+
template_filename = find_template(views, data, engine)
|
328
|
+
unless template_filename
|
329
|
+
raise Goliath::Validation::InternalServerError, "Template #{data} not found in #{views} for #{engine}"
|
330
|
+
end
|
331
|
+
|
332
|
+
template = Tilt.new(template_filename, nil, options)
|
333
|
+
output = layout_template.render(scope, locals) do
|
334
|
+
template.render(scope, locals)
|
335
|
+
end
|
336
|
+
|
337
|
+
else
|
338
|
+
template = Tilt[engine].new(nil, nil, options){ data }
|
339
|
+
output = layout_template.render(scope, locals) do
|
340
|
+
template.render(scope, locals)
|
341
|
+
end
|
328
342
|
|
329
|
-
template = Tilt.new(template_filename, nil, options)
|
330
|
-
output = layout_template.render(scope, locals) do
|
331
|
-
template.render(scope, locals)
|
332
343
|
end
|
333
344
|
|
334
345
|
output.extend(ContentTyped).content_type = content_type if content_type
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Rack
|
3
|
+
module Types
|
4
|
+
class Base
|
5
|
+
include Goliath::Rack::Validator
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@short_name = self.class.name.split("::").last
|
9
|
+
end
|
10
|
+
|
11
|
+
def coerce(val, opts={})
|
12
|
+
begin
|
13
|
+
_coerce(val)
|
14
|
+
rescue => e
|
15
|
+
return opts[:default] if opts[:default]
|
16
|
+
raise Goliath::Rack::Validation::FailedCoerce.new(
|
17
|
+
validation_error(400, opts[:message] || e.message)
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Rack
|
3
|
+
module Types
|
4
|
+
class Boolean < Base
|
5
|
+
TRUE_STRINGS = ['true', 't', '1']
|
6
|
+
FALSE_STRINGS = ['false', 'f', '0']
|
7
|
+
ERROR_MESSAGE = "%s is not a boolean value"
|
8
|
+
|
9
|
+
def _coerce(val)
|
10
|
+
downcased_val = val.downcase
|
11
|
+
return true if TRUE_STRINGS.include?(downcased_val)
|
12
|
+
return false if FALSE_STRINGS.include?(downcased_val)
|
13
|
+
raise ERROR_MESSAGE % val
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Rack
|
3
|
+
module Types
|
4
|
+
CORE_TYPES = [Integer, Float]
|
5
|
+
|
6
|
+
CORE_TYPES.each do |type|
|
7
|
+
klass = Class.new(Base)
|
8
|
+
klass.class_eval <<-EOT, __FILE__, __LINE__ + 1
|
9
|
+
def _coerce(val)
|
10
|
+
#{type}(val)
|
11
|
+
end
|
12
|
+
EOT
|
13
|
+
|
14
|
+
const_set(type.name, klass)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Rack
|
3
|
+
|
4
|
+
module Validation
|
5
|
+
class FailedCoerce < StandardError
|
6
|
+
attr_reader :error
|
7
|
+
def initialize(error)
|
8
|
+
@error = error
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Coerce
|
13
|
+
NOT_CLASS_ERROR = "Params as must be a class"
|
14
|
+
INVALID_COERCE_TYPE = "%s does not respond to coerce"
|
15
|
+
|
16
|
+
def coerce_setup!(opts={})
|
17
|
+
as = opts.delete(:as)
|
18
|
+
if as
|
19
|
+
unless Class === as
|
20
|
+
raise Exception.new(NOT_CLASS_ERROR)
|
21
|
+
end
|
22
|
+
@coerce_instance = as.new
|
23
|
+
unless @coerce_instance.respond_to?(:coerce)
|
24
|
+
raise Exception.new(INVALID_COERCE_TYPE % @coerce_instance)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(env)
|
30
|
+
begin
|
31
|
+
coerce_value(env['params']) if @coerce_instance
|
32
|
+
nil
|
33
|
+
rescue FailedCoerce => e
|
34
|
+
return e.error unless @optional
|
35
|
+
end
|
36
|
+
super if defined?(super)
|
37
|
+
end
|
38
|
+
|
39
|
+
def coerce_value(params)
|
40
|
+
opts = {:default => @default, :message => @message}
|
41
|
+
value_before_coerce = fetch_key(params)
|
42
|
+
value_after_coerce = @coerce_instance.coerce(value_before_coerce, opts)
|
43
|
+
fetch_key(params, value_after_coerce)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Rack
|
3
|
+
module Validation
|
4
|
+
class FailedCoerce < StandardError
|
5
|
+
attr_reader :error
|
6
|
+
def initialize(error)
|
7
|
+
@error = error
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# A middleware to validate that a given parameter is provided
|
12
|
+
# and/or coerce a given parameter to a given type.
|
13
|
+
#
|
14
|
+
# By default, Goliath supports Integer, Boolean, Float and Symbol.
|
15
|
+
# You can also create a custom coerce type by creating a
|
16
|
+
# class that has an instance method, coerce. Example:
|
17
|
+
#
|
18
|
+
# class CustomJSON
|
19
|
+
# def coerce(value, opts={})
|
20
|
+
# MultiJson.load(value)
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Where value is the value that should be coerced and
|
25
|
+
# opts is a hash that contains two potential values:
|
26
|
+
#
|
27
|
+
# - default is the default value optionally specified in the middleware declaration.
|
28
|
+
# This means default will be nil if it was not set.
|
29
|
+
# - message is the failure message optionally specified in the middleware declaration. This means message will be nil if it was not set.
|
30
|
+
#
|
31
|
+
# If default is not set, Integer, Boolean, Float and Symbol will return validation_error, otherwise params[key] will be set to default.
|
32
|
+
#
|
33
|
+
# If message is not set, it will have the error message of the exception caught by the coercion, otherwise the error message will be set to message.
|
34
|
+
#
|
35
|
+
# For your custom CoerceTypes, you can raise Goliath::Rack::Validation::FailedCoerce.new(value) where value is what will be returned from the call method.
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# use Goliath::Rack::Validation::Param, {:key => 'mode', :type => 'Mode'}
|
39
|
+
# use Goliath::Rack::Validation::Param, {:key => 'data.credentials.login', :type => 'Login'}
|
40
|
+
# use Goliath::Rack::Validation::Param, {:key => %w(data credentials password), :type => 'Password'}
|
41
|
+
#
|
42
|
+
# use Goliath::Rack::Validation::CoerceValue, :key => 'flag', :as => Goliath::Rack::Types::Boolean
|
43
|
+
#
|
44
|
+
# (or include Goliath::Rack::Types to reference the types without the namespaces.)
|
45
|
+
#
|
46
|
+
# include Goliath::Rack::Types
|
47
|
+
# use Goliath::Rack::Validation::Param, :key => 'amount', :as => Float
|
48
|
+
class Param
|
49
|
+
include Goliath::Rack::Validator
|
50
|
+
include Coerce
|
51
|
+
include Required
|
52
|
+
UKNOWN_OPTIONS = "Uknown options: %s"
|
53
|
+
|
54
|
+
attr_reader :key, :type, :optional, :message, :default
|
55
|
+
|
56
|
+
# Creates the Goliath::Rack::Validation::Param validator
|
57
|
+
#
|
58
|
+
# @param app The app object
|
59
|
+
# @param opts [Hash] The validator options
|
60
|
+
# @option opts [String] :key The key to look for in params (default: id)
|
61
|
+
# if the value is an array it defines path with nested keys (ex: ["data", "login"] or
|
62
|
+
# dot syntax: 'data.login')
|
63
|
+
# @option opts [String] :type The type string to put in the error message. (default: :key)
|
64
|
+
# @option opts [String] :message The message string to display after the type string. (default: 'identifier missing')
|
65
|
+
# @option opts [Class] :as The type to coerce params[key] to. (default: String)
|
66
|
+
# @option opts [String] :default (default: validation_error)
|
67
|
+
#
|
68
|
+
# @return [Goliath::Rack::Validation::Param] The validator
|
69
|
+
def initialize(app, opts = {})
|
70
|
+
@app = app
|
71
|
+
@optional = opts.delete(:optional) || false
|
72
|
+
@key = opts.delete(:key)
|
73
|
+
raise Exception.new("key option required") unless @key
|
74
|
+
|
75
|
+
@type = opts.delete(:type) || @key
|
76
|
+
@message = opts.delete(:message) || 'identifier missing'
|
77
|
+
@default = opts.delete(:default)
|
78
|
+
|
79
|
+
coerce_setup!(opts)
|
80
|
+
required_setup!(opts)
|
81
|
+
|
82
|
+
raise Exception.new(UKNOWN_OPTIONS % opts.inspect) unless opts.empty?
|
83
|
+
end
|
84
|
+
|
85
|
+
def call(env)
|
86
|
+
previous_call = super
|
87
|
+
return previous_call if previous_call
|
88
|
+
|
89
|
+
@app.call(env)
|
90
|
+
end
|
91
|
+
|
92
|
+
def fetch_key(params, set_value=nil)
|
93
|
+
key_path = Array(@key)
|
94
|
+
current_value = params
|
95
|
+
|
96
|
+
# check that the full path is present
|
97
|
+
# omit the last part of the path
|
98
|
+
val = key_path[0...-1].each do |key_part|
|
99
|
+
# if the key is missing or is nil
|
100
|
+
if !current_value.is_a?(Hash) || current_value[key_part].nil?
|
101
|
+
break
|
102
|
+
end
|
103
|
+
|
104
|
+
current_value = current_value[key_part]
|
105
|
+
end
|
106
|
+
|
107
|
+
current_value[key_path[-1]] = set_value unless set_value.nil?
|
108
|
+
val.nil? ? val : current_value[key_path[-1]]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Rack
|
3
|
+
module Validation
|
4
|
+
module Required
|
5
|
+
NON_WHITESPACE_REGEXP = %r![^[:space:]]!
|
6
|
+
|
7
|
+
def required_setup!(opts={})
|
8
|
+
if @key.is_a?(String) && @key.include?('.')
|
9
|
+
@key = @key.split('.')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
unless @optional
|
15
|
+
return validation_error(400, "#{@type} #{@message}") unless key_valid?(env['params'])
|
16
|
+
end
|
17
|
+
super if defined?(super)
|
18
|
+
end
|
19
|
+
|
20
|
+
def key_valid?(params)
|
21
|
+
val = fetch_key(params)
|
22
|
+
|
23
|
+
case val
|
24
|
+
when nil
|
25
|
+
return false
|
26
|
+
|
27
|
+
when String
|
28
|
+
# if val is a string it must not be empty
|
29
|
+
return false if val !~ NON_WHITESPACE_REGEXP
|
30
|
+
|
31
|
+
when Array
|
32
|
+
unless val.compact.empty?
|
33
|
+
val.each do |k|
|
34
|
+
return true unless k.is_a?(String)
|
35
|
+
return true unless k !~ NON_WHITESPACE_REGEXP
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
|
42
|
+
true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -7,31 +7,35 @@ module Goliath
|
|
7
7
|
#
|
8
8
|
# @example
|
9
9
|
# use Goliath::Rack::Validation::RequiredParam, {:key => 'mode', :type => 'Mode'}
|
10
|
+
# use Goliath::Rack::Validation::RequiredParam, {:key => 'data.credentials.login', :type => 'Login'}
|
11
|
+
# use Goliath::Rack::Validation::RequiredParam, {:key => %w(data credentials password), :type => 'Password'}
|
10
12
|
#
|
11
13
|
class RequiredParam
|
12
14
|
include Goliath::Rack::Validator
|
13
15
|
attr_reader :type, :key, :message
|
14
16
|
|
15
17
|
# extracted from activesupport 3.0.9
|
16
|
-
|
17
|
-
|
18
|
-
else
|
19
|
-
NON_WHITESPACE_REGEXP = %r![^\s#{[0x3000].pack("U")}]!
|
20
|
-
end
|
21
|
-
|
18
|
+
NON_WHITESPACE_REGEXP = %r![^[:space:]]!
|
19
|
+
|
22
20
|
# Creates the Goliath::Rack::Validation::RequiredParam validator
|
23
21
|
#
|
24
22
|
# @param app The app object
|
25
23
|
# @param opts [Hash] The validator options
|
26
24
|
# @option opts [String] :key The key to look for in params (default: id)
|
25
|
+
# if the value is an array it defines path with nested keys (ex: ["data", "login"] or
|
26
|
+
# dot syntax: 'data.login')
|
27
27
|
# @option opts [String] :type The type string to put in the error message. (default: :key)
|
28
28
|
# @option opts [String] :message The message string to display after the type string. (default: 'identifier missing')
|
29
29
|
# @return [Goliath::Rack::Validation::RequiredParam] The validator
|
30
30
|
def initialize(app, opts = {})
|
31
31
|
@app = app
|
32
32
|
@key = opts[:key] || 'id'
|
33
|
-
@type = opts[:type] || @key
|
33
|
+
@type = opts[:type] || @key
|
34
34
|
@message = opts[:message] || 'identifier missing'
|
35
|
+
|
36
|
+
if @key.is_a?(String) && @key.include?('.')
|
37
|
+
@key = @key.split('.')
|
38
|
+
end
|
35
39
|
end
|
36
40
|
|
37
41
|
def call(env)
|
@@ -40,18 +44,39 @@ module Goliath
|
|
40
44
|
end
|
41
45
|
|
42
46
|
def key_valid?(params)
|
43
|
-
|
44
|
-
|
45
|
-
return false
|
46
|
-
end
|
47
|
+
key_path = Array(@key)
|
48
|
+
current_value = params
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
# check that the full path is present
|
51
|
+
# omit the last part of the path
|
52
|
+
key_path[0...-1].each do |key_part|
|
53
|
+
# if the key is missing or is nil the validation failed
|
54
|
+
if !current_value.is_a?(Hash) || current_value[key_part].nil?
|
55
|
+
return false
|
54
56
|
end
|
57
|
+
|
58
|
+
current_value = current_value[key_part]
|
59
|
+
end
|
60
|
+
|
61
|
+
# if we are here the full path is available, now test the real key
|
62
|
+
val = current_value[key_path[-1]]
|
63
|
+
|
64
|
+
case val
|
65
|
+
when nil
|
66
|
+
return false
|
67
|
+
|
68
|
+
when String
|
69
|
+
# if val is a string it must not be empty
|
70
|
+
return false if val !~ NON_WHITESPACE_REGEXP
|
71
|
+
|
72
|
+
when Array
|
73
|
+
unless val.compact.empty?
|
74
|
+
val.each do |k|
|
75
|
+
return true unless k.is_a?(String)
|
76
|
+
return true unless k !~ NON_WHITESPACE_REGEXP
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
55
80
|
return false
|
56
81
|
end
|
57
82
|
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module Goliath
|
2
2
|
module Rack
|
3
3
|
module Validation
|
4
|
+
autoload :Coerce, 'goliath/rack/validation/coerce'
|
5
|
+
autoload :Required, 'goliath/rack/validation/required'
|
6
|
+
autoload :Param, 'goliath/rack/validation/param'
|
4
7
|
autoload :BooleanValue, 'goliath/rack/validation/boolean_value'
|
5
8
|
autoload :DefaultParams, 'goliath/rack/validation/default_params'
|
6
9
|
autoload :NumericRange, 'goliath/rack/validation/numeric_range'
|
data/lib/goliath/rack.rb
CHANGED
@@ -6,6 +6,7 @@ module Goliath
|
|
6
6
|
autoload :Builder, 'goliath/rack/builder'
|
7
7
|
autoload :DefaultMimeType, 'goliath/rack/default_mime_type'
|
8
8
|
autoload :DefaultResponseFormat, 'goliath/rack/default_response_format'
|
9
|
+
autoload :Favicon, 'goliath/rack/favicon'
|
9
10
|
autoload :Formatters, 'goliath/rack/formatters'
|
10
11
|
autoload :Heartbeat, 'goliath/rack/heartbeat'
|
11
12
|
autoload :JSONP, 'goliath/rack/jsonp'
|
@@ -15,9 +16,8 @@ module Goliath
|
|
15
16
|
autoload :SimpleAroundwareFactory, 'goliath/rack/simple_aroundware_factory'
|
16
17
|
autoload :Templates, 'goliath/rack/templates'
|
17
18
|
autoload :Tracer, 'goliath/rack/tracer'
|
19
|
+
autoload :Types, 'goliath/rack/types'
|
18
20
|
autoload :Validator, 'goliath/rack/validator'
|
19
21
|
autoload :Validation, 'goliath/rack/validation'
|
20
|
-
#
|
21
|
-
autoload :AsyncAroundware, 'goliath/deprecated/async_aroundware'
|
22
22
|
end
|
23
23
|
end
|