koala 3.1.0 → 3.2.0

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
  SHA256:
3
- metadata.gz: 3209d60dbf8ab51fac40f7b990f221076bb453329641194dce55b800d0611856
4
- data.tar.gz: 9dfb75f13c1b1e187c325453793b6dfcdc085a0f42e119d40df7a30d7b492250
3
+ metadata.gz: 9f879bff1a0797804e8a89a2d8d34366ba21fafedd5317b4649326169dc9b7ed
4
+ data.tar.gz: 8f77e5f1d2f3eebcf5e08983ba4f2af69b64566d7d2d4dac6185a8b5c3222ba6
5
5
  SHA512:
6
- metadata.gz: 2aa7c4c61594d111e5654788712a56eea9005e4e3f6ce133536de6d2089f6014a6a0f5a059012b3bccf2204862b6c28212b517a4a264ea4b2cc272530d11cb4f
7
- data.tar.gz: 318d505d832d0d2e70cd9df39040254c1eea64bdf3c743cf29c6311e8fe2cd68159f3237a27a6a066136b5d3306029771fae2935fc4c768860a2c12525f29137
6
+ metadata.gz: 704dee302c62d906fd9f065abc86ea0e9268a4b160460ccfb455f8a7dbd3d41631bacf75e155953e5fd9d54e01fa81abc39f7730db4ea1023893b80dd378aa22
7
+ data.tar.gz: 38c9e731f1a76e3d441c3e783032a8f306168723e1552e00167dcd0bd5ad696833afffd2797ff4f4e0ed97e986578f08533b58f5064fa0610819fb409d10ddd3
data/Gemfile CHANGED
@@ -13,7 +13,7 @@ group :development, :test do
13
13
  end
14
14
 
15
15
  group :test do
16
- gem "rspec", '~> 3.4'
16
+ gem "rspec", "~> 3.0", "< 3.10" # resrict rspec version until https://github.com/rspec/rspec-support/pull/537 gets merged
17
17
  gem "vcr", github: 'vcr/vcr', ref: '8ced6c96e01737a418cd270e0382a8c2c6d85f7f' # needs https://github.com/vcr/vcr/pull/907 for ruby 3.1
18
18
  gem "webmock"
19
19
  gem "simplecov"
data/changelog.md CHANGED
@@ -15,7 +15,19 @@ Testing improvements:
15
15
 
16
16
  Others:
17
17
 
18
- v3.1.0 (2022-18-01)
18
+ v3.2.0 (2022-05-27)
19
+ ======
20
+
21
+ New features:
22
+
23
+ * Exposes limiting headers(`x-business-use-case-usage, x-ad-account-usage, x-app-usage`) to APIError ([#668](https://github.com/arsduo/koala/pull/668))
24
+ * Add `rate_limit_hook` configuration to get rate limiting headers (`x-business-use-case-usage, x-ad-account-usage, x-app-usage`) ([#670](https://github.com/arsduo/koala/pull/670))
25
+
26
+ Testing improvements:
27
+
28
+ * Fix builds for ruby 3.x
29
+
30
+ v3.1.0 (2022-01-18)
19
31
  ======
20
32
 
21
33
  New features:
@@ -17,7 +17,7 @@ module Koala
17
17
 
18
18
  # Facebook can return debug information in the response headers -- see
19
19
  # https://developers.facebook.com/docs/graph-api/using-graph-api#bugdebug
20
- DEBUG_HEADERS = ["x-fb-debug", "x-fb-rev", "x-fb-trace-id"]
20
+ DEBUG_HEADERS = %w[x-fb-debug x-fb-rev x-fb-trace-id x-business-use-case-usage x-ad-account-usage x-app-usage]
21
21
 
22
22
  def error_if_appropriate
23
23
  if http_status >= 400
data/lib/koala/api.rb CHANGED
@@ -13,14 +13,16 @@ module Koala
13
13
  # signed by default, unless you pass appsecret_proof:
14
14
  # false as an option to the API call. (See
15
15
  # https://developers.facebook.com/docs/graph-api/securing-requests/)
16
+ # @param [Block] rate_limit_hook block called with limits received in facebook response headers
16
17
  # @note If no access token is provided, you can only access some public information.
17
18
  # @return [Koala::Facebook::API] the API client
18
- def initialize(access_token = Koala.config.access_token, app_secret = Koala.config.app_secret)
19
+ def initialize(access_token = Koala.config.access_token, app_secret = Koala.config.app_secret, rate_limit_hook = Koala.config.rate_limit_hook)
19
20
  @access_token = access_token
20
21
  @app_secret = app_secret
22
+ @rate_limit_hook = rate_limit_hook
21
23
  end
22
24
 
23
- attr_reader :access_token, :app_secret
25
+ attr_reader :access_token, :app_secret, :rate_limit_hook
24
26
 
25
27
  include GraphAPIMethods
26
28
 
@@ -58,6 +60,18 @@ module Koala
58
60
  API::GraphCollection.evaluate(response, self)
59
61
  end
60
62
 
63
+ if rate_limit_hook
64
+ limits = %w(x-business-use-case-usage x-ad-account-usage x-app-usage).each_with_object({}) do |key, hash|
65
+ value = response.headers.fetch(key, nil)
66
+ next unless value
67
+ hash[key] = JSON.parse(response.headers[key])
68
+ rescue JSON::ParserError => e
69
+ Koala::Utils.logger.error("#{e.class}: #{e.message} while parsing #{key} = #{value}")
70
+ end
71
+
72
+ rate_limit_hook.call(limits) if limits.keys.any?
73
+ end
74
+
61
75
  # now process as appropriate for the given call (get picture header, etc.)
62
76
  post_processing ? post_processing.call(desired_data) : desired_data
63
77
  end
@@ -31,6 +31,9 @@ class Koala::Configuration
31
31
  # Whether or not to mask tokens
32
32
  attr_accessor :mask_tokens
33
33
 
34
+ # Called with the info for the rate limits in the response header
35
+ attr_accessor :rate_limit_hook
36
+
34
37
  # Certain Facebook services (beta, video) require you to access different
35
38
  # servers. If you're using your own servers, for instance, for a proxy,
36
39
  # you can change both the matcher (what value to change when updating the URL) and the
data/lib/koala/errors.rb CHANGED
@@ -23,7 +23,10 @@ module Koala
23
23
  :fb_error_user_title,
24
24
  :fb_error_trace_id,
25
25
  :fb_error_debug,
26
- :fb_error_rev
26
+ :fb_error_rev,
27
+ :fb_buc_usage,
28
+ :fb_ada_usage,
29
+ :fb_app_usage
27
30
 
28
31
  # Create a new API Error
29
32
  #
@@ -66,6 +69,9 @@ module Koala
66
69
  self.fb_error_trace_id = error_info["x-fb-trace-id"]
67
70
  self.fb_error_debug = error_info["x-fb-debug"]
68
71
  self.fb_error_rev = error_info["x-fb-rev"]
72
+ self.fb_buc_usage = json_parse_for(error_info, "x-business-use-case-usage")
73
+ self.fb_ada_usage = json_parse_for(error_info, "x-ad-account-usage")
74
+ self.fb_app_usage = json_parse_for(error_info, "x-app-usage")
69
75
 
70
76
  error_array = []
71
77
  %w(type code error_subcode message error_user_title error_user_msg x-fb-trace-id).each do |key|
@@ -82,6 +88,20 @@ module Koala
82
88
 
83
89
  super(message)
84
90
  end
91
+
92
+ private
93
+
94
+ # refs: https://developers.facebook.com/docs/graph-api/overview/rate-limiting/#headers
95
+ # NOTE: The header will contain a JSON-formatted string that describes current application rate limit usage.
96
+ def json_parse_for(error_info, key)
97
+ string = error_info[key]
98
+ return if string.nil?
99
+
100
+ JSON.parse(string)
101
+ rescue JSON::ParserError => e
102
+ Koala::Utils.logger.error("#{e.class}: #{e.message} while parsing #{key} = #{string}")
103
+ nil
104
+ end
85
105
  end
86
106
 
87
107
  # Facebook returned an invalid response body
data/lib/koala/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Koala
2
- VERSION = "3.1.0"
2
+ VERSION = "3.2.0"
3
3
  end
data/readme.md CHANGED
@@ -177,6 +177,37 @@ Koala::Facebook::RealtimeUpdates.meet_challenge(params, your_verify_token)
177
177
  ```
178
178
  For more information about meet_challenge and the RealtimeUpdates class, check out the Real-Time Updates page on the wiki.
179
179
 
180
+ Rate limits
181
+ -----------
182
+
183
+ We support Facebook rate limit informations as defined here: [https://developers.facebook.com/docs/graph-api/overview/rate-limiting/](https://developers.facebook.com/docs/graph-api/overview/rate-limiting/)
184
+
185
+ The information is available either via the `Facebook::APIError`:
186
+
187
+ ```ruby
188
+ error.fb_buc_usage
189
+ error.fb_ada_usage
190
+ error.fb_app_usage
191
+ ```
192
+
193
+ Or with the rate_limit_hook:
194
+
195
+ ```ruby
196
+ # App level configuration
197
+
198
+ Koala.configure do |config|
199
+ config.rate_limit_hook = ->(limits) {
200
+ limits["x-app-usage"] # {"call_count"=>0, "total_cputime"=>0, "total_time"=>0}
201
+ limits["x-ad-account-usage"] # {"acc_id_util_pct"=>9.67}
202
+ limits["x-business-use-case-usage"] # {"123456789012345"=>[{"type"=>"messenger", "call_count"=>1, "total_cputime"=>1, "total_time"=>1, "estimated_time_to_regain_access"=>0}]}
203
+ }
204
+ end
205
+
206
+ # Per API configuration
207
+
208
+ Koala::Facebook::API.new('', '', ->(limits) {})
209
+ ```
210
+
180
211
  Test Users
181
212
  ----------
182
213
 
@@ -282,4 +282,81 @@ describe "Koala::Facebook::API" do
282
282
  expect(@service.graph_call('anything')).to be_falsey
283
283
  end
284
284
  end
285
+
286
+ describe "Rate limit hook" do
287
+ it "is called when x-business-use-case-usage header is present" do
288
+ api = Koala::Facebook::API.new('', '', ->(limits) {
289
+ expect(limits["x-business-use-case-usage"]).to eq({"123456789012345"=>[{"type"=>"messenger", "call_count"=>1, "total_cputime"=>1, "total_time"=>1, "estimated_time_to_regain_access"=>0}]})
290
+ })
291
+
292
+ result = {"a" => 2}
293
+ response = Koala::HTTPService::Response.new(200, result.to_json, { "x-business-use-case-usage" => "{\"123456789012345\":[{\"type\":\"messenger\",\"call_count\":1,\"total_cputime\":1,\"total_time\":1,\"estimated_time_to_regain_access\":0}]}" })
294
+ allow(Koala).to receive(:make_request).and_return(response)
295
+
296
+ api.graph_call('anything')
297
+ end
298
+
299
+ it "is called when x-ad-account-usage header is present" do
300
+ api = Koala::Facebook::API.new('', '', ->(limits) {
301
+ expect(limits["x-ad-account-usage"]).to eq({"acc_id_util_pct"=>9.67})
302
+ })
303
+
304
+ result = {"a" => 2}
305
+ response = Koala::HTTPService::Response.new(200, result.to_json, { "x-ad-account-usage" => "{\"acc_id_util_pct\":9.67}" })
306
+ allow(Koala).to receive(:make_request).and_return(response)
307
+
308
+ api.graph_call('anything')
309
+ end
310
+
311
+ it "is called when x-app-usage header is present" do
312
+ api = Koala::Facebook::API.new('', '', ->(limits) {
313
+ expect(limits["x-app-usage"]).to eq({"call_count"=>0, "total_cputime"=>0, "total_time"=>0})
314
+ })
315
+
316
+ result = {"a" => 2}
317
+ response = Koala::HTTPService::Response.new(200, result.to_json, { "x-app-usage" => "{\"call_count\":0,\"total_cputime\":0,\"total_time\":0}" })
318
+ allow(Koala).to receive(:make_request).and_return(response)
319
+
320
+ api.graph_call('anything')
321
+ end
322
+
323
+ it "isn't called if none of the rate limit header is present" do
324
+ rate_limit_hook_called = false
325
+
326
+ api = Koala::Facebook::API.new('', '', ->(limits) {
327
+ rate_limit_hook_called = true
328
+ })
329
+
330
+ result = {"a" => 2}
331
+ response = Koala::HTTPService::Response.new(200, result.to_json, {})
332
+ allow(Koala).to receive(:make_request).and_return(response)
333
+
334
+ api.graph_call('anything')
335
+
336
+ expect(rate_limit_hook_called).to be(false)
337
+ end
338
+
339
+ it "isn't called if no rate limit hook is defined" do
340
+ api = Koala::Facebook::API.new('', '', ->(limits) {
341
+ #noop
342
+ })
343
+
344
+ result = {"a" => 2}
345
+ response = Koala::HTTPService::Response.new(200, result.to_json, { "x-ad-account-usage" => "{\"acc_id_util_pct\"9.67}"})
346
+ allow(Koala).to receive(:make_request).and_return(response)
347
+
348
+ expect(Koala::Utils.logger).to receive(:error).with("JSON::ParserError: 859: unexpected token at '{\"acc_id_util_pct\"9.67}' while parsing x-ad-account-usage = {\"acc_id_util_pct\"9.67}")
349
+ api.graph_call('anything')
350
+ end
351
+
352
+ it "logs an error if the rate limit header can't be properly parsed" do
353
+ api = Koala::Facebook::API.new('', '', nil)
354
+
355
+ result = {"a" => 2}
356
+ response = Koala::HTTPService::Response.new(200, result.to_json, {})
357
+ allow(Koala).to receive(:make_request).and_return(response)
358
+
359
+ api.graph_call('anything')
360
+ end
361
+ end
285
362
  end
@@ -1,5 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
+ BUC_USAGE_JSON = "{\"123456789012345\":[{\"type\":\"messenger\",\"call_count\":1,\"total_cputime\":1,\"total_time\":1,\"estimated_time_to_regain_access\":0}]}"
4
+ ADA_USAGE_JSON = "{\"acc_id_util_pct\":9.67}"
5
+ APP_USAGE_JSON = "{\"call_count\":0,\"total_cputime\":0,\"total_time\":0}"
6
+
3
7
  describe Koala::Facebook::APIError do
4
8
  it "is a Koala::KoalaError" do
5
9
  expect(Koala::Facebook::APIError.new(nil, nil)).to be_a(Koala::KoalaError)
@@ -32,7 +36,10 @@ describe Koala::Facebook::APIError do
32
36
  'error_user_title' => 'error user title',
33
37
  'x-fb-trace-id' => 'fb trace id',
34
38
  'x-fb-debug' => 'fb debug token',
35
- 'x-fb-rev' => 'fb revision'
39
+ 'x-fb-rev' => 'fb revision',
40
+ 'x-business-use-case-usage' => BUC_USAGE_JSON,
41
+ 'x-ad-account-usage' => ADA_USAGE_JSON,
42
+ 'x-app-usage' => APP_USAGE_JSON
36
43
  }
37
44
  Koala::Facebook::APIError.new(400, '', error_info)
38
45
  }
@@ -46,7 +53,10 @@ describe Koala::Facebook::APIError do
46
53
  :fb_error_user_title => 'error user title',
47
54
  :fb_error_trace_id => 'fb trace id',
48
55
  :fb_error_debug => 'fb debug token',
49
- :fb_error_rev => 'fb revision'
56
+ :fb_error_rev => 'fb revision',
57
+ :fb_buc_usage => JSON.parse(BUC_USAGE_JSON),
58
+ :fb_ada_usage => JSON.parse(ADA_USAGE_JSON),
59
+ :fb_app_usage => JSON.parse(APP_USAGE_JSON)
50
60
  }.each_pair do |accessor, value|
51
61
  it "sets #{accessor} to #{value}" do
52
62
  expect(error.send(accessor)).to eq(value)
@@ -76,7 +86,10 @@ describe Koala::Facebook::APIError do
76
86
  :fb_error_code => 1,
77
87
  :fb_error_subcode => 'subcode',
78
88
  :fb_error_user_msg => 'error user message',
79
- :fb_error_user_title => 'error user title'
89
+ :fb_error_user_title => 'error user title',
90
+ :fb_buc_usage => nil,
91
+ :fb_ada_usage => nil,
92
+ :fb_app_usage => nil
80
93
  }.each_pair do |accessor, value|
81
94
  expect(error.send(accessor)).to eq(value)
82
95
  end
@@ -593,7 +593,7 @@ describe "Koala::Facebook::GraphAPI in batch mode" do
593
593
  hash_including(@other_access_token_args.dup),
594
594
  anything,
595
595
  anything
596
- ).and_return(Koala::HTTPService::Response.new(200, "", ""))
596
+ ).and_return(Koala::HTTPService::Response.new(200, "", {}))
597
597
 
598
598
  # Page the collection
599
599
  app_event_types.next_page
@@ -11,7 +11,10 @@ module Koala
11
11
  expect(GraphErrorChecker::DEBUG_HEADERS).to match_array([
12
12
  "x-fb-rev",
13
13
  "x-fb-debug",
14
- "x-fb-trace-id"
14
+ "x-fb-trace-id",
15
+ "x-business-use-case-usage",
16
+ "x-ad-account-usage",
17
+ "x-app-usage"
15
18
  ])
16
19
  end
17
20
 
@@ -84,10 +87,25 @@ module Koala
84
87
  "x-fb-debug" => double("fb debug"),
85
88
  "x-fb-rev" => double("fb rev"),
86
89
  "x-fb-trace-id" => double("fb trace id"),
90
+ "x-business-use-case-usage" => { 'a' => 1, 'b' => 2 }.to_json,
91
+ "x-ad-account-usage" => { 'c' => 3, 'd' => 4 }.to_json,
92
+ "x-app-usage" => { 'e' => 5, 'f' => 6 }.to_json
87
93
  )
88
94
  expect(error.fb_error_trace_id).to eq(headers["x-fb-trace-id"])
89
95
  expect(error.fb_error_debug).to eq(headers["x-fb-debug"])
90
96
  expect(error.fb_error_rev).to eq(headers["x-fb-rev"])
97
+ expect(error.fb_buc_usage).to eq({ 'a' => 1, 'b' => 2 })
98
+ expect(error.fb_ada_usage).to eq({ 'c' => 3, 'd' => 4 })
99
+ expect(error.fb_app_usage).to eq({ 'e' => 5, 'f' => 6 })
100
+ end
101
+
102
+ it "logs if one of the FB debug headers can't be parsed" do
103
+ headers.merge!(
104
+ "x-app-usage" => '{invalid:json}'
105
+ )
106
+
107
+ expect(Koala::Utils.logger).to receive(:error).with("JSON::ParserError: 859: unexpected token at '{invalid:json}' while parsing x-app-usage = {invalid:json}")
108
+ expect(error.fb_app_usage).to eq(nil)
91
109
  end
92
110
 
93
111
  context "it returns an AuthenticationError" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: koala
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Koppel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-18 00:00:00.000000000 Z
11
+ date: 2022-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday