relax 0.0.4 → 0.0.5
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 +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
|