resto 0.0.3 → 0.0.4

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.
Files changed (47) hide show
  1. data/lib/blankslate.rb +110 -0
  2. data/lib/resto.rb +264 -0
  3. data/lib/resto/attributes.rb +56 -0
  4. data/lib/resto/extra/copy.rb +34 -0
  5. data/lib/resto/extra/delegation.rb +26 -0
  6. data/lib/resto/extra/hash_args.rb +56 -0
  7. data/lib/resto/format.rb +20 -0
  8. data/lib/resto/format/default.rb +11 -0
  9. data/lib/resto/format/json.rb +30 -0
  10. data/lib/resto/format/plain.rb +14 -0
  11. data/lib/resto/format/xml.rb +66 -0
  12. data/lib/resto/property.rb +50 -0
  13. data/lib/resto/property/handler.rb +57 -0
  14. data/lib/resto/property/integer.rb +29 -0
  15. data/lib/resto/property/string.rb +19 -0
  16. data/lib/resto/property/time.rb +43 -0
  17. data/lib/resto/request/base.rb +88 -0
  18. data/lib/resto/request/factory.rb +66 -0
  19. data/lib/resto/request/header.rb +58 -0
  20. data/lib/resto/request/option.rb +126 -0
  21. data/lib/resto/request/uri.rb +50 -0
  22. data/lib/resto/response/base.rb +85 -0
  23. data/lib/resto/translator/request_factory.rb +44 -0
  24. data/lib/resto/translator/response_factory.rb +28 -0
  25. data/lib/resto/validate.rb +37 -0
  26. data/lib/resto/validate/inclusion.rb +39 -0
  27. data/lib/resto/validate/length.rb +36 -0
  28. data/lib/resto/validate/presence.rb +24 -0
  29. data/lib/resto/version.rb +5 -0
  30. data/resto.gemspec +43 -0
  31. data/spec/resto/extra/copy_spec.rb +58 -0
  32. data/spec/resto/extra/hash_args_spec.rb +71 -0
  33. data/spec/resto/format/default_spec.rb +24 -0
  34. data/spec/resto/format/json_spec.rb +29 -0
  35. data/spec/resto/format/plain_spec.rb +21 -0
  36. data/spec/resto/format/xml_spec.rb +105 -0
  37. data/spec/resto/property/handler_spec.rb +57 -0
  38. data/spec/resto/property/integer_spec.rb +67 -0
  39. data/spec/resto/property/time_spec.rb +124 -0
  40. data/spec/resto/property_spec.rb +60 -0
  41. data/spec/resto/request/base_spec.rb +253 -0
  42. data/spec/resto/request/factory_spec.rb +114 -0
  43. data/spec/resto/translator/response_factory_spec.rb +93 -0
  44. data/spec/resto/validate/presence_spec.rb +102 -0
  45. data/spec/resto_spec.rb +531 -0
  46. data/spec/spec_helper.rb +119 -0
  47. metadata +48 -3
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ module Resto
4
+ module Validate
5
+
6
+ autoload :Inclusion, 'resto/validate/inclusion'
7
+ autoload :Length, 'resto/validate/length'
8
+ autoload :Presence, 'resto/validate/presence'
9
+
10
+ def initialize
11
+ @if = lambda { |resource| true }
12
+ @unless = lambda { |resource| false }
13
+ end
14
+
15
+ def validate?(resource)
16
+ (@if.call(resource) && !@unless.call(resource))
17
+ end
18
+
19
+ def message(error_message)
20
+ tap { @message = error_message }
21
+ end
22
+
23
+ def if(&block)
24
+ tap { @if = block }
25
+ end
26
+
27
+ def unless(&block)
28
+ tap { @unless = block }
29
+ end
30
+
31
+ # .on - Specifies when this validation is active (default is :save, other
32
+ # options :create, :update).
33
+ # def on(args)
34
+ # tap { }
35
+ # end
36
+ end
37
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ module Resto
4
+ module Validate
5
+ class Inclusion
6
+ include Validate
7
+
8
+ def initialize
9
+ @allow_nil = false
10
+ @allow_blank = false
11
+ end
12
+
13
+ def in(range)
14
+ tap { @range = range }
15
+ end
16
+
17
+ def allow_nil
18
+ tap { @allow_nil = true }
19
+ end
20
+
21
+ def allow_blank
22
+ tap { @allow_blank = true }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ #
28
+ # .in - An enumerable object of available items. %w( m f ), 0..99, %w( jpg gif png )
29
+ # .message - Specifies a custom error message (default is: “is not included in the list”).
30
+ # .allow_nil - If set to true, skips this validation if the attribute is nil (default is false).
31
+ # .allow_blank - If set to true, skips this validation if the attribute is blank (default is false).
32
+ # .if - Specifies a method, proc or string to call to determine if the validation should occur
33
+ # (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The method,
34
+ # proc or string should return or evaluate to a true or false value.
35
+ # .unless - Specifies a method, proc or string to call to determine if the validation should not occur
36
+ # (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }).
37
+ # The method, proc or string should return or evaluate to a true or false value.
38
+
39
+
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ module Resto
4
+ module Validate
5
+ class Length
6
+ include Validate
7
+
8
+ def is(number)
9
+ tap { @number = number }
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ # .is - The exact size of the attribute.
16
+ # .within - A range specifying the minimum and maximum size of the attribute.
17
+ # .allow_nil - Attribute may be nil; skip validation.
18
+ # .allow_blank - Attribute may be blank; skip validation.
19
+ # .if - Specifies a method, proc or string to call to determine if the
20
+ # validation should occur (e.g. :if => :allow_validation, or :if =>
21
+ # Proc.new { |user| user.signup_step > 2 }). The method, proc or string should
22
+ # return or evaluate to a true or false value.
23
+ # .unless - oposite of .if
24
+ #
25
+ # messages
26
+ # :too_long - The error message if the attribute goes over the maximum
27
+ # (default is: “is too long (maximum is %count characters)”).
28
+ # :too_short - The error message if the attribute goes under the minimum
29
+ # (default is: “is too short (min is %count characters)”).
30
+ # :wrong_length - The error message if using the :is method and the attribute
31
+ # is the wrong size (default is: “is the wrong length
32
+ # (should be %count characters)”).
33
+ # :message - The error message to use for a :minimum, :maximum, or
34
+ # :is violation. An alias of the appropriate too_long/too_short/wrong_length
35
+ # message.
36
+
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ module Resto
4
+ module Validate
5
+ class Presence
6
+ include Validate
7
+
8
+ def attribute_value(resource, attribute_method)
9
+
10
+ error_key = (attribute_method.to_s + "_presence").to_sym
11
+ value_before_cast = resource.send("#{attribute_method}_without_cast")
12
+
13
+ error =
14
+ if (validate?(resource) && value_before_cast.to_s.strip.empty?)
15
+ ":#{attribute_method} #{@message || "can’t be blank"}"
16
+ else
17
+ nil
18
+ end
19
+
20
+ resource.add_error(error_key, error)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module Resto
4
+ VERSION = "0.0.4"
5
+ end
data/resto.gemspec ADDED
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ require File.expand_path("../lib/resto/version", __FILE__)
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "resto"
8
+ s.version = Resto::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Anders Törnqvist"]
11
+ s.email = ["anders@elabs.se"]
12
+ s.homepage = "http://rubygems.org/gems/resto"
13
+ s.summary = "Restful Web Service"
14
+ s.description = "Restful Web Service"
15
+
16
+ s.required_ruby_version = ::Gem::Requirement.new(">= 1.8.7")
17
+ s.required_rubygems_version = ">= 1.3.6"
18
+ s.rubyforge_project = "resto"
19
+
20
+ s.add_runtime_dependency "yajl-ruby", "0.8.2"
21
+ s.add_runtime_dependency "nokogiri", ">=1.4.4"
22
+ # s.add_dependency "activesupport", "3.0.0" ???
23
+ s.add_development_dependency "bundler", ">= 1.0.11"
24
+ s.add_development_dependency "rspec", ">= 2.5.0"
25
+ s.add_development_dependency "fuubar", ">= 0.0.4"
26
+ s.add_development_dependency "webmock", ">= 1.6.2"
27
+ s.add_development_dependency "code-cleaner", ">= 0.8.2"
28
+ s.add_development_dependency "reek"
29
+ s.add_development_dependency "metrical"
30
+ s.add_development_dependency "simplecov"
31
+ s.add_development_dependency "ephemeral_response"
32
+
33
+ # CLI testing
34
+ # http://github.com/radar/guides/blob/master/gem-development.md
35
+ # s.add_dependency "thor"
36
+ # s.add_development_dependency "cucumber"
37
+ # s.add_development_dependency "aruba"
38
+
39
+ s.files = Dir.glob("{lib,spec}/**/*") + %w(resto.gemspec)
40
+ # s.files = `git ls-files`.split("\n")
41
+ #s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
42
+ s.require_path = 'lib'
43
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'resto/extra/copy'
5
+ describe Resto::Extra::Copy do
6
+ describe ".request_base" do
7
+ before do
8
+ @request_base = Resto::Request::Base.new.port(40)
9
+ .url('http://www.aftonbladet.se:92/customers')
10
+ .query('q=adam')
11
+ .path('contacts')
12
+
13
+ @new_request_base = Resto::Extra::Copy.request_base(@request_base)
14
+ .url('http://new.se:99/other')
15
+ .query('q=not-same')
16
+ .path('other-contacts/')
17
+ .headers({ "accept"=> "other", "user-agent"=> "Ruby" })
18
+ .append_path(2)
19
+ end
20
+
21
+ it { @new_request_base.object_id.should_not == @request_base.object_id }
22
+ it { @request_base.read_port.should == 40 }
23
+ it { @request_base.composed_path.should == '/contacts?q=adam' }
24
+
25
+ it do
26
+ @request_base.composed_headers.should == { "accept"=> "*/*",
27
+ "user-agent"=> "Ruby" }
28
+ end
29
+
30
+ it do
31
+ @request_base.composed_headers.object_id.should_not ==
32
+ @new_request_base.composed_headers.object_id
33
+ end
34
+
35
+ it { @new_request_base.read_port.should == 40 }
36
+
37
+ it do
38
+ @new_request_base.composed_headers.should == { "accept"=> "other",
39
+ "user-agent"=> "Ruby" }
40
+ end
41
+
42
+ it do
43
+ @new_request_base.composed_path.should == "/other-contacts/2?q=not-same"
44
+ end
45
+ end
46
+
47
+ describe ".response_base" do
48
+ before do
49
+ @response_base = Resto::Response::Base.new.format(:json)
50
+ @new_response_base = Resto::Extra::Copy.response_base(@response_base)
51
+ .http_response('response')
52
+ end
53
+
54
+ it { @response_base.instance_eval { @response }.should == nil }
55
+ it { @new_response_base.object_id.should_not == @response_base.object_id }
56
+ it { @new_response_base.instance_eval { @response }.should == 'response' }
57
+ end
58
+ end
@@ -0,0 +1,71 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'resto/extra/hash_args'
5
+
6
+ describe Resto::Extra::HashArgs do
7
+
8
+ class_context(%Q{
9
+ class BasicAuthication < Resto::Extra::HashArgs
10
+ key :username
11
+ key :password
12
+ end}) do
13
+
14
+ it "returns the value from the block when no value is found by key" do
15
+ BasicAuthication.new(nil).fetch(:username) { 'anders' }.should == 'anders'
16
+ end
17
+
18
+ it "returns the value found by the key" do
19
+ BasicAuthication.new({'username' => 'anders', :password => 'secret'})
20
+ .fetch(:password) { 'other' }.should == 'secret'
21
+ end
22
+
23
+ it "the key is translated to its symbol" do
24
+ BasicAuthication.new({'username' => 'anders', :password => 'secret'})
25
+ .fetch(:username) { 'other' }.should == 'anders'
26
+ end
27
+ end
28
+
29
+ class_context(%Q{
30
+ class FormatExt < Resto::Extra::HashArgs
31
+ key :extension
32
+ end}) do
33
+
34
+ it "returns the value from the block" do
35
+ FormatExt.new({}).fetch(:extension) { 'block' }.should == 'block'
36
+ end
37
+
38
+ if RUBY_VERSION < '1.9'
39
+
40
+ it "raises IndexError when no value and no block" do
41
+ expect { FormatExt.new({}).fetch(:extension) }
42
+ .to raise_error(IndexError, 'key not found')
43
+ end
44
+
45
+ else
46
+
47
+ it "raises KeyError when no value and no block" do
48
+ lambda { FormatExt.new({}).fetch(:extension) }
49
+ .should raise_error(KeyError, 'key not found: :extension')
50
+ end
51
+
52
+ end
53
+
54
+ it "raises" do
55
+ expect { FormatExt.new({:username => "anders"}) }
56
+ .to raise_error(ArgumentError, /The key 'username'/)
57
+
58
+ expect { FormatExt.new("string") }
59
+ .to raise_error(ArgumentError, "'string' must be a Hash")
60
+
61
+ expect { FormatExt.new(:extension => 'value', 'extension' => 'value') }
62
+ .to raise_error(ArgumentError, "duplicated keys: extension, extension")
63
+
64
+ expect { FormatExt.new({:invalid_key => 'invalid' }) }
65
+ .to raise_error(ArgumentError, /The key 'invalid_key' is not valid/)
66
+
67
+ expect { FormatExt.new({:extension => 'value' }).fetch(:invalid_key) }
68
+ .to raise_error(ArgumentError, /The key 'invalid_key' is not valid/)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'resto/format'
5
+
6
+ describe "Resto::Format.get" do
7
+ subject { Resto::Format.get }
8
+
9
+ its(:extension) { should == nil }
10
+ its(:accept) { should == '*/*' }
11
+ its(:content_type) { should == nil }
12
+ describe ".encode(text)" do
13
+ it("returns text") do
14
+ subject.encode('very important').should == 'very important'
15
+ end
16
+ end
17
+
18
+ describe ".decode(text)" do
19
+ it("returns text") do
20
+ subject.encode('somehting important').should == 'somehting important'
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'resto/format'
5
+ require 'yajl'
6
+
7
+ describe "Resto::Format.get(:json)" do
8
+ subject { Resto::Format.get(:json) }
9
+
10
+ its(:extension) { should == 'json' }
11
+ its(:accept) { should == 'application/json, */*' }
12
+ its(:content_type) { should == 'application/json' }
13
+
14
+ describe ".encode(hash)" do
15
+ before { @result = subject.encode({ :bar => "some string",
16
+ :foo => 12425125}) }
17
+
18
+ it { @result.should =~ /bar\":\"some string/ }
19
+ it { @result.should =~ /foo\":12425125/ }
20
+ end
21
+
22
+ describe '.decode(json)' do
23
+ json = Yajl::Encoder.encode( { :foo => 12425125, :bar => "some string" })
24
+ expected = { 'foo' => 12425125, 'bar' => "some string" }
25
+
26
+ it { subject.decode(json).should == expected }
27
+ end
28
+
29
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'resto/format'
5
+
6
+ describe "Resto::Format.get(:plain)" do
7
+ subject { Resto::Format.get(:plain) }
8
+
9
+ its(:extension) { should == nil }
10
+ its(:accept) { should == 'text/plain, */*' }
11
+ its(:content_type) { should == 'text/plain' }
12
+
13
+ context ".encode(text)" do
14
+ it("returns text") { subject.encode('important').should == 'important' }
15
+ end
16
+
17
+ context ".decode(text)" do
18
+ it("returns text") { subject.encode('important').should == 'important' }
19
+ end
20
+
21
+ end
@@ -0,0 +1,105 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'resto/format'
5
+
6
+ describe "Resto::Format.get(:xml)" do
7
+ subject { Resto::Format.get(:xml) }
8
+
9
+ its(:extension) { should == 'xml' }
10
+ its(:accept) { should == 'application/xml, */*' }
11
+ its(:content_type) { should == 'application/xml;charset=utf-8' }
12
+
13
+ let(:hash) do
14
+ {:zone =>
15
+ { 'default-ttl' => 600,
16
+ 'domain' => "example.com",
17
+ 'ns1' => nil,
18
+ 'ns-type' => "pri_sec",
19
+ 'nx-ttl' => 900,
20
+ 'slave-nameservers' => nil
21
+ }
22
+ }
23
+ end
24
+
25
+ describe ".encode(hash)" do
26
+ before { @result = subject.encode(hash) }
27
+
28
+ it { @result.should =~ /<\?xml version=\"1.0\"\?>/ }
29
+ it { @result.should =~ /<zone>.+<\/zone>\Z/m }
30
+ it { @result.should =~ /<default-ttl>600<\/default-ttl>/ }
31
+ it { @result.should =~ /<ns1\/>/ }
32
+ it { @result.should =~ /<slave-nameservers\/>/ }
33
+ end
34
+
35
+ let(:xml) do
36
+ '<?xml version="1.0"?><zone>
37
+ <default-ttl>600</default-ttl>
38
+ <domain>example.com</domain>
39
+ <ns1/>
40
+ <ns-type>pri_sec</ns-type>
41
+ <nx-ttl>900</nx-ttl>
42
+ <slave-nameservers/>
43
+ </zone>'
44
+ end
45
+
46
+ let(:xml_collection) do
47
+ '<?xml version="1.0"?>
48
+ <zones>
49
+ <zone>
50
+ <default-ttl>600</default-ttl>
51
+ <domain>example.com</domain>
52
+ <ns1/>
53
+ <ns-type>pri_sec</ns-type>
54
+ <nx-ttl>900</nx-ttl>
55
+ <slave-nameservers/>
56
+ </zone>
57
+ <zone>
58
+ <default-ttl>700</default-ttl>
59
+ <domain>example.com</domain>
60
+ <ns1/>
61
+ <ns-type>pri_sec</ns-type>
62
+ <nx-ttl>800</nx-ttl>
63
+ <slave-nameservers/>
64
+ </zone>
65
+ </zones>'
66
+ end
67
+
68
+ let(:attributes) do
69
+ { 'default-ttl' => "600",
70
+ 'domain' => "example.com",
71
+ 'ns1' => "",
72
+ 'ns-type' => "pri_sec",
73
+ 'nx-ttl' => "900",
74
+ 'slave-nameservers' => ""
75
+ }
76
+ end
77
+
78
+ let(:second_attributes) do
79
+ { 'default-ttl' => "700",
80
+ 'domain' => "example.com",
81
+ 'ns1' => "",
82
+ 'ns-type' => "pri_sec",
83
+ 'nx-ttl' => "800",
84
+ 'slave-nameservers' => ""
85
+ }
86
+ end
87
+
88
+ describe '.decode(xml, xpath)' do
89
+ context 'when one item' do
90
+ it { subject.decode(xml, :xpath => '//zone').should == attributes }
91
+ end
92
+
93
+ context 'when 0 items' do
94
+ it { subject.decode(xml, :xpath => '//xx0').should == {} }
95
+ end
96
+
97
+ context 'when a collection of two items' do
98
+ it do
99
+ subject.decode(xml_collection, :xpath => '//zone').should ==
100
+ [attributes, second_attributes]
101
+ end
102
+ end
103
+ end
104
+
105
+ end