koala 3.0.0.beta1 → 3.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 04714e9f9ae4e70420567406530119f6560518d1
4
- data.tar.gz: 92ec819da43149fc5de98f6ec931afc6736c8199
3
+ metadata.gz: 18d5d8c95bece65d5f4bab2640d75645dc5fb343
4
+ data.tar.gz: 6ec125b20dea63584e6dd69fc02ecd08f0800ce2
5
5
  SHA512:
6
- metadata.gz: 51df761c7444da9dfb2c78b1fc30216b6fb3e5463c8e3bda322cae0f01e5e6fde5eecd62fc63f81527d25277972ff8ec00db71ca56aae9322bcfa626484e2470
7
- data.tar.gz: f0545dd4651b30877186dd1f9bf3abe921a73a73f778980434536d4ad7923e17085fa64df53f7f449986d4fe5c460e08e6ceba605ddcc4a70de90c1ed21881c7
6
+ metadata.gz: 31a10322d86c4c49bd877ad5891cde67db496e506c6b8936d8cf3e67b6bc82e6faed1fe1e93f02dcd123ea8aee77026adc681a3b197006335e7d2f8aac40d968
7
+ data.tar.gz: f8d69365d42baf15acead1fcc5e38e334084dc49539358189aee046bf611d9c7761ec4ffeac690b07d3a647116ef81ac1e0fdfa2bc1d11010c5ae30dc239a189
@@ -3,10 +3,10 @@ sudo: false
3
3
  cache: bundler
4
4
  rvm:
5
5
  # MRI
6
- - 2.0
7
6
  - 2.1
8
7
  - 2.2
9
8
  - 2.3.1
9
+ - 2.4.0
10
10
  # Rubinius is failing due to segfaults on Travis (and takes significantly longer to run)
11
11
  # those builds will be restored later
12
12
  # jruby
@@ -1,28 +1,44 @@
1
1
  v3.0.0
2
2
  ======
3
3
 
4
- Key breaking changes:
4
+ **Key breaking changes:**
5
5
 
6
+ * Koala now requires Ruby 2.1+ (or equivalent for JRuby, etc.)
7
+ * API#api now returns a Koala::HTTPService::Response object; graph_call handles further processing
8
+ * GraphBatchAPI no longer inherits from API (the interface is otherwise unchanged)
9
+ * Empty response bodies in batch API calls will raise a JSON::ParserError rather than returning nil
6
10
  * HTTPService.make_request now requires an HTTPService::Request object (Koala.make_request does
7
11
  not)
8
12
  * HTTPService behavior *should not* change, but in edge cases might. (If so, please let me know.)
9
- * Empty response bodies in batch API calls will raise a JSON parse error rather than returning nil
13
+ * API#search now requires a "type"/:type argument, matching Facebook's behavior (improving their
14
+ cryptic error message)
15
+
16
+ Updated features:
17
+
18
+ * TestUser#befriend will provide the appsecret_proof if a secret is set (thanks, kwasimensah!)
19
+ * API#search now requires an object type parameter to be included, matching Facebook's API (#575)
10
20
 
11
21
  Removed features:
12
22
 
