koala 1.5.0 → 1.6.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.
@@ -1,3 +1,5 @@
1
+ require 'addressable/uri'
2
+
1
3
  require 'koala/api/graph_collection'
2
4
  require 'koala/http_service/uploadable_io'
3
5
 
@@ -40,13 +42,20 @@ module Koala
40
42
  # @param args any additional arguments
41
43
  # (fields, metadata, etc. -- see {http://developers.facebook.com/docs/reference/api/ Facebook's documentation})
42
44
  # @param options (see Koala::Facebook::API#api)
45
+ # @param block for post-processing. It receives the result data; the
46
+ # return value of the method is the result of the block, if
47
+ # provided. (see Koala::Facebook::API#api)
43
48
  #
44
49
  # @raise [Koala::Facebook::APIError] if the ID is invalid or you don't have access to that object
45
50
  #
51
+ # @example
52
+ # get_object("me") # => {"id" => ..., "name" => ...}
53
+ # get_object("me") {|data| data['education']} # => only education section of profile
54
+ #
46
55
  # @return a hash of object data
47
- def get_object(id, args = {}, options = {})
48
- # Fetchs the given object from the graph.
49
- graph_call(id, args, "get", options)
56
+ def get_object(id, args = {}, options = {}, &block)
57
+ # Fetches the given object from the graph.
58
+ graph_call(id, args, "get", options, &block)
50
59
  end
51
60
 
52
61
  # Get information about multiple Facebook objects in one call.
@@ -54,15 +63,16 @@ module Koala
54
63
  # @param ids an array or comma-separated string of object IDs
55
64
  # @param args (see #get_object)
56
65
  # @param options (see Koala::Facebook::API#api)
66
+ # @param block (see Koala::Facebook::API#api)
57
67
  #
58
68
  # @raise [Koala::Facebook::APIError] if any ID is invalid or you don't have access to that object
59
69
  #
60
70
  # @return an array of object data hashes
61
- def get_objects(ids, args = {}, options = {})
62
- # Fetchs all of the given objects from the graph.
71
+ def get_objects(ids, args = {}, options = {}, &block)
72
+ # Fetches all of the given objects from the graph.
63
73
  # If any of the IDs are invalid, they'll raise an exception.
64
74
  return [] if ids.empty?
65
- graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options)
75
+ graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options, &block)
66
76
  end
67
77
 
68
78
  # Write an object to the Graph for a specific user.
@@ -71,20 +81,21 @@ module Koala
71
81
  # @note put_object is (for historical reasons) the same as put_connections.
72
82
  # Please use put_connections; in a future version of Koala (2.0?),
73
83
  # put_object will issue a POST directly to an individual object, not to a connection.
74
- def put_object(parent_object, connection_name, args = {}, options = {})
75
- put_connections(parent_object, connection_name, args, options)
84
+ def put_object(parent_object, connection_name, args = {}, options = {}, &block)
85
+ put_connections(parent_object, connection_name, args, options, &block)
76
86
  end
77
87
 
78
88
  # Delete an object from the Graph if you have appropriate permissions.
79
89
  #
80
90
  # @param id (see #get_object)
81
91
  # @param options (see #get_object)
92
+ # @param block (see Koala::Facebook::API#api)
82
93
  #
83
94
  # @return true if successful, false (or an APIError) if not
84
- def delete_object(id, options = {})
95
+ def delete_object(id, options = {}, &block)
85
96
  # Deletes the object with the given ID from the graph.
86
- raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Delete requires an access token"}) unless @access_token
87
- graph_call(id, {}, "delete", options)
97
+ raise AuthenticationError.new(nil, nil, "Delete requires an access token") unless @access_token
98
+ graph_call(id, {}, "delete", options, &block)
88
99
  end
89
100
 
90
101
  # Fetch information about a given connection (e.g. type of activity -- feed, events, photos, etc.)
@@ -98,11 +109,12 @@ module Koala
98
109
  # @param connection_name what
99
110
  # @param args any additional arguments
100
111
  # @param options (see #get_object)
112
+ # @param block (see Koala::Facebook::API#api)
101
113
  #
102
114
  # @return [Koala::Facebook::API::GraphCollection] an array of object hashes (in most cases)
