dzl 1.0.0.rc9 → 1.0.0.rc10

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
@@ -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: