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