rc-rest 2.0.0 → 2.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/History.txt CHANGED
@@ -1,3 +1,9 @@
1
+ = 2.1.0
2
+
3
+ * Don't split strings into extra params pairs on newlines
4
+ * Added RCRest#post_multipart and RCRest#make_multipart
5
+ * Fixed various query parameter incompatiblities
6
+
1
7
  = 2.0.0
2
8
 
3
9
  * Added +method+ parameter to RCRest#make_url to add path components
data/Rakefile CHANGED
@@ -1,13 +1,13 @@
1
1
  require 'hoe'
2
2
  require './lib/rc_rest'
3
3
 
4
- DEV_DOC_PATH = 'Libraries/rc_rest'
4
+ DEV_DOC_PATH = 'Libraries/rc-rest'
5
5
 
6
6
  hoe = Hoe.new 'rc-rest', RCRest::VERSION do |p|
7
7
  p.summary = 'Robot Co-op REST web services base class'
8
8
  p.description = 'This library makes it easy to implement REST-like web services APIs.'
9
9
  p.author = 'Eric Hodel'
10
- p.email = 'eric@robotcoop.com'
10
+ p.email = 'drbrain@segment7.net'
11
11
  p.url = "http://dev.robotcoop.com/#{DEV_DOC_PATH}"
12
12
  p.rubyforge_name = 'rctools'
13
13
 
@@ -18,7 +18,7 @@ SPEC = hoe.spec
18
18
 
19
19
  begin
20
20
  require '../tasks'
21
- rescue RuntimeError
21
+ rescue LoadError
22
22
  end
23
23
 
24
24
  # vim: syntax=Ruby
data/lib/rc_rest.rb CHANGED
@@ -14,7 +14,7 @@ require 'rexml/document'
14
14
  # +parse_response+:: Extracts information from the server response.
15
15
  #
16
16
  # If you have extra URL paramaters (application id, output type) or need to
17
- # perform URL customization, override +make_url+.
17
+ # perform URL customization, override +make_url+ and +make_multipart+.
18
18
  #
19
19
  # class FakeService < RCRest
20
20
  #
@@ -49,7 +49,7 @@ class RCRest
49
49
  ##
50
50
  # You are using this version of RCRest
51
51
 
52
- VERSION = '2.0.0'
52
+ VERSION = '2.1.0'
53
53
 
54
54
  ##
55
55
  # Abstract Error class.
@@ -74,6 +74,20 @@ class RCRest
74
74
  raise NotImplementedError
75
75
  end
76
76
 
77
+ def expand_params(params) # :nodoc:
78
+ expanded_params = []
79
+
80
+ params.each do |k,v|
81
+ if v.respond_to? :each and not String === v then
82
+ v.each { |v| expanded_params << [k, v] }
83
+ else
84
+ expanded_params << [k, v]
85
+ end
86
+ end
87
+
88
+ expanded_params.sort_by { |k,v| [k.to_s, v.to_s] }
89
+ end
90
+
77
91
  ##
78
92
  # Performs a GET request for method +method+ with +params+. Calls
79
93
  # #parse_response on the concrete class with an REXML::Document instance and
@@ -126,27 +140,39 @@ class RCRest
126
140
  # http://example.com/api/method?a=1
127
141
 
128
142
  def make_url(method, params = nil)
129
- expanded_params = []
130
-
131
- params.each do |k,v|
132
- if v.respond_to? :each then
133
- v.each { |v| expanded_params << [k, v] }
134
- else
135
- expanded_params << [k, v]
136
- end
143
+ escaped_params = expand_params(params).map do |k,v|
144
+ k = URI.escape(k.to_s).gsub(';', '%3B').gsub('+', '%2B').gsub('&', '%26')
145
+ v = URI.escape(v.to_s).gsub(';', '%3B').gsub('+', '%2B').gsub('&', '%26')
146
+ "#{k}=#{v}"
137
147
  end