103
- def get_connection(id, connection_name, args = {}, options = {})
104
- # Fetchs the connections for given object.
105
- graph_call("#{id}/#{connection_name}", args, "get", options)
115
+ def get_connection(id, connection_name, args = {}, options = {}, &block)
116
+ # Fetches the connections for given object.
117
+ graph_call("#{id}/#{connection_name}", args, "get", options, &block)
106
118
  end
107
119
  alias_method :get_connections, :get_connection
108
120
 
@@ -126,12 +138,13 @@ module Koala
126
138
  # @param connection_name (see #get_connection)
127
139
  # @param args (see #get_connection)
128
140
  # @param options (see #get_object)
141
+ # @param block (see Koala::Facebook::API#api)
129
142
  #
130
143
  # @return a hash containing the new object's id
131
- def put_connections(id, connection_name, args = {}, options = {})
144
+ def put_connections(id, connection_name, args = {}, options = {}, &block)
132
145
  # Posts a certain connection
133
- raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
134
- graph_call("#{id}/#{connection_name}", args, "post", options)
146
+ raise AuthenticationError.new(nil, nil, "Write operations require an access token") unless @access_token
147
+ graph_call("#{id}/#{connection_name}", args, "post", options, &block)
135
148
  end
136
149
 
137
150
  # Delete an object's connection (for instance, unliking the object).
@@ -142,12 +155,13 @@ module Koala
142
155
  # @param connection_name (see #get_connection)
143
156
  # @args (see #get_connection)
144
157
  # @param options (see #get_object)
158
+ # @param block (see Koala::Facebook::API#api)
145
159
  #
146
160
  # @return (see #delete_object)
147
- def delete_connections(id, connection_name, args = {}, options = {})
161
+ def delete_connections(id, connection_name, args = {}, options = {}, &block)
148
162
  # Deletes a given connection
149
- raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Delete requires an access token"}) unless @access_token
150
- graph_call("#{id}/#{connection_name}", args, "delete", options)
163
+ raise AuthenticationError.new(nil, nil, "Delete requires an access token") unless @access_token
164
+ graph_call("#{id}/#{connection_name}", args, "delete", options, &block)
151
165
  end
152
166
 
153
167
  # Fetches a photo.
@@ -156,15 +170,17 @@ module Koala
156
170
  #
157
171
  # @param options options for Facebook (see #get_object).
158
172
  # To get a different size photo, pass :type => size (small, normal, large, square).
173
+ # @param block (see Koala::Facebook::API#api)
159
174
  #
160
175
  # @note to delete photos or videos, use delete_object(id)
161
176
  #
162
177
  # @return the URL to the image
163
- def get_picture(object, args = {}, options = {})
178
+ def get_picture(object, args = {}, options = {}, &block)
164
179
  # Gets a picture object, returning the URL (which Facebook sends as a header)
165
- graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
180
+ resolved_result = graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
166
181
  result["Location"]
167
182
  end
183
+ block ? block.call(resolved_result) : resolved_result
168
184
  end
169
185
 
170
186
  # Upload a photo.
@@ -180,6 +196,7 @@ module Koala
180
196
  # @param args (see #get_object)
181
197
  # @param target_id the Facebook object to which to post the picture (default: "me")
182
198
  # @param options (see #get_object)
199
+ # @param block (see Koala::Facebook::API#api)
183
200
  #
184
201
  # @example
185
202
  # put_picture(file, content_type, {:message => "Message"}, 01234560)
@@ -190,16 +207,16 @@ module Koala
190
207
  # @note to access the media after upload, you'll need the user_photos or user_videos permission as appropriate.
191
208
  #
192
209
  # @return (see #put_connections)
193
- def put_picture(*picture_args)
194
- put_connections(*parse_media_args(picture_args, "photos"))
210
+ def put_picture(*picture_args, &block)
211
+ put_connections(*parse_media_args(picture_args, "photos"), &block)
195
212
  end
196
213
 
197
214
  # Upload a video. Functions exactly the same as put_picture.
198
215
  # @see #put_picture
199
- def put_video(*video_args)
216
+ def put_video(*video_args, &block)
200
217
  args = parse_media_args(video_args, "videos")
201
218
  args.last[:video] = true
202
- put_connections(*args)
219
+ put_connections(*args, &block)
203
220
  end
204
221
 
205
222
  # Write directly to the user's wall.
@@ -213,6 +230,7 @@ module Koala
213
230
  # (see the {https://developers.facebook.com/docs/guides/attachments/ stream attachments} documentation.)
214
231
  # @param target_id the target wall
215
232
  # @param options (see #get_object)
233
+ # @param block (see Koala::Facebook::API#api)
216
234
  #
217
235
  # @example
218
236
  # @api.put_wall_post("Hello there!", {
@@ -225,8 +243,8 @@ module Koala
225
243
  #
226
244
  # @see #put_connections
227
245
  # @return (see #put_connections)
228
- def put_wall_post(message, attachment = {}, target_id = "me", options = {})
229
- put_connections(target_id, "feed", attachment.merge({:message => message}), options)
246
+ def put_wall_post(message, attachment = {}, target_id = "me", options = {}, &block)
247
+ put_connections(target_id, "feed", attachment.merge({:message => message}), options, &block)
230
248
  end
231
249
 
232
250
  # Comment on a given object.
@@ -238,11 +256,12 @@ module Koala
238
256
  # @param id (see #get_object)
239
257
  # @param message the comment to write
240
258
  # @param options (see #get_object)
259
+ # @param block (see Koala::Facebook::API#api)
241
260
  #
242
261
  # @return (see #put_connections)
243
- def put_comment(id, message, options = {})
262
+ def put_comment(id, message, options = {}, &block)
244
263
  # Writes the given comment on the given post.
245
- put_connections(id, "comments", {:message => message}, options)
264
+ put_connections(id, "comments", {:message => message}, options, &block)
246
265
  end
247
266
 
248
267
  # Like a given object.
@@ -252,11 +271,12 @@ module Koala
252
271
  #
253
272
  # @param id (see #get_object)
254
273
  # @param options (see #get_object)
274
+ # @param block (see Koala::Facebook::API#api)
255
275
  #
256
276
  # @return (see #put_connections)
257
- def put_like(id, options = {})
277
+ def put_like(id, options = {}, &block)
258
278
  # Likes the given post.
259
- put_connections(id, "likes", {}, options)
279
+ put_connections(id, "likes", {}, options, &block)
260
280
  end
261
281
 
262
282
  # Unlike a given object.
@@ -264,12 +284,13 @@ module Koala
264
284
  #
265
285
  # @param id (see #get_object)
266
286
  # @param options (see #get_object)
287
+ # @param block (see Koala::Facebook::API#api)
267
288
  #
268
289
  # @return (see #delete_object)
269
- def delete_like(id, options = {})
290
+ def delete_like(id, options = {}, &block)
270
291
  # Unlikes a given object for the logged-in user
271
- raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Unliking requires an access token"}) unless @access_token
272
- graph_call("#{id}/likes", {}, "delete", options)
292
+ raise AuthenticationError.new(nil, nil, "Unliking requires an access token") unless @access_token
293
+ graph_call("#{id}/likes", {}, "delete", options, &block)
273
294
  end
274
295
 
275
296
  # Search for a given query among visible Facebook objects.
@@ -278,11 +299,12 @@ module Koala
278
299
  # @param search_terms the query to search for
279
300
  # @param args additional arguments, such as type, fields, etc.
280
301
  # @param options (see #get_object)
302
+ # @param block (see Koala::Facebook::API#api)
281
303
  #
282
304
  # @return [Koala::Facebook::API::GraphCollection] an array of search results
283
- def search(search_terms, args = {}, options = {})
305
+ def search(search_terms, args = {}, options = {}, &block)
284
306
  args.merge!({:q => search_terms}) unless search_terms.nil?
285
- graph_call("search", args, "get", options)
307
+ graph_call("search", args, "get", options, &block)
286
308
  end
287
309
 
288
310
  # Convenience Methods
@@ -296,8 +318,11 @@ module Koala
296
318
  # @param query the FQL query to perform
297
319
  # @param args (see #get_object)
298
320
  # @param options (see #get_object)
299
- def fql_query(query, args = {}, options = {})
300
- get_object("fql", args.merge(:q => query), options)
321
+ # @param block (see Koala::Facebook::API#api)
322
+ #
323
+ # @return the result of the FQL query.
324
+ def fql_query(query, args = {}, options = {}, &block)
325
+ get_object("fql", args.merge(:q => query), options, &block)
301
326
  end
302
327
 
303
328
  # Make an FQL multiquery.
@@ -306,6 +331,7 @@ module Koala
306
331
  # @param queries a hash of query names => FQL queries
307
332
  # @param args (see #get_object)
308
333
  # @param options (see #get_object)
334
+ # @param block (see Koala::Facebook::API#api)
309
335
  #
310
336
  # @example
311
337
  # @api.fql_multiquery({
@@ -316,11 +342,13 @@ module Koala
316
342
  # # instead of [{"name":"query1", "fql_result_set":[]},{"name":"query2", "fql_result_set":[]}]
317
343
  #
318
344
  # @return a hash of FQL results keyed to the appropriate query
319
- def fql_multiquery(queries = {}, args = {}, options = {})
320
- if results = get_object("fql", args.merge(:q => MultiJson.dump(queries)), options)
345
+ def fql_multiquery(queries = {}, args = {}, options = {}, &block)
346
+ resolved_results = if results = get_object("fql", args.merge(:q => MultiJson.dump(queries)), options)
321
347
  # simplify the multiquery result format
322
348
  results.inject({}) {|outcome, data| outcome[data["name"]] = data["fql_result_set"]; outcome}
323
349
  end
350
+
351
+ block ? block.call(resolved_results) : resolved_results
324
352
  end
325
353
 
326
354
  # Get a page's access token, allowing you to act as the page.
@@ -329,30 +357,34 @@ module Koala
329
357
  # @param id the page ID
330
358
  # @param args (see #get_object)
331
359
  # @param options (see #get_object)
360
+ # @param block (see Koala::Facebook::API#api)
332
361
  #
333
362
  # @return the page's access token (discarding expiration and any other information)
334
- def get_page_access_token(id, args = {}, options = {})
335
- result = get_object(id, args.merge(:fields => "access_token"), options) do
363
+ def get_page_access_token(id, args = {}, options = {}, &block)
364
+ access_token = get_object(id, args.merge(:fields => "access_token"), options) do |result|
336
365
  result ? result["access_token"] : nil
337
366
  end
367
+
368
+ block ? block.call(access_token) : access_token
338
369
  end
339
370
 
340
- # Fetchs the comments from fb:comments widgets for a given set of URLs (array or comma-separated string).
371
+ # Fetches the comments from fb:comments widgets for a given set of URLs (array or comma-separated string).
341
372
  # See https://developers.facebook.com/blog/post/490.
342
373
  #
343
374
  # @param urls the URLs for which you want comments
344
375
  # @param args (see #get_object)
345
376
  # @param options (see #get_object)
377
+ # @param block (see Koala::Facebook::API#api)
346
378
  #
347
379
  # @returns a hash of urls => comment arrays
348
- def get_comments_for_urls(urls = [], args = {}, options = {})
380
+ def get_comments_for_urls(urls = [], args = {}, options = {}, &block)
349
381
  return [] if urls.empty?
350
382
  args.merge!(:ids => urls.respond_to?(:join) ? urls.join(",") : urls)
351
- get_object("comments", args, options)
383
+ get_object("comments", args, options, &block)
352
384
  end
353
385
 
354
- def set_app_restrictions(app_id, restrictions_hash, args = {}, options = {})
355
- graph_call(app_id, args.merge(:restrictions => MultiJson.dump(restrictions_hash)), "post", options)
386
+ def set_app_restrictions(app_id, restrictions_hash, args = {}, options = {}, &block)
387
+ graph_call(app_id, args.merge(:restrictions => MultiJson.dump(restrictions_hash)), "post", options, &block)
356
388
  end
357
389
 
358
390
  # Certain calls such as {#get_connections} return an array of results which you can page through
@@ -364,10 +396,11 @@ module Koala
364
396
  #
365
397
  # @param params an array of arguments to graph_call
366
398
  # as returned by {Koala::Facebook::GraphCollection.parse_page_url}.
399
+ # @param block (see Koala::Facebook::API#api)
367
400
  #
368
401
  # @return Koala::Facebook::GraphCollection the appropriate page of results (an empty array if there are none)
369
- def get_page(params)
370
- graph_call(*params)
402
+ def get_page(params, &block)
403
+ graph_call(*params, &block)
371
404
  end
372
405
 
373
406
  # Execute a set of Graph API calls as a batch.
@@ -386,10 +419,21 @@ module Koala
386
419
  #
387
420
  # @example
388
421
  # results = @api.batch do |batch_api|
389
- # batch_api.get_object('me')
390
- # batch_api.get_object(KoalaTest.user1)
422
+ # batch_api.get_object('me')
423
+ # batch_api.get_object(KoalaTest.user1)
424
+ # end
425
+ # # => [{'id' => my_id, ...}, {'id' => koppel_id, ...}]
426
+ #
427
+ # # You can also provide blocks to your operations to process the
428
+ # # results, which is often useful if you're constructing batch
429
+ # # requests in various locations and want to keep the code
430
+ # # together in logical places.
431
+ # # See readme.md and the wiki for more examples.
432
+ # @api.batch do |batch_api|
433
+ # batch_api.get_object('me') {|data| data["id"] }
434
+ # batch_api.get_object(KoalaTest.user1) {|data| data["name"] }
391
435
  # end
392
- # # => [{"id" => my_id, ...}, {"id"" => koppel_id, ...}]
436
+ # # => [my_id, "Alex Koppel"]
393
437
  #
394
438
  # @return an array of results from your batch calls (as if you'd made them individually),
395
439
  # arranged in the same order they're made.
@@ -422,7 +466,7 @@ module Koala
422
466
  # @return the result from Facebook
423
467
  def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
424
468
  result = api(path, args, verb, options) do |response|
425
- error = check_response(response)
469
+ error = check_response(response.status, response.body)
426
470
  raise error if error
427
471
  end
428
472
 
@@ -435,12 +479,38 @@ module Koala
435
479
 
436
480
  private
437
481
 
438
- def check_response(response)
439
- # check for Graph API-specific errors
440
- # this returns an error, which is immediately raised (non-batch)
441
- # or added to the list of batch results (batch)
442
- if response.is_a?(Hash) && error_details = response["error"]
443
- APIError.new(error_details)
482
+ def check_response(http_status, response_body)
483
+ # Check for Graph API-specific errors. This returns an error of the appropriate type
484
+ # which is immediately raised (non-batch) or added to the list of batch results (batch)
485
+ http_status = http_status.to_i
486
+
487
+ if http_status >= 400
488
+ begin
489
+ response_hash = MultiJson.load(response_body)
490
+ rescue MultiJson::DecodeError
491
+ response_hash = {}
492
+ end
493
+
494
+ if response_hash['error_code']
495
+ # Old batch api error format. This can be removed on July 5, 2012.
496
+ # See https://developers.facebook.com/roadmap/#graph-batch-api-exception-format
497
+ error_info = {
498
+ 'code' => response_hash['error_code'],
499
+ 'message' => response_hash['error_description']
500
+ }
501
+ else
502
+ error_info = response_hash['error'] || {}
503
+ end
504
+
505
+ if error_info['type'] == 'OAuthException' &&
506
+ ( !error_info['code'] || [102, 190, 450, 452, 2500].include?(error_info['code'].to_i))
507
+
508
+ # See: https://developers.facebook.com/docs/authentication/access-token-expiration/
509
+ # https://developers.facebook.com/bugs/319643234746794?browse=search_4fa075c0bd9117b20604672
510
+ AuthenticationError.new(http_status, response_body, error_info)
511
+ else
512
+ ClientError.new(http_status, response_body, error_info)
513
+ end
444
514
  end
445
515
  end
446
516
 
@@ -468,9 +538,9 @@ module Koala
468
538
  def url?(data)
469
539
  return false unless data.is_a? String
470
540
  begin
471
- uri = URI.parse(data)
541
+ uri = Addressable::URI.parse(data)
472
542
  %w( http https ).include?(uri.scheme)
473
- rescue URI::BadURIError
543
+ rescue Addressable::URI::InvalidURIError
474
544
  false
475
545
  end
476
546
  end
@@ -19,35 +19,23 @@ module Koala
19
19
  end
20
20
 
21
21
  def graph_call_in_batch(path, args = {}, verb = "get", options = {}, &post_processing)
22
- # for batch APIs, we queue up the call details (incl. post-processing)
23
- batch_calls << BatchOperation.new(
24
- :url => path,
25
- :args => args,
26
- :method => verb,
27
- :access_token => options['access_token'] || access_token,
28
- :http_options => options,
29
- :post_processing => post_processing
30
- )
31
- nil # batch operations return nothing immediately
22
+ # for batch APIs, we queue up the call details (incl. post-processing)
23
+ batch_calls << BatchOperation.new(
24
+ :url => path,
25
+ :args => args,
26
+ :method => verb,
27
+ :access_token => options['access_token'] || access_token,
28
+ :http_options => options,
29
+ :post_processing => post_processing
30
+ )
31
+ nil # batch operations return nothing immediately
32
32
  end
