koala 3.0.0.beta1 → 3.0.0.beta2

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.
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