rubyrest 0.0.5 → 0.1.0
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/CHANGELOG +9 -5
- data/README +18 -23
- data/Rakefile +4 -4
- data/lib/rubyrest/application.rb +221 -0
- data/lib/rubyrest/atom.rb +247 -223
- data/lib/rubyrest/client.rb +19 -29
- data/lib/rubyrest/engine.rb +34 -83
- data/lib/rubyrest/resource.rb +87 -0
- data/lib/rubyrest/webrick.rb +92 -0
- data/lib/rubyrest.rb +10 -15
- metadata +5 -9
- data/examples/hello.rb +0 -57
- data/lib/rubyrest/config.rb +0 -80
- data/lib/rubyrest/servlets.rb +0 -233
- data/lib/rubyrest/tools.rb +0 -72
data/lib/rubyrest/atom.rb
CHANGED
@@ -5,245 +5,269 @@
|
|
5
5
|
#
|
6
6
|
# $Id:$
|
7
7
|
module RubyRest
|
8
|
-
|
9
8
|
module Atom
|
10
|
-
|
11
|
-
|
12
|
-
NAMESPACES = {
|
9
|
+
|
10
|
+
NAMESPACES = {
|
13
11
|
"xmlns" => "http://www.w3.org/2005/Atom",
|
14
|
-
"xmlns:
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
ATOMSERV_TYPE = "application/atomserv+xml".freeze
|
19
|
-
HTML_TYPE = "text/html".freeze
|
20
|
-
WORKSPACE_METHOD = "dashboard".freeze
|
21
|
-
MODULEID = "Ruby-on-Rest (http://rubyrest.rubyforge.org)".freeze
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
#
|
27
|
-
# Formats the response as a Atom feed, Atom Entry or
|
28
|
-
# Atom Service Document
|
29
|
-
def format_response( request, response )
|
30
|
-
|
31
|
-
builder = Builder::XmlMarkup.new( :target => response.body )
|
32
|
-
builder.instruct!
|
33
|
-
|
34
|
-
if @service_method == "dashboard"
|
35
|
-
response[ "content-type" ] = ATOMSERV_TYPE
|
36
|
-
build_service_document( @result, builder, request.request_uri )
|
37
|
-
else
|
38
|
-
response[ "content-type" ] = ATOM_TYPE
|
39
|
-
if @result.respond_to? "each"
|
40
|
-
title = @property || @model
|
41
|
-
build_feed( @result, builder, request.request_uri, request.path, title )
|
42
|
-
else build_entry( @result, builder, request.path ) end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
|
47
|
-
# Builds an Atom Service Document. This is a representation of the
|
48
|
-
# user's dashboard or initial workspace.
|
49
|
-
def build_service_document( collections, builder, uri )
|
50
|
-
builder.service( NAMESPACES ){
|
51
|
-
builder.workspace {
|
52
|
-
builder.title "Dashboard"
|
53
|
-
if collections != nil and collections.respond_to?( :each )
|
54
|
-
collections.each { |col|
|
55
|
-
builder.collection( { "href" => "/#{col}" } ) {
|
56
|
-
builder.title col
|
57
|
-
builder.accept "entry"
|
58
|
-
}
|
59
|
-
}
|
60
|
-
end
|
61
|
-
}
|
62
|
-
}
|
63
|
-
end
|
64
|
-
|
65
|
-
# Builds an Atom Feed representation of the specified collection
|
66
|
-
# of entries
|
67
|
-
#
|
68
|
-
def build_feed( entries, builder, uri, path, title )
|
69
|
-
builder.feed( NAMESPACES ) {
|
70
|
-
builder.id uri
|
71
|
-
builder.link( { "rel" => "self", "href" => path, "type" => ATOM_TYPE } )
|
72
|
-
builder.link( { "rel" => "alternate", "href" => uri, "type" => HTML_TYPE } )
|
73
|
-
builder.title title
|
74
|
-
builder.updated format_atom_date( Time.now )
|
75
|
-
entries.each { |object| build_entry( object, builder, uri ) }
|
76
|
-
}
|
77
|
-
end
|
78
|
-
|
79
|
-
# Builds an Atom Entry representation of the specified
|
80
|
-
# object.
|
81
|
-
#
|
82
|
-
# The object is supposed to implement the following mandatory methods:
|
83
|
-
# atom_id, atom_title, atom_author, atom_updated, atom_summary
|
84
|
-
#
|
85
|
-
# The object can implement the following optionnal methods:
|
86
|
-
# atom_related, atom_content
|
87
|
-
#
|
88
|
-
def build_entry( object, builder, uri )
|
89
|
-
|
90
|
-
entry_link = uri
|
91
|
-
entry_link = "#{uri}/#{object.atom_id}" if @id == nil
|
92
|
-
|
93
|
-
builder.entry( NAMESPACES ) {
|
94
|
-
builder.title object.atom_title
|
95
|
-
builder.author { builder.name object.atom_author }
|
96
|
-
builder.updated format_atom_date( object.atom_updated )
|
97
|
-
builder.id entry_link
|
98
|
-
builder.summary object.atom_summary
|
99
|
-
builder.link( :rel => :alternate, :href => entry_link )
|
100
|
-
|
101
|
-
if object.respond_to?( :atom_related )
|
102
|
-
related_entities = object.atom_related( @principal )
|
103
|
-
if related_entities != nil
|
104
|
-
related_entities.each{ |related|
|
105
|
-
case related[:type]
|
106
|
-
when :child
|
107
|
-
builder.link( :rel => related[:type], :href => "#{entry_link}/#{related[:model]}", :title => related[:model], :type => ATOM_TYPE )
|
108
|
-
when :parent
|
109
|
-
builder.link( :rel => related[:type], :href => "/#{related[:model]}/#{object.send related[:model]}", :title => related[:model], :type => ATOM_TYPE )
|
110
|
-
else :siblings
|
111
|
-
builder.link( :rel => related[:type], :href => "/#{related[:model]}", :title => related[:model], :type => ATOM_TYPE )
|
112
|
-
end
|
113
|
-
}
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
builder.moodisland :content do
|
118
|
-
object.atom_content( builder ) if object.respond_to? :atom_content
|
119
|
-
end
|
120
|
-
}
|
12
|
+
"xmlns:rubyrest" => "http://rubyrest.rubyforge.org/ns#"
|
13
|
+
}
|
14
|
+
|
15
|
+
ATOM_DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
121
16
|
|
17
|
+
|
18
|
+
|
19
|
+
# Ruby-on-Rest specialization of the REXML document
|
20
|
+
# class. Adds some convenient ways of accessing data from
|
21
|
+
# a Atom document
|
22
|
+
class Document < REXML::Document
|
23
|
+
|
24
|
+
def method_missing( name, *args )
|
25
|
+
get_value( name )
|
122
26
|
end
|
123
27
|
|
124
|
-
#
|
125
|
-
#
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
# Returns the Atom Entry Summary. Synonym of atom_title
|
138
|
-
def atom_summary
|
139
|
-
atom_title
|
140
|
-
end
|
28
|
+
# Resolves the missing method into a content property
|
29
|
+
# and returns its text value
|
30
|
+
def get_value( name, required=true )
|
31
|
+
location = "/entry/rubyrest:content/#{name}"
|
32
|
+
value = text( location )
|
33
|
+
raise "missing value at location #{location} in #{self.to_s}" if required && !value
|
34
|
+
return value
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_value( name, value )
|
38
|
+
get_text( "/entry/rubyrest:content/#{name}" ).value = value
|
39
|
+
end
|
141
40
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
41
|
+
# Shortcut for the id element contained
|
42
|
+
# within the content.
|
43
|
+
def id
|
44
|
+
text( "/entry/rubyrest:content/id" )
|
45
|
+
end
|
146
46
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
47
|
+
# Overrides the default implementation
|
48
|
+
# by returning a new Ruby-on-Rest Atom Document
|
49
|
+
def clone
|
50
|
+
self.class.new self.to_s
|
51
|
+
end
|
151
52
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
53
|
+
end
|
54
|
+
|
55
|
+
# Main Atom formatter class, composed of more specialized objects
|
56
|
+
# such as Feed, Entry, ServiceDocument and property formatters
|
57
|
+
class Formatter
|
58
|
+
|
59
|
+
attr_reader :app
|
60
|
+
|
61
|
+
# Inits all the specific formatters and property handlers used by this
|
62
|
+
# main formatter
|
63
|
+
def initialize( app )
|
64
|
+
@app = app
|
156
65
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
location = "/entry/moodisland:content/#{name}"
|
166
|
-
method_name = name.to_s.chomp
|
167
|
-
if method_name == name.to_s
|
168
|
-
text( location )
|
169
|
-
else get_text( location ).value = args[0] end
|
170
|
-
end
|
171
|
-
|
172
|
-
# Shortcut for the id element contained
|
173
|
-
# within the content.
|
174
|
-
def id
|
175
|
-
text( "/entry/moodisland:content/id" )
|
176
|
-
end
|
177
|
-
|
178
|
-
# Overrides the default implementation
|
179
|
-
# by returning a new Ruby-on-Rest Atom Document
|
180
|
-
def clone
|
181
|
-
self.class.new self.to_s
|
182
|
-
end
|
183
|
-
|
184
|
-
end
|
185
|
-
|
66
|
+
@formatters=Hash.new
|
67
|
+
@formatters[:feed]=FeedFormatter.new( self )
|
68
|
+
@formatters[:entry]=EntryFormatter.new( self )
|
69
|
+
@formatters[:service_doc]=ServiceDocFormatter.new( self )
|
70
|
+
|
71
|
+
@props=Hash.new
|
72
|
+
@props[:simple]=SimpleProperty.new( self )
|
73
|
+
@props[:date]=DateProperty.new( self )
|
186
74
|
end
|
187
75
|
|
76
|
+
# Resolves the specified name into a formatter
|
77
|
+
# object
|
78
|
+
def formatter_for_name( name )
|
79
|
+
raise "No formatter was found for name #{name}" if !@formatters[name]
|
80
|
+
return @formatters[name]
|
81
|
+
end
|
188
82
|
|
189
|
-
#
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
83
|
+
# Resolves the formatter to use
|
84
|
+
def formatter_for_model( object )
|
85
|
+
return @formatters[:feed] if @app.is_a_collection( object )
|
86
|
+
return @formatters[:service_doc] if @app.is_a_service_doc( object )
|
87
|
+
@formatters[:entry]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Performs actual formatting
|
91
|
+
def format( model, params )
|
92
|
+
formatter_for_model( model ).format( model, params )
|
93
|
+
end
|
94
|
+
|
95
|
+
# Resolves the specified options into the appropiate
|
96
|
+
# property handler
|
97
|
+
def property( options )
|
98
|
+
@props[options[:type]]||@props[:simple]
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
class SimpleProperty
|
104
|
+
|
105
|
+
def initialize( parent )
|
106
|
+
@parent = parent
|
107
|
+
end
|
108
|
+
|
109
|
+
def tag( options )
|
110
|
+
value = options[:tag]||options[:property]
|
111
|
+
return value.to_s
|
112
|
+
end
|
113
|
+
|
114
|
+
def object_value( object, options )
|
115
|
+
object.send options[:property]
|
116
|
+
end
|
117
|
+
|
118
|
+
def xml_value( object, options )
|
119
|
+
return object.get_value( tag( options ), options[:required]||true )
|
120
|
+
end
|
121
|
+
|
122
|
+
def bind( object, options, value )
|
123
|
+
object.send options[:property].to_s + "=", value
|
124
|
+
end
|
125
|
+
|
126
|
+
def parse( object, options, xml )
|
127
|
+
return if options[:bind] == :response
|
128
|
+
bind( object, options, value_from_xml( options, xml ) )
|
224
129
|
end
|
225
130
|
|
226
|
-
|
227
|
-
|
228
|
-
|
131
|
+
def value_from_xml( options, xml )
|
132
|
+
xml_value( xml, options )
|
133
|
+
end
|
134
|
+
|
135
|
+
def value_from_model( options, object )
|
136
|
+
object_value( object, options ).to_s
|
137
|
+
end
|
138
|
+
|
139
|
+
def format( object, options, xml )
|
140
|
+
return if options[:bind] == :request
|
141
|
+
value = value_from_model( options, object )
|
142
|
+
new_element = xml.add_element( tag( options ) )
|
143
|
+
new_element.add_text( value.to_s ) if value != nil
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
class DateProperty < SimpleProperty
|
149
|
+
|
150
|
+
def date2string( date )
|
151
|
+
date = Time.now if !date
|
152
|
+
date.strftime( ATOM_DATE_FORMAT )
|
153
|
+
end
|
154
|
+
|
155
|
+
def string2date( date )
|
156
|
+
Date.strptime( value, ATOM_DATE_FORMAT )
|
157
|
+
end
|
158
|
+
|
159
|
+
def value_from_model( options, object )
|
160
|
+
date2string( object_value( object, options ) )
|
161
|
+
end
|
162
|
+
|
163
|
+
def value_from_xml( options, xml )
|
164
|
+
string2date( xml_value( xml, options ) )
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
class DomainFormatter
|
170
|
+
def initialize( parent )
|
171
|
+
@parent = parent
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class FeedFormatter < DomainFormatter
|
176
|
+
|
177
|
+
def format( objects, params )
|
178
|
+
params[:content_type]="application/atom+xml"
|
179
|
+
xml = REXML::Document.new
|
180
|
+
xml << REXML::XMLDecl.default
|
181
|
+
feed = xml.add_element( "feed", NAMESPACES )
|
182
|
+
feed.add_element( "id" ).add_text( params[:path] )
|
183
|
+
feed.add_element( "title" ).add_text( params[:path] )
|
184
|
+
objects.each{ |o| @parent.formatter_for_name(:entry).format_entry( o, feed, params ) } if objects
|
185
|
+
return xml
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
class EntryFormatter < DomainFormatter
|
191
|
+
|
192
|
+
def format( object, params )
|
193
|
+
params[:content_type]="application/atom+xml"
|
194
|
+
xml = REXML::Document.new
|
195
|
+
xml << REXML::XMLDecl.default
|
196
|
+
format_entry( object, xml, params )
|
197
|
+
return xml
|
198
|
+
end
|
199
|
+
|
200
|
+
def format_entry( object, xml, params )
|
201
|
+
resource = @parent.app.resource_for_model( object )
|
202
|
+
raise "no resource found for entry #{object}" if !resource
|
229
203
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
204
|
+
entry = xml.add_element( "entry", NAMESPACES )
|
205
|
+
entry.add_element( "title" )
|
206
|
+
entry.add_element( "author" )
|
207
|
+
entry.add_element( "updated" )
|
208
|
+
entry.add_element( "id" )
|
209
|
+
entry.add_element( "summary" )
|
210
|
+
|
211
|
+
resource.links.each{ |link|
|
212
|
+
entry.add_element( "link", link )
|
213
|
+
}
|
214
|
+
|
215
|
+
content = entry.add_element( "rubyrest:content" )
|
216
|
+
resource.props.each{ |map|
|
217
|
+
@parent.property( map ).format( object, map, content )
|
218
|
+
}
|
246
219
|
end
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
class ServiceDocFormatter < DomainFormatter
|
224
|
+
|
225
|
+
def format( service_doc, params )
|
226
|
+
params[:content_type]="application/atomserv+xml"
|
227
|
+
xml = REXML::Document.new
|
228
|
+
xml << REXML::XMLDecl.default
|
229
|
+
service = xml.add_element( "service", NAMESPACES )
|
230
|
+
workspace = service.add_element( "workspace" )
|
231
|
+
workspace.add_element( "atom:title" )
|
232
|
+
service_doc.collections.each{ |col|
|
233
|
+
collection = workspace.add_element( "collection", { "href" => col.uri } )
|
234
|
+
collection.add_element( "atom:title" ).add_text( col.title )
|
235
|
+
collection.add_element( "accept" ).add_text( col.accept ) if col.accept
|
236
|
+
}
|
237
|
+
return xml
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
# Superclass provided by the framework, so that
|
243
|
+
# it's easy to identify resources to be formatted as
|
244
|
+
# service document entries
|
245
|
+
class ServiceDocument
|
246
|
+
|
247
|
+
attr_reader :collections
|
248
|
+
|
249
|
+
def initialize
|
250
|
+
@collections = []
|
251
|
+
end
|
252
|
+
|
253
|
+
def add( uri, title, accept )
|
254
|
+
@collections << ServiceCollection.new( uri, title, accept )
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
258
|
+
|
259
|
+
class ServiceCollection
|
260
|
+
|
261
|
+
attr_accessor :uri, :title, :accept
|
262
|
+
|
263
|
+
def initialize( uri, title, accept = nil )
|
264
|
+
@uri = uri
|
265
|
+
@title = title
|
266
|
+
@accept = accept
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
|
247
271
|
|
248
272
|
end
|
249
273
|
end
|
data/lib/rubyrest/client.rb
CHANGED
@@ -10,31 +10,25 @@ module RubyRest
|
|
10
10
|
class Default
|
11
11
|
|
12
12
|
# Configures the server name and port
|
13
|
-
def
|
14
|
-
@host =
|
13
|
+
def initialize( host, port )
|
14
|
+
@host = host
|
15
15
|
@port = port
|
16
16
|
end
|
17
17
|
|
18
|
-
#
|
19
|
-
#
|
20
|
-
def
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
# Returns the port number on which to
|
25
|
-
# connect to
|
26
|
-
def self.port
|
27
|
-
@port
|
18
|
+
# Encodes the specified path, by replacing spaces
|
19
|
+
# for the + character. The server will decode
|
20
|
+
def encode_path( path )
|
21
|
+
path.gsub( " ", "+" )
|
28
22
|
end
|
29
23
|
|
30
24
|
# Converts the specified hash of data into a
|
31
25
|
# query string, then performs a GET http request.
|
32
26
|
# The response body is returned as an an Atom document if
|
33
27
|
# the response status code is 200.
|
34
|
-
def retrieve( path, data, api_key )
|
28
|
+
def retrieve( path, data = nil, api_key = nil )
|
35
29
|
path << to_query_string( data ) if data
|
36
30
|
headers = prepare_headers( api_key )
|
37
|
-
rsp = http.get( path, headers )
|
31
|
+
rsp = http.get( encode_path( path ), headers )
|
38
32
|
return to_xml( rsp ) if rsp.code.to_i == 200
|
39
33
|
end
|
40
34
|
|
@@ -42,11 +36,11 @@ module RubyRest
|
|
42
36
|
# simplified Atom Entry, then performs a POST http request.
|
43
37
|
# The response body is returned as an an Atom Entry if
|
44
38
|
# the response status code is 201.
|
45
|
-
def create( path, data, api_key )
|
39
|
+
def create( path, data, api_key = nil )
|
46
40
|
body = nil
|
47
41
|
body = hash2entry( data ).to_s if data != nil
|
48
42
|
headers = prepare_headers( api_key )
|
49
|
-
rsp = http.post( path, body, headers )
|
43
|
+
rsp = http.post( encode_path( path ), body, headers )
|
50
44
|
return to_xml( rsp ) if rsp.code.to_i == 201
|
51
45
|
end
|
52
46
|
|
@@ -54,21 +48,21 @@ module RubyRest
|
|
54
48
|
# simplified Atom Entry, then performs a PUT http request.
|
55
49
|
# The response body is returned as an an Atom Entry if
|
56
50
|
# the response status code is 200.
|
57
|
-
def update( path, data, api_key )
|
51
|
+
def update( path, data, api_key = nil)
|
58
52
|
body = nil
|
59
53
|
body = hash2entry( data ).to_s if data != nil
|
60
54
|
headers = prepare_headers( api_key )
|
61
|
-
rsp = http.put( path, body, headers )
|
55
|
+
rsp = http.put( encode_path( path ), body, headers )
|
62
56
|
return to_xml( rsp ) if rsp.code.to_i == 200
|
63
57
|
end
|
64
58
|
|
65
59
|
# Converts the specified hash of data into a
|
66
60
|
# query string, then performs a DELETE http request.
|
67
61
|
# This method simply returns the response status code
|
68
|
-
def delete( path, data, api_key )
|
62
|
+
def delete( path, data =nil, api_key = nil )
|
69
63
|
path << to_query_string( data ) if data
|
70
64
|
headers = prepare_headers( api_key )
|
71
|
-
rsp = http.delete( path, headers )
|
65
|
+
rsp = http.delete( encode_path( path ), headers )
|
72
66
|
rsp.code.to_i
|
73
67
|
end
|
74
68
|
|
@@ -81,7 +75,7 @@ module RubyRest
|
|
81
75
|
# Convenience method that returns a new HTTP object
|
82
76
|
# for each call
|
83
77
|
def http
|
84
|
-
Net::HTTP.new(
|
78
|
+
Net::HTTP.new( @host, @port )
|
85
79
|
end
|
86
80
|
|
87
81
|
# Builds a headers hash, with the specified
|
@@ -95,13 +89,9 @@ module RubyRest
|
|
95
89
|
# Converts the specified hash of data into a simplified
|
96
90
|
# Atom Entry document.
|
97
91
|
def hash2entry( data )
|
98
|
-
doc = RubyRest::Atom::
|
99
|
-
|
100
|
-
content
|
101
|
-
data.each { |name,value|
|
102
|
-
body = REXML::Element.new( name.to_s, content )
|
103
|
-
body.text = value
|
104
|
-
}
|
92
|
+
doc = RubyRest::Atom::Document.new
|
93
|
+
content = doc.add_element( "entry" ).add_element( "rubyrest:content" )
|
94
|
+
data.each { |name,value| content.add_element( name.to_s ).add_text( value ) }
|
105
95
|
return doc
|
106
96
|
end
|
107
97
|
|
@@ -109,7 +99,7 @@ module RubyRest
|
|
109
99
|
# which can be a Feed or Entry, or Service Document
|
110
100
|
def to_xml( rsp )
|
111
101
|
begin
|
112
|
-
RubyRest::Atom::
|
102
|
+
RubyRest::Atom::Document.new( rsp.body ) if rsp.body
|
113
103
|
rescue => e
|
114
104
|
puts "unable to parse response body: " + e.message
|
115
105
|
puts "---- response body ----"
|