koala 3.1.0 → 3.2.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.
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