33
33
 
34
- def check_graph_batch_api_response(response)
35
- if response.is_a?(Hash) && response["error"] && !response["error"].is_a?(Hash)
36
- # old error format -- see http://developers.facebook.com/blog/post/596/
37
- APIError.new({"type" => "Error #{response["error"]}", "message" => response["error_description"]}.merge(response))
38
- else
39
- check_graph_api_response(response)
40
- end
41
- end
42
-
43
- # redefine the graph_call and check_response methods
44
- # so we can use this API inside the batch block just like any regular Graph API
34
+ # redefine the graph_call method so we can use this API inside the batch block
35
+ # just like any regular Graph API
45
36
  alias_method :graph_call_outside_batch, :graph_call
46
37
  alias_method :graph_call, :graph_call_in_batch
47
38
 
48
- alias_method :check_graph_api_response, :check_response
49
- alias_method :check_response, :check_graph_batch_api_response
50
-
51
39
  # execute the queued batch calls
52
40
  def execute(http_options = {})
53
41
  return [] unless batch_calls.length > 0
@@ -62,7 +50,7 @@ module Koala
62
50
  unless response
63
51
  # Facebook sometimes reportedly returns an empty body at times
64
52
  # see https://github.com/arsduo/koala/issues/184
65
- raise APIError.new({"type" => "BadFacebookResponse", "message" => "Facebook returned invalid batch response: #{response.inspect}"})
53
+ raise BadFacebookResponse.new(200, '', "Facebook returned an empty body")
66
54
  end
67
55
 
68
56
  # map the results with post-processing included
@@ -72,13 +60,16 @@ module Koala
72
60
  batch_op = batch_calls[index]
73
61
  index += 1
74
62
 
63
+ raw_result = nil
75
64
  if call_result
76
- # (see note in regular api method about JSON parsing)
77
- body = MultiJson.load("[#{call_result['body'].to_s}]")[0]
65
+ if ( error = check_response(call_result['code'], call_result['body'].to_s) )
66
+ raw_result = error
67
+ else
68
+ # (see note in regular api method about JSON parsing)
69
+ body = MultiJson.load("[#{call_result['body'].to_s}]")[0]
78
70
 
79
- unless call_result["code"].to_i >= 500 || error = check_response(body)
80
71
  # Get the HTTP component they want
81
- data = case batch_op.http_options[:http_component]
72
+ raw_result = case batch_op.http_options[:http_component]
82
73
  when :status
83
74
  call_result["code"].to_i
84
75
  when :headers
@@ -87,20 +78,19 @@ module Koala
87
78
  else
88
79
  body
89
80
  end
90
-
91
- # process it if we are given a block to process with
92
- batch_op.post_processing ? batch_op.post_processing.call(data) : data
93
- else
94
- error || APIError.new({"type" => "HTTP #{call_result["code"].to_s}", "message" => "Response body: #{body}"})
95
81
  end
82
+ end
83
+
84
+ # turn any results that are pageable into GraphCollections
85
+ # and pass to post-processing callback if given
86
+ result = GraphCollection.evaluate(raw_result, @original_api)
87
+ if batch_op.post_processing
88
+ batch_op.post_processing.call(result)
96
89
  else
97
- nil
90
+ result
98
91
  end
99
92
  end
100
93
  end
101
-
102
- # turn any results that are pageable into GraphCollections
103
- batch_result.inject([]) {|processed_results, raw| processed_results << GraphCollection.evaluate(raw, @original_api)}
104
94
  end
105
95
 
106
96
  end
@@ -1,3 +1,5 @@
1
+ require 'addressable/uri'
2
+
1
3
  module Koala
2
4
  module Facebook
3
5
  class API
@@ -87,7 +89,7 @@ module Koala
87
89
  #
88
90
  # @return an array of parameters that can be provided via graph_call(*parsed_params)
89
91
  def self.parse_page_url(url)
90
- uri = URI.parse(url)
92
+ uri = Addressable::URI.parse(url)
91
93
 
