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.

Files changed (102) hide show
  1. data/.gitignore +3 -0
  2. data/Guardfile +8 -0
  3. data/HISTORY.md +10 -0
  4. data/LICENSE +1 -1
  5. data/README.md +28 -29
  6. data/Rakefile +10 -2
  7. data/examples/activerecord/config/srv.rb +2 -1
  8. data/examples/activerecord/srv.rb +14 -5
  9. data/examples/api_proxy.rb +3 -7
  10. data/examples/around.rb +38 -0
  11. data/examples/async_aroundware_demo.rb +2 -2
  12. data/examples/async_upload.rb +1 -1
  13. data/examples/auth_and_rate_limit.rb +1 -1
  14. data/examples/clone.rb +26 -0
  15. data/examples/config/websocket.rb +1 -0
  16. data/examples/custom_logs.rb +21 -0
  17. data/examples/custom_server.rb +6 -44
  18. data/examples/early_abort.rb +6 -3
  19. data/examples/echo.rb +1 -1
  20. data/examples/fiber_pool.rb +35 -0
  21. data/examples/grape/config/apiserver.rb +8 -0
  22. data/examples/grape/server.rb +67 -0
  23. data/examples/gziped.rb +1 -1
  24. data/examples/params.rb +36 -0
  25. data/examples/rasterize/rasterize.rb +1 -2
  26. data/examples/router.rb +15 -0
  27. data/examples/template.rb +14 -7
  28. data/examples/test.rb +31 -0
  29. data/examples/upload.rb +17 -0
  30. data/examples/views/joke.markdown +4 -4
  31. data/examples/websocket.rb +39 -0
  32. data/examples/ws/favicon.ico +0 -0
  33. data/examples/ws/index.erb +50 -0
  34. data/goliath.gemspec +24 -6
  35. data/lib/goliath/api.rb +15 -82
  36. data/lib/goliath/application.rb +3 -17
  37. data/lib/goliath/connection.rb +16 -9
  38. data/lib/goliath/constants.rb +2 -0
  39. data/lib/goliath/env.rb +2 -3
  40. data/lib/goliath/goliath.rb +24 -34
  41. data/lib/goliath/plugins/latency.rb +7 -3
  42. data/lib/goliath/rack/builder.rb +1 -37
  43. data/lib/goliath/rack/favicon.rb +31 -0
  44. data/lib/goliath/rack/formatters/json.rb +1 -1
  45. data/lib/goliath/rack/heartbeat.rb +1 -0
  46. data/lib/goliath/rack/jsonp.rb +1 -0
  47. data/lib/goliath/rack/params.rb +2 -17
  48. data/lib/goliath/rack/render.rb +0 -1
  49. data/lib/goliath/rack/templates.rb +18 -7
  50. data/lib/goliath/rack/types/base.rb +24 -0
  51. data/lib/goliath/rack/types/boolean.rb +18 -0
  52. data/lib/goliath/rack/types/core.rb +19 -0
  53. data/lib/goliath/rack/types/symbol.rb +16 -0
  54. data/lib/goliath/rack/types.rb +10 -0
  55. data/lib/goliath/rack/validation/coerce.rb +48 -0
  56. data/lib/goliath/rack/validation/param.rb +113 -0
  57. data/lib/goliath/rack/validation/request_method.rb +1 -1
  58. data/lib/goliath/rack/validation/required.rb +47 -0
  59. data/lib/goliath/rack/validation/required_param.rb +42 -17
  60. data/lib/goliath/rack/validation.rb +3 -0
  61. data/lib/goliath/rack.rb +2 -2
  62. data/lib/goliath/request.rb +41 -9
  63. data/lib/goliath/response.rb +0 -2
  64. data/lib/goliath/runner.rb +55 -4
  65. data/lib/goliath/server.rb +7 -3
  66. data/lib/goliath/test_helper.rb +70 -16
  67. data/lib/goliath/test_helper_ws.rb +42 -0
  68. data/lib/goliath/version.rb +1 -1
  69. data/lib/goliath/websocket.rb +80 -0
  70. data/pkg/goliath-0.9.4.gem +0 -0
  71. data/pkg/goliath-1.0.0.beta.1.gem +0 -0
  72. data/spec/integration/async_request_processing.rb +1 -1
  73. data/spec/integration/early_abort_spec.rb +3 -10
  74. data/spec/integration/echo_spec.rb +8 -8
  75. data/spec/integration/jsonp_spec.rb +31 -0
  76. data/spec/integration/keepalive_spec.rb +2 -2
  77. data/spec/integration/template_spec.rb +10 -5
  78. data/spec/integration/test_helper_spec.rb +33 -0
  79. data/spec/integration/valid_spec.rb +35 -5
  80. data/spec/integration/websocket_spec.rb +44 -0
  81. data/spec/unit/api_spec.rb +2 -19
  82. data/spec/unit/connection_spec.rb +3 -0
  83. data/spec/unit/rack/formatters/json_spec.rb +3 -3
  84. data/spec/unit/rack/heartbeat_spec.rb +13 -0
  85. data/spec/unit/rack/params_spec.rb +2 -8
  86. data/spec/unit/rack/validation/param_spec.rb +443 -0
  87. data/spec/unit/rack/validation/request_method_spec.rb +5 -0
  88. data/spec/unit/rack/validation/required_param_spec.rb +71 -1
  89. data/spec/unit/runner_spec.rb +21 -7
  90. data/test/echo_test.rb +25 -0
  91. data/test/test_helper.rb +5 -0
  92. metadata +316 -78
  93. data/examples/env_use_statements.rb +0 -20
  94. data/examples/favicon.rb +0 -40
  95. data/examples/rack_routes.rb +0 -125
  96. data/examples/rasterize/thumb/f7ad4cb03e5bfd0e2c43db8e598fb3cd.png +0 -0
  97. data/examples/valid.rb +0 -17
  98. data/lib/goliath/deprecated/async_aroundware.rb +0 -133
  99. data/lib/goliath/deprecated/mongo_receiver.rb +0 -84
  100. data/lib/goliath/deprecated/response_receiver.rb +0 -97
  101. data/spec/integration/rack_routes_spec.rb +0 -169
  102. data/spec/unit/rack/builder_spec.rb +0 -40
