dzl 1.0.0.beta0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +1 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +9 -0
  5. data/README.md +44 -0
  6. data/Rakefile +15 -0
  7. data/config.ru +41 -0
  8. data/dzl.gemspec +21 -0
  9. data/lib/dzl/doc/endpoint_doc.rb +70 -0
  10. data/lib/dzl/doc/router_doc.rb +26 -0
  11. data/lib/dzl/doc/task.rb +5 -0
  12. data/lib/dzl/doc/templates/endpoint.erb +55 -0
  13. data/lib/dzl/doc/templates/home.erb +8 -0
  14. data/lib/dzl/doc.rb +58 -0
  15. data/lib/dzl/dsl_proxies/defaults.rb +10 -0
  16. data/lib/dzl/dsl_proxies/endpoint.rb +30 -0
  17. data/lib/dzl/dsl_proxies/parameter.rb +75 -0
  18. data/lib/dzl/dsl_proxies/parameter_block.rb +61 -0
  19. data/lib/dzl/dsl_proxies/protection.rb +6 -0
  20. data/lib/dzl/dsl_proxies/router.rb +62 -0
  21. data/lib/dzl/dsl_proxy.rb +8 -0
  22. data/lib/dzl/dsl_subject.rb +15 -0
  23. data/lib/dzl/dsl_subjects/defaults.rb +12 -0
  24. data/lib/dzl/dsl_subjects/endpoint.rb +79 -0
  25. data/lib/dzl/dsl_subjects/parameter/allowed_values.rb +60 -0
  26. data/lib/dzl/dsl_subjects/parameter/type_conversion.rb +59 -0
  27. data/lib/dzl/dsl_subjects/parameter.rb +100 -0
  28. data/lib/dzl/dsl_subjects/parameter_block.rb +64 -0
  29. data/lib/dzl/dsl_subjects/protection.rb +31 -0
  30. data/lib/dzl/dsl_subjects/router.rb +87 -0
  31. data/lib/dzl/errors.rb +33 -0
  32. data/lib/dzl/examples/base.rb +9 -0
  33. data/lib/dzl/examples/fun_with_handlers.rb +33 -0
  34. data/lib/dzl/examples/fun_with_hooks.rb +65 -0
  35. data/lib/dzl/examples/fun_with_params.rb +71 -0
  36. data/lib/dzl/examples/fun_with_requests.rb +47 -0
  37. data/lib/dzl/examples/route_profile.rb +158 -0
  38. data/lib/dzl/examples/trey.rb +97 -0
  39. data/lib/dzl/logger.rb +65 -0
  40. data/lib/dzl/rack_interface.rb +95 -0
  41. data/lib/dzl/request.rb +54 -0
  42. data/lib/dzl/response_context/request_helpers.rb +11 -0
  43. data/lib/dzl/response_context.rb +47 -0
  44. data/lib/dzl/validator.rb +10 -0
  45. data/lib/dzl/validators/size.rb +32 -0
  46. data/lib/dzl/validators/value.rb +30 -0
  47. data/lib/dzl/value_or_error.rb +32 -0
  48. data/lib/dzl/version.rb +3 -0
  49. data/lib/dzl.rb +96 -0
  50. data/spec/dsl_subject_spec.rb +14 -0
  51. data/spec/endpoint_doc_spec.rb +25 -0
  52. data/spec/fun_with_handlers_spec.rb +37 -0
  53. data/spec/fun_with_hooks_spec.rb +61 -0
  54. data/spec/fun_with_params_spec.rb +197 -0
  55. data/spec/fun_with_requests_spec.rb +101 -0
  56. data/spec/logger_spec.rb +48 -0
  57. data/spec/route_params_spec.rb +13 -0
  58. data/spec/router_doc_spec.rb +32 -0
  59. data/spec/spec_helper.rb +8 -0
  60. data/spec/trey_spec.rb +135 -0
  61. data/spec/value_or_error_spec.rb +44 -0
  62. metadata +142 -0