138
148
 
139
- sorted_params = expanded_params.sort_by { |k,v| [k.to_s, v.to_s] }
140
-
141
- escaped_params = sorted_params.map do |k,v|
142
- "#{URI.escape k.to_s}=#{URI.escape v.to_s}"
143
- end
149
+ query = escaped_params.join '&'
144
150
 
145
151
  url = @url + "./#{method}"
146
- url.query = escaped_params.join '&'
152
+ url.query = query
147
153
  return url
148
154
  end
149
155
 
156
+ ##
157
+ # Creates a multipart form post for the Hash of parameters +params+.
158
+ # Override this then call super if you need to add extra params like an
159
+ # application id, output type, etc.
160
+ #
161
+ # #make_multipart handles arguments similarly to #make_url.
162
+
163
+ def make_multipart(params)
164
+ boundary = (0...8).map { rand(255).to_s 16 }.join '_'
165
+ data = expand_params(params).map do |key, value|
166
+ [ "--#{boundary}",
167
+ "Content-Disposition: form-data; name=\"#{key}\"",
168
+ nil,
169
+ value]
170
+ end
171
+
172
+ data << "--#{boundary}--"
173
+ return [boundary, data.join("\r\n")]
174
+ end
175
+
150
176
  ##
151
177
  # Must parse results from +xml+, an REXML::Document, into something sensible
152
178
  # for the API.
@@ -184,5 +210,35 @@ class RCRest
184
210
  raise
185
211
  end
186
212
 
213
+ ##
214
+ # Performs a POST request for method +method+ with +params+, submitting a
215
+ # multipart form. Calls #parse_response on the concrete class with an
216
+ # REXML::Document instance and returns its result.
217
+
218
+ def post_multipart(method, params = {})
219
+ url = make_url method, {}
220
+ url.query = nil
221
+
222
+ boundary, data = make_multipart params
223
+
224
+ req = Net::HTTP::Post.new url.path
225
+ req.content_type = "multipart/form-data; boundary=#{boundary}"
226
+ req.body = data
227
+
228
+ res = Net::HTTP.start url.host, url.port do |http|
229
+ http.request req
230
+ end
231
+
232
+ xml = REXML::Document.new res.body
233
+
234
+ check_error xml
235
+
236
+ return parse_response(xml)
237
+ rescue Net::HTTPError => e
238
+ xml = REXML::Document.new e.res.body
239
+ check_error xml
240
+ raise
241
+ end
242
+
187
243
  end
188
244
 
data/test/test_rc_rest.rb CHANGED
@@ -25,6 +25,10 @@ class FakeService < RCRest
25
25
  post :method, :param => 'value'
26
26
  end
27
27
 
28
+ def do_post_multipart
29
+ post_multipart :method, :param => 'value'
30
+ end
31
+
28
32
  def parse_response(xml)
29
33
  return xml
30
34
  end
@@ -42,6 +46,8 @@ class TestFakeService < Test::Unit::TestCase
42
46
  Net::HTTP.responses = []
43
47
 
44
48
  @fs = FakeService.new
49
+
50
+ srand 0
45
51
  end
46
52
 
47
53
  def test_check_error
@@ -94,6 +100,30 @@ class TestFakeService < Test::Unit::TestCase
94
100
  assert_equal '/method', Net::HTTP.paths.first
95
101
  end
96
102
 
103
+ def test_do_post_multipart
104
+ xml = '<result>stuff</result>'
105
+ Net::HTTP.responses << xml
106
+
107
+ result = @fs.do_post_multipart
108
+
109
+ assert_equal xml, result.to_s
110
+
111
+ assert_equal 1, Net::HTTP.params.length
112
+ assert_equal 1, Net::HTTP.paths.length
113
+ assert_empty Net::HTTP.responses
114
+
115
+ expected = <<-EOF.strip
116
+ --ac_2f_75_c0_43_fb_c3_67\r
117
+ Content-Disposition: form-data; name="param"\r
118
+ \r
119
+ value\r
120
+ --ac_2f_75_c0_43_fb_c3_67--
121
+ EOF
122
+
123
+ assert_equal expected, Net::HTTP.params.first
124
+ assert_equal '/method', Net::HTTP.paths.first
125
+ end
126
+
97
127
  end
