relax 0.0.1

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