dzl 1.0.0.rc9 → 1.0.0.rc10

Sign up to get free protection for your applications and to get access to all the features.
data/config.ru CHANGED
@@ -30,10 +30,10 @@ end
30
30
  # run Dzl::Examples::RouteProfile
31
31
  # end
32
32
 
33
- require 'dzl/examples/fun_with_handlers'
34
- map '/' do
35
- run Dzl::Examples::FunWithHandlers
36
- end
33
+ # require 'dzl/examples/fun_with_handlers'
34
+ # map '/' do
35
+ # run Dzl::Examples::FunWithHandlers
36
+ # end
37
37
 
38
38
  # require 'dzl/examples/fun_with_hooks'
39
39
  # map '/' do
@@ -45,10 +45,10 @@ end
45
45
  # run Dzl::Examples::FunWithScopes
46
46
  # end
47
47
 
48
- # require 'dzl/examples/fun_with_hashes'
49
- # map '/' do
50
- # run Dzl::Examples::FunWithHashes
51
- # end
48
+ require 'dzl/examples/fun_with_hashes'
49
+ map '/' do
50
+ run Dzl::Examples::FunWithHashes
51
+ end
52
52
 
53
53
  # require 'dzl/examples/fun_with_pblocks'
54
54
  # map '/' do
data/lib/dzl/core_ext.rb CHANGED
@@ -6,10 +6,20 @@ class Hash
6
6
  def recursively_symbolize_keys!
7
7
  self.symbolize_keys!
8
8
  self.values.each do |v|
9
- if v.is_a?(Hash)
9
+ if v.is_a?(Hash) || v.is_a?(Array)
10
10
  v.recursively_symbolize_keys!
11
11
  end
12
12
  end
13
13
  self
14
14
  end
15
- end
15
+ end
16
+
17
+ class Array
18
+ def recursively_symbolize_keys!
19
+ self.each do |item|
20
+ if item.is_a?(Hash) || item.is_a?(Array)
21
+ item.recursively_symbolize_keys!
22
+ end
23
+ end
24
+ end
25
+ end
@@ -48,10 +48,6 @@ class Dzl::DSLProxies::Parameter < Dzl::DSLProxy
48
48
  end
49
49
 
50
50
  if type == Hash
51
- unless @subject.router.subject(2).is_a?(Dzl::DSLSubjects::Endpoint)
52
- raise Dzl::NYI.new("Hash parameters may only be specified in endpoints, for now")
53
- end
54
-
55
51
  raise Dzl::RetryBlockPlease.new(
56
52
  subject: HashValidator.new(
57
53
  @subject.opts.except(:type_opts).reverse_merge(format: :json)
@@ -3,4 +3,8 @@ class Dzl::DSLProxies::Protection < Dzl::DSLProxy
3
3
  raise ArgumentError unless [:username, :password].all? {|k| opts[k].present?}
4
4
  @subject.opts[:http_basic] = opts
5
5
  end
6
- end
6
+ def api_key(opts)
7
+ raise ArgumentError unless [:header, :valid_keys].all? {|k| opts[k].present?}
8
+ @subject.opts[:api_key] = opts
9
+ end
10
+ end
@@ -21,6 +21,9 @@ class Dzl::DSLSubjects::ParameterBlock < Dzl::DSLSubject
21
21
  if param.opts[:type] == Hash
22
22
  param = if parandidate.nil? && param.opts[:required]
23
23
  Dzl::ValueOrError.new(e: param.opts[:header] ? :missing_required_header : :missing_required_param)
24
+ elsif parandidate.nil?
25
+ # TODO HashValidator with default values
26
+ Dzl::ValueOrError.new(v: :__no_value__)
24
27
  else
25
28
  unless parandidate.is_a?(Hash)
26
29
  if param.opts[:format] == :json
@@ -1,6 +1,7 @@
1
1
  require 'dzl/dsl_proxies/protection'
2
2
 
3
3
  class Dzl::RespondWithHTTPBasicChallenge < StandardError; end
4
+ class Dzl::RespondWithInvalidAPIKey < StandardError; end
4
5
 
5
6
  class Dzl::DSLSubjects::Protection < Dzl::DSLSubject
6
7
  def initialize
@@ -15,17 +16,34 @@ class Dzl::DSLSubjects::Protection < Dzl::DSLSubject
15
16
  if @opts[:http_basic].present?
16
17
  @auth = Rack::Auth::Basic::Request.new(request.env)
17
18
  if @auth.provided? && @auth.basic? && @auth.credentials
19
+ # Invalid basic auth credentials
18
20
  unless @auth.credentials[0] == @opts[:http_basic][:username] &&
19
21
  @auth.credentials[1] == @opts[:http_basic][:password]
20
- Dzl::ValueOrError.new(e: :invalid_http_basic_credentials)
21
- else
22
- Dzl::ValueOrError.new(v: nil)
22
+ return Dzl::ValueOrError.new(e: :invalid_http_basic_credentials)
23
23
  end
24
+ # No basic auth credentials provided
24
25
  else
25
- Dzl::ValueOrError.new(e: :no_http_basic_credentials)
26
+ return Dzl::ValueOrError.new(e: :no_http_basic_credentials)
26
27
  end
27
- else
28
- Dzl::ValueOrError.new(v: nil)
29
28
  end
29
+
30
+ if @opts[:api_key].present?
31
+ api_key_header = @opts[:api_key][:header]
32
+ allowed_keys = @opts[:api_key][:valid_keys]
33
+ request_key = request.headers[api_key_header]
34
+
35
+ if request_key
36
+ # Invalid API key provided
37
+ unless allowed_keys.include? request_key
38
+ return Dzl::ValueOrError.new(e: :invalid_api_key)
39
+ end
40
+ # No API key provided
41
+ else
42
+ return Dzl::ValueOrError.new(e: :no_api_key)
43
+ end
44
+ end
45
+
46
+ # Auth passed
47
+ return Dzl::ValueOrError.new(v: nil)
30
48
  end
31
- end
49
+ end
@@ -99,7 +99,12 @@ class Dzl::DSLSubjects::Router < Dzl::DSLSubject
99
99
  raise Dzl::RespondWithHTTPBasicChallenge
100
100
  end
101
101
 
102
+ if !errors.empty? &&
103
+ errors.values.any? {|v| v == :no_api_key || v == :invalid_api_key}
104
+ raise Dzl::RespondWithInvalidAPIKey
105
+ end
106
+
102
107
  endpoint || raise(Dzl::NotFound.new(errors))
103
108
  end
104
109
 
105
- end
110
+ end
@@ -28,4 +28,33 @@ class Dzl::Examples::FunWithHashes < Dzl::Examples::Base
28
28
  allowed_values %w{one two three}
29
29
  end
30
30
  end
31
+
32
+ pblock :ingredients do
33
+ required :ingredients do
34
+ type Hash
35
+ required :cheese, :meat, :bread
36
+ end
37
+ end
38
+
39
+ post '/boring_sandwich' do
40
+ import_pblock :ingredients
41
+ end
42
+
43
+ post '/awesome_sandwich' do
44
+ import_pblock :ingredients
45
+ parameter :ingredients do
46
+ required(:meat) { type Array }
47
+ end
48
+ end
49
+
50
+ post '/another_boring_sandwich' do
51
+ import_pblock :ingredients
52
+ end
53
+
54
+ post '/elis_bug' do
55
+ optional :hash do
56
+ type Hash
57
+ optional(:id) { type Fixnum }
58
+ end
59
+ end
31
60
  end
@@ -48,6 +48,12 @@ class Dzl::Examples::FunWithParams < Dzl::Examples::Base
48
48
  end
49
49
  end
50
50
 
51
+ endpoint '/api' do
52
+ protect do
53
+ api_key header: 'x_api_key', valid_keys: ['valid-key']
54
+ end
55
+ end
56
+
51
57
  endpoint '/arithmetic' do
52
58
  optional :int do
53
59
  type Fixnum
@@ -15,6 +15,8 @@ module Dzl::RackInterface
15
15
  [__router.handle_request(request), nil]
16
16
  rescue Dzl::RespondWithHTTPBasicChallenge
17
17
  [respond_with_http_basic_challenge, nil]
18
+ rescue Dzl::RespondWithInvalidAPIKey
19
+ [respond_with_invalid_api_key, nil]
18
20
  rescue Dzl::Error => e
19
21
  [respond_with_dzl_error_handler(e), nil]
20
22
  rescue StandardError => e
@@ -51,6 +53,14 @@ module Dzl::RackInterface
51
53
  response.finish
52
54
  end
53
55
 
56
+ def respond_with_invalid_api_key
57
+ response = Rack::Response.new
58
+ response.status = 401
59
+ response.headers['Content-Type'] = 'text/html'
60
+ response.write("Not Authorized\n")
61
+ response.finish
62
+ end
63
+
54
64
  def respond_with_standard_error_handler(e)
55
65
  response = Rack::Response.new
56
66
  response.headers['Content-Type'] = 'application/json'
@@ -108,4 +118,4 @@ module Dzl::RackInterface
108
118
  end
109
119
  end
110
120
  end
111
- end
121
+ end
data/lib/dzl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dzl
2
- VERSION = "1.0.0.rc9"
2
+ VERSION = "1.0.0.rc10"
3
3
  end
@@ -24,6 +24,16 @@ class HashValidator
24
24
  @template[:opts]
25
25
  end
26
26
 
27
+ def overwrite_opts(opts)
28
+ @template[:opts].merge(opts)
29
+ end
30
+
31
+ def clone
32
+ copy = HashValidator.new
33
+ copy.instance_variable_set(:@template, Marshal.load(Marshal.dump(@template)))
34
+ copy
35
+ end
36
+
27
37
  def valid?(hsh)
28
38
  return false unless hsh.keys.all? { |key| top[:keys].include?(key) }
29
39
 
@@ -71,7 +81,6 @@ class HashValidator
71
81
  def key(k, opts, &block)
72
82
  top[:keys][k] = {
73
83
  opts: opts,
74
- block: block,
75
84
  keys: {}
76
85
  }
77
86
 
@@ -29,16 +29,6 @@ describe 'hash parameters' do
29
29
  }.to raise_exception(Dzl::Deprecated)
30
30
  end
31
31
 
32
- specify 'NYI: hash parameters are currently only allowed in anonymous pblocks' do
33
- expect {
34
- class T1 < Dzl::Examples::Base
35
- pblock :broken do
36
- required(:foo) { type Hash }
37
- end
38
- end
39
- }.to raise_error(Dzl::NYI)
40
- end
41
-
42
32
  specify 'hash parameters retry their blocks against a hash validator' do
43
33
  HashValidator.any_instance.should_receive(:key).with(:key, {required: true, type: String})
44
34
  class T1 < Dzl::Examples::Base
@@ -86,6 +76,38 @@ describe 'hash parameters' do
86
76
  end
87
77
  end
88
78
 
79
+ context 'importing & reopening' do
80
+ specify 'works, basically' do
81
+ boring_sandwich = {
82
+ bread: 'multi-grain',
83
+ meat: 'roast beast',
84
+ cheese: 'swiss'
85
+ }
86
+
87
+ awesome_sandwich = {
88
+ bread: 'sourdough',
89
+ meat: ['roast beast', 'salami', 'turkey'],
90
+ cheese: 'cheddar'
91
+ }
92
+
93
+ post('/boring_sandwich', {ingredients: boring_sandwich.to_json}) do |response|
94
+ response.status.should == 200
95
+ end
96
+
97
+ post('/awesome_sandwich', {ingredients: awesome_sandwich.to_json}) do |response|
98
+ response.status.should == 200
99
+ end
100
+
101
+ post('/another_boring_sandwich', {ingredients: awesome_sandwich.to_json}) do |response|
102
+ response.status.should == 404
103
+ end
104
+
105
+ post('/another_boring_sandwich', {ingredients: boring_sandwich.to_json}) do |response|
106
+ response.status.should == 200
107
+ end
108
+ end
109
+ end
110
+
89
111
  context 'validate' do
90
112
  specify 'JSON post bodies' do
91
113
  header 'Content-Type', 'application/json'
@@ -153,4 +175,17 @@ describe 'hash parameters' do
153
175
  get('/mixed', valid).status.should == 200
154
176
  end
155
177
  end
178
+
179
+ context "eli's bug" do
180
+ specify 'omitted optional hashes are OK' do
181
+ post('/elis_bug', {hash: {id: 1}.to_json}) do |response|
182
+ response.status.should == 200
183
+ end
184
+
185
+ post('/elis_bug') do |response|
186
+ JSON.parse(response.body)['errors'].should == nil
187
+ response.status.should == 200
188
+ end
189
+ end
190
+ end
156
191
  end
@@ -172,6 +172,23 @@ describe Dzl::Examples::FunWithParams do
172
172
  end
173
173
  end
174
174
 
175
+ describe '/api' do
176
+ it 'should 401 if no api key provided' do
177
+ get '/api'
178
+ last_response.status.should == 401
179
+ end
180
+
181
+ it 'should 401 if invalid api key provided' do
182
+ get '/api', {}, {"HTTP_X_API_KEY" => 'invalid-key'}
183
+ last_response.status.should == 401
184
+ end
185
+
186
+ it 'should accept valid api key' do
187
+ get '/api', {}, {"HTTP_X_API_KEY" => 'valid-key'}
188
+ last_response.status.should == 200
189
+ end
190
+ end
191
+
175
192
  describe '/arithmetic' do
176
193
  it 'should not allow :int < 5' do
177
194
  get('/arithmetic', {int: 4}) do |response|
@@ -280,4 +280,38 @@ describe HashValidator do
280
280
  v.valid?({foo: 'three'}).should == true
281
281
  end
282
282
  end
283
+
284
+ context 'cloning' do
285
+ before(:each) do
286
+ @original = HashValidator.new do
287
+ required(:foo)
288
+ end
289
+ end
290
+
291
+ specify 'dupes the data in @template' do
292
+ orig_template = @original.instance_variable_get(:@template)
293
+
294
+ cloned = @original.clone
295
+
296
+ @original.instance_variable_get(:@template).object_id.should == orig_template.object_id
297
+ @original.instance_variable_get(:@template).object_id.should_not == cloned.instance_variable_get(:@template).object_id
298
+ @original.instance_variable_get(:@template).should == cloned.instance_variable_get(:@template)
299
+
300
+ cloned.required(:foo) { type Fixnum }
301
+
302
+ @original.instance_variable_get(:@template).should_not == cloned.instance_variable_get(:@template)
303
+ @original.instance_variable_get(:@template)[:keys][:foo][:opts][:type].should == String
304
+ cloned.instance_variable_get(:@template)[:keys][:foo][:opts][:type].should == Fixnum
305
+ end
306
+
307
+ specify 'validation works as expected' do
308
+ cloned = @original.clone
309
+ cloned.required(:foo) { type Fixnum }
310
+
311
+ @original.valid?({foo: 'hello'}).should == true
312
+ @original.valid?({foo: 1}).should == false
313
+ cloned.valid?({foo: 'hello'}).should == false
314
+ cloned.valid?({foo: 1}).should == true
315
+ end
316
+ end
283
317
  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.rc9
4
+ version: 1.0.0.rc10
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -14,7 +14,7 @@ date: 2012-05-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
17
- requirement: &70219390708320 !ruby/object:Gem::Requirement
17
+ requirement: &70114952344160 !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: *70219390708320
25
+ version_requirements: *70114952344160
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: activesupport
28
- requirement: &70219390707820 !ruby/object:Gem::Requirement
28
+ requirement: &70114952343660 !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: *70219390707820
36
+ version_requirements: *70114952343660
37
37
  description: Small, fast racktivesupport web framework with handy DSL and explicit
38
38
  parameter validation.
39
39
  email: