relax 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Tyler Hunt
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,4 @@
1
+ Relax
2
+ =====
3
+
4
+ A simple library for creating REST consumers.
data/lib/relax/api.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'openssl'
2
+ require 'net/https'
3
+ require 'uri'
4
+ require 'date'
5
+ require 'base64'
6
+ require 'erb'
7
+
8
+ module Relax
9
+ class API
10
+ attr_reader :endpoint
11
+
12
+ def initialize(endpoint)
13
+ @endpoint = URI::parse(endpoint)
14
+ end
15
+
16
+ private
17
+
18
+ def default_query
19
+ Query.new
20
+ end
21
+
22
+ def query(request)
23
+ Query.new(default_query.merge(request.to_query))
24
+ end
25
+
26
+ def call(request, response_class)
27
+ uri = @endpoint.clone
28
+ uri.query = query(request).to_s
29
+ response = request(uri)
30
+ puts "Response:\n#{response.body}\n\n" if $DEBUG
31
+ response_class.new(response.body)
32
+ end
33
+
34
+ def request(uri)
35
+ puts "Request:\n#{uri.to_s}\n\n" if $DEBUG
36
+ http = Net::HTTP.new(uri.host, uri.port)
37
+
38
+ if uri.scheme == 'https'
39
+ http.use_ssl = true
40
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
41
+ end
42
+
43
+ http.start do |http|
44
+ request = Net::HTTP::Get.new("#{uri.path}?#{uri.query}")
45
+ http.request(request)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,39 @@
1
+ require 'erb'
2
+ require 'uri'
3
+
4
+ require 'relax/symbolic_hash'
5
+
6
+ module Relax
7
+ class Query < SymbolicHash
8
+ def to_s
9
+ keys.sort { |a, b| a.to_s <=> b.to_s }.collect do |key|
10
+ "#{key.to_s}=#{self.class.escape_value(fetch(key))}"
11
+ end.join('&')
12
+ end
13
+
14
+ class << self
15
+ def parse(uri)
16
+ query = uri.query.split('&').inject({}) do |query, parameter|
17
+ key, value = parameter.split('=')
18
+ query[key] = unescape_value(value)
19
+ query
20
+ end
21
+ self.new(query)
22
+ end
23
+
24
+ def escape_value(value)
25
+ ERB::Util.url_encode(value.to_s).gsub('%20', '+')
26
+ end
27
+
28
+ def unescape_value(value)
29
+ URI.unescape(value)
30
+ end
31
+ end
32
+
33
+ protected
34
+
35
+ def convert_value(value)
36
+ value.to_s
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,49 @@
1
+ require 'relax/query'
2
+
3
+ module Relax
4
+ class Request
5
+ def initialize(options = {})
6
+ self.class.class_variables.each do |variable|
7
+ instance_variable_set(variable.slice(1..-1), self.class.send(:class_variable_get, variable))
8
+ end
9
+
10
+ options.each do |key, value|
11
+ instance_variable_set "@#{key}", value
12
+ end
13
+ end
14
+
15
+ def to_query
16
+ keys.inject(Query.new) do |parameters, key|
17
+ parameters[convert_key(key)] = send(key)
18
+ parameters
19
+ end
20
+ end
21
+
22
+ def to_s
23
+ keys.sort { |a, b| a.to_s <=> b.to_s }.collect do |key|
24
+ "#{key.to_s}=#{ERB::Util.url_encode(send('[]', key).to_s)}"
25
+ end.join('&')
26
+ end
27
+
28
+ class << self
29
+ def parameter(name, options = {})
30
+ attr_accessor name
31
+ class_variable_set("@@#{name}", options.delete(:value)) if options[:value]
32
+ end
33
+
34
+ def []=(key, value)
35
+ parameter(key, {:value => value})
36
+ end
37
+ end
38
+
39
+ protected
40
+
41
+ def keys
42
+ instance_variables.collect { |v| v.sub('@', '') }
43
+ end
44
+
45
+ def convert_key(key)
46
+ key
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,110 @@
1
+ require 'rubygems'
2
+ require 'hpricot'
3
+
4
+ require 'date'
5
+
6
+ module Relax
7
+ class Response
8
+ attr_accessor :xml
9
+
10
+ def initialize(xml)
11
+ @xml = Hpricot.XML(xml.to_s)
12
+
13
+ if parameter = self.class.instance_variable_get('@parameters')
14
+ parameter.each do |parameter, options|
15
+ element = options[:element] ? options[:element] : parameter
16
+
17
+ if attribute = options[:attribute] and attribute == true
18
+ node = attribute(root, element)
19
+ elsif attribute
20
+ node = attribute(element(element), attribute)
21
+ elsif options[:collection]
22
+ node = elements(element)
23
+ else
24
+ node = element(element)
25
+ end
26
+
27
+ if options[:collection]
28
+ value = node.collect do |element|
29
+ options[:collection].new(element)
30
+ end
31
+ else
32
+ case options[:type]
33
+ when :float
34
+ value = float_value(node)
35
+
36
+ when :integer
37
+ value = integer_value(node)
38
+
39
+ when :text
40
+ else
41
+ value = text_value(node)
42
+ end
43
+ end
44
+
45
+ instance_variable_set("@#{parameter}", value)
46
+ end
47
+ end
48
+ end
49
+
50
+ def root
51
+ @xml.root
52
+ end
53
+
54
+ def is?(name)
55
+ root.name.gsub(/.*:(.*)/, '\1') == node_name(name)
56
+ end
57
+
58
+ def element(name)
59
+ root.at(root_path(name))
60
+ end
61
+
62
+ def attribute(element, name)
63
+ element[name]
64
+ end
65
+
66
+ def elements(name)
67
+ root.search(root_path(name))
68
+ end
69
+
70
+ def value(value)
71
+ value.is_a?(Hpricot::Elem) ? value.inner_text : value.to_s
72
+ end
73
+
74
+ def text_value(value)
75
+ value(value)
76
+ end
77
+
78
+ def integer_value(value)
79
+ value(value).to_i
80
+ end
81
+
82
+ def float_value(value)
83
+ value(value).to_f
84
+ end
85
+
86
+ def date_value(value)
87
+ Date.parse(value(value))
88
+ end
89
+
90
+ alias :has? :element
91
+
92
+ class << self
93
+ def parameter(name, options = {})
94
+ attr_accessor name
95
+ @parameters ||= {}
96
+ @parameters[name] = options
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ def node_name(name)
103
+ name.to_s.downcase
104
+ end
105
+
106
+ def root_path(name)
107
+ "/#{node_name(name)}"
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,65 @@
1
+ module Relax
2
+ class SymbolicHash < Hash
3
+ def initialize(constructor = {})
4
+ if constructor.is_a?(Hash)
5
+ super()
6
+ update(constructor)
7
+ else
8
+ super(constructor)
9
+ end
10
+ end
11
+
12
+ def [](key)
13
+ super(convert_key(key))
14
+ end
15
+
16
+ def []=(key, value)
17
+ super(convert_key(key), convert_value(value))
18
+ end
19
+
20
+ def update(other_hash)
21
+ other_hash.each_pair { |key, value| store(convert_key(key), convert_value(value)) }
22
+ self
23
+ end
24
+
25
+ alias_method :merge!, :update
26
+
27
+ def fetch(key, *extras)
28
+ super(convert_key(key), *extras)
29
+ end
30
+
31
+ def values_at(*indices)
32
+ indices.collect { |key| self[convert_key(key)] }
33
+ end
34
+
35
+ def dup
36
+ SymbolicHash.new(self)
37
+ end
38
+
39
+ def merge(hash)
40
+ self.dup.update(hash)
41
+ end
42
+
43
+ def delete(key)
44
+ super(convert_key(key))
45
+ end
46
+
47
+ def key?(key)
48
+ super(convert_key(key))
49
+ end
50
+
51
+ alias_method :include?, :key?
52
+ alias_method :has_key?, :key?
53
+ alias_method :member?, :key?
54
+
55
+ protected
56
+
57
+ def convert_key(key)
58
+ !key.kind_of?(Symbol) ? key.to_sym : key
59
+ end
60
+
61
+ def convert_value(value)
62
+ value
63
+ end
64
+ end
65
+ end
data/lib/relax.rb ADDED
@@ -0,0 +1,7 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ require 'relax/api'
4
+ require 'relax/symbolic_hash'
5
+ require 'relax/query'
6
+ require 'relax/request'
7
+ require 'relax/response'
@@ -0,0 +1,45 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ require 'relax/query'
4
+
5
+ describe 'a query' do
6
+ before(:each) do
7
+ @uri = URI::parse('http://example.com/?action=search&query=keyword')
8
+ @query = Relax::Query.new
9
+ end
10
+
11
+ it 'should convert to a query string' do
12
+ @query[:action] = 'Search'
13
+ @query[:query] = 'strings'
14
+ @query.to_s.should == 'action=Search&query=strings'
15
+ end
16
+
17
+ it 'should convert its values to strings' do
18
+ date = Date.today
19
+ @query[:date] = date
20
+ @query.to_s.should == "date=#{date.to_s}"
21
+ end
22
+
23
+ it 'should escape its values using "+" instead of "%20"' do
24
+ Relax::Query.send(:escape_value, 'two words').should == 'two+words'
25
+ end
26
+
27
+ it 'should sort its parameters' do
28
+ @query[:charlie] = 3
29
+ @query[:alpha] = 1
30
+ @query[:bravo] = 2
31
+ @query.to_s.should == 'alpha=1&bravo=2&charlie=3'
32
+ end
33
+
34
+ it 'should encode its parameter values' do
35
+ @query[:spaces] = 'two words'
36
+ @query[:url] = 'http://example.com/'
37
+ @query.to_s.should == 'spaces=two+words&url=http%3A%2F%2Fexample.com%2F'
38
+ end
39
+
40
+ it 'should be able to parse query strings' do
41
+ parsed_query = Relax::Query.parse(@uri)
42
+ parsed_query[:action].should == 'search'
43
+ parsed_query[:query].should == 'keyword'
44
+ end
45
+ end
@@ -0,0 +1,86 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ require 'relax/request'
4
+
5
+ class TestRequest < Relax::Request
6
+ parameter :action
7
+ parameter :token_id
8
+ parameter :user_id
9
+ end
10
+
11
+ class ChildRequest < TestRequest
12
+ parameter :child_id
13
+ end
14
+
15
+ describe 'an option initialized request', :shared => true do
16
+ it 'should have its values set by the options hash' do
17
+ request = TestRequest.new(:action => 'FetchAll', :token_id => 123)
18
+ request.action.should eql('FetchAll')
19
+ request.token_id.should eql(123)
20
+ request.user_id.should be_nil
21
+ end
22
+ end
23
+
24
+ describe 'a request that converts to a query', :shared => true do
25
+ before(:each) do
26
+ @query = TestRequest.new(:action => 'Search', :token_id => 123).to_query
27
+ end
28
+
29
+ it 'should include its parameters in the query' do
30
+ @query[:action].should eql('Search')
31
+ @query[:token_id].should eql('123')
32
+ @query[:user_id].should be_nil
33
+ end
34
+
35
+ it 'should only include parameters in the query if they are set' do
36
+ @query.key?(:action).should be_true
37
+ @query.key?(:token_id).should be_true
38
+ @query.key?(:user_id).should be_false
39
+ end
40
+ end
41
+
42
+ describe 'a normal request' do
43
+ it_should_behave_like 'a request that converts to a query'
44
+ it_should_behave_like 'an option initialized request'
45
+ end
46
+
47
+ describe 'a template request' do
48
+ it_should_behave_like 'a request that converts to a query'
49
+ it_should_behave_like 'an option initialized request'
50
+
51
+ before(:each) do
52
+ TestRequest[:api_key] = '123456'
53
+ TestRequest[:secret] = 'shhh!'
54
+ end
55
+
56
+ it 'should always have the template values in its query' do
57
+ request = TestRequest.new
58
+ request.api_key.should eql('123456')
59
+ request.secret.should eql('shhh!')
60
+ end
61
+
62
+ it 'should allow its template variables to be overridden' do
63
+ request = TestRequest.new(:secret => 'abracadabra')
64
+ request.api_key.should eql('123456')
65
+ request.secret.should eql('abracadabra')
66
+ end
67
+
68
+ it 'should pass its template on to its children' do
69
+ request = ChildRequest.new
70
+ request.api_key.should eql('123456')
71
+ request.secret.should eql('shhh!')
72
+ end
73
+
74
+ it 'should allow template parameters on its children that are additive' do
75
+ ChildRequest[:query] = '1a2b3c'
76
+ child = ChildRequest.new
77
+ child.api_key.should eql('123456')
78
+ child.secret.should eql('shhh!')
79
+ child.query.should eql('1a2b3c')
80
+
81
+ parent = TestRequest.new
82
+ parent.api_key.should eql('123456')
83
+ parent.secret.should eql('shhh!')
84
+ parent.respond_to?(:query).should be_false
85
+ end
86
+ end
@@ -0,0 +1,82 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ require 'relax/response'
4
+
5
+ XML = <<EOF
6
+ <?xml version="1.0"?>
7
+ <RESTResponse>
8
+ <Tokens>
9
+ <TokenId>JPMQARDVJK</TokenId>
10
+ <Status>Active</Status>
11
+ </Tokens>
12
+ <Tokens>
13
+ <TokenId>RDVJKJPMQA</TokenId>
14
+ <Status>Inactive</Status>
15
+ </Tokens>
16
+ <Status>Success</Status>
17
+ <RequestId valid="true">44287</RequestId>
18
+ </RESTResponse>
19
+ EOF
20
+
21
+ class TestResponse < Relax::Response
22
+ class Token < Relax::Response
23
+ parameter :token_id, :element => :tokenid
24
+ parameter :status
25
+ end
26
+
27
+ parameter :status
28
+ parameter :request_id, :element => :requestid, :type => :integer
29
+ parameter :valid_request, :element => :requestid, :attribute => :valid
30
+ parameter :tokens, :collection => Token
31
+ end
32
+
33
+ describe 'a response' do
34
+ before(:each) do
35
+ @response = Relax::Response.new(XML)
36
+ end
37
+
38
+ it 'should allow access to the root' do
39
+ root = @response.root
40
+ root.should be_an_instance_of(Hpricot::Elem)
41
+ root.name.should eql('restresponse')
42
+ end
43
+
44
+ it 'should be checkable by the name of its root' do
45
+ @response.is?(:RESTResponse).should be_true
46
+ end
47
+
48
+ it 'should allow access to an element by its name' do
49
+ @response.element(:RequestId).should be_an_instance_of(Hpricot::Elem)
50
+ end
51
+
52
+ it 'should allow access to an element\'s elements by its name' do
53
+ tokens = @response.elements(:Tokens)
54
+ tokens.should be_an_instance_of(Hpricot::Elements)
55
+ tokens.should_not be_empty
56
+ end
57
+
58
+ it 'should allow access to an element\'s value by its name' do
59
+ token = Relax::Response.new(@response.elements(:Tokens).first)
60
+ token.element(:TokenId).inner_text.should eql('JPMQARDVJK')
61
+ token.element(:Status).inner_text.should eql('Active')
62
+ end
63
+
64
+ it 'should have a means of checking for the existence of a node' do
65
+ @response.has?(:Status).should_not be_nil
66
+ @response.has?(:Errors).should be_nil
67
+ end
68
+
69
+ it 'should be able to define children of Request without modifying parent' do
70
+ Relax::Response.new(XML).respond_to?(:status).should be_false
71
+ TestResponse.new(XML).respond_to?(:status).should be_true
72
+ end
73
+
74
+ it 'should automatically pull parameters from the XML' do
75
+ response = TestResponse.new(XML)
76
+ response.status.should eql('Success')
77
+ response.request_id.should eql(44287)
78
+ response.valid_request.should eql('true')
79
+ response.tokens.length.should eql(2)
80
+ response.tokens.first.status.should eql('Active')
81
+ end
82
+ end
@@ -0,0 +1,67 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ require 'relax/symbolic_hash'
4
+
5
+ describe 'a symbolic hash' do
6
+ before(:each) do
7
+ @url = 'http://example.com/'
8
+ @query = Relax::SymbolicHash.new
9
+ end
10
+
11
+ it 'should be accessible via string or symbol keys' do
12
+ @query[:amount] = 10
13
+ @query[:amount].should == 10
14
+ @query['amount'].should == 10
15
+ end
16
+
17
+ it 'should convert keys to symbols' do
18
+ @query['symbol'] = 'aleph'
19
+ @query[:symbol].should == 'aleph'
20
+ end
21
+
22
+ it 'should convert keys to symbols' do
23
+ @query['symbol'] = 'aleph'
24
+ @query[:symbol].should == 'aleph'
25
+ end
26
+
27
+ it 'should test for keys by symbol' do
28
+ @query[:symbol] = 'aleph'
29
+ @query.key?('symbol').should be_true
30
+ end
31
+
32
+ it 'should delete values with a symbolic key' do
33
+ @query[:symbol] = 'aleph'
34
+ @query.delete('symbol')
35
+ @query.key?(:symbol).should be_false
36
+ end
37
+
38
+ it 'should be mergeable' do
39
+ @query[:one] = 2
40
+ merged_query = @query.merge({ :one => 1, :two => 2 })
41
+ merged_query[:one].should == 1
42
+ merged_query[:two].should == 2
43
+ end
44
+
45
+ it 'should be able to duplicate itself' do
46
+ @query[:one] = 'uno'
47
+ @query[:two] = 'dos'
48
+ new_query = @query.dup
49
+ new_query[:one].should == 'uno'
50
+ new_query[:two].should == 'dos'
51
+
52
+ @query[:three] == 'tres'
53
+ new_query.key?(:three).should be_false
54
+ end
55
+
56
+ it 'should be able to get multiple values by symbol' do
57
+ @query[:one] = 1
58
+ @query[:two] = 2
59
+ @query.values_at(:one, :two).should == [1, 2]
60
+ end
61
+
62
+ it 'should be instantiable with a hash' do
63
+ query = Relax::SymbolicHash.new({ :one => 1, :two => 2 })
64
+ query[:one].should == 1
65
+ query[:two].should == 2
66
+ end
67
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.2
3
+ specification_version: 1
4
+ name: relax
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2007-09-24 00:00:00 -04:00
8
+ summary: A simple library for creating REST consumers.
9
+ require_paths:
10
+ - lib
11
+ email: tyler@protoh.com
12
+ homepage: http://protoh.com/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: relax
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Tyler Hunt
31
+ files:
32
+ - lib/relax.rb
33
+ - lib/relax
34
+ - lib/relax/response.rb
35
+ - lib/relax/api.rb
36
+ - lib/relax/request.rb
37
+ - lib/relax/query.rb
38
+ - lib/relax/symbolic_hash.rb
39
+ - README
40
+ - LICENSE
41
+ test_files:
42
+ - spec/request_spec.rb
43
+ - spec/symbolic_hash_spec.rb
44
+ - spec/query_spec.rb
45
+ - spec/response_spec.rb
46
+ rdoc_options: []
47
+
48
+ extra_rdoc_files:
49
+ - README
50
+ - LICENSE
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ requirements: []
56
+
57
+ dependencies:
58
+ - !ruby/object:Gem::Dependency
59
+ name: hpricot
60
+ version_requirement:
61
+ version_requirements: !ruby/object:Gem::Version::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0.5"
66
+ version: