dzl 1.0.0.beta4 → 1.0.0.rc0

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.
data/config.ru CHANGED
@@ -40,7 +40,12 @@ end
40
40
  # run Dzl::Examples::FunWithHooks
41
41
  # end
42
42
 
43
- require 'dzl/examples/fun_with_scopes'
43
+ # require 'dzl/examples/fun_with_scopes'
44
+ # map '/' do
45
+ # run Dzl::Examples::FunWithScopes
46
+ # end
47
+
48
+ require 'dzl/examples/fun_with_hashes'
44
49
  map '/' do
45
- run Dzl::Examples::FunWithScopes
50
+ run Dzl::Examples::FunWithHashes
46
51
  end
@@ -0,0 +1,15 @@
1
+ class Hash
2
+ def try_keys(*keys)
3
+ keys.inject(self) { |c, k| c.has_key?(k) ? c[k] : nil }
4
+ end
5
+
6
+ def recursively_symbolize_keys!
7
+ self.symbolize_keys!
8
+ self.values.each do |v|
9
+ if v.is_a?(Hash)
10
+ v.recursively_symbolize_keys!
11
+ end
12
+ end
13
+ self
14
+ end
15
+ end
@@ -42,6 +42,12 @@ class Dzl::DSLProxies::Parameter < Dzl::DSLProxy
42
42
  def type(type, type_opts = {})
43
43
  @subject.validations[:type] = type
44
44
  @subject.opts[:type_opts] = type_opts
45
+
46
+ if type == Hash && !type_opts.try_keys(:validator).is_a?(HashValidator)
47
+ raise ArgumentError.new("Must pass :validator, an instance of HashValidator")
48
+ elsif type == Hash && block_given?
49
+ type_opts[:validator].instance_exec(&Proc.new)
50
+ end
45
51
  end
46
52
 
47
53
  def integer
@@ -64,9 +64,21 @@ class Dzl::DSLSubjects::Parameter < Dzl::DSLSubject
64
64
  input = begin
65
65
  if opts[:preformatted]
66
66
  if input.is_a?(param_type)
67
- Dzl::ValueOrError.new(
68
- v: input
69
- )
67
+ if param_type == Hash
68
+ if @opts[:type_opts][:validator].valid?(input)
69
+ Dzl::ValueOrError.new(
70
+ v: input
71
+ )
72
+ else
73
+ Dzl::ValueOrError.new(
74
+ e: :hash_validation_failed
75
+ )
76
+ end
77
+ else
78
+ Dzl::ValueOrError.new(
79
+ v: input
80
+ )
81
+ end
70
82
  else
71
83
  Dzl::ValueOrError.new(
72
84
  e: :type_conversion_error
@@ -0,0 +1,20 @@
1
+ require 'dzl/examples/base'
2
+
3
+ class Dzl::Examples::FunWithHashes < Dzl::Examples::Base
4
+ post '/h' do
5
+ required :foo do
6
+ type Hash, validator: HashValidator.new do
7
+ required :str
8
+ required :ary do
9
+ type Array
10
+ allowed_values [3, 5, 7]
11
+ end
12
+
13
+ required :nest do
14
+ type Hash
15
+ required(:int) { type Fixnum }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
data/lib/dzl/request.rb CHANGED
@@ -24,7 +24,7 @@ class Dzl::Request < Rack::Request
24
24
  @params ||= begin
25
25
  @request_body = body.read
26
26
  unless preformatted_params.empty?
27
- @preformatted_keys = preformatted_params.keys.collect {|k| k.to_sym}
27
+ @preformatted_keys = preformatted_params.keys
28
28
  end
29
29
 
30
30
  super.merge(preformatted_params)
@@ -63,6 +63,12 @@ class Dzl::Request < Rack::Request
63
63
 
64
64
  protected
65
65
  def preformatted_params
66
- @preformatted_params ||= (content_type == "application/json") ? JSON.parse(@request_body) : {}
66
+ @preformatted_params ||= begin
67
+ if content_type == "application/json"
68
+ JSON.parse(@request_body).recursively_symbolize_keys!
69
+ else
70
+ {}
71
+ end
72
+ end
67
73
  end
68
74
  end
data/lib/dzl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dzl
2
- VERSION = "1.0.0.beta4"
2
+ VERSION = "1.0.0.rc0"
3
3
  end
data/lib/dzl.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'active_support'
2
2
  require 'active_support/core_ext'
3
+ require 'hash_validator'
4
+ require 'dzl/core_ext'
3
5
  require 'dzl/version'
4
6
  require 'dzl/logger'
5
7
  require 'dzl/errors'
@@ -18,7 +20,7 @@ require 'dzl/dsl_subjects/parameter_block'
18
20
  require 'dzl/dsl_subjects/endpoint'
19
21
 
20
22
  module Dzl
21
- class NYI < StandardError; end
23
+ class NYI < Dzl::Error; end
22
24
 
23
25
  def self.env
24
26
  ENV['RACK_ENV']
@@ -0,0 +1,124 @@
1
+ class HashValidator
2
+ class << self
3
+ alias_method :orig_new, :new
4
+ def new(*args)
5
+ v = orig_new(*args)
6
+ v.instance_exec(&Proc.new) if block_given?
7
+ v
8
+ end
9
+ end
10
+
11
+ def initialize
12
+ @template = {
13
+ keys: {}
14
+ }
15
+
16
+ @key_stack = []
17
+ @dsl_proxy = DSLProxy.new(self)
18
+ end
19
+
20
+ def valid?(hsh)
21
+ top[:keys].each do |k, v|
22
+ if !hsh.has_key?(k)
23
+ if v[:opts][:required]
24
+ return false
25
+ else
26
+ next
27
+ end
28
+ end
29
+
30
+ input = hsh[k]
31
+
32
+ # Check type of input
33
+ return false unless input.is_a?(v[:opts][:type])
34
+
35
+ if input.is_a?(Hash)
36
+ @key_stack.push(k)
37
+
38
+ valid?(input) or begin
39
+ @key_stack = []
40
+ return false
41
+ end
42
+
43
+ @key_stack.pop
44
+ elsif input.is_a?(Array)
45
+ return false if v[:opts][:allowed_values].present? &&
46
+ !input.all? {|_input| v[:opts][:allowed_values].include?(_input)}
47
+
48
+ return false if v[:opts][:forbidden_values].present? &&
49
+ input.any? {|_input| v[:opts][:forbidden_values].include?(_input)}
50
+ else
51
+ return false if v[:opts][:allowed_values].present? &&
52
+ !v[:opts][:allowed_values].include?(hsh[k])
53
+
54
+ return false if v[:opts][:forbidden_values].present? &&
55
+ v[:opts][:forbidden_values].include?(hsh[k])
56
+ end
57
+ end
58
+
59
+ true
60
+ end
61
+
62
+ def key(k, opts, &block)
63
+ top[:keys][k] = {
64
+ opts: opts,
65
+ block: block,
66
+ keys: {}
67
+ }
68
+
69
+ @key_stack.push(k)
70
+ @dsl_proxy.instance_exec(&block) if block_given?
71
+ @key_stack.pop
72
+ end
73
+
74
+ def top
75
+ @key_stack.inject(@template) { |ref, key| ref[:keys][key] } || @template
76
+ end
77
+
78
+ def add_option(k, v)
79
+ top[:opts][k] = v
80
+ end
81
+
82
+ def method_missing(m, *args, &block)
83
+ if @dsl_proxy.respond_to?(m)
84
+ @dsl_proxy.send(m, *args, &block)
85
+ else
86
+ super(m, *args, &block)
87
+ end
88
+ end
89
+
90
+ class DSLProxy
91
+ def initialize(subject)
92
+ @subject = subject
93
+ end
94
+
95
+ def key(k, opts = {}, &block)
96
+ opts.reverse_merge!({
97
+ type: String
98
+ })
99
+
100
+ @subject.key(k, opts, &block)
101
+ end
102
+
103
+ def optional(k, opts = {}, &block)
104
+ key(k, opts.merge({required: false}), &block)
105
+ end
106
+
107
+ def required(k, opts = {}, &block)
108
+ key(k, opts.merge({required: true}), &block)
109
+ end
110
+
111
+ def type(klass)
112
+ raise ArgumentError unless klass.is_a?(Class)
113
+ @subject.add_option(:type, klass)
114
+ end
115
+
116
+ def allowed_values(ary)
117
+ @subject.add_option(:allowed_values, ary)
118
+ end
119
+
120
+ def forbidden_values(ary)
121
+ @subject.add_option(:forbidden_values, ary)
122
+ end
123
+ end
124
+ end
@@ -0,0 +1 @@
1
+ require 'hash_validator/hash_validator'
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+ require 'rack/test'
3
+ require 'dzl/examples/fun_with_hashes'
4
+
5
+ describe 'hash parameters' do
6
+ include Rack::Test::Methods
7
+ def app; Dzl::Examples::FunWithHashes; end
8
+
9
+ context 'DSL' do
10
+ specify 'hash parameters must have a validator, for now' do
11
+ expect {
12
+ class T1 < Dzl::Examples::Base
13
+ get '/foo' do
14
+ required :bar do
15
+ type Hash
16
+ end
17
+ end
18
+ end
19
+ }.to raise_exception(ArgumentError)
20
+
21
+ expect {
22
+ class T1 < Dzl::Examples::Base
23
+ get '/foo' do
24
+ required :bar do
25
+ type Hash, validator: HashValidator.new
26
+ end
27
+ end
28
+ end
29
+ }.to_not raise_exception(ArgumentError)
30
+ end
31
+ end
32
+
33
+ context 'validate' do
34
+ specify 'JSON post bodies' do
35
+ header 'Content-Type', 'application/json'
36
+ valid_body = {
37
+ foo: {
38
+ str: 'hello',
39
+ ary: [7, 3],
40
+ nest: {
41
+ int: 5
42
+ }
43
+ }
44
+ }
45
+
46
+ invalid_body = {
47
+ foo: {
48
+ str: 'hello',
49
+ ary: [7, 6, 5],
50
+ nest: {
51
+ int: 7
52
+ }
53
+ }
54
+ }
55
+
56
+ missing_foo = {
57
+ baz: {
58
+ str: 'hello',
59
+ ary: [7, 5],
60
+ nest: {
61
+ int: 5
62
+ }
63
+ }
64
+ }
65
+
66
+ post('/h', valid_body.to_json).status.should == 200
67
+ post('/h', missing_foo.to_json).status.should == 404
68
+ post('/h', invalid_body.to_json).status.should == 404
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,244 @@
1
+ require 'spec_helper'
2
+ require 'hash_validator'
3
+
4
+ describe HashValidator do
5
+ context 'required/optional keys' do
6
+ ok = {foo: 'bar', bar: 'foo'}
7
+ also_ok = {foo: 'bar'}
8
+ bad = {bar: 'foo'}
9
+
10
+ v = HashValidator.new
11
+ v.required(:foo)
12
+ v.optional(:bar)
13
+
14
+ v.valid?(ok).should == true
15
+ v.valid?(bad).should == false
16
+
17
+ v.valid?(also_ok).should == true
18
+ end
19
+
20
+ context 'key type specification' do
21
+ specify 'key type defaults to string' do
22
+ ok = {foo: 'bar'}
23
+ bad = {foo: 1}
24
+
25
+ v = HashValidator.new
26
+ v.required(:foo)
27
+
28
+ v.valid?(ok).should == true
29
+ v.valid?(bad).should == false
30
+ end
31
+
32
+ specify 'must be ruby classes' do
33
+ v = HashValidator.new
34
+ v.required(:foo) { type Fixnum }
35
+ v.required(:bar) { type Array }
36
+
37
+ ok = {foo: 3, bar: [1, 2, 3]}
38
+ bad = {foo: 3, bar: 4}
39
+
40
+ v.valid?(ok).should == true
41
+ v.valid?(bad).should == false
42
+
43
+ expect {
44
+ v.required(:baz) { type :invalid }
45
+ }.to raise_exception(ArgumentError)
46
+ end
47
+
48
+ specify 'mixed type arrays are allowed' do
49
+ v = HashValidator.new
50
+ v.required(:foo) { type Array }
51
+
52
+ v.valid?({
53
+ foo: [1, 2, 'three']
54
+ }).should == true
55
+ end
56
+ end
57
+
58
+ context 'arrays of allowed values may be specified' do
59
+ specify 'for strings and fixnums' do
60
+ v = HashValidator.new
61
+ v.required :foo do
62
+ type Fixnum
63
+ allowed_values [1, 2, 3]
64
+ end
65
+
66
+ v.required :bar do
67
+ allowed_values %w{one two three}
68
+ end
69
+
70
+ ok = {foo: 2, bar: 'two'}
71
+ bad = {foo: 4, bar: 'four'}
72
+
73
+ v.valid?(ok).should == true
74
+ v.valid?(bad).should == false
75
+ end
76
+
77
+ specify 'for arrays' do
78
+ v = HashValidator.new
79
+ v.required :foo do
80
+ type Array
81
+ allowed_values [1, 2, 3]
82
+ end
83
+
84
+ v.optional :bar do
85
+ type Array
86
+ allowed_values [1, 2, 'three']
87
+ end
88
+
89
+ v.valid?({foo: [2, 3, 4]}).should == false
90
+ v.valid?({foo: [2, 3]}).should == true
91
+ v.valid?({foo: [2], bar: [1, 'three']}).should == true
92
+ v.valid?({foo: [2], bar: [4]}).should == false
93
+
94
+ v = HashValidator.new do
95
+ required :ary do
96
+ type Array
97
+ allowed_values [3, 5, 7]
98
+ end
99
+ end
100
+
101
+ v.valid?({
102
+ ary: [2, 4]
103
+ }).should == false
104
+ end
105
+ end
106
+
107
+ context 'arrays of forbidden values may be specified' do
108
+ specify 'for strings and fixnums' do
109
+ v = HashValidator.new
110
+ v.required :foo do
111
+ type Fixnum
112
+ forbidden_values [1, 2, 3]
113
+ end
114
+
115
+ v.required :bar do
116
+ forbidden_values %w{one two three}
117
+ end
118
+
119
+ bad = {foo: 2, bar: 'two'}
120
+ ok = {foo: 4, bar: 'four'}
121
+
122
+ v.valid?(ok).should == true
123
+ v.valid?(bad).should == false
124
+ end
125
+
126
+ specify 'for arrays' do
127
+ v = HashValidator.new
128
+ v.required :foo do
129
+ type Array
130
+ forbidden_values [1, 2, 3]
131
+ end
132
+
133
+ v.valid?({foo: [2, 3, 4]}).should == false
134
+ v.valid?({foo: [2, 3]}).should == false
135
+ v.valid?({foo: [0, 4]}).should == true
136
+ end
137
+ end
138
+
139
+ context 'nested hash validation' do
140
+ specify 'works as expected' do
141
+ v = HashValidator.new
142
+ v.required(:foo) do
143
+ type Hash
144
+ required(:bar) { type Fixnum }
145
+ required :baz
146
+ end
147
+
148
+ v.valid?({
149
+ foo: {
150
+ bar: 4,
151
+ baz: 'omg'
152
+ }
153
+ }).should == true
154
+
155
+ v.valid?({
156
+ foo: {
157
+ bar: 5,
158
+ baz: 7
159
+ }
160
+ }).should == false
161
+ end
162
+
163
+ specify 'supports multiple levels of nesting' do
164
+ v = HashValidator.new
165
+
166
+ v.required(:hsh1) do
167
+ type Hash
168
+ required(:hsh2) do
169
+ type Hash
170
+ required(:hsh3) do
171
+ type Hash
172
+ required :foo
173
+ end
174
+ end
175
+ end
176
+
177
+ v.valid?({
178
+ hsh1: {
179
+ hsh2: {
180
+ hsh3: {
181
+ foo: 'hello'
182
+ }
183
+ }
184
+ }
185
+ }).should == true
186
+ end
187
+
188
+ specify 'supports multiple levels of nesting among other keys' do
189
+ v = HashValidator.new
190
+
191
+ v.required(:hsh1) do
192
+ type Hash
193
+ required :str
194
+ required(:int) { type Fixnum }
195
+
196
+ required(:hsh2) do
197
+ type Hash
198
+ required(:ary) do
199
+ type Array
200
+ allowed_values [1, 2, 3, 'four']
201
+ end
202
+ end
203
+ end
204
+
205
+ v.valid?({
206
+ hsh1: {
207
+ str: 'hello',
208
+ int: 8,
209
+ hsh2: {
210
+ ary: [1, 'fo']
211
+ }
212
+ }
213
+ }).should == false
214
+
215
+ v.valid?({
216
+ hsh1: {
217
+ str: 'hello',
218
+ int: 5,
219
+ hsh2: {
220
+ ary: [1, 'four']
221
+ }
222
+ }
223
+ }).should == true
224
+ end
225
+ end
226
+
227
+ specify '.new instance_execs a block, if given' do
228
+ v = HashValidator.new do
229
+ required :hsh1 do
230
+ type Hash
231
+ required :str
232
+ end
233
+
234
+ required(:ary) { type Array }
235
+ end
236
+
237
+ v.valid?({
238
+ hsh1: {
239
+ str: 'hello'
240
+ },
241
+ ary: [1, 2, 3]
242
+ }).should == true
243
+ end
244
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dzl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta4
4
+ version: 1.0.0.rc0
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-03-26 00:00:00.000000000 Z
13
+ date: 2012-03-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
17
- requirement: &70279829324860 !ruby/object:Gem::Requirement
17
+ requirement: &70190801357240 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 1.4.1
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70279829324860
25
+ version_requirements: *70190801357240
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: activesupport
28
- requirement: &70279829322760 !ruby/object:Gem::Requirement
28
+ requirement: &70190801356740 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ~>
@@ -33,7 +33,7 @@ dependencies:
33
33
  version: 3.2.2
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *70279829322760
36
+ version_requirements: *70190801356740
37
37
  description: Small, fast racktivesupport web framework with handy DSL and explicit
38
38
  parameter validation.
39
39
  email:
@@ -52,6 +52,7 @@ files:
52
52
  - config.ru
53
53
  - dzl.gemspec
54
54
  - lib/dzl.rb
55
+ - lib/dzl/core_ext.rb
55
56
  - lib/dzl/doc.rb
56
57
  - lib/dzl/doc/endpoint_doc.rb
57
58
  - lib/dzl/doc/router_doc.rb
@@ -78,6 +79,7 @@ files:
78
79
  - lib/dzl/errors.rb
79
80
  - lib/dzl/examples/base.rb
80
81
  - lib/dzl/examples/fun_with_handlers.rb
82
+ - lib/dzl/examples/fun_with_hashes.rb
81
83
  - lib/dzl/examples/fun_with_hooks.rb
82
84
  - lib/dzl/examples/fun_with_params.rb
83
85
  - lib/dzl/examples/fun_with_requests.rb
@@ -95,13 +97,17 @@ files:
95
97
  - lib/dzl/validators/value.rb
96
98
  - lib/dzl/value_or_error.rb
97
99
  - lib/dzl/version.rb
100
+ - lib/hash_validator.rb
101
+ - lib/hash_validator/hash_validator.rb
98
102
  - spec/dsl_subject_spec.rb
99
103
  - spec/endpoint_doc_spec.rb
100
104
  - spec/fun_with_handlers_spec.rb
105
+ - spec/fun_with_hashes_spec.rb
101
106
  - spec/fun_with_hooks_spec.rb
102
107
  - spec/fun_with_params_spec.rb
103
108
  - spec/fun_with_requests_spec.rb
104
109
  - spec/fun_with_scopes_spec.rb
110
+ - spec/hash_validator_spec.rb
105
111
  - spec/logger_spec.rb
106
112
  - spec/route_params_spec.rb
107
113
  - spec/router_doc_spec.rb
@@ -136,10 +142,12 @@ test_files:
136
142
  - spec/dsl_subject_spec.rb
137
143
  - spec/endpoint_doc_spec.rb
138
144
  - spec/fun_with_handlers_spec.rb
145
+ - spec/fun_with_hashes_spec.rb
139
146
  - spec/fun_with_hooks_spec.rb
140
147
  - spec/fun_with_params_spec.rb
141
148
  - spec/fun_with_requests_spec.rb
142
149
  - spec/fun_with_scopes_spec.rb
150
+ - spec/hash_validator_spec.rb
143
151
  - spec/logger_spec.rb
144
152
  - spec/route_params_spec.rb
145
153
  - spec/router_doc_spec.rb