relax 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README +25 -7
- data/lib/relax.rb +2 -0
- data/lib/relax/parsers.rb +13 -0
- data/lib/relax/parsers/base.rb +34 -0
- data/lib/relax/parsers/factory.rb +43 -0
- data/lib/relax/parsers/hpricot.rb +145 -0
- data/lib/relax/parsers/rexml.rb +158 -0
- data/lib/relax/query.rb +5 -5
- data/lib/relax/request.rb +1 -1
- data/lib/relax/response.rb +25 -127
- data/spec/parsers/factory_spec.rb +29 -0
- data/spec/parsers/hpricot_spec.rb +35 -0
- data/spec/parsers/rexml_spec.rb +40 -0
- data/spec/query_spec.rb +16 -1
- data/spec/response_spec.rb +10 -21
- metadata +67 -43
data/LICENSE
CHANGED
data/README
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
= Relax
|
2
2
|
|
3
|
-
Relax is a simple library for
|
3
|
+
Relax is a simple library that provides a foundation for writing REST consumer
|
4
|
+
APIs, including the logic to handle the HTTP requests, build URLs with query
|
5
|
+
parameters, and parse XML responses.
|
4
6
|
|
5
|
-
|
6
|
-
functionality common to all REST consumers, including:
|
7
|
+
It provides a basic set of functionality common to most REST consumers:
|
7
8
|
|
8
9
|
- building HTTP queries (Relax::Request)
|
9
10
|
- issuing HTTP requests (Relax::Service)
|
@@ -106,8 +107,7 @@ There are three main pieces to every service call module:
|
|
106
107
|
2. a Relax::Response object
|
107
108
|
3. a call method that calls Relax::Service#call
|
108
109
|
|
109
|
-
|
110
|
-
can be easily utilized.
|
110
|
+
Here's what the PhotoSearch module looks like:
|
111
111
|
|
112
112
|
module Flickr
|
113
113
|
module PhotoSearch
|
@@ -137,8 +137,23 @@ can be easily utilized.
|
|
137
137
|
|
138
138
|
As you can see, we have our request (PhotoSearchRequest), response
|
139
139
|
(PhotoSearchResponse), and call method (actually, two in this case: search and
|
140
|
-
find_by_tag). This
|
141
|
-
|
140
|
+
find_by_tag). This now needs to be included into our Flickr::Service class,
|
141
|
+
and then we'll be able to use it by calling either of the call methods.
|
142
|
+
|
143
|
+
module Flickr
|
144
|
+
class Service < Relax::Service
|
145
|
+
include Flickr::PhotoSearch
|
146
|
+
|
147
|
+
ENDPOINT = 'http://api.flickr.com/services/rest/'
|
148
|
+
|
149
|
+
def initialize(api_key)
|
150
|
+
super(ENDPOINT)
|
151
|
+
Request[:api_key] = api_key
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
Now we're ready to make a call against the API:
|
142
157
|
|
143
158
|
flickr = Flickr::Service.new(ENV['FLICKR_API_KEY'])
|
144
159
|
relax = flickr.find_by_tag('relax', :per_page => 10)
|
@@ -151,3 +166,6 @@ can use it by calling either of the call methods.
|
|
151
166
|
|
152
167
|
This will output the IDs and titles for the first 10 photos on Flickr that have
|
153
168
|
the tag "relax."
|
169
|
+
|
170
|
+
|
171
|
+
Copyright (c) 2007-2008 Tyler Hunt, released under the MIT license
|
data/lib/relax.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
2
|
|
3
3
|
require 'relax/query'
|
4
|
+
require 'relax/parsers'
|
4
5
|
require 'relax/request'
|
5
6
|
require 'relax/response'
|
6
7
|
require 'relax/service'
|
@@ -8,4 +9,5 @@ require 'relax/symbolic_hash'
|
|
8
9
|
|
9
10
|
module Relax
|
10
11
|
class MissingParameter < ArgumentError ; end
|
12
|
+
class UnrecognizedParser < ArgumentError ; end
|
11
13
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Relax
|
2
|
+
module Parsers
|
3
|
+
|
4
|
+
class Base
|
5
|
+
|
6
|
+
attr_reader :parent
|
7
|
+
attr_reader :parameters
|
8
|
+
|
9
|
+
def initialize(raw, parent)
|
10
|
+
@parent = parent
|
11
|
+
@parameters = parent.class.instance_variable_get('@parameters')
|
12
|
+
parse!
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse!; end
|
16
|
+
|
17
|
+
def root; end
|
18
|
+
def is?(name); end
|
19
|
+
def has?(name); end
|
20
|
+
def element(name); end
|
21
|
+
def elements(name); end
|
22
|
+
|
23
|
+
def attribute(element, name); end
|
24
|
+
def value(value); end
|
25
|
+
def text_value(value); end
|
26
|
+
def integer_value(value); end
|
27
|
+
def float_value(value); end
|
28
|
+
def date_value(value); end
|
29
|
+
def time_value(value); end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Relax
|
2
|
+
module Parsers
|
3
|
+
|
4
|
+
##
|
5
|
+
# Manages the Relax::Parsers in the library.
|
6
|
+
#
|
7
|
+
module Factory
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
##
|
12
|
+
# Returns the parser class which has been registered for the given
|
13
|
+
# +name+.
|
14
|
+
#
|
15
|
+
def get(name)
|
16
|
+
@@parsers ||= {}
|
17
|
+
@@parsers[name] || raise(UnrecognizedParser, "Given parser name not recognized: #{name.inspect}. Expected one of: #{@@parsers.keys.inspect}")
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Registers a new parser with the factory. The +name+ should be unique,
|
22
|
+
# but if not, it will override the previously defined parser for the
|
23
|
+
# given +name+.
|
24
|
+
#
|
25
|
+
def register(name, klass)
|
26
|
+
@@parsers ||= {}
|
27
|
+
@@parsers[:default] = klass if @@parsers.empty?
|
28
|
+
@@parsers[name] = klass
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Removes all registered parsers from the factory.
|
33
|
+
#
|
34
|
+
def clear!
|
35
|
+
@@parsers = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hpricot'
|
3
|
+
|
4
|
+
module Relax
|
5
|
+
module Parsers
|
6
|
+
|
7
|
+
##
|
8
|
+
# Parses the server's raw response using the Hpricot library.
|
9
|
+
#
|
10
|
+
class Hpricot < Base
|
11
|
+
|
12
|
+
FACTORY_NAME = :hpricot
|
13
|
+
|
14
|
+
def initialize(raw, parent)
|
15
|
+
@xml = ::Hpricot.XML(raw)
|
16
|
+
super(raw, parent)
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse!
|
20
|
+
if parameters
|
21
|
+
parameters.each do |parameter, options|
|
22
|
+
begin
|
23
|
+
element = options[:element] || parameter
|
24
|
+
|
25
|
+
if attribute = options[:attribute] and attribute == true
|
26
|
+
node = attribute(root, element)
|
27
|
+
elsif attribute
|
28
|
+
node = attribute(element(element), attribute)
|
29
|
+
elsif options[:collection]
|
30
|
+
node = elements(element)
|
31
|
+
else
|
32
|
+
node = element(element)
|
33
|
+
end
|
34
|
+
|
35
|
+
if options[:collection]
|
36
|
+
value = node.collect do |element|
|
37
|
+
options[:collection].new(element)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
case type = options[:type]
|
41
|
+
when Response
|
42
|
+
value = type.new(node)
|
43
|
+
|
44
|
+
when :date
|
45
|
+
value = date_value(node)
|
46
|
+
|
47
|
+
when :time
|
48
|
+
value = time_value(node)
|
49
|
+
|
50
|
+
when :float
|
51
|
+
value = float_value(node)
|
52
|
+
|
53
|
+
when :integer
|
54
|
+
value = integer_value(node)
|
55
|
+
|
56
|
+
when :text
|
57
|
+
else
|
58
|
+
value = text_value(node)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
parent.instance_variable_set("@#{parameter}", value)
|
63
|
+
rescue ::Hpricot::Error
|
64
|
+
raise Relax::MissingParameter if options[:required]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the root of the XML document.
|
71
|
+
def root
|
72
|
+
@xml.root
|
73
|
+
end
|
74
|
+
|
75
|
+
# Checks the name of the root node.
|
76
|
+
def is?(name)
|
77
|
+
root.name.gsub(/.*:(.*)/, '\1') == node_name(name)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a set of elements matching name.
|
81
|
+
def elements(name)
|
82
|
+
root.search(root_path(name))
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns an element of the specified name.
|
86
|
+
def element(name)
|
87
|
+
root.at(root_path(name))
|
88
|
+
end
|
89
|
+
alias :has? :element
|
90
|
+
|
91
|
+
# Returns an attribute on an element.
|
92
|
+
def attribute(element, name)
|
93
|
+
element[name]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Gets the value of an element or attribute.
|
97
|
+
def value(value)
|
98
|
+
value.is_a?(::Hpricot::Elem) ? value.inner_text : value.to_s
|
99
|
+
end
|
100
|
+
|
101
|
+
# Gets a text value.
|
102
|
+
def text_value(value)
|
103
|
+
value(value)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Gets an integer value.
|
107
|
+
def integer_value(value)
|
108
|
+
value(value).to_i
|
109
|
+
end
|
110
|
+
|
111
|
+
# Gets a float value.
|
112
|
+
def float_value(value)
|
113
|
+
value(value).to_f
|
114
|
+
end
|
115
|
+
|
116
|
+
# Gets a date value.
|
117
|
+
def date_value(value)
|
118
|
+
Date.parse(value(value))
|
119
|
+
end
|
120
|
+
|
121
|
+
# Gets a time value.
|
122
|
+
def time_value(value)
|
123
|
+
Time.parse(value(value))
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
|
130
|
+
# Converts a name to a node name.
|
131
|
+
def node_name(name)
|
132
|
+
name.to_s
|
133
|
+
end
|
134
|
+
|
135
|
+
# Gets the XPath expression representing the root node.
|
136
|
+
def root_path(name)
|
137
|
+
"/#{node_name(name)}"
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
Factory.register(Hpricot::FACTORY_NAME, Hpricot)
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rexml/document'
|
3
|
+
|
4
|
+
module Relax
|
5
|
+
module Parsers
|
6
|
+
|
7
|
+
##
|
8
|
+
# Parsers the server's response using the REXML library.
|
9
|
+
#
|
10
|
+
# Benefits:
|
11
|
+
#
|
12
|
+
# * XML Namespace support (parameter :foo, :namespace => 'bar')
|
13
|
+
#
|
14
|
+
# Drawbacks:
|
15
|
+
#
|
16
|
+
# * Case sensitive field names (<Status>..</> != parameter :status)
|
17
|
+
#
|
18
|
+
class REXML < Base
|
19
|
+
|
20
|
+
FACTORY_NAME = :rexml
|
21
|
+
|
22
|
+
def initialize(raw, parent)
|
23
|
+
@xml = ::REXML::Document.new(raw)
|
24
|
+
super(raw, parent)
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse!
|
28
|
+
if parameters
|
29
|
+
parameters.each do |parameter, options|
|
30
|
+
begin
|
31
|
+
element = options[:element] || parameter
|
32
|
+
namespace = options[:namespace]
|
33
|
+
|
34
|
+
if attribute = options[:attribute] and attribute == true
|
35
|
+
node = attribute(root, element, namespace)
|
36
|
+
elsif attribute
|
37
|
+
node = attribute(element(element), attribute, namespace)
|
38
|
+
elsif options[:collection]
|
39
|
+
node = elements(element, namespace)
|
40
|
+
else
|
41
|
+
node = element(element, namespace)
|
42
|
+
end
|
43
|
+
|
44
|
+
if options[:collection]
|
45
|
+
value = node.collect do |element|
|
46
|
+
options[:collection].new(element.deep_clone)
|
47
|
+
end
|
48
|
+
else
|
49
|
+
case type = options[:type]
|
50
|
+
when Response
|
51
|
+
value = type.new(node)
|
52
|
+
|
53
|
+
when :date
|
54
|
+
value = date_value(node)
|
55
|
+
|
56
|
+
when :time
|
57
|
+
value = time_value(node)
|
58
|
+
|
59
|
+
when :float
|
60
|
+
value = float_value(node)
|
61
|
+
|
62
|
+
when :integer
|
63
|
+
value = integer_value(node)
|
64
|
+
|
65
|
+
when :text
|
66
|
+
else
|
67
|
+
value = text_value(node)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
parent.instance_variable_set("@#{parameter}", value)
|
72
|
+
rescue
|
73
|
+
raise(Relax::MissingParameter) if node.nil? && options[:required]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the root of the XML document.
|
80
|
+
def root
|
81
|
+
@xml.root
|
82
|
+
end
|
83
|
+
|
84
|
+
# Checks the name of the root node.
|
85
|
+
def is?(name, namespace = nil)
|
86
|
+
root.name == node_name(name, nil)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns a set of elements matching name.
|
90
|
+
def elements(name, namespace = nil)
|
91
|
+
root.get_elements(node_path(name, namespace))
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns an element of the specified name.
|
95
|
+
def element(name, namespace = nil)
|
96
|
+
root.elements[node_path(name, namespace)]
|
97
|
+
end
|
98
|
+
alias :has? :element
|
99
|
+
|
100
|
+
# Returns an attribute on an element.
|
101
|
+
def attribute(element, name, namespace = nil)
|
102
|
+
element.attribute(name)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Gets the value of an element or attribute.
|
106
|
+
def value(value)
|
107
|
+
value.is_a?(::REXML::Element) ? value.text : value.to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
# Gets a text value.
|
111
|
+
def text_value(value)
|
112
|
+
value(value)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Gets an integer value.
|
116
|
+
def integer_value(value)
|
117
|
+
value(value).to_i
|
118
|
+
end
|
119
|
+
|
120
|
+
# Gets a float value.
|
121
|
+
def float_value(value)
|
122
|
+
value(value).to_f
|
123
|
+
end
|
124
|
+
|
125
|
+
# Gets a date value.
|
126
|
+
def date_value(value)
|
127
|
+
Date.parse(value(value))
|
128
|
+
end
|
129
|
+
|
130
|
+
# Gets a time value.
|
131
|
+
def time_value(value)
|
132
|
+
Time.parse(value(value))
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
|
139
|
+
# Converts a name to a node name.
|
140
|
+
def node_name(name, namespace = nil)
|
141
|
+
"#{namespace.to_s + ':' if namespace}#{name}"
|
142
|
+
end
|
143
|
+
|
144
|
+
# Gets the XPath expression representing the root node.
|
145
|
+
def root_path(name)
|
146
|
+
"/#{node_name(name)}"
|
147
|
+
end
|
148
|
+
|
149
|
+
def node_path(name, namespace = nil)
|
150
|
+
"#{node_name(name, namespace)}"
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
Factory.register(REXML::FACTORY_NAME, REXML)
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
data/lib/relax/query.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'cgi'
|
2
2
|
require 'uri'
|
3
3
|
|
4
4
|
require 'relax/symbolic_hash'
|
@@ -18,8 +18,8 @@ module Relax
|
|
18
18
|
# Parses a URL and returns a Query with its query portion.
|
19
19
|
def parse(uri)
|
20
20
|
query = uri.query.split('&').inject({}) do |query, parameter|
|
21
|
-
key, value = parameter.split('=')
|
22
|
-
query[key] = unescape_value(value)
|
21
|
+
key, value = parameter.split('=', 2)
|
22
|
+
query[unescape_value(key)] = unescape_value(value)
|
23
23
|
query
|
24
24
|
end
|
25
25
|
self.new(query)
|
@@ -27,12 +27,12 @@ module Relax
|
|
27
27
|
|
28
28
|
# Escapes a query parameter value.
|
29
29
|
def escape_value(value)
|
30
|
-
|
30
|
+
CGI.escape(value.to_s).gsub('%20', '+')
|
31
31
|
end
|
32
32
|
|
33
33
|
# Unescapes a query parameter value.
|
34
34
|
def unescape_value(value)
|
35
|
-
|
35
|
+
CGI.unescape(value)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
data/lib/relax/request.rb
CHANGED
@@ -57,7 +57,7 @@ module Relax
|
|
57
57
|
# the parameter name separated by two underscores. This method can be
|
58
58
|
# overridden by child classes.
|
59
59
|
def convert_complex_key(key, parameter)
|
60
|
-
"#{key}.#{parameter}"
|
60
|
+
"#{convert_key(key)}.#{convert_key(parameter)}"
|
61
61
|
end
|
62
62
|
protected :convert_complex_key
|
63
63
|
|
data/lib/relax/response.rb
CHANGED
@@ -1,8 +1,3 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'hpricot'
|
3
|
-
|
4
|
-
require 'date'
|
5
|
-
|
6
1
|
module Relax
|
7
2
|
# Response is intended to be a parent class for responses passed to
|
8
3
|
# Service#call.
|
@@ -12,122 +7,26 @@ module Relax
|
|
12
7
|
# #element and #attribute.
|
13
8
|
class Response
|
14
9
|
attr_accessor :raw
|
15
|
-
|
16
|
-
|
17
|
-
# New takes in the XML from the response. For the initial response, this
|
18
|
-
# will be the root element, but child elements may also be passed into
|
19
|
-
# Response objects.
|
10
|
+
|
11
|
+
# New takes in and parses the raw response.
|
20
12
|
#
|
21
13
|
# This will raise a MissingParameter error if a parameterd marked as
|
22
|
-
# required is not present in the
|
14
|
+
# required is not present in the parsed response.
|
23
15
|
def initialize(xml)
|
24
|
-
@raw
|
25
|
-
@
|
26
|
-
|
27
|
-
if parameters = self.class.instance_variable_get('@parameters')
|
28
|
-
parameters.each do |parameter, options|
|
29
|
-
begin
|
30
|
-
element = options[:element] || parameter
|
31
|
-
|
32
|
-
if attribute = options[:attribute] and attribute == true
|
33
|
-
node = attribute(root, element)
|
34
|
-
elsif attribute
|
35
|
-
node = attribute(element(element), attribute)
|
36
|
-
elsif options[:collection]
|
37
|
-
node = elements(element)
|
38
|
-
else
|
39
|
-
node = element(element)
|
40
|
-
end
|
41
|
-
|
42
|
-
if options[:collection]
|
43
|
-
value = node.collect do |element|
|
44
|
-
options[:collection].new(element)
|
45
|
-
end
|
46
|
-
else
|
47
|
-
case type = options[:type]
|
48
|
-
when Response
|
49
|
-
value = type.new(node)
|
50
|
-
|
51
|
-
when :date
|
52
|
-
value = date_value(node)
|
53
|
-
|
54
|
-
when :time
|
55
|
-
value = time_value(node)
|
56
|
-
|
57
|
-
when :float
|
58
|
-
value = float_value(node)
|
59
|
-
|
60
|
-
when :integer
|
61
|
-
value = integer_value(node)
|
62
|
-
|
63
|
-
when :text
|
64
|
-
else
|
65
|
-
value = text_value(node)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
instance_variable_set("@#{parameter}", value)
|
70
|
-
rescue Hpricot::Error
|
71
|
-
raise MissingParameter if options[:required]
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# Returns the root of the XML document.
|
78
|
-
def root
|
79
|
-
@xml.root
|
16
|
+
@raw = xml
|
17
|
+
@parser = Relax::Parsers::Factory.get(parser_name).new(xml.to_s, self)
|
80
18
|
end
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
root.name.gsub(/.*:(.*)/, '\1') == node_name(name)
|
85
|
-
end
|
86
|
-
|
87
|
-
# Returns an element of the specified name.
|
88
|
-
def element(name)
|
89
|
-
root.at(root_path(name))
|
90
|
-
end
|
91
|
-
alias :has? :element
|
92
|
-
|
93
|
-
# Returns an attribute on an element.
|
94
|
-
def attribute(element, name)
|
95
|
-
element[name]
|
19
|
+
|
20
|
+
def parser_name
|
21
|
+
self.class.instance_variable_get('@parser') || :default
|
96
22
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
def value(value)
|
105
|
-
value.is_a?(Hpricot::Elem) ? value.inner_text : value.to_s
|
106
|
-
end
|
107
|
-
|
108
|
-
# Gets a text value.
|
109
|
-
def text_value(value)
|
110
|
-
value(value)
|
111
|
-
end
|
112
|
-
|
113
|
-
# Gets an integer value.
|
114
|
-
def integer_value(value)
|
115
|
-
value(value).to_i
|
116
|
-
end
|
117
|
-
|
118
|
-
# Gets a float value.
|
119
|
-
def float_value(value)
|
120
|
-
value(value).to_f
|
121
|
-
end
|
122
|
-
|
123
|
-
# Gets a date value.
|
124
|
-
def date_value(value)
|
125
|
-
Date.parse(value(value))
|
126
|
-
end
|
127
|
-
|
128
|
-
# Gets a time value.
|
129
|
-
def time_value(value)
|
130
|
-
Time.parse(value(value))
|
23
|
+
|
24
|
+
def method_missing(method, *args) #:nodoc:
|
25
|
+
if @parser.respond_to?(method)
|
26
|
+
@parser.__send__(method, *args)
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
131
30
|
end
|
132
31
|
|
133
32
|
class << self
|
@@ -140,6 +39,7 @@ module Relax
|
|
140
39
|
@parameters.each do |name, options|
|
141
40
|
subclass.parameter(name, options)
|
142
41
|
end if @parameters
|
42
|
+
subclass.parser(@parser) if @parser
|
143
43
|
end
|
144
44
|
|
145
45
|
# Specifes a parameter that will be automatically parsed when the
|
@@ -159,22 +59,20 @@ module Relax
|
|
159
59
|
@parameters ||= {}
|
160
60
|
@parameters[name] = options
|
161
61
|
end
|
62
|
+
|
63
|
+
# Specifies the parser to use when decoding the server response. If
|
64
|
+
# no parser is specified for the response, then the default parser will
|
65
|
+
# be used.
|
66
|
+
#
|
67
|
+
# See Relax::Parsers for a list of available parsers.
|
68
|
+
def parser(name)
|
69
|
+
@parser ||= name
|
70
|
+
end
|
162
71
|
|
163
72
|
def ===(response)
|
164
73
|
response.is_a?(Class) ? response.ancestors.include?(self) : super
|
165
74
|
end
|
166
75
|
end
|
167
76
|
|
168
|
-
private
|
169
|
-
|
170
|
-
# Converts a name to a node name.
|
171
|
-
def node_name(name)
|
172
|
-
name.to_s
|
173
|
-
end
|
174
|
-
|
175
|
-
# Gets the XPath expression representing the root node.
|
176
|
-
def root_path(name)
|
177
|
-
"/#{node_name(name)}"
|
178
|
-
end
|
179
77
|
end
|
180
78
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
require 'relax'
|
4
|
+
require 'relax/parsers/factory'
|
5
|
+
|
6
|
+
class TestParser ; end
|
7
|
+
|
8
|
+
describe 'a parser factory' do
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
@factory = Relax::Parsers::Factory
|
12
|
+
Relax::Parsers::Factory.register(:test, TestParser)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should raise UnrecognizedParser for un-registered names' do
|
16
|
+
lambda {
|
17
|
+
@factory.get(:bad_name)
|
18
|
+
}.should raise_error(Relax::UnrecognizedParser)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should return a registered parser class' do
|
22
|
+
@factory.get(:test).should ==TestParser
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should register the first registered parser as the default' do
|
26
|
+
@factory.get(:default).should ==Relax::Parsers::Hpricot
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require File.dirname(__FILE__) + '/../parser_helper'
|
3
|
+
|
4
|
+
|
5
|
+
class HpricotTestResponse < Relax::Response
|
6
|
+
class Token < Relax::Response
|
7
|
+
parser :hpricot
|
8
|
+
parameter :token_id, :element => :tokenid
|
9
|
+
parameter :status
|
10
|
+
end
|
11
|
+
|
12
|
+
class Error < Relax::Response
|
13
|
+
parser :hpricot
|
14
|
+
parameter :code, :type => :integer
|
15
|
+
parameter :message
|
16
|
+
end
|
17
|
+
|
18
|
+
parser :hpricot
|
19
|
+
parameter :status, :required => true
|
20
|
+
parameter :request_id, :element => :requestid, :type => :integer
|
21
|
+
parameter :valid_request, :element => :requestid, :attribute => :valid
|
22
|
+
parameter :tokens, :collection => Token
|
23
|
+
parameter :error, :type => Error
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
describe 'an Hpricot parser' do
|
28
|
+
|
29
|
+
before(:each) do
|
30
|
+
@response = HpricotTestResponse.new(XML)
|
31
|
+
end
|
32
|
+
|
33
|
+
it_should_behave_like 'a successfully parsed response'
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require File.dirname(__FILE__) + '/../parser_helper'
|
3
|
+
|
4
|
+
|
5
|
+
class RexmlTestResponse < Relax::Response
|
6
|
+
class Token < Relax::Response
|
7
|
+
parser :rexml
|
8
|
+
parameter :token_id, :element => 'TokenId'
|
9
|
+
parameter :status, :element => 'Status'
|
10
|
+
end
|
11
|
+
|
12
|
+
class Error < Relax::Response
|
13
|
+
parser :rexml
|
14
|
+
parameter :code, :element => 'Code', :type => :integer
|
15
|
+
parameter :message, :element => 'Message'
|
16
|
+
end
|
17
|
+
|
18
|
+
parser :rexml
|
19
|
+
parameter :status, :element => 'Status', :required => true
|
20
|
+
parameter :request_id, :element => 'RequestId', :type => :integer
|
21
|
+
parameter :valid_request, :element => 'RequestId', :attribute => :valid
|
22
|
+
parameter :namespace, :element => 'Namespace', :namespace => 'ns1'
|
23
|
+
parameter :tokens, :element => 'Tokens', :collection => Token
|
24
|
+
parameter :error, :element => 'Error', :type => Error
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
describe 'a REXML parser' do
|
29
|
+
|
30
|
+
before(:each) do
|
31
|
+
@response = RexmlTestResponse.new(XML)
|
32
|
+
end
|
33
|
+
|
34
|
+
it_should_behave_like 'a successfully parsed response'
|
35
|
+
|
36
|
+
it 'should parse namespaced parameters' do
|
37
|
+
@response.namespace.should eql('Passed')
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/spec/query_spec.rb
CHANGED
@@ -23,7 +23,7 @@ describe 'a query' do
|
|
23
23
|
it 'should escape its values using "+" instead of "%20"' do
|
24
24
|
Relax::Query.send(:escape_value, 'two words').should == 'two+words'
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
it 'should sort its parameters' do
|
28
28
|
@query[:charlie] = 3
|
29
29
|
@query[:alpha] = 1
|
@@ -42,4 +42,19 @@ describe 'a query' do
|
|
42
42
|
parsed_query[:action].should eql('search')
|
43
43
|
parsed_query[:query].should eql('keyword')
|
44
44
|
end
|
45
|
+
|
46
|
+
it 'should parse key value pairs into only two parts' do
|
47
|
+
parsed_query = Relax::Query.parse(URI.parse("http://example.com/?action=test=&foo=bar"))
|
48
|
+
parsed_query[:action].should eql('test=')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should unescape query string key-value pair keys' do
|
52
|
+
parsed_query = Relax::Query.parse(URI.parse("http://example.com/?action%20helper=test"))
|
53
|
+
parsed_query[:"action helper"].should eql('test')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should unescape query string key-value pair values' do
|
57
|
+
parsed_query = Relax::Query.parse(URI.parse("http://example.com/?action=test%20action"))
|
58
|
+
parsed_query[:action].should eql('test action')
|
59
|
+
end
|
45
60
|
end
|
data/spec/response_spec.rb
CHANGED
@@ -1,26 +1,5 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/spec_helper'
|
2
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
|
-
<Error>
|
19
|
-
<Code>1</Code>
|
20
|
-
<Message>Failed</Message>
|
21
|
-
</Error>
|
22
|
-
</RESTResponse>
|
23
|
-
EOF
|
24
3
|
|
25
4
|
class BaseResponse < Relax::Response
|
26
5
|
parameter :status, :required => true
|
@@ -34,6 +13,7 @@ class TestResponse < BaseResponse
|
|
34
13
|
end
|
35
14
|
|
36
15
|
class Error < Relax::Response
|
16
|
+
parser :hpricot
|
37
17
|
parameter :code, :type => :integer
|
38
18
|
parameter :message
|
39
19
|
end
|
@@ -43,6 +23,7 @@ class TestResponse < BaseResponse
|
|
43
23
|
parameter :error, :type => Error
|
44
24
|
end
|
45
25
|
|
26
|
+
|
46
27
|
describe 'a response' do
|
47
28
|
before(:each) do
|
48
29
|
@response = Relax::Response.new(XML)
|
@@ -106,4 +87,12 @@ describe 'a response' do
|
|
106
87
|
it 'should raise MissingParameter if required parameters are missing' do
|
107
88
|
proc { TestResponse.new('') }.should raise_error(Relax::MissingParameter)
|
108
89
|
end
|
90
|
+
|
91
|
+
it 'should use the default parser when undefined' do
|
92
|
+
TestResponse::Token.new('').parser_name.should ==:default
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should use the defined parser when given' do
|
96
|
+
TestResponse::Error.new('').parser_name.should ==:hpricot
|
97
|
+
end
|
109
98
|
end
|
metadata
CHANGED
@@ -1,66 +1,90 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4
|
3
|
-
specification_version: 1
|
4
2
|
name: relax
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2007-11-17 00:00:00 -05: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:
|
4
|
+
version: 0.0.5
|
25
5
|
platform: ruby
|
26
|
-
signing_key:
|
27
|
-
cert_chain:
|
28
|
-
post_install_message:
|
29
6
|
authors:
|
30
7
|
- Tyler Hunt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-10-20 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hpricot
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0.6"
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: tyler@tylerhunt.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README
|
33
|
+
- LICENSE
|
31
34
|
files:
|
32
35
|
- lib/relax
|
36
|
+
- lib/relax/parsers
|
37
|
+
- lib/relax/parsers/base.rb
|
38
|
+
- lib/relax/parsers/factory.rb
|
39
|
+
- lib/relax/parsers/hpricot.rb
|
40
|
+
- lib/relax/parsers/rexml.rb
|
41
|
+
- lib/relax/parsers.rb
|
33
42
|
- lib/relax/query.rb
|
34
43
|
- lib/relax/request.rb
|
35
44
|
- lib/relax/response.rb
|
36
45
|
- lib/relax/service.rb
|
37
46
|
- lib/relax/symbolic_hash.rb
|
38
47
|
- lib/relax.rb
|
39
|
-
-
|
40
|
-
-
|
41
|
-
|
48
|
+
- spec/parsers/factory_spec.rb
|
49
|
+
- spec/parsers/hpricot_spec.rb
|
50
|
+
- spec/parsers/rexml_spec.rb
|
42
51
|
- spec/query_spec.rb
|
43
52
|
- spec/request_spec.rb
|
44
53
|
- spec/response_spec.rb
|
45
54
|
- spec/symbolic_hash_spec.rb
|
46
|
-
rdoc_options: []
|
47
|
-
|
48
|
-
extra_rdoc_files:
|
49
55
|
- README
|
50
56
|
- LICENSE
|
51
|
-
|
52
|
-
|
53
|
-
|
57
|
+
has_rdoc: true
|
58
|
+
homepage: http://tylerhunt.com/
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
54
61
|
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
55
76
|
requirements: []
|
56
77
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
78
|
+
rubyforge_project: relax
|
79
|
+
rubygems_version: 1.2.0
|
80
|
+
signing_key:
|
81
|
+
specification_version: 2
|
82
|
+
summary: A simple library for creating REST consumers.
|
83
|
+
test_files:
|
84
|
+
- spec/parsers/factory_spec.rb
|
85
|
+
- spec/parsers/hpricot_spec.rb
|
86
|
+
- spec/parsers/rexml_spec.rb
|
87
|
+
- spec/query_spec.rb
|
88
|
+
- spec/request_spec.rb
|
89
|
+
- spec/response_spec.rb
|
90
|
+
- spec/symbolic_hash_spec.rb
|