dzl 1.0.0.beta0

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.
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,47 @@
1
+ require 'dzl/examples/base'
2
+
3
+ class Dzl::Examples::FunWithRequests < Dzl::Examples::Base
4
+ endpoint '/foo' do
5
+ handle do
6
+ 'get'
7
+ end
8
+ end
9
+
10
+ endpoint '/post_op', :post do
11
+ handle do
12
+ 'post'
13
+ end
14
+ end
15
+
16
+ endpoint '/multi_op', :post, :put do
17
+ handle do
18
+ request.request_method.downcase
19
+ end
20
+ end
21
+
22
+ get '/get_only'
23
+ delete '/delete_only'
24
+ get '/get_and_post', :post
25
+
26
+ get '/validated_header' do
27
+ required_header :key do
28
+ validate_with { |k| k == 'hello' }
29
+ end
30
+ end
31
+
32
+ get '/ambiguous' do
33
+ required :foo
34
+
35
+ handle do
36
+ params[:foo]
37
+ end
38
+ end
39
+
40
+ get '/ambiguous' do
41
+ required :bar
42
+
43
+ handle do
44
+ params[:bar]
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,158 @@
1
+ require 'dzl/examples/base'
2
+
3
+ class Dzl::Examples::RouteProfile < Dzl::Examples::base
4
+ global_pblock do end
5
+
6
+ #150 routes
7
+ endpoint '/Lorem' do end
8
+ endpoint '/ipsum' do end
9
+ endpoint '/dolor' do end
10
+ endpoint '/sit' do end
11
+ endpoint '/amet' do end
12
+ endpoint '/consectetur' do end
13
+ endpoint '/adipiscing' do end
14
+ endpoint '/elit' do end
15
+ endpoint '/Aenean' do end
16
+ endpoint '/pulvinar' do end
17
+ endpoint '/ante' do end
18
+ endpoint '/id' do end
19
+ endpoint '/venenatis' do end
20
+ endpoint '/eleifend' do end
21
+ endpoint '/Vivamus' do end
22
+ endpoint '/mattis' do end
23
+ endpoint '/tempor' do end
24
+ endpoint '/bibendum' do end
25
+ endpoint '/nulla' do end
26
+ endpoint '/lobortis' do end
27
+ endpoint '/vitae' do end
28
+ endpoint '/Vestibulum' do end
29
+ endpoint '/primis' do end
30
+ endpoint '/in' do end
31
+ endpoint '/faucibus' do end
32
+ endpoint '/orci' do end
33
+ endpoint '/luctus' do end
34
+ endpoint '/et' do end
35
+ endpoint '/ultrices' do end
36
+ endpoint '/posuere' do end
37
+ endpoint '/cubilia' do end
38
+ endpoint '/Curae;' do end
39
+ endpoint '/Proin' do end
40
+ endpoint '/nisi' do end
41
+ endpoint '/malesuada' do end
42
+ endpoint '/eu' do end
43
+ endpoint '/scelerisque' do end
44
+ endpoint '/ut' do end
45
+ endpoint '/eget' do end
46
+ endpoint '/augue' do end
47
+ endpoint '/Aliquam' do end
48
+ endpoint '/erat' do end
49
+ endpoint '/volutpat' do end
50
+ endpoint '/Praesent' do end
51
+ endpoint '/leo' do end
52
+ endpoint '/commodo' do end
53
+ endpoint '/ac' do end
54
+ endpoint '/suscipit' do end
55
+ endpoint '/sed' do end
56
+ endpoint '/magna' do end
57
+ endpoint '/Fusce' do end
58
+ endpoint '/lacinia' do end
59
+ endpoint '/lorem' do end
60
+ endpoint '/felis' do end
61
+ endpoint '/fermentum' do end
62
+ endpoint '/non' do end
63
+ endpoint '/molestie' do end
64
+ endpoint '/ligula' do end
65
+ endpoint '/sollicitudin' do end
66
+ endpoint '/Cras' do end
67
+ endpoint '/auctor' do end
68
+ endpoint '/mi' do end
69
+ endpoint '/eros' do end
70
+ endpoint '/quis' do end
71
+ endpoint '/velit' do end
72
+ endpoint '/facilisis' do end
73
+ endpoint '/a' do end
74
+ endpoint '/metus' do end
75
+ endpoint '/Ut' do end
76
+ endpoint '/enim' do end
77
+ endpoint '/aliquam' do end
78
+ endpoint '/cursus' do end
79
+ endpoint '/Etiam' do end
80
+ endpoint '/imperdiet' do end
81
+ endpoint '/rhoncus' do end
82
+ endpoint '/nibh' do end
83
+ endpoint '/tincidunt' do end
84
+ endpoint '/ullamcorper' do end
85
+ endpoint '/Nulla' do end
86
+ endpoint '/dapibus' do end
87
+ endpoint '/nunc' do end
88
+ endpoint '/at' do end
89
+ endpoint '/vestibulum' do end
90
+ endpoint '/tortor' do end
91
+ endpoint '/pharetra' do end
92
+ endpoint '/vulputate' do end
93
+ endpoint '/accumsan' do end
94
+ endpoint '/Pellentesque' do end
95
+ endpoint '/Nam' do end
96
+ endpoint '/elementum' do end
97
+ endpoint '/ultricies' do end
98
+ endpoint '/Morbi' do end
99
+ endpoint '/varius' do end
100
+ endpoint '/lacus' do end
101
+ endpoint '/mollis' do end
102
+ endpoint '/Quisque' do end
103
+ endpoint '/porttitor' do end
104
+ endpoint '/arcu' do end
105
+ endpoint '/mauris' do end
106
+ endpoint '/hendrerit' do end
107
+ endpoint '/pretium' do end
108
+ endpoint '/placerat' do end
109
+ endpoint '/quam' do end
110
+ endpoint '/feugiat' do end
111
+ endpoint '/diam' do end
112
+ endpoint '/aliquet' do end
113
+ endpoint '/convallis' do end
114
+ endpoint '/Donec' do end
115
+ endpoint '/dignissim' do end
116
+ endpoint '/lectus' do end
117
+ endpoint '/rutrum' do end
118
+ endpoint '/nisl' do end
119
+ endpoint '/ornare' do end
120
+ endpoint '/tristique' do end
121
+ endpoint '/sapien' do end
122
+ endpoint '/euismod' do end
123
+ endpoint '/vel' do end
124
+ endpoint '/massa' do end
125
+ endpoint '/porta' do end
126
+ endpoint '/nec' do end
127
+ endpoint '/habitant' do end
128
+ endpoint '/morbi' do end
129
+ endpoint '/senectus' do end
130
+ endpoint '/netus' do end
131
+ endpoint '/fames' do end
132
+ endpoint '/turpis' do end
133
+ endpoint '/egestas' do end
134
+ endpoint '/Curabitur' do end
135
+ endpoint '/pellentesque' do end
136
+ endpoint '/libero' do end
137
+ endpoint '/Duis' do end
138
+ endpoint '/congue' do end
139
+ endpoint '/iaculis' do end
140
+ endpoint '/In' do end
141
+ endpoint '/Nunc' do end
142
+ endpoint '/Sed' do end
143
+ endpoint '/blandit' do end
144
+ endpoint '/tempus' do end
145
+ endpoint '/tellus' do end
146
+ endpoint '/condimentum' do end
147
+ endpoint '/interdum' do end
148
+ endpoint '/fringilla' do end
149
+ endpoint '/viverra' do end
150
+ endpoint '/justo' do end
151
+ endpoint '/consequat' do end
152
+ endpoint '/gravida' do end
153
+ endpoint '/risus' do end
154
+ endpoint '/urna' do end
155
+ endpoint '/semper' do end
156
+ endpoint '/dui' do end
157
+
158
+ end
@@ -0,0 +1,97 @@
1
+ require 'dzl/examples/base'
2
+
3
+ class Dzl::Examples::Trey < Dzl::Examples::Base
4
+ METRIC_NAMES_OR_WHATEVER ||= %w{m1 m2 m3 m4 m5 m6}
5
+
6
+ # this could be handy
7
+ # defaults do
8
+ # type Array do
9
+ # unique
10
+ # separator ' '
11
+ # end
12
+ # end
13
+
14
+ # global parameter block applies to all routes
15
+ # each route gets it's own copy, so, they can
16
+ # mess with the predefined parameters as much as
17
+ # they'd like
18
+ global_pblock do
19
+ # required_header :api_key do
20
+ # validate_with { |key| key == 'open sesame' }
21
+ # end
22
+
23
+ param :page_ids do
24
+ type Array
25
+ size <= 5
26
+ end
27
+
28
+ param :post_ids do
29
+ type Array
30
+ size <= 100
31
+ end
32
+
33
+ param :metrics do
34
+ type Array
35
+ allowed_values METRIC_NAMES_OR_WHATEVER
36
+ end
37
+
38
+ param :since, :until do
39
+ type Date
40
+ end
41
+
42
+ param :interval do
43
+ allowed_values %w{day week month}
44
+ default 'day'
45
+ end
46
+
47
+ param :limit do
48
+ type Fixnum
49
+ allowed_values 1..250
50
+ default 250
51
+ end
52
+
53
+ param :sort do
54
+ default 'created_time'
55
+ end
56
+
57
+ param :order do
58
+ prevalidate_transform do |input|
59
+ input.downcase
60
+ end
61
+ allowed_values ['asc', 'desc', 'ascending', 'descending']
62
+ default :desc
63
+ end
64
+ end
65
+
66
+ endpoint '/page_insights' do
67
+ required :page_ids, :metrics, :since, :until
68
+ optional :interval
69
+ forbid :post_ids, :limit, :sort, :order
70
+ end
71
+
72
+ endpoint '/post_insights' do
73
+ required :post_ids, :metrics
74
+ forbid :page_ids, :since, :until, :interval, :limit, :sort, :order
75
+ end
76
+
77
+ # endpoint '/post_insights' do
78
+ # required :page_ids do
79
+ # size <= 5
80
+ # end
81
+
82
+ # required :metrics, :since, :until
83
+ # forbid :post_ids, :interval, :limit, :sort, :order
84
+ # end
85
+
86
+ endpoint '/posts' do
87
+ required :page_ids, :metrics
88
+ optional :since, :until, :limit, :sort, :order
89
+ forbid :post_ids
90
+ end
91
+
92
+ # endpoint '/posts' do
93
+ # required :post_ids, :metrics
94
+ # optional :since, :until, :limit, :sort, :order
95
+ # forbid :page_ids
96
+ # end
97
+ end
data/lib/dzl/logger.rb ADDED
@@ -0,0 +1,65 @@
1
+ class ActiveSupport::BufferedLogger
2
+ def self.timestamp
3
+ DateTime.now.strftime("[%Y-%m-%d %H:%M:%S] - ")
4
+ end
5
+
6
+ alias_method :orig_add, :add
7
+ def add(severity, message = nil, progname = nil, &block)
8
+ message = "#{ActiveSupport::BufferedLogger.timestamp}#{message}"
9
+ orig_add(severity, message, progname, &block)
10
+ end
11
+ end
12
+
13
+ module Dzl
14
+ class Logger
15
+ LOG_METHODS = [:debug, :info, :warn, :error, :fatal]
16
+ def initialize(app_root)
17
+ @app_root = app_root
18
+ @loggers = {
19
+ default: create_logger # log/[environment].log
20
+ }
21
+ end
22
+
23
+ # Get the standard logger methods defined
24
+ LOG_METHODS.each do |severity|
25
+ define_method(severity) do |msg|
26
+ log(severity, msg)
27
+ end
28
+ end
29
+
30
+ ############
31
+ # Receives all the default log methods on AppClass.logger
32
+ # and forwards to the default logger
33
+ ############
34
+ def log(severity, msg)
35
+ @loggers[:default].send(severity, msg)
36
+ end
37
+
38
+ ############
39
+ # The idea here is that you can call
40
+ # AppClass.logger.tidy.debug("Something")
41
+ # and we'll write your log message to tidy.environment.log
42
+ ############
43
+ def method_missing(m, *args, &block)
44
+ if @loggers[:default].respond_to?(m)
45
+ # Don't create things like flush.development.log (-:
46
+ @loggers[:default].send(m, *args, &block)
47
+ else
48
+ @loggers[m] ||= create_logger(m.to_s)
49
+ end
50
+ end
51
+
52
+ private
53
+ def create_logger(name = nil)
54
+ logfile = name ? "#{name}.#{Dzl.env}.log" : "#{Dzl.env}.log"
55
+ ActiveSupport::BufferedLogger.new(
56
+ File.join(@app_root, 'log', logfile),
57
+ %w{ staging production }.include?(Dzl.env) ? ::Logger::INFO : ::Logger::DEBUG
58
+ )
59
+ end
60
+ end
61
+
62
+ def self.logger
63
+ @@logger ||= Logger.new
64
+ end
65
+ end
@@ -0,0 +1,95 @@
1
+ require 'rack'
2
+ require 'dzl/request'
3
+
4
+ module Dzl::RackInterface
5
+ PROFILE_REQUESTS = false
6
+
7
+ def call(env)
8
+ response = nil
9
+ request = nil
10
+ start_time = Time.now
11
+ start_profile if PROFILE_REQUESTS
12
+ response = begin
13
+ request = Dzl::Request.new(env)
14
+ __router.handle_request(request)
15
+ rescue Dzl::RespondWithHTTPBasicChallenge
16
+ respond_with_http_basic_challenge
17
+ rescue Dzl::Error => e
18
+ respond_with_dzl_error_handler(e)
19
+ rescue StandardError => e
20
+ respond_with_standard_error_handler(e)
21
+ end
22
+
23
+ if response[0] < 100
24
+ response = respond_with_standard_error_handler(StandardError.new('Application did not respond'))
25
+ end
26
+
27
+ stop_profiling_and_print if PROFILE_REQUESTS
28
+ log_request(request, response, (Time.now - start_time)) unless request.silent?
29
+
30
+ if Dzl.production? || Dzl.staging?
31
+ (response[0] < 500) ? response : [response[0], [], [response[0].to_s]]
32
+ else
33
+ response
34
+ end
35
+ end
36
+
37
+ def respond_with_http_basic_challenge
38
+ response = Rack::Response.new
39
+ response['WWW-Authenticate'] = %(Basic realm="Dzl HTTP Basic")
40
+ response.status = 401
41
+ response.headers['Content-Type'] = 'text/html'
42
+ response.write("Not Authorized\n")
43
+ response.finish
44
+ end
45
+
46
+ def respond_with_standard_error_handler(e)
47
+ response = Rack::Response.new
48
+ response.headers['Content-Type'] = 'application/json'
49
+ response.status = 500
50
+
51
+ response.write({
52
+ status: 500,
53
+ error_class: e.class.to_s,
54
+ errors: e.to_s,
55
+ trace: e.backtrace
56
+ }.to_json)
57
+
58
+ response.finish
59
+ end
60
+
61
+ def respond_with_dzl_error_handler(e)
62
+ response = Rack::Response.new
63
+ response.headers['Content-Type'] = 'application/json'
64
+
65
+ if e.is_a?(Dzl::RequestError)
66
+ response.status = e.status
67
+ response.write(e.to_json)
68
+ else
69
+ response.status = e.status
70
+ response.write(e.to_json)
71
+ end
72
+
73
+ response.finish
74
+ end
75
+
76
+ def start_profile
77
+ require 'ruby-prof'
78
+ RubyProf.start
79
+ end
80
+
81
+ def stop_profiling_and_print
82
+ result = RubyProf.stop
83
+ printer = RubyProf::GraphHtmlPrinter.new(result)
84
+ printer.print(
85
+ File.open('/Projects/dzl/profile.html', 'w'),
86
+ min_percent: 5
87
+ )
88
+ end
89
+
90
+ def log_request(request, response, seconds)
91
+ logger.info "#{request.request_method} #{request.path}"
92
+ logger.info "PARAMS: #{request.params}"
93
+ logger.info "#{response[0]} in #{seconds * 1000}ms"
94
+ end
95
+ end
@@ -0,0 +1,54 @@
1
+ class Dzl::Request < Rack::Request
2
+ attr_accessor :silent
3
+
4
+ def initialize(env)
5
+ super(env)
6
+
7
+ @endpoints = Hash.new({})
8
+ end
9
+
10
+ def headers
11
+ @headers ||= begin
12
+ env.each_with_object({}) do |env_pair, headers|
13
+ k, v = env_pair
14
+ if header = (/HTTP_(.+)/.match(k.upcase.gsub('-', '_'))[1]) rescue nil
15
+ headers[header.downcase] = v
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ def params
22
+ @params ||= super
23
+ end
24
+
25
+ def overwrite_headers(new_headers)
26
+ @headers = new_headers
27
+ end
28
+
29
+ def overwrite_params(new_params)
30
+ @params = new_params
31
+ end
32
+
33
+ def handle_with_endpoint(endpoint)
34
+ overwrite_params(@endpoints[endpoint][:params])
35
+ overwrite_headers(@endpoints[endpoint][:headers])
36
+ endpoint.handle(self).finish
37
+ end
38
+
39
+ def params_and_headers_for_endpoint(endpoint, params_and_headers = nil)
40
+ if params_and_headers
41
+ raise ArgumentError unless params_and_headers.is_a?(Hash) &&
42
+ params_and_headers.has_key?(:params) &&
43
+ params_and_headers.has_key?(:headers)
44
+
45
+ @endpoints[endpoint].merge!(params_and_headers)
46
+ else
47
+ @endpoints[endpoint].slice(:params, :headers)
48
+ end
49
+ end
50
+
51
+ def silent?
52
+ @silent == true
53
+ end
54
+ end
@@ -0,0 +1,11 @@
1
+ class Dzl::ResponseContext
2
+ module RequestHelpers
3
+ def params
4
+ request.params
5
+ end
6
+
7
+ def headers
8
+ request.headers
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,47 @@
1
+ require 'dzl/response_context/request_helpers'
2
+
3
+ class Dzl::ResponseContext
4
+ include RequestHelpers
5
+ attr_reader :request, :response, :endpoint
6
+
7
+ @@default_handler = Proc.new do
8
+ response['Content-Type'] = 'application/json'
9
+ {
10
+ headers: headers,
11
+ params: params
12
+ }.to_json
13
+ end
14
+
15
+ def initialize(endpoint, request, handler = nil)
16
+ @request = request
17
+ @endpoint = endpoint
18
+ @handler = handler
19
+ __build_response_with_defaults__
20
+ end
21
+
22
+ def logger
23
+ @logger ||= @endpoint.router.app.logger
24
+ end
25
+
26
+ def __respond__
27
+ @endpoint.hooks[:after_validate].each do |proc|
28
+ self.instance_exec(&proc)
29
+ end
30
+
31
+ value = @handler ? self.instance_exec(&@handler) : self.instance_exec(&@@default_handler)
32
+
33
+ unless @response.body.present?
34
+ @response.write(value)
35
+ end
36
+
37
+ @response
38
+ end
39
+
40
+ def __build_response_with_defaults__
41
+ @response = Rack::Response.new
42
+
43
+ if ct = @endpoint.router.defaults[:content_type]
44
+ @response['Content-Type'] = ct
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,10 @@
1
+ class Dzl::Validator
2
+ def validate(input)
3
+ raise "You must implement #validate in your Dzl::Validator subclass"
4
+ end
5
+ end
6
+
7
+ module Dzl::Validators; end
8
+
9
+ require 'dzl/validators/size'
10
+ require 'dzl/validators/value'
@@ -0,0 +1,32 @@
1
+ module Dzl::Validators
2
+ class Size < Dzl::Validator
3
+ attr_reader :conditions
4
+ def initialize
5
+ @conditions = []
6
+ end
7
+
8
+ def validate(input)
9
+ unless input.respond_to?(:size)
10
+ return Dzl::ValueOrError.new(
11
+ e: :cannot_validate_size
12
+ )
13
+ end
14
+
15
+ valid = @conditions.all? do |op, n|
16
+ input.size.send(op, n)
17
+ end
18
+
19
+ if valid
20
+ Dzl::ValueOrError.new(v: input)
21
+ else
22
+ Dzl::ValueOrError.new(e: :size_validation_failed)
23
+ end
24
+ end
25
+
26
+ [:==, :<=, :>=, :<, :>].each do |op|
27
+ define_method(op) do |n|
28
+ @conditions << [op, n]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ module Dzl::Validators
2
+ class Value < Dzl::Validator
3
+ attr_reader :conditions
4
+ def initialize
5
+ @conditions = []
6
+ end
7
+
8
+ def validate(input)
9
+ valid = @conditions.all? do |op, n|
10
+ return Dzl::ValueOrError.new(
11
+ e: :value_validation_failed
12
+ ) unless input.respond_to?(op)
13
+
14
+ input.send(op, n)
15
+ end
16
+
17
+ if valid
18
+ Dzl::ValueOrError.new(v: input)
19
+ else
20
+ Dzl::ValueOrError.new(e: :value_validation_failed)
21
+ end
22
+ end
23
+
24
+ [:==, :<=, :>=, :<, :>].each do |op|
25
+ define_method(op) do |n|
26
+ @conditions << [op, n]
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ class Dzl::ValueOrError
2
+ attr_reader :error, :value
3
+
4
+ def initialize(opts = {})
5
+ @error = opts[:error] || opts[:e]
6
+ @value = opts[:value] || opts[:v]
7
+
8
+ if @error && @value
9
+ raise ArgumentError, "it's ValueOrError, not ValueAndError"
10
+ end
11
+
12
+ unless @error || opts.has_key?(:v) || opts.has_key?(:value)
13
+ raise ArgumentError, "Must provide :value key, even if nil"
14
+ end
15
+ end
16
+
17
+ def error?
18
+ @error.present?
19
+ end
20
+
21
+ def value?
22
+ !error?
23
+ end
24
+
25
+ def to_s
26
+ if error?
27
+ "e: #{@error.inspect}"
28
+ else
29
+ "v: #{@value.inspect}"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module Dzl
2
+ VERSION = "1.0.0.beta0"
3
+ end