dzl 1.0.0.beta4 → 1.0.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
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