98
128
 
99
129
  class TestRCRest < Test::Unit::TestCase
@@ -111,13 +141,52 @@ class TestRCRest < Test::Unit::TestCase
111
141
  assert_raise NotImplementedError do r.check_error nil end
112
142
  end
113
143
 
144
+ def test_make_multipart
145
+ srand 0
146
+
147
+ r = RCRest.allocate
148
+ boundary, data = r.make_multipart :a => 'b c', :x => 'y z',
149
+ :array => ['v2', 'v1'],
150
+ :newlines => "a\nb"
151
+
152
+ assert_equal 'ac_2f_75_c0_43_fb_c3_67', boundary
153
+
154
+ expected = <<-EOF.strip
155
+ --ac_2f_75_c0_43_fb_c3_67\r
156
+ Content-Disposition: form-data; name="a"\r
157
+ \r
158
+ b c\r
159
+ --ac_2f_75_c0_43_fb_c3_67\r
160
+ Content-Disposition: form-data; name="array"\r
161
+ \r
162
+ v1\r
163
+ --ac_2f_75_c0_43_fb_c3_67\r
164
+ Content-Disposition: form-data; name="array"\r
165
+ \r
166
+ v2\r
167
+ --ac_2f_75_c0_43_fb_c3_67\r
168
+ Content-Disposition: form-data; name="newlines"\r
169
+ \r
170
+ a
171
+ b\r
172
+ --ac_2f_75_c0_43_fb_c3_67\r
173
+ Content-Disposition: form-data; name="x"\r
174
+ \r
175
+ y z\r
176
+ --ac_2f_75_c0_43_fb_c3_67--
177
+ EOF
178
+
179
+ assert_equal expected, data
180
+ end
181
+
114
182
  def test_make_url
115
183
  r = RCRest.allocate
116
184
  r.instance_variable_set :@url, URI.parse('http://example.com/')
117
185
 
118
- url = r.make_url :method, :a => 'b c', :x => 'y z', :array => ['v2', 'v1']
186
+ url = r.make_url :method, :a => 'b c', :x => 'y z', :array => ['v2', 'v1'],
187
+ :newlines => "a\nb", :funky => 'a;b+c&d'
119
188
 
120
- assert_equal 'http://example.com/method?a=b%20c&array=v1&array=v2&x=y%20z',
189
+ assert_equal 'http://example.com/method?a=b%20c&array=v1&array=v2&funky=a%3Bb%2Bc%26d&newlines=a%0Ab&x=y%20z',
121
190
  url.to_s
122
191
  end
123
192
 
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.0.6
2
+ rubygems_version: 0.9.0.7
3
3
  specification_version: 1
4
4
  name: rc-rest
5
5
  version: !ruby/object:Gem::Version
6
- version: 2.0.0
7
- date: 2006-11-27 00:00:00 -08:00
6
+ version: 2.1.0
7
+ date: 2006-11-29 00:00:00 -08:00
8
8
  summary: Robot Co-op REST web services base class
9
9
  require_paths:
10
10
  - lib
11
- email: eric@robotcoop.com
12
- homepage: http://dev.robotcoop.com/Libraries/rc_rest
11
+ email: drbrain@segment7.net
12
+ homepage: http://dev.robotcoop.com/Libraries/rc-rest
13
13
  rubyforge_project: rctools
14
14
  description: This library makes it easy to implement REST-like web services APIs.
15
15
  autorequire:
@@ -58,5 +58,5 @@ dependencies:
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 1.1.4
61
+ version: 1.1.5
62
62
  version: