rc-rest 1.0.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/LICENSE +27 -0
- data/Manifest.txt +7 -0
- data/README +27 -0
- data/Rakefile +66 -0
- data/lib/rc_rest.rb +115 -0
- data/lib/rc_rest/uri_stub.rb +51 -0
- data/test/test_rc_rest.rb +97 -0
- metadata +52 -0
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright 2006 Eric Hodel, The Robot Co-op. All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
4
|
+
modification, are permitted provided that the following conditions
|
5
|
+
are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright
|
10
|
+
notice, this list of conditions and the following disclaimer in the
|
11
|
+
documentation and/or other materials provided with the distribution.
|
12
|
+
3. Neither the names of the authors nor the names of their contributors
|
13
|
+
may be used to endorse or promote products derived from this software
|
14
|
+
without specific prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
|
17
|
+
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
19
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
|
20
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
21
|
+
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
22
|
+
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
23
|
+
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
24
|
+
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
25
|
+
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
26
|
+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
|
data/Manifest.txt
ADDED
data/README
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
= rc-rest
|
2
|
+
|
3
|
+
Rubyforge Project:
|
4
|
+
|
5
|
+
http://rubyforge.org/projects/rctools/
|
6
|
+
|
7
|
+
Documentation:
|
8
|
+
|
9
|
+
http://dev.robotcoop.com/Libraries/rc-rest/
|
10
|
+
|
11
|
+
== About
|
12
|
+
|
13
|
+
This is an abstract class for REST web service APIs. By itself it isn't at all
|
14
|
+
useful.
|
15
|
+
|
16
|
+
== Installing rc-rest
|
17
|
+
|
18
|
+
Just install the gem:
|
19
|
+
|
20
|
+
$ sudo gem install rc-rest
|
21
|
+
|
22
|
+
== Using rc-rest
|
23
|
+
|
24
|
+
rc-rest is used by gems such as yahoo-search, google-geocode and geocoder-us.
|
25
|
+
If you'd like to write bindings a web service using rc-rest see RCRest, its
|
26
|
+
tests or the above-mentioned gems for examples.
|
27
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
|
7
|
+
$VERBOSE = nil
|
8
|
+
|
9
|
+
spec = Gem::Specification.new do |s|
|
10
|
+
s.name = 'rc-rest'
|
11
|
+
s.version = '1.0.0'
|
12
|
+
s.summary = 'Robot Co-op REST web services base class'
|
13
|
+
s.description = 'This library makes it easy to implement REST-like web services APIs.'
|
14
|
+
s.author = 'Eric Hodel'
|
15
|
+
s.email = 'eric@robotcoop.com'
|
16
|
+
|
17
|
+
s.has_rdoc = true
|
18
|
+
s.files = File.read('Manifest.txt').split($/)
|
19
|
+
s.require_path = 'lib'
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'Run tests'
|
23
|
+
task :default => [ :test ]
|
24
|
+
|
25
|
+
Rake::TestTask.new('test') do |t|
|
26
|
+
t.libs << 'test'
|
27
|
+
t.pattern = 'test/test_*.rb'
|
28
|
+
t.verbose = true
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'Update Manifest.txt'
|
32
|
+
task :update_manifest do
|
33
|
+
sh "find . -type f | sed -e 's%./%%' | egrep -v 'svn|swp|~' | egrep -v '^(doc|pkg)/' | sort > Manifest.txt"
|
34
|
+
end
|
35
|
+
|
36
|
+
desc 'Generate RDoc'
|
37
|
+
Rake::RDocTask.new :rdoc do |rd|
|
38
|
+
rd.rdoc_dir = 'doc'
|
39
|
+
rd.rdoc_files.add 'lib', 'README', 'LICENSE'
|
40
|
+
rd.main = 'README'
|
41
|
+
rd.options << '-d' if `which dot` =~ /\/dot/
|
42
|
+
rd.options << '-t Robot Co-op REST Web Services'
|
43
|
+
end
|
44
|
+
|
45
|
+
desc 'Generate RDoc for dev.robotcoop.com'
|
46
|
+
Rake::RDocTask.new :dev_rdoc do |rd|
|
47
|
+
rd.rdoc_dir = '../../../www/trunk/dev/html/Libraries/rc-rest'
|
48
|
+
rd.rdoc_files.add 'lib', 'README', 'LICENSE'
|
49
|
+
rd.main = 'README'
|
50
|
+
rd.options << '-d' if `which dot` =~ /\/dot/
|
51
|
+
rd.options << '-t Robot Co-op REST Web Services'
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'Build Gem'
|
55
|
+
Rake::GemPackageTask.new spec do |pkg|
|
56
|
+
pkg.need_tar = true
|
57
|
+
end
|
58
|
+
|
59
|
+
desc 'Clean up'
|
60
|
+
task :clean => [ :clobber_rdoc, :clobber_package ]
|
61
|
+
|
62
|
+
desc 'Clean up'
|
63
|
+
task :clobber => [ :clean ]
|
64
|
+
|
65
|
+
# vim: syntax=Ruby
|
66
|
+
|
data/lib/rc_rest.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'rexml/document'
|
3
|
+
|
4
|
+
##
|
5
|
+
# Abstract class for implementing REST APIs.
|
6
|
+
#
|
7
|
+
# === Example
|
8
|
+
#
|
9
|
+
# The following methods must be implemented in sublcasses:
|
10
|
+
#
|
11
|
+
# +initialize+:: Sets @url to the service enpoint.
|
12
|
+
# +check_error+:: Checks for errors in the server response.
|
13
|
+
# +parse_response+:: Extracts information from the server response.
|
14
|
+
#
|
15
|
+
# If you have extra URL paramaters (application id, output type) or need to
|
16
|
+
# perform URL customization, override +make_url+.
|
17
|
+
#
|
18
|
+
# class FakeService < RCRest
|
19
|
+
#
|
20
|
+
# class Error < RCRest::Error; end
|
21
|
+
#
|
22
|
+
# def initialize(appid)
|
23
|
+
# @appid = appid
|
24
|
+
# @url = URI.parse 'http://example.com/test'
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# def check_error(xml)
|
28
|
+
# raise Error, xml.elements['error'].text if xml.elements['error']
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# def make_url(params)
|
32
|
+
# params[:appid] = @appid
|
33
|
+
# super params
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# def parse_response(xml)
|
37
|
+
# return xml
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# def test(query)
|
41
|
+
# get :q => query
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# end
|
45
|
+
|
46
|
+
class RCRest
|
47
|
+
|
48
|
+
##
|
49
|
+
# Error class.
|
50
|
+
|
51
|
+
class Error < RuntimeError; end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Web services initializer.
|
55
|
+
#
|
56
|
+
# Concrete web services implementations must set the +url+ instance
|
57
|
+
# variable which must be a URI.
|
58
|
+
|
59
|
+
def initialize
|
60
|
+
raise NotImplementedError
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Must extract and raise an error from +xml+, an REXML::Document, if any.
|
65
|
+
# Must returns if no error could be found.
|
66
|
+
|
67
|
+
def check_error(xml)
|
68
|
+
raise NotImplementedError
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Performs a GET request with +params+. Calls the parse_response method on
|
73
|
+
# the concrete class with an REXML::Document instance and returns its
|
74
|
+
# result.
|
75
|
+
|
76
|
+
def get(params = {})
|
77
|
+
url = make_url params
|
78
|
+
|
79
|
+
url.open do |xml|
|
80
|
+
res = REXML::Document.new xml.read
|
81
|
+
|
82
|
+
check_error res
|
83
|
+
|
84
|
+
return parse_response(res)
|
85
|
+
end
|
86
|
+
rescue OpenURI::HTTPError => e
|
87
|
+
xml = REXML::Document.new e.io.read
|
88
|
+
check_error xml
|
89
|
+
raise
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Creates a URI from the Hash +params+. Override this then call super if
|
94
|
+
# you need to add extra params like an application id or output type.
|
95
|
+
|
96
|
+
def make_url(params)
|
97
|
+
escaped_params = params.sort_by { |k,v| k.to_s }.map do |k,v|
|
98
|
+
"#{URI.escape k.to_s}=#{URI.escape v.to_s}"
|
99
|
+
end
|
100
|
+
|
101
|
+
url = @url.dup
|
102
|
+
url.query = escaped_params.join '&'
|
103
|
+
return url
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Must parse results from +xml+, an REXML::Document, into something sensible
|
108
|
+
# for the API.
|
109
|
+
|
110
|
+
def parse_response(xml)
|
111
|
+
raise NotImplementedError
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module URI # :nodoc:
|
4
|
+
end
|
5
|
+
|
6
|
+
##
|
7
|
+
# This stub overrides OpenURI's open method to allow programs that use OpenURI
|
8
|
+
# to be easily tested.
|
9
|
+
#
|
10
|
+
# == Usage
|
11
|
+
#
|
12
|
+
# require 'rc_rest/uri_stub'
|
13
|
+
#
|
14
|
+
# class TestMyClass < Test::Unit::TestCase
|
15
|
+
#
|
16
|
+
# def setup
|
17
|
+
# URI::HTTP.responses = []
|
18
|
+
# URI::HTTP.uris = []
|
19
|
+
#
|
20
|
+
# @obj = MyClass.new
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# def test_my_method
|
24
|
+
# URI::HTTP.responses << 'some text open would ordinarily return'
|
25
|
+
#
|
26
|
+
# result = @obj.my_method
|
27
|
+
#
|
28
|
+
# assert_equal :something_meaninfgul, result
|
29
|
+
#
|
30
|
+
# assert_equal true, URI::HTTP.responses.empty?
|
31
|
+
# assert_equal 1, URI::HTTP.uris.length
|
32
|
+
# assert_equal 'http://example.com/path', URI::HTTP.uris.first
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# end
|
36
|
+
|
37
|
+
class URI::HTTP # :nodoc:
|
38
|
+
|
39
|
+
class << self
|
40
|
+
attr_accessor :responses, :uris
|
41
|
+
end
|
42
|
+
|
43
|
+
alias original_open open
|
44
|
+
|
45
|
+
def open
|
46
|
+
self.class.uris << self.to_s
|
47
|
+
yield StringIO.new(self.class.responses.shift)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rc_rest/uri_stub'
|
3
|
+
require 'rc_rest'
|
4
|
+
|
5
|
+
class FakeService < RCRest
|
6
|
+
|
7
|
+
class Error < RCRest::Error; end
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@url = URI.parse 'http://example.com/test'
|
11
|
+
end
|
12
|
+
|
13
|
+
def check_error(xml)
|
14
|
+
raise Error, xml.elements['error'].text if xml.elements['error']
|
15
|
+
end
|
16
|
+
|
17
|
+
def test
|
18
|
+
get
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_response(xml)
|
22
|
+
return xml
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class TestFakeService < Test::Unit::TestCase
|
28
|
+
|
29
|
+
def setup
|
30
|
+
URI::HTTP.responses = []
|
31
|
+
URI::HTTP.uris = []
|
32
|
+
|
33
|
+
@fs = FakeService.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_check_error
|
37
|
+
xml = REXML::Document.new '<error>you broked it</error>'
|
38
|
+
@fs.check_error xml
|
39
|
+
|
40
|
+
rescue FakeService::Error => e
|
41
|
+
assert_equal 'you broked it', e.message
|
42
|
+
|
43
|
+
else
|
44
|
+
flunk 'expected an error'
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_get
|
48
|
+
xml = '<result>stuff</result>'
|
49
|
+
URI::HTTP.responses << xml
|
50
|
+
|
51
|
+
result = @fs.test
|
52
|
+
|
53
|
+
assert_equal xml, result.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_get_error
|
57
|
+
def @fs.make_url(*args) # HACK extend uri_stub with error raising ability
|
58
|
+
u = Object.new
|
59
|
+
def u.open
|
60
|
+
xml = '<error>you did the bad thing</error>'
|
61
|
+
raise OpenURI::HTTPError.new('400 Bad Request', StringIO.new(xml))
|
62
|
+
end
|
63
|
+
return u
|
64
|
+
end
|
65
|
+
|
66
|
+
assert_raise FakeService::Error do @fs.test end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
class TestRCRest < Test::Unit::TestCase
|
72
|
+
|
73
|
+
def test_initialize
|
74
|
+
assert_raise NotImplementedError do RCRest.new end
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_check_error
|
78
|
+
r = RCRest.allocate
|
79
|
+
assert_raise NotImplementedError do r.check_error nil end
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_make_url
|
83
|
+
r = RCRest.allocate
|
84
|
+
r.instance_variable_set :@url, URI.parse('http://example.com/')
|
85
|
+
|
86
|
+
url = r.make_url :a => 'b c', :x => 'y z'
|
87
|
+
|
88
|
+
assert_equal 'http://example.com/?a=b%20c&x=y%20z', url.to_s
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_parse_response
|
92
|
+
r = RCRest.allocate
|
93
|
+
assert_raise NotImplementedError do r.parse_response nil end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
metadata
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.99
|
3
|
+
specification_version: 1
|
4
|
+
name: rc-rest
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 1.0.0
|
7
|
+
date: 2006-06-15 00:00:00 -07:00
|
8
|
+
summary: Robot Co-op REST web services base class
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: eric@robotcoop.com
|
12
|
+
homepage:
|
13
|
+
rubyforge_project:
|
14
|
+
description: This library makes it easy to implement REST-like web services APIs.
|
15
|
+
autorequire:
|
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
|
+
- Eric Hodel
|
31
|
+
files:
|
32
|
+
- LICENSE
|
33
|
+
- Manifest.txt
|
34
|
+
- README
|
35
|
+
- Rakefile
|
36
|
+
- lib/rc_rest.rb
|
37
|
+
- lib/rc_rest/uri_stub.rb
|
38
|
+
- test/test_rc_rest.rb
|
39
|
+
test_files: []
|
40
|
+
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
executables: []
|
46
|
+
|
47
|
+
extensions: []
|
48
|
+
|
49
|
+
requirements: []
|
50
|
+
|
51
|
+
dependencies: []
|
52
|
+
|