@@ -0,0 +1,79 @@
1
+ require 'dzl/dsl_proxies/endpoint'
2
+
3
+ class Dzl::DSLSubjects::Endpoint < Dzl::DSLSubject
4
+ attr_reader :pblock, :route_regex, :route, :router, :hooks
5
+ attr_accessor :handler
6
+ include Dzl::EndpointDoc
7
+
8
+ def initialize(route, opts, router)
9
+ @route = route
10
+ @opts = opts
11
+ @router = router
12
+ @pblock = Dzl::DSLSubjects::ParameterBlock.new(:anonymous, {}, @router)
13
+ @pblock.dsl_proxy.import_pblock(:__default)
14
+ @dsl_proxy = Dzl::DSLProxies::Endpoint.new(self)
15
+ @hooks = {
16
+ after_validate: []
17
+ }
18
+
19
+ analyze_route
20
+ end
21
+
22
+ def as_json(opts=nil)
23
+ {
24
+ opts: @opts,
25
+ pblock: @pblock
26
+ }
27
+ end
28
+
29
+ def handle(request)
30
+ request.silent = true if @opts[:silent]
31
+ Dzl::ResponseContext.new(self, request, @handler).__respond__
32
+ end
33
+
34
+ def validate(request)
35
+ unless @opts[:request_methods].include?(request.request_method.downcase.to_sym)
36
+ return Dzl::ValueOrError.new(e: :request_method_not_supported)
37
+ end
38
+
39
+ route_params = extract_route_parameters(request.path)
40
+ params = {
41
+ params: request.params.merge(route_params).symbolize_keys,
42
+ headers: request.headers.symbolize_keys
43
+ }
44
+
45
+ pblock.validate(params, request)
46
+ end
47
+
48
+ def extract_route_parameters(path)
49
+ path_splits = path.split('/')
50
+ path_splits.delete('')
51
+
52
+ return nil if path_splits.size != @route_splits.size
53
+
54
+ Hash[
55
+ *@route_splits.collect do |rsplit|
56
+ psplit = path_splits.shift
57
+ next unless rsplit.starts_with?(':')
58
+ [rsplit[1..-1].to_sym, psplit]
59
+ end.compact.flatten
60
+ ]
61
+ end
62
+
63
+ private
64
+ def analyze_route
65
+ @route = "/#{@route}" if @route.is_a?(Symbol)
66
+
67
+ if params = /\/:([^\/]+)/.match(@route)
68
+ params[1..-1].each {|p| @pblock.dsl_proxy.required(p.to_sym, in_path: true)}
69
+ end
70
+
71
+ @route_splits = @route.split('/').select{|s| not s.empty?}
72
+
73
+ route_regex_string = @route_splits.collect do |route_part|
74
+ route_part.starts_with?(':') ? "/.*?" : "/#{route_part}"
75
+ end.push('$').join('')
76
+
77
+ @route_regex = Regexp.new(route_regex_string)
78
+ end
79
+ end
@@ -0,0 +1,60 @@
1
+ class Dzl::DSLSubjects::Parameter
2
+ module AllowedValues
3
+ def allowed_values(input)
4
+ unless (@validations[:allowed_values].present? ||
5
+ @validations[:disallowed_values].present?)
6
+ return Dzl::ValueOrError.new(v: input)
7
+ end
8
+
9
+ if @validations[:disallowed_values].present?
10
+ valid = true
11
+
12
+ if input.is_a?(Array)
13
+ valid = input.none? do |value|
14
+ @validations[:disallowed_values].include?(value)
15
+ end
16
+ elsif input.is_a?(String)
17
+ valid = !@validations[:disallowed_values].include?(input)
18
+ end
19
+
20
+ return Dzl::ValueOrError.new(e: :disallowed_values_failed) unless valid
21
+ end
22
+
23
+ if @validations[:allowed_values].present?
24
+ valid = true
25
+
26
+ if param_type == Array
27
+ valid = input.all? do |value|
28
+ @validations[:allowed_values].include?(value)
29
+ end
30
+ elsif param_type == String
31
+ valid = @validations[:allowed_values].include?(input)
32
+ end
33
+
34
+ return Dzl::ValueOrError.new(e: :allowed_values_failed) unless valid
35
+ end
36
+
37
+ Dzl::ValueOrError.new(v: input)
38
+ end
39
+
40
+ def regex_match(input)
41
+ return Dzl::ValueOrError.new(v: input) unless @validations[:matches].present?
42
+
43
+ error = nil
44
+
45
+ if input.is_a?(String)
46
+ unless @validations[:matches].any? {|re| re.match(input)}
47
+ error = Dzl::ValueOrError.new(e: :regex_match_fail)
48
+ end
49
+ elsif input.is_a?(Array)
50
+ input.each do |v|
51
+ unless @validations[:matches].any? {|re| re.match(v)}
52
+ error = Dzl::ValueOrError.new(e: :regex_match_fail)
53
+ end
54
+ end
55
+ end
56
+
57
+ return error ? error : Dzl::ValueOrError.new(v: input)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,59 @@
1
+ class Dzl::DSLSubjects::Parameter
2
+ module TypeConversion
3
+ def convert_type(input)
4
+ if param_type == String
5
+ v = Dzl::ValueOrError.new(v: input)
6
+
7
+ if v.value.empty? && @opts[:required]
8
+ v = Dzl::ValueOrError.new(
9
+ e: :missing_required_param
10
+ )
11
+ else
12
+ v
13
+ end
14
+ elsif param_type == Array
15
+ v = Dzl::ValueOrError.new(
16
+ v: input.split(
17
+ (@opts[:type_opts][:separator] rescue nil) || ' '
18
+ )
19
+ )
20
+
21
+ if v.value.empty? && @opts[:required]
22
+ Dzl::ValueOrError.new(
23
+ e: :empty_required_array
24
+ )
25
+ else
26
+ v
27
+ end
28
+ elsif param_type == Integer || param_type == Fixnum
29
+ if (input = input.to_i.to_s) == input
30
+ Dzl::ValueOrError.new(v: input.to_i)
31
+ else
32
+ Dzl::ValueOrError.new(
33
+ e: :type_conversion_error
34
+ )
35
+ end
36
+ elsif param_type == Date || param_type == Time
37
+ input = Time.parse(input) rescue nil
38
+ if input
39
+ input = input.to_date if param_type == Date
40
+ Dzl::ValueOrError.new(v: input)
41
+ else
42
+ Dzl::ValueOrError.new(
43
+ e: :type_conversion_error
44
+ )
45
+ end
46
+ end
47
+ end
48
+
49
+ def prevalidate_transform(input)
50
+ if @validations.has_key?(:prevalidate_transform)
51
+ @validations[:prevalidate_transform].each do |transform|
52
+ input = transform.call(input)
53
+ end
54
+ end
55
+
56
+ Dzl::ValueOrError.new(v: input)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,100 @@
1
+ require 'dzl/dsl_proxies/parameter'
2
+
3
+ class Dzl::ParameterError < StandardError; end
4
+
5
+ class Dzl::DSLSubjects::Parameter < Dzl::DSLSubject
6
+ require 'dzl/dsl_subjects/parameter/type_conversion'
7
+ require 'dzl/dsl_subjects/parameter/allowed_values'
8
+ include TypeConversion
9
+ include AllowedValues
10
+
11
+ attr_reader :validations, :opts
12
+ attr_writer :default
13
+
14
+ def initialize(name, opts)
15
+ @name = name
16
+ @opts = opts
17
+ @validations = {
18
+ type: String
19
+ }
20
+ @dsl_proxy = Dzl::DSLProxies::Parameter.new(self)
21
+ end
22
+
23
+ def clone
24
+ deep_copy = self.dup
25
+ deep_copy.dup_data
26
+ deep_copy
27
+ end
28
+
29
+ def dup_data
30
+ @opts = @opts.clone
31
+ @validations = @validations.clone
32
+ @dsl_proxy = Dzl::DSLProxies::Parameter.new(self)
33
+ end
34
+
35
+ def param_type
36
+ @validations[:type]
37
+ end
38
+
39
+ def overwrite_opts(opts)
40
+ if @opts[:in_path] && opts[:required] == false
41
+ raise Dzl::ParameterError.new("Cannot set in-path param #{name} to optional")
42
+ end
43
+
44
+ @opts.merge!(opts)
45
+ end
46
+
47
+ # Returns a symbol describe the error if error,
48
+ # returns the transformed value if not
49
+ # TODO symbol values?
50
+ def validate(input)
51
+ # Validate type
52
+ unless input
53
+ if @opts[:required]
54
+ return Dzl::ValueOrError.new(
55
+ e: @opts[:header] ? :missing_required_header : :missing_required_param
56
+ )
57
+ else
58
+ return Dzl::ValueOrError.new(
59
+ v: @opts.has_key?(:default_value) ? @opts[:default_value] : :__no_value__
60
+ )
61
+ end
62
+ end
63
+
64
+ input = convert_type(input)
65
+ return input if input.error?
66
+
67
+ input = prevalidate_transform(input.value)
68
+ return input if input.error?
69
+
70
+ input = allowed_values(input.value)
71
+ return input if input.error?
72
+
73
+ input = regex_match(input.value)
74
+ return input if input.error?
75
+
76
+ # Validator procs
77
+ if @validations.has_key?(:procs)
78
+ @validations[:procs].each do |vproc|
79
+ vproc.call(input.value) or
80
+ return Dzl::ValueOrError.new(e: :validator_poc_failed)
81
+ end
82
+ end
83
+
84
+ # Validator classes
85
+ @validations.select {|k, v| v.kind_of?(Dzl::Validator)}.each do |vary|
86
+ name, validator = vary
87
+ input = validator.validate(input.value)
88
+ return input if input.error?
89
+ end
90
+
91
+ Dzl::ValueOrError.new(v: input.value)
92
+ end
93
+
94
+ def as_json(opts=nil)
95
+ {
96
+ opts: @opts,
97
+ validations: @validations
98
+ }
99
+ end
100
+ end
@@ -0,0 +1,64 @@
1
+ require 'dzl/dsl_proxies/parameter_block'
2
+
3
+ class Dzl::DSLSubjects::ParameterBlock < Dzl::DSLSubject
4
+ attr_accessor :params
5
+ attr_reader :router
6
+
7
+ def initialize(name, opts, router)
8
+ @name = name
9
+ @opts = opts
10
+ @router = router
11
+ @params = {}
12
+ @dsl_proxy = Dzl::DSLProxies::ParameterBlock.new(self)
13
+ end
14
+
15
+ def validate(parandidates, request)
16
+ errors = @params.each_with_object({}) do |pary, errors|
17
+ pname, param = pary
18
+ parandidate_key = param.opts[:header] ? :headers : :params
19
+
20
+ # verror = value or error.
21
+ verror = @params[pname].validate(parandidates[parandidate_key][pname])
22
+ if verror.error?
23
+ errors[pname] = verror.error
24
+ else
25
+ parandidates[parandidate_key][pname] = verror.value unless verror.value == :__no_value__
26
+ end
27
+ end || {}
28
+
29
+ # Check for extra request params we are not expecting
30
+ parandidates[:params].each do |pname, value|
31
+ unless @params.keys.include?(pname)
32
+ parandidates[:params].delete(pname)
33
+ errors[pname] = :unknown_param
34
+ end
35
+ end
36
+
37
+ if !errors.empty?
38
+ Dzl::ValueOrError.new(e: errors)
39
+ elsif @opts[:protection]
40
+ protection_errors = @opts[:protection].collect do |protection|
41
+ protection.allow?(parandidates, request)
42
+ end.select { |result| result.error? }
43
+
44
+ if protection_errors.empty?
45
+ Dzl::ValueOrError.new(v: parandidates)
46
+ else
47
+ protection_errors[0]
48
+ end
49
+ else
50
+ Dzl::ValueOrError.new(v: parandidates)
51
+ end
52
+ end
53
+
54
+ def to_s
55
+ "pblock:#{name}"
56
+ end
57
+
58
+ def as_json(opts=nil)
59
+ {
60
+ opts: @opts,
61
+ params: @params
62
+ }
63
+ end
64
+ end
@@ -0,0 +1,31 @@
1
+ require 'dzl/dsl_proxies/protection'
2
+
3
+ class Dzl::RespondWithHTTPBasicChallenge < StandardError; end
4
+
5
+ class Dzl::DSLSubjects::Protection < Dzl::DSLSubject
6
+ def initialize
7
+ super
8
+ @dsl_proxy = Dzl::DSLProxies::Protection.new(self)
9
+ end
10
+
11
+ def allow?(parandidates, request)
12
+ params = parandidates[:params]
13
+ headers = parandidates[:headers]
14
+
15
+ if @opts[:http_basic].present?
16
+ @auth = Rack::Auth::Basic::Request.new(request.env)
17
+ if @auth.provided? && @auth.basic? && @auth.credentials
18
+ unless @auth.credentials[0] == @opts[:http_basic][:username] &&
19
+ @auth.credentials[1] == @opts[:http_basic][:password]
20
+ Dzl::ValueOrError.new(e: :invalid_http_basic_credentials)
21
+ else
22
+ Dzl::ValueOrError.new(v: nil)
23
+ end
24
+ else
25
+ Dzl::ValueOrError.new(e: :no_http_basic_credentials)
26
+ end
27
+ else
28
+ Dzl::ValueOrError.new(v: nil)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,87 @@
1
+ require 'dzl/dsl_proxies/router'
2
+ require 'dzl/dsl_subjects/defaults'
3
+
4
+ class Dzl::DSLSubjects::Router < Dzl::DSLSubject
5
+ include Dzl::RouterDoc
6
+ attr_reader :pblocks, :defaults_dslsub, :defaults, :app
7
+
8
+ def initialize(app)
9
+ @pblocks = {}
10
+ @endpoints_by_route = {}
11
+ @stack = []
12
+ @defaults = {}
13
+ @defaults_dslsub = Dzl::DSLSubjects::Defaults.new(self)
14
+ @dsl_proxy = Dzl::DSLProxies::Router.new(self)
15
+ @app = app
16
+ end
17
+
18
+ def call_with_subject(proc, subject)
19
+ @stack.push(subject)
20
+ proc.call
21
+ @stack.pop
22
+ end
23
+
24
+ def subject
25
+ @stack.last
26
+ end
27
+
28
+ def routes
29
+ @endpoints_by_route.keys
30
+ end
31
+
32
+ def endpoints
33
+ @endpoints = @endpoints_by_route.values.flatten
34
+ end
35
+
36
+ def add_endpoint(ept)
37
+ @endpoints_by_route[ept.route] ||= []
38
+ @endpoints_by_route[ept.route] << ept
39
+ end
40
+
41
+ def pblocks
42
+ @pblocks
43
+ end
44
+
45
+ def add_pblock(pb)
46
+ @pblocks[pb.name] = pb
47
+ end
48
+
49
+ def as_json(opts=nil)
50
+ @endpoints
51
+ end
52
+
53
+ def handle_request(request)
54
+ endpoint = find_endpoint(request)
55
+ response = request.handle_with_endpoint(endpoint)
56
+ end
57
+
58
+ def find_endpoint(request)
59
+ errors = {}
60
+ raise Dzl::NotFound if routes.empty?
61
+
62
+ endpoint = endpoints.find do |endpoint|
63
+ if request.path.match(endpoint.route_regex)
64
+ validation = endpoint.validate(request)
65
+ if validation.value?
66
+ # use our validated/transformed/params
67
+ request.params_and_headers_for_endpoint(
68
+ endpoint,
69
+ validation.value
70
+ )
71
+ true
72
+ else
73
+ errors[endpoint.route] = validation.error
74
+ false
75
+ end
76
+ end
77
+ end
78
+
79
+ if !errors.empty? &&
80
+ errors.values.any? {|v| v == :no_http_basic_credentials || v == :invalid_http_basic_credentials}
81
+ raise Dzl::RespondWithHTTPBasicChallenge
82
+ end
83
+
84
+ endpoint || raise(Dzl::NotFound.new(errors))
85
+ end
86
+
87
+ end
data/lib/dzl/errors.rb ADDED
@@ -0,0 +1,33 @@
1
+ class Dzl::Error < StandardError
2
+ attr_reader :data, :status
3
+
4
+ def initialize(data = {})
5
+ @data = data
6
+ @status = 500
7
+ end
8
+
9
+ def to_json
10
+ {
11
+ status: @status,
12
+ error_class: self.class.to_s,
13
+ errors: @data,
14
+ trace: self.backtrace
15
+ }.to_json
16
+ end
17
+ end
18
+
19
+ class Dzl::RequestError < Dzl::Error; end
20
+
21
+ class Dzl::NotFound < Dzl::RequestError
22
+ def initialize(data = {})
23
+ super(data)
24
+ @status = 404
25
+ end
26
+ end
27
+
28
+ class Dzl::BadRequest < Dzl::RequestError
29
+ def initialize(data = {})
30
+ super(data)
31
+ @status = 400
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ module Dzl::Examples
2
+ class Base
3
+ def self.root
4
+ @@root ||= File.expand_path('../../../../', __FILE__)
5
+ end
6
+
7
+ include Dzl
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ require 'dzl/examples/base'
2
+
3
+ class Dzl::Examples::FunWithHandlers < Dzl::Examples::Base
4
+ defaults do
5
+ content_type 'application/json'
6
+ end
7
+
8
+ endpoint '/say_bar' do
9
+ optional :foo, :bar, :baz, :bam
10
+
11
+ handle do
12
+ params[:bar]
13
+ end
14
+ end
15
+
16
+ endpoint '/say_bar_and_api_key' do
17
+ required :bar
18
+ required_header :api_key
19
+
20
+ handle do
21
+ {
22
+ bar: params[:bar],
23
+ api_key: headers[:api_key]
24
+ }.to_json
25
+ end
26
+ end
27
+
28
+ get '/raise' do
29
+ handle do
30
+ raise 'omg'
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,65 @@
1
+ require 'dzl/examples/base'
2
+
3
+ class Dzl::Examples::FunWithHooks < Dzl::Examples::Base
4
+ defaults do
5
+ content_type 'application/json'
6
+ end
7
+
8
+ endpoint '/pre' do
9
+ required :foo do
10
+ type Fixnum
11
+ value >= 4
12
+ prevalidate_transform do |input|
13
+ input == 1 ? 4 : input
14
+ end
15
+ end
16
+ end
17
+
18
+ endpoint '/post' do
19
+ required :foo do
20
+ type Fixnum
21
+ value >= 4
22
+ end
23
+
24
+ after_validate do
25
+ params[:foo] *= 2
26
+ end
27
+ end
28
+
29
+ endpoint '/multiply' do
30
+ required :x, :y do
31
+ type Fixnum
32
+ end
33
+
34
+ after_validate do
35
+ params[:z] = params[:x] * params[:y]
36
+ end
37
+ end
38
+
39
+ endpoint '/omg_math' do
40
+ optional :x, :y, :z do
41
+ type Fixnum
42
+ end
43
+
44
+ optional :prefix
45
+
46
+ # NEVER DO THIS IN YOUR APP IT IS SO UGLY
47
+ after_validate do
48
+ params[:multiply_then_add] = params[:x] * params[:y]
49
+ end
50
+
51
+ after_validate do
52
+ params[:multiply_then_add] += params[:z]
53
+ end
54
+
55
+ after_validate do
56
+ params[:speak] = "#{params[:prefix]} #{params[:multiply_then_add]}"
57
+ end
58
+ end
59
+
60
+ endpoint '/vomit' do
61
+ after_validate do
62
+ raise Dzl::BadRequest.new("This isn't quite what I was expecting")
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,71 @@
1
+ require 'dzl/examples/base'
2
+
3
+ class Dzl::Examples::FunWithParams < Dzl::Examples::Base
4
+ endpoint '/foo' do
5
+ required :foo do
6
+ type Array
7
+ disallowed_values %w{zilch zip nada}
8
+ end
9
+ end
10
+
11
+ endpoint '/bar' do
12
+ required :foo do
13
+ type Array, separator: '+'
14
+ end
15
+ end
16
+
17
+ endpoint '/baz' do
18
+ required :foo do
19
+ type Array, separator: ','
20
+ end
21
+ end
22
+
23
+ endpoint '/bar' do
24
+ required :foo
25
+ end
26
+
27
+ endpoint '/foo/:bar' do
28
+ required :bar do
29
+ type Time
30
+ end
31
+ end
32
+
33
+ endpoint '/protected' do
34
+ protect do
35
+ http_basic username: 'no', password: 'way'
36
+ end
37
+ end
38
+
39
+ endpoint '/arithmetic' do
40
+ optional :int do
41
+ type Fixnum
42
+ value >= 5
43
+ end
44
+
45
+ optional :str do
46
+ value == 'hello'
47
+ end
48
+
49
+ optional :date do
50
+ type Date
51
+ value > Date.parse('2012-01-01')
52
+ end
53
+ end
54
+
55
+ endpoint '/defaults' do
56
+ optional :foo do
57
+ default 'hello'
58
+ end
59
+
60
+ optional :bar
61
+ optional :baz do
62
+ default 'world'
63
+ end
64
+
65
+ optional :nil do
66
+ default nil
67
+ end
68
+ end
69
+
70
+ endpoint '/foo/:bar'
71
+ end