13
23
  * Removed support for the Rest API, since Facebook removed support for it (#568)
14
24
  * Removed support for FQL, which Facebook removed on August 8, 2016 (#569)
15
25
  * Removed legacy duplication of serveral constants in the Koala module (#569)
26
+ * Removed API#get_comments_for_urls, which pointed to a no-longer-extant endpoint(#570)
16
27
 
17
28
  Internal improvements:
18
29
 
19
30
  * Completely rewrote HTTPService.make_request and several others, extracting most logic into
20
31
  HTTPService::Request (#566)
32
+ * API#api now returns a Koala::HTTPService::Response object (#584)
33
+ * GraphBatchAPI no longer inherits from API (#580)
34
+ * GraphBatchAPI has been refactored to be simpler and more readable (thanks, acuppy!) (#551)
21
35
  * Use the more secure JSON.parse instead of JSON.load (thanks, lautis!) (#567)
36
+ * Use JSON's quirks_mode option to remove hacky JSON hack (#573 -- thanks sakuro for the
37
+ suggestion!)
22
38
 
23
39
  Testing improvements:
24
40
 
25
- * Fixed a bunch of failing specs
41
+ * Fixed a bunch of failing mocked specs
26
42
 
27
43
  v2.5.0
28
44
  ======
@@ -21,6 +21,9 @@ Gem::Specification.new do |gem|
21
21
  gem.extra_rdoc_files = ["readme.md", "changelog.md"]
22
22
  gem.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Koala"]
23
23
 
24
+ gem.required_ruby_version = '>= 2.1'
25
+
24
26
  gem.add_runtime_dependency("faraday")
25
27
  gem.add_runtime_dependency("addressable")
28
+ gem.add_runtime_dependency("json", ">= 2.0")
26
29
  end
@@ -1,5 +1,5 @@
1
1
  # graph_batch_api and legacy are required at the bottom, since they depend on API being defined
2
- require 'koala/api/graph_api'
2
+ require 'koala/api/graph_api_methods'
3
3
  require 'koala/api/graph_collection'
4
4
  require 'openssl'
5
5
 
@@ -24,6 +24,45 @@ module Koala
24
24
 
25
25
  include GraphAPIMethods
26
26
 
27
+ # Make a call directly to the Graph API.
28
+ # (See any of the other methods for example invocations.)
29
+ #
30
+ # @param path the Graph API path to query (no leading / needed)
31
+ # @param args (see #get_object)
32
+ # @param verb the type of HTTP request to make (get, post, delete, etc.)
33
+ # @options (see #get_object)
34
+ #
35
+ # @yield response when making a batch API call, you can pass in a block
36
+ # that parses the results, allowing for cleaner code.
37
+ # The block's return value is returned in the batch results.
38
+ # See the code for {#get_picture} for examples.
39
+ # (Not needed in regular calls; you'll probably rarely use this.)
40
+ #
41
+ # @raise [Koala::Facebook::APIError] if Facebook returns an error
42
+ #
43
+ # @return the result from Facebook
44
+ def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
45
+ # enable appsecret_proof by default
46
+ options = {:appsecret_proof => true}.merge(options) if @app_secret
47
+ response = api(path, args, verb, options)
48
+
49
+ error = GraphErrorChecker.new(response.status, response.body, response.headers).error_if_appropriate
50
+ raise error if error
51
+
52
+ # if we want a component other than the body (e.g. redirect header for images), provide that
53
+ http_component = options[:http_component]
54
+ desired_data = if options[:http_component]
55
+ http_component == :response ? response : response.send(http_component)
56
+ else
57
+ # turn this into a GraphCollection if it's pageable
58
+ API::GraphCollection.evaluate(response.data, self)
59
+ end
60
+
61
+ # now process as appropriate for the given call (get picture header, etc.)
62
+ post_processing ? post_processing.call(desired_data) : desired_data
63
+ end
64
+
65
+
27
66
  # Makes a request to the appropriate Facebook API.
28
67
  # @note You'll rarely need to call this method directly.
29
68
  #
@@ -42,14 +81,10 @@ module Koala
42
81
  # @option options [Boolean] :beta use Facebook's beta tier
43
82
  # @option options [Boolean] :use_ssl force SSL for this request, even if it's tokenless.
44
83
  # (All API requests with access tokens use SSL.)
45
- # @param error_checking_block a block to evaluate the response status for additional JSON-encoded errors
46
- #
47
- # @yield The response for evaluation
48
- #
49
84
  # @raise [Koala::Facebook::ServerError] if Facebook returns an error (response status >= 500)
50
85
  #
51
- # @return the body of the response from Facebook (unless another http_component is requested)
52
- def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
86
+ # @return a Koala::HTTPService::Response object representing the returned Facebook data
87
+ def api(path, args = {}, verb = "get", options = {})
53
88
  # we make a copy of args so the modifications (added access_token & appsecret_proof)
54
89
  # do not affect the received argument
55
90
  args = args.dup
@@ -58,6 +93,7 @@ module Koala
58
93
  # This is explicitly needed in batch requests so GraphCollection
59
94
  # results preserve any specific access tokens provided
60
95
  args["access_token"] ||= @access_token || @app_access_token if @access_token || @app_access_token
96
+
61
97
  if options.delete(:appsecret_proof) && args["access_token"] && @app_secret
62
98
  args["appsecret_proof"] = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), @app_secret, args["access_token"])
63
99
  end
@@ -75,18 +111,7 @@ module Koala
75
111
  raise Koala::Facebook::ServerError.new(result.status.to_i, result.body)
76
112
  end
77
113
 
78
- yield result if error_checking_block
79
-
80
- # if we want a component other than the body (e.g. redirect header for images), return that
81
- if component = options[:http_component]
82
- component == :response ? result : result.send(options[:http_component])
83
- else
84
- # parse the body as JSON and run it through the error checker (if provided)
85
- # Note: Facebook sometimes sends results like "true" and "false", which are valid[RFC7159]
86
- # but unsupported by Ruby's stdlib[RFC4627] and cause JSON.parse to fail -- so we account for
87
- # that by wrapping the result in []
88
- JSON.parse("[#{result.body.to_s}]")[0]
89
- end
114
+ result
90
115
  end
91
116
 
92
117
  private
@@ -112,6 +137,9 @@ module Koala
112
137
  def preserve_form_arguments?(options)
113
138
  options[:format] == :json || options[:preserve_form_arguments] || Koala.config.preserve_form_arguments
114
139
  end
140
+
141
+ def check_response(http_status, body, headers)
142
+ end
115
143
  end
116
144
  end
117
145
  end
@@ -32,7 +32,6 @@ module Koala
32
32
  # for the active user from the cookie provided by Facebook.
33
33
  # See the Koala and Facebook documentation for more information.
34
34
  module GraphAPIMethods
35
-
36
35
  # Objects
37
36
 
38
37
  # Get information about a Facebook object.
@@ -107,7 +106,7 @@ module Koala
107
106
  # @return true if successful, false (or an APIError) if not
108
107
  def delete_object(id, options = {}, &block)
109
108
  # Deletes the object with the given ID from the graph.
110
- raise AuthenticationError.new(nil, nil, "Delete requires an access token") unless @access_token
109
+ raise AuthenticationError.new(nil, nil, "Delete requires an access token") unless access_token
111
110
  graph_call(id, {}, "delete", options, &block)
112
111
  end
113
112
 
@@ -157,7 +156,7 @@ module Koala
157
156
  # @return a hash containing the new object's id
158
157
  def put_connections(id, connection_name, args = {}, options = {}, &block)
159
158
  # Posts a certain connection
160
- raise AuthenticationError.new(nil, nil, "Write operations require an access token") unless @access_token
159
+ raise AuthenticationError.new(nil, nil, "Write operations require an access token") unless access_token
161
160
 
162
161
  graph_call("#{id}/#{connection_name}", args, "post", options, &block)
163
162
  end
@@ -175,7 +174,7 @@ module Koala
175
174
  # @return (see #delete_object)
176
175
  def delete_connections(id, connection_name, args = {}, options = {}, &block)
177
176
  # Deletes a given connection
178
- raise AuthenticationError.new(nil, nil, "Delete requires an access token") unless @access_token
177
+ raise AuthenticationError.new(nil, nil, "Delete requires an access token") unless access_token
179
178
  graph_call("#{id}/#{connection_name}", args, "delete", options, &block)
180
179
  end
181
180
 
@@ -336,7 +335,7 @@ module Koala
336
335
  # @return (see #delete_object)
337
336
  def delete_like(id, options = {}, &block)
338
337
  # Unlikes a given object for the logged-in user
339
- raise AuthenticationError.new(nil, nil, "Unliking requires an access token") unless @access_token
338
+ raise AuthenticationError.new(nil, nil, "Unliking requires an access token") unless access_token
340
339
  graph_call("#{id}/likes", {}, "delete", options, &block)
341
340
  end
342
341
 
@@ -344,14 +343,16 @@ module Koala
344
343
  # See {http://developers.facebook.com/docs/reference/api/#searching Facebook documentation} for more information.
345
344
  #
346
345
  # @param search_terms the query to search for
347
- # @param args additional arguments, such as type, fields, etc.
346
+ # @param args object type and any additional arguments, such as fields, etc.
348
347
  # @param options (see #get_object)
349
348
  # @param block (see Koala::Facebook::API#api)
350
349
  #
351
350
  # @return [Koala::Facebook::API::GraphCollection] an array of search results
352
351
  def search(search_terms, args = {}, options = {}, &block)
353
- args.merge!({:q => search_terms}) unless search_terms.nil?
354
- graph_call("search", args, "get", options, &block)
352
+ # Normally we wouldn't enforce Facebook API behavior, but the API fails with cryptic error
353
+ # messages if you fail to include a type term. For a convenience method, that is valuable.
354
+ raise ArgumentError, "type must be includedin args when searching" unless args[:type] || args["type"]
355
+ graph_call("search", args.merge("q" => search_terms), "get", options, &block)
355
356
  end
356
357
 
357
358
  # Convenience Methods
@@ -391,21 +392,6 @@ module Koala
391
392
  block ? block.call(access_token_info) : access_token_info
392
393
  end
393
394
 
394
- # Fetches the comments from fb:comments widgets for a given set of URLs (array or comma-separated string).
395
- # See https://developers.facebook.com/blog/post/490.
396
- #
397
- # @param urls the URLs for which you want comments
398
- # @param args (see #get_object)
399
- # @param options (see #get_object)
400
- # @param block (see Koala::Facebook::API#api)
401
- #
402
- # @returns a hash of urls => comment arrays
403
- def get_comments_for_urls(urls = [], args = {}, options = {}, &block)
404
- return [] if urls.empty?
405
- args.merge!(:ids => urls.respond_to?(:join) ? urls.join(",") : urls)
406
- get_object("comments", args, options, &block)
407
- end
408
-
409
395
  # App restrictions require you to JSON-encode the restriction value. This
410
396
  # is neither obvious nor intuitive, so this convenience method is
411
397
  # provided.
@@ -479,44 +465,8 @@ module Koala
479
465
  end
480
466
  end
481
467
 
482
- # Make a call directly to the Graph API.
483
- # (See any of the other methods for example invocations.)
484
- #
485
- # @param path the Graph API path to query (no leading / needed)
486
- # @param args (see #get_object)
487
- # @param verb the type of HTTP request to make (get, post, delete, etc.)
488
- # @options (see #get_object)
489
- #
490
- # @yield response when making a batch API call, you can pass in a block
491
- # that parses the results, allowing for cleaner code.
492
- # The block's return value is returned in the batch results.
493
- # See the code for {#get_picture} for examples.
494
- # (Not needed in regular calls; you'll probably rarely use this.)
495
- #
496
- # @raise [Koala::Facebook::APIError] if Facebook returns an error
497
- #
498
- # @return the result from Facebook
499
- def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
500
- # enable appsecret_proof by default
501
- options = {:appsecret_proof => true}.merge(options) if @app_secret
502
- result = api(path, args, verb, options) do |response|
503
- error = check_response(response.status, response.body, response.headers)
504
- raise error if error
505
- end
506
-
507
- # turn this into a GraphCollection if it's pageable
508
- result = API::GraphCollection.evaluate(result, self)
509
-
510
- # now process as appropriate for the given call (get picture header, etc.)
511
- post_processing ? post_processing.call(result) : result
512
- end
513
-
514
468
  private
515
469
 
516
- def check_response(http_status, body, headers)
517
- GraphErrorChecker.new(http_status, body, headers).error_if_appropriate
518
- end
519
-
520
470
  def parse_media_args(media_args, method)
521
471
  # photo and video uploads can accept different types of arguments (see above)
522
472
  # so here, we parse the arguments into a form directly usable in put_connections
@@ -4,13 +4,12 @@ require 'koala/api/batch_operation'
4
4
  module Koala
5
5
  module Facebook
6
6
  # @private
7
- class GraphBatchAPI < API
7
+ class GraphBatchAPI
8
8
  # inside a batch call we can do anything a regular Graph API can do
9
9
  include GraphAPIMethods
10
10
 
11
11
  attr_reader :original_api
12
12
  def initialize(api)
13
- super(api.access_token, api.app_secret)
14
13
  @original_api = api
15
14
  end
16
15
 
@@ -18,7 +17,9 @@ module Koala
18
17
  @batch_calls ||= []
19
18
  end
20
19
 
21
- def graph_call_in_batch(path, args = {}, verb = "get", options = {}, &post_processing)
20
+ # Enqueue a call into the batch for later processing.
21
+ # See API#graph_call
22
+ def graph_call(path, args = {}, verb = 'get', options = {}, &post_processing)
22
23
  # normalize options for consistency
23
24
  options = Koala::Utils.symbolize_hash(options)
24
25
 
@@ -34,73 +35,117 @@ module Koala
34
35
  nil # batch operations return nothing immediately
35
36
  end
36
37
 
37
- # redefine the graph_call method so we can use this API inside the batch block
38
- # just like any regular Graph API
39
- alias_method :graph_call_outside_batch, :graph_call
40
- alias_method :graph_call, :graph_call_in_batch
41
-
42
38
  # execute the queued batch calls
43
39
  def execute(http_options = {})
44
- return [] unless batch_calls.length > 0
40
+ return [] if batch_calls.empty?
41
+
45
42
  # Turn the call args collected into what facebook expects
46
- args = {}
47
- args["batch"] = JSON.dump(batch_calls.map { |batch_op|
48
- args.merge!(batch_op.files) if batch_op.files
49
- batch_op.to_batch_params(access_token, app_secret)
50
- })
43
+ args = { 'batch' => batch_args }
44
+ batch_calls.each do |call|
45
+ args.merge! call.files || {}
46
+ end
51
47
 
52
- batch_result = graph_call_outside_batch('/', args, 'post', http_options) do |response|
53
- unless response
54
- # Facebook sometimes reportedly returns an empty body at times
55
- # see https://github.com/arsduo/koala/issues/184
56
- raise BadFacebookResponse.new(200, '', "Facebook returned an empty body")
57
- end
48
+ original_api.graph_call('/', args, 'post', http_options, &handle_response)
49
+ end
50
+
51
+ def handle_response
52
+ lambda do |response|
53
+ raise bad_response if response.nil?
54
+ response.map(&generate_results)
55
+ end
56
+ end
57
+
58
+ def generate_results
59
+ index = 0
60
+ lambda do |call_result|
61
+ batch_op = batch_calls[index]; index += 1
62
+ post_process = batch_op.post_processing
58
63
 
59
- # map the results with post-processing included
60
- index = 0 # keep compat with ruby 1.8 - no with_index for map
61
- response.map do |call_result|
62
- # Get the options hash
63
- batch_op = batch_calls[index]
64
- index += 1
65
-
66
- raw_result = nil
67
- if call_result
68
- parsed_headers = if call_result.has_key?('headers')
69
- call_result['headers'].inject({}) { |headers, h| headers[h['name']] = h['value']; headers}
70
- else
71
- {}
72
- end
73
-
74
- if (error = check_response(call_result['code'], call_result['body'].to_s, parsed_headers))
75
- raw_result = error
76
- else
77
- # (see note in regular api method about JSON parsing)
78
- body = JSON.parse("[#{call_result['body'].to_s}]")[0]
79
-
80
- # Get the HTTP component they want
81
- raw_result = case batch_op.http_options[:http_component]
82
- when :status
83
- call_result["code"].to_i
84
- when :headers
85
- # facebook returns the headers as an array of k/v pairs, but we want a regular hash
86
- parsed_headers
87
- else
88
- body
89
- end
90
- end
91
- end
92
-
93
- # turn any results that are pageable into GraphCollections
94
- # and pass to post-processing callback if given
95
- result = GraphCollection.evaluate(raw_result, @original_api)
96
- if batch_op.post_processing
97
- batch_op.post_processing.call(result)
98
- else
99
- result
100
- end
64
+ # turn any results that are pageable into GraphCollections
65
+ result = GraphCollection.evaluate(
66
+ result_from_response(call_result, batch_op),
67
+ original_api
68
+ )
69
+
70
+ # and pass to post-processing callback if given
71
+ if post_process
72
+ post_process.call(result)
73
+ else
74
+ result
101
75
  end
102
76
  end
103
77
  end
78
+
79
+ def bad_response
80
+ # Facebook sometimes reportedly returns an empty body at times
81
+ BadFacebookResponse.new(200, '', 'Facebook returned an empty body')
82
+ end
83
+
84
+ def result_from_response(response, options)
85
+ return nil if response.nil?
86
+
87
+ headers = coerced_headers_from_response(response)
88
+ error = error_from_response(response, headers)
89
+ component = options.http_options[:http_component]
90
+
91
+ error || result_from_component({
92
+ :component => component,
93
+ :response => response,
94
+ :headers => headers
95
+ })
96
+ end
97
+
98
+ def coerced_headers_from_response(response)
99
+ headers = response.fetch('headers', [])
100
+
101
+ headers.each_with_object({}) do |h, memo|
102
+ memo.merge! h.fetch('name') => h.fetch('value')
103
+ end
104
+ end
105
+
106
+ def error_from_response(response, headers)
107
+ code = response['code']
108
+ body = response['body'].to_s
109
+
110
+ GraphErrorChecker.new(code, body, headers).error_if_appropriate
111
+ end
112
+
113
+ def batch_args
114
+ calls = batch_calls.map do |batch_op|
115
+ batch_op.to_batch_params(access_token, app_secret)
116
+ end
117
+
118
+ JSON.dump calls
119
+ end
120
+
121
+ def json_body(response)
122
+ # quirks_mode is needed because Facebook sometimes returns a raw true or false value --
123
+ # in Ruby 2.4 we can drop that.
124
+ JSON.parse(response.fetch('body'), quirks_mode: true)
125
+ end
126
+
127
+ def result_from_component(options)
128
+ component = options.fetch(:component)
129
+ response = options.fetch(:response)
130
+ headers = options.fetch(:headers)
131
+
132
+ # Get the HTTP component they want
133
+ case component
134
+ when :status then response['code'].to_i
135
+ # facebook returns the headers as an array of k/v pairs, but we want a regular hash
136
+ when :headers then headers
137
+ # (see note in regular api method about JSON parsing)
138
+ else json_body(response)
139
+ end
140
+ end
141
+
142
+ def access_token
143
+ original_api.access_token
144
+ end
145
+
146
+ def app_secret
147
+ original_api.app_secret
148
+ end
104
149
  end
105
150
  end
106
151
  end