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 +4 -4
- data/Gemfile +1 -1
- data/changelog.md +13 -1
- data/lib/koala/api/graph_error_checker.rb +1 -1
- data/lib/koala/api.rb +16 -2
- data/lib/koala/configuration.rb +3 -0
- data/lib/koala/errors.rb +21 -1
- data/lib/koala/version.rb +1 -1
- data/readme.md +31 -0
- data/spec/cases/api_spec.rb +77 -0
- data/spec/cases/error_spec.rb +16 -3
- data/spec/cases/graph_api_batch_spec.rb +1 -1
- data/spec/cases/graph_error_checker_spec.rb +19 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f879bff1a0797804e8a89a2d8d34366ba21fafedd5317b4649326169dc9b7ed
|
4
|
+
data.tar.gz: 8f77e5f1d2f3eebcf5e08983ba4f2af69b64566d7d2d4dac6185a8b5c3222ba6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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",
|
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.
|
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 = [
|
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
|
data/lib/koala/configuration.rb
CHANGED
@@ -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
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
|
|
data/spec/cases/api_spec.rb
CHANGED
@@ -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
|
data/spec/cases/error_spec.rb
CHANGED
@@ -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.
|
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-
|
11
|
+
date: 2022-05-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|