92
94
  base = uri.path.sub(/^\//, '')
93
95
  params = CGI.parse(uri.query)
@@ -21,7 +21,7 @@ module Koala
21
21
  #
22
22
  # @return true if successful, false if not. (This call currently doesn't give useful feedback on failure.)
23
23
  def set_app_properties(properties, args = {}, options = {})
24
- raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "setAppProperties requires an access token"}) unless @access_token
24
+ raise AuthenticationError.new(nil, nil, "setAppProperties requires an access token") unless @access_token
25
25
  rest_call("admin.setAppProperties", args.merge(:properties => MultiJson.dump(properties)), options, "post")
26
26
  end
27
27
 
@@ -44,8 +44,24 @@ module Koala
44
44
 
45
45
  api("method/#{fb_method}", args.merge('format' => 'json'), verb, options) do |response|
46
46
  # check for REST API-specific errors
47
- if response.is_a?(Hash) && response["error_code"]
48
- raise APIError.new("type" => response["error_code"], "message" => response["error_msg"])
47
+ if response.status >= 400
48
+ begin
49
+ response_hash = MultiJson.load(response.body)
50
+ rescue MultiJson::DecodeError
51
+ response_hash = {}
52
+ end
53
+
54
+ error_info = {
55
+ 'code' => response_hash['error_code'],
56
+ 'error_subcode' => response_hash['error_subcode'],
57
+ 'message' => response_hash['error_msg']
58
+ }
59
+
60
+ if response.status >= 500
61
+ raise ServerError.new(response.status, response.body, error_info)
62
+ else
63
+ raise ClientError.new(response.status, response.body, error_info)
64
+ end
49
65
  end
50
66
  end
51
67
  end
data/lib/koala/api.rb CHANGED
@@ -35,9 +35,9 @@ module Koala
35
35
  # (All API requests with access tokens use SSL.)
36
36
  # @param error_checking_block a block to evaluate the response status for additional JSON-encoded errors
37
37
  #
38
- # @yield The response body for evaluation
38
+ # @yield The response for evaluation
39
39
  #
40
- # @raise [Koala::Facebook::APIError] if Facebook returns an error (response status >= 500)
40
+ # @raise [Koala::Facebook::ServerError] if Facebook returns an error (response status >= 500)
41
41
  #
42
42
  # @return the body of the response from Facebook (unless another http_component is requested)
43
43
  def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
@@ -50,43 +50,23 @@ module Koala
50
50
  # make the request via the provided service
51
51
  result = Koala.make_request(path, args, verb, options)
52
52
 
53
- # Check for any 500 errors before parsing the body
54
- # since we're not guaranteed that the body is valid JSON
55
- # in the case of a server error
56
- raise APIError.new({"type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}"}) if result.status >= 500
57
-
58
- # parse the body as JSON and run it through the error checker (if provided)
59
- # Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
60
- # and cause MultiJson.load to fail -- so we account for that by wrapping the result in []
61
- body = MultiJson.load("[#{result.body.to_s}]")[0]
62
- yield body if error_checking_block
53
+ if result.status.to_i >= 500
54
+ raise Koala::Facebook::ServerError.new(result.status.to_i, result.body)
55
+ end
56
+
57
+ yield result if error_checking_block
63
58
 
64
59
  # if we want a component other than the body (e.g. redirect header for images), return that
65
60
  if component = options[:http_component]
66
61
  component == :response ? result : result.send(options[:http_component])
67
62
  else
68
- body
63
+ # parse the body as JSON and run it through the error checker (if provided)
64
+ # Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
65
+ # and cause MultiJson.load to fail -- so we account for that by wrapping the result in []
66
+ MultiJson.load("[#{result.body.to_s}]")[0]
69
67
  end
70
68
  end
71
69
  end
72
-
73
- class APIError < StandardError
74
- attr_accessor :fb_error_type, :fb_error_code, :fb_error_message, :raw_response
75
-
76
- # Creates a new APIError.
77
- #
78
- # Assigns the error type (as reported by Facebook) to #fb_error_type
79
- # and the raw error response available to #raw_response.
80
- #
81
- # @param details error details containing "type" and "message" keys.
82
- def initialize(details = {})
83
- self.raw_response = details
84
- self.fb_error_type = details["type"]
85
- self.fb_error_code = details["code"]
86
- self.fb_error_message = details["message"]
87
- super("#{fb_error_type}: #{details["message"]}")
88
- end
89
- end
90
70
  end
91
71
  end
92
72