@@ -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
- if klass.maps?
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
@@ -13,7 +13,7 @@ module Goliath
13
13
 
14
14
  def post_process(env, status, headers, body)
15
15
  if json_response?(headers)
16
- body = [MultiJson.encode(body)]
16
+ body = [MultiJson.dump(body)]
17
17
  end
18
18
  [status, headers, body]
19
19
  end
@@ -16,6 +16,7 @@ module Goliath
16
16
 
17
17
  def call(env)
18
18
  if env['PATH_INFO'] == @opts[:path]
19
+ env[Goliath::Constants::RACK_LOGGER] = Log4r::Logger.root unless @opts[:log]
19
20
  @opts[:response]
20
21
  else
21
22
  @app.call(env)
@@ -18,6 +18,7 @@ module Goliath
18
18
  response = body
19
19
  end
20
20
 
21
+ headers[Goliath::Constants::CONTENT_TYPE] = 'application/javascript'
21
22
  [status, headers, ["#{env.params['callback']}(#{response})"]]
22
23
  end
23
24
  end
@@ -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.decode(body)
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
 
@@ -31,7 +31,6 @@ module Goliath
31
31
  end
32
32
 
33
33
  extra = { 'Content-Type' => get_content_type(env),
34
- 'Server' => 'PostRank Goliath API Server',
35
34
  'Vary' => [headers.delete('Vary'), 'Accept'].compact.join(',') }
36
35
 
37
36
  [status, extra.merge(headers), body]
@@ -321,14 +321,25 @@ module Goliath
321
321
  Tilt.new(layout_filename, nil, options)
322
322
  end
323
323
 
324
- template_filename = find_template(views, data, engine)
325
- unless template_filename
326
- raise Goliath::Validation::InternalServerError, "Template #{data} not found in #{views} for #{engine}"
327
- end
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,16 @@
1
+ module Goliath
2
+ module Rack
3
+ module Types
4
+ class Symbol < Base
5
+ ERROR_MESSAGE = "%s can't convert to Symbol"
6
+ def _coerce(val)
7
+ begin
8
+ val.to_sym
9
+ rescue
10
+ raise ERROR_MESSAGE % val
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ module Goliath
2
+ module Rack
3
+ module Types
4
+ require 'goliath/rack/types/base'
5
+ require 'goliath/rack/types/core'
6
+ require 'goliath/rack/types/symbol'
7
+ require 'goliath/rack/types/boolean'
8
+ end
9
+ end
10
+ end
@@ -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
@@ -21,7 +21,7 @@ module Goliath
21
21
  # @return [Goliath::Rack::Validation::RequestMethod] The validator
22
22
  def initialize(app, methods = [])
23
23
  @app = app
24
- @methods = methods
24
+ @methods = Array(methods)
25
25
  end
26
26
 
27
27
  def call(env)
@@ -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
- if defined?(Encoding) && "".respond_to?(:encode)
17
- NON_WHITESPACE_REGEXP = %r![^[:space:]]!
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.capitalize
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
- if !params.has_key?(key) || params[key].nil? ||
44
- (params[key].is_a?(String) && params[key] !~ NON_WHITESPACE_REGEXP)
45
- return false
46
- end
47
+ key_path = Array(@key)
48
+ current_value = params
47
49
 
48
- if params[key].is_a?(Array)
49
- unless params[key].compact.empty?
50
- params[key].each do |k|
51
- return true unless k.is_a?(String)
52
- return true unless k !~ NON_WHITESPACE_REGEXP
53
- end
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