qoobaa-opensocial 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.
@@ -0,0 +1,263 @@
1
+ # Copyright (c) 2008 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module OpenSocial #:nodoc:
16
+
17
+ # Provides a wrapper for a single request to an OpenSocial endpoint, either
18
+ # as a standalone request, or as a fragment of an RPC request.
19
+ #
20
+ # The Request class wraps an HTTP request to an OpenSocial endpoint for
21
+ # social data. A Request may be used, directly, to access social resources
22
+ # that may not be currently wrapped by Request's child classes. Used in this
23
+ # was it gives near-raw access to social data. The Request class supplies
24
+ # OAuth signing when the supplied connection contains appropriate
25
+ # credentials.
26
+ #
27
+
28
+
29
+ class Request
30
+ GET = ".get"
31
+
32
+ # Defines the connection that will be used in the request.
33
+ attr_accessor :connection
34
+
35
+ # Defines the guid for the request.
36
+ attr_accessor :guid
37
+
38
+ # Defines the selector for the request.
39
+ attr_accessor :selector
40
+
41
+ # Defines the pid for the request.
42
+ attr_accessor :pid
43
+
44
+ # Defines the key used to lookup the request result in an RPC request.
45
+ attr_accessor :key
46
+
47
+ # Initializes a request using the optionally supplied connection, guid,
48
+ # selector, and pid.
49
+ def initialize(connection = nil, guid = nil, selector = nil, pid = nil)
50
+ @connection = connection
51
+ @guid = guid
52
+ @selector = selector
53
+ @pid = pid
54
+ end
55
+
56
+ # Generates a request given the service, guid, selector, and pid, to the
57
+ # OpenSocial endpoint by constructing the service URI and dispatching the
58
+ # request. When data is returned, it is parsed as JSON after being
59
+ # optionally unescaped.
60
+ def send_request(service, guid, selector = nil, pid = nil,
61
+ unescape = false, extra_fields = {})
62
+ if !@connection
63
+ raise RequestException.new("Request requires a valid connection.")
64
+ end
65
+
66
+ uri = @connection.service_uri(@connection.container[:rest] + service,
67
+ guid, selector, pid, extra_fields)
68
+ data = dispatch(uri)
69
+
70
+ if unescape
71
+ JSON.parse(data.os_unescape)
72
+ else
73
+ JSON.parse(data)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ # Dispatches a request to a given URI with optional POST data. If a
80
+ # request's connection has specified HMAC-SHA1 authentication, OAuth
81
+ # parameters and signature are appended to the request.
82
+ def dispatch(uri, post_data = nil)
83
+ http = Net::HTTP.new(uri.host, uri.port)
84
+
85
+ if post_data
86
+ req = Net::HTTP::Post.new(uri.request_uri)
87
+
88
+ # FIXME: post_data is a serialized JSON string - set_form_data
89
+ # requires a hash, using body= method instead (doesn't work as
90
+ # well)
91
+
92
+ # req.set_form_data(post_data)
93
+ req.body = post_data
94
+ else
95
+ req = Net::HTTP::Get.new(uri.request_uri)
96
+ end
97
+
98
+ @connection.sign!(http, req)
99
+
100
+ if post_data
101
+ resp = http.post(req.path, post_data)
102
+ check_for_json_error!(resp)
103
+ else
104
+ resp = http.get(req.path)
105
+ check_for_http_error!(resp, uri) # TODO uri for debugging only
106
+ end
107
+
108
+ return resp.body
109
+ end
110
+
111
+ # Checks the response object's status code. If the response is is
112
+ # unauthorized, an exception is raised.
113
+ def check_for_http_error!(resp, req_uri=nil) # TODO uri for debugging only
114
+ if !resp.kind_of?(Net::HTTPSuccess)
115
+ if resp.is_a?(Net::HTTPUnauthorized)
116
+ if Object.const_defined?(:HoptoadNotifier)
117
+ HoptoadNotifier.notify(:error_class => "Myspace API Auth error",
118
+ :error_message => "Myspace API Auth error",
119
+ :request => {:params => {"body" => resp.body,
120
+ "uri" => req_uri.to_s}})
121
+ end
122
+ raise AuthException.new("The request lacked proper authentication " +
123
+ "credentials to retrieve data.")
124
+ else
125
+ resp.value
126
+ end
127
+ end
128
+ end
129
+
130
+ # Checks the JSON response for a status code. If a code is present an
131
+ # exception is raised.
132
+ def check_for_json_error!(resp)
133
+ json = JSON.parse(resp.body)
134
+ if json.is_a?(Hash) && json.has_key?("code") && json.has_key?("message")
135
+ rc = json["code"]
136
+ message = json["message"]
137
+ case rc
138
+ when 401
139
+ raise AuthException.new("The request lacked proper authentication " +
140
+ "credentials to retrieve data.")
141
+ else
142
+ raise RequestException.new("The request returned an unsupported " +
143
+ "status code: #{rc} #{message}.")
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ # Provides a wrapper for a single RPC request to an OpenSocial endpoint,
150
+ # composed of one or more individual requests.
151
+ #
152
+ # The RpcRequest class wraps an HTTP request to an OpenSocial endpoint for
153
+ # social data. An RpcRequest is intended to be used as a container for one
154
+ # or more Requests (or Fetch*Requests), but may also be used with a manually
155
+ # constructed post body. The RpcRequest class uses OAuth signing inherited
156
+ # from the Request class, when appropriate OAuth credentials are supplied.
157
+ #
158
+
159
+
160
+ class RpcRequest < Request
161
+
162
+ # Defines the requests sent in the single RpcRequest. The requests are
163
+ # stored a key/value pairs.
164
+ attr_accessor :requests
165
+
166
+ # Initializes an RpcRequest with the supplied connection and an optional
167
+ # hash of requests.
168
+ def initialize(connection, requests = {})
169
+ @connection = connection
170
+
171
+ @requests = requests
172
+ end
173
+
174
+ # Adds one or more requests to the RpcRequest. Expects a hash of key/value
175
+ # pairs (key used to refernece the data when it returns => the Request).
176
+ def add(requests = {})
177
+ @requests.merge!(requests)
178
+ end
179
+
180
+ # Sends an RpcRequest to the OpenSocial endpoint by constructing JSON for
181
+ # the POST body and delegating the request to send_request. If an
182
+ # RpcRequest is sent with an empty list of requests, an exception is
183
+ # thrown. The response JSON is optionally unescaped (defaulting to true).
184
+ def send(unescape = true)
185
+ if @requests.length == 0
186
+ raise RequestException.new("RPC request requires a non-empty hash " +
187
+ "of requests in order to be sent.")
188
+ end
189
+
190
+ json = send_request(request_json, unescape)
191
+ end
192
+
193
+ # Sends an RpcRequest to the OpenSocial endpoint by constructing the
194
+ # service URI and dispatching the request. This method is public so that
195
+ # an arbitrary POST body can be constructed and sent. The response JSON is
196
+ # optionally unescaped.
197
+ def send_request(post_data, unescape)
198
+ uri = @connection.service_uri(@connection.container[:rpc], nil, nil, nil)
199
+ data = dispatch(uri, post_data)
200
+
201
+ parse_response(data, unescape)
202
+ end
203
+
204
+ private
205
+
206
+ # Parses the response JSON. First, the JSON is unescaped, when specified,
207
+ # then for each element specified in @requests, the appropriate response
208
+ # is selected from the larger JSON response. This element is then delegated
209
+ # to the appropriate class to be turned into a native object (Person,
210
+ # Activity, etc.)
211
+ def parse_response(response, unescape)
212
+ if unescape
213
+ parsed = JSON.parse(response.os_unescape)
214
+ else
215
+ parsed = JSON.parse(response)
216
+ end
217
+ keyed_by_id = key_by_id(parsed)
218
+
219
+ native_objects = {}
220
+ @requests.each_pair do |key, request|
221
+ native_object = request.parse_rpc_response(keyed_by_id[key.to_s])
222
+ native_objects.merge!({key => native_object})
223
+ end
224
+
225
+ return native_objects
226
+ end
227
+
228
+ # Constructs a hash of the elements in data referencing each element
229
+ # by its 'id' attribute.
230
+ def key_by_id(data)
231
+ keyed_by_id = {}
232
+ for entry in data
233
+ keyed_by_id.merge!({entry["id"] => entry})
234
+ end
235
+
236
+ return keyed_by_id
237
+ end
238
+
239
+ # Modifies each request in an outgoing RpcRequest so that its key is set
240
+ # to the value specified when added to the RpcRequest.
241
+ def request_json
242
+ keyed_requests = []
243
+ @requests.each_pair do |key, request|
244
+ request.key = key
245
+ keyed_requests << request
246
+ end
247
+
248
+ return keyed_requests.to_json
249
+ end
250
+ end
251
+
252
+ # An exception thrown when a request cannot return data.
253
+ #
254
+
255
+
256
+ class RequestException < RuntimeError; end
257
+
258
+ # An exception thrown when a request returns a 401 unauthorized status.
259
+ #
260
+
261
+
262
+ class AuthException < RuntimeError; end
263
+ end
@@ -0,0 +1,32 @@
1
+ # Copyright (c) 2008 Engine Yard
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ class String #:nodoc:
23
+ def snake_case
24
+ gsub(/\B[A-Z]/, '_\&').downcase
25
+ end
26
+
27
+ def camel_case
28
+ words = split('_')
29
+ camel = words[1..-1].map { |e| e.capitalize }.join
30
+ words[0] + camel
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2008 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ class String #:nodoc:
16
+ # Removes backslash escaping.
17
+ def os_unescape
18
+ unescaped = self.gsub(/\\\"/, '"')
19
+ unescaped = unescaped.gsub('"{', "{")
20
+ unescaped = unescaped.gsub('}"', "}")
21
+ unescaped = unescaped.gsub(/\"\"/, "\"")
22
+ end
23
+ end
@@ -0,0 +1,99 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{qoobaa-opensocial}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jakub Ku\305\272ma", "Piotr Sarnacki"]
12
+ s.date = %q{2010-03-24}
13
+ s.description = %q{OpenSocial Google Gem}
14
+ s.email = %q{qoobaa@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "NOTICE",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/opensocial.rb",
28
+ "lib/opensocial/activity.rb",
29
+ "lib/opensocial/appdata.rb",
30
+ "lib/opensocial/auth/action_controller_request.rb",
31
+ "lib/opensocial/auth/base.rb",
32
+ "lib/opensocial/base.rb",
33
+ "lib/opensocial/connection.rb",
34
+ "lib/opensocial/group.rb",
35
+ "lib/opensocial/person.rb",
36
+ "lib/opensocial/request.rb",
37
+ "lib/opensocial/string/merb_string.rb",
38
+ "lib/opensocial/string/os_string.rb",
39
+ "qoobaa-opensocial.gemspec",
40
+ "test/fixtures/activities.json",
41
+ "test/fixtures/activity.json",
42
+ "test/fixtures/appdata.json",
43
+ "test/fixtures/appdatum.json",
44
+ "test/fixtures/group.json",
45
+ "test/fixtures/groups.json",
46
+ "test/fixtures/people.json",
47
+ "test/fixtures/person.json",
48
+ "test/fixtures/person_appdata_rpc.json",
49
+ "test/fixtures/person_rpc.json",
50
+ "test/helper.rb",
51
+ "test/test_activity.rb",
52
+ "test/test_appdata.rb",
53
+ "test/test_connection.rb",
54
+ "test/test_group.rb",
55
+ "test/test_online.rb",
56
+ "test/test_person.rb",
57
+ "test/test_request.rb",
58
+ "test/test_rpcrequest.rb"
59
+ ]
60
+ s.homepage = %q{http://github.com/qoobaa/opensocial}
61
+ s.rdoc_options = ["--charset=UTF-8"]
62
+ s.require_paths = ["lib"]
63
+ s.rubygems_version = %q{1.3.6}
64
+ s.summary = %q{OpenSocial Google Gem}
65
+ s.test_files = [
66
+ "test/test_request.rb",
67
+ "test/test_rpcrequest.rb",
68
+ "test/test_connection.rb",
69
+ "test/test_appdata.rb",
70
+ "test/test_person.rb",
71
+ "test/test_activity.rb",
72
+ "test/helper.rb",
73
+ "test/test_group.rb",
74
+ "test/test_online.rb"
75
+ ]
76
+
77
+ if s.respond_to? :specification_version then
78
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
79
+ s.specification_version = 3
80
+
81
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
82
+ s.add_runtime_dependency(%q<oauth>, [">= 0"])
83
+ s.add_development_dependency(%q<test-unit>, [">= 2"])
84
+ s.add_development_dependency(%q<json_pure>, [">= 0"])
85
+ s.add_development_dependency(%q<mocha>, [">= 0"])
86
+ else
87
+ s.add_dependency(%q<oauth>, [">= 0"])
88
+ s.add_dependency(%q<test-unit>, [">= 2"])
89
+ s.add_dependency(%q<json_pure>, [">= 0"])
90
+ s.add_dependency(%q<mocha>, [">= 0"])
91
+ end
92
+ else
93
+ s.add_dependency(%q<oauth>, [">= 0"])
94
+ s.add_dependency(%q<test-unit>, [">= 2"])
95
+ s.add_dependency(%q<json_pure>, [">= 0"])
96
+ s.add_dependency(%q<mocha>, [">= 0"])
97
+ end
98
+ end
99
+
@@ -0,0 +1,28 @@
1
+ {
2
+ "totalResults" : 2,
3
+ "startIndex" : 0,
4
+ "entry" : [
5
+ {
6
+ "id" : "http://example.org/activities/example.org:87ead8dead6beef/self/af3778",
7
+ "title" : { "type" : "html",
8
+ "value" : "<a href=\"foo\">some activity</a>"
9
+ },
10
+ "updated" : "2008-02-20T23:35:37.266Z",
11
+ "body" : "Some details for some activity",
12
+ "bodyId" : "383777272",
13
+ "url" : "http://api.example.org/activity/feeds/.../af3778",
14
+ "userId" : "example.org:34KJDCSKJN2HHF0DW20394"
15
+ },
16
+ {
17
+ "id" : "http://example.org/activities/example.org:87ead8dead6beef/self/af3779",
18
+ "title" : { "type" : "html",
19
+ "value" : "<a href=\"foo\">some activity</a>"
20
+ },
21
+ "updated" : "2008-02-20T23:35:38.266Z",
22
+ "body" : "Some details for some second activity",
23
+ "bodyId" : "383777273",
24
+ "url" : "http://api.example.org/activity/feeds/.../af3779",
25
+ "userId" : "example.org:34KJDCSKJN2HHF0DW20394"
26
+ }
27
+ ]
28
+ }