rito 0.1.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 +7 -0
- data/CHANGELOG.md +30 -0
- data/LICENSE +21 -0
- data/README.md +182 -0
- data/lib/rito/client.rb +118 -0
- data/lib/rito/connection.rb +39 -0
- data/lib/rito/endpoints/account_v1.rb +45 -0
- data/lib/rito/endpoints/base.rb +35 -0
- data/lib/rito/endpoints/lol/challenges_v1.rb +31 -0
- data/lib/rito/endpoints/lol/champion_mastery_v4.rb +33 -0
- data/lib/rito/endpoints/lol/champion_v3.rb +17 -0
- data/lib/rito/endpoints/lol/clash_v1.rb +27 -0
- data/lib/rito/endpoints/lol/league_v4.rb +58 -0
- data/lib/rito/endpoints/lol/match_v5.rb +34 -0
- data/lib/rito/endpoints/lol/spectator_v5.rb +22 -0
- data/lib/rito/endpoints/lol/status_v4.rb +15 -0
- data/lib/rito/endpoints/lol/summoner_v4.rb +35 -0
- data/lib/rito/endpoints/lol/tournament_v5.rb +69 -0
- data/lib/rito/endpoints/lor.rb +95 -0
- data/lib/rito/endpoints/tft.rb +123 -0
- data/lib/rito/endpoints/valorant.rb +92 -0
- data/lib/rito/errors.rb +55 -0
- data/lib/rito/instrumentation.rb +17 -0
- data/lib/rito/middleware/authentication.rb +21 -0
- data/lib/rito/middleware/rate_limit_observe.rb +32 -0
- data/lib/rito/middleware/rate_limit_throttle.rb +58 -0
- data/lib/rito/middleware/riot_errors.rb +81 -0
- data/lib/rito/models/account.rb +38 -0
- data/lib/rito/models/lol.rb +80 -0
- data/lib/rito/models/match.rb +71 -0
- data/lib/rito/models/summoner.rb +23 -0
- data/lib/rito/rate_limiting/adaptive_limiter.rb +71 -0
- data/lib/rito/rate_limiting/bucket.rb +83 -0
- data/lib/rito/rate_limiting/header_parser.rb +53 -0
- data/lib/rito/rate_limiting/null_limiter.rb +11 -0
- data/lib/rito/rate_limiting/redis_limiter.rb +153 -0
- data/lib/rito/routing.rb +79 -0
- data/lib/rito/version.rb +5 -0
- data/lib/rito.rb +71 -0
- metadata +109 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Lol
|
|
6
|
+
class SpectatorV5 < Base
|
|
7
|
+
ROUTING = :platform
|
|
8
|
+
|
|
9
|
+
# Returns nil when the summoner is not in an active game (HTTP 404).
|
|
10
|
+
def active_game(summoner_id, region: nil)
|
|
11
|
+
get("/lol/spectator/v5/active-games/by-summoner-id/#{escape(summoner_id)}", region)
|
|
12
|
+
rescue NotFound
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def featured_games(region: nil)
|
|
17
|
+
get('/lol/spectator/v5/featured-games', region)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Lol
|
|
6
|
+
class SummonerV4 < Base
|
|
7
|
+
ROUTING = :platform
|
|
8
|
+
|
|
9
|
+
def by_puuid(puuid, region: nil)
|
|
10
|
+
Models::Summoner.from_api(
|
|
11
|
+
get("/lol/summoner/v4/summoners/by-puuid/#{escape(puuid)}", region)
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def by_name(name, region: nil)
|
|
16
|
+
Models::Summoner.from_api(
|
|
17
|
+
get("/lol/summoner/v4/summoners/by-name/#{escape(name)}", region)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def by_account_id(account_id, region: nil)
|
|
22
|
+
Models::Summoner.from_api(
|
|
23
|
+
get("/lol/summoner/v4/summoners/by-account/#{escape(account_id)}", region)
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def by_summoner_id(summoner_id, region: nil)
|
|
28
|
+
Models::Summoner.from_api(
|
|
29
|
+
get("/lol/summoner/v4/summoners/#{escape(summoner_id)}", region)
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Lol
|
|
6
|
+
class TournamentV5 < Base
|
|
7
|
+
ROUTING = :platform
|
|
8
|
+
PREFIX = '/lol/tournament/v5'
|
|
9
|
+
|
|
10
|
+
def code(tournament_code, region: nil)
|
|
11
|
+
get("#{PREFIX}/codes/by-code/#{escape(tournament_code)}", region)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_codes(tournament_id:, count: nil, body: {}, region: nil)
|
|
15
|
+
params = { 'tournamentId' => tournament_id }
|
|
16
|
+
params['count'] = count if count
|
|
17
|
+
post("#{PREFIX}/codes", region, params: params, body: body)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update_code(tournament_code, body: {}, region: nil)
|
|
21
|
+
put("#{PREFIX}/codes/by-code/#{escape(tournament_code)}", region, body: body)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def lobby_events(tournament_code, region: nil)
|
|
25
|
+
get("#{PREFIX}/lobby-events/by-code/#{escape(tournament_code)}", region)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create_provider(body:, region: nil)
|
|
29
|
+
post("#{PREFIX}/providers", region, body: body)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def create_tournament(body:, region: nil)
|
|
33
|
+
post("#{PREFIX}/tournaments", region, body: body)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class TournamentStubV5 < Base
|
|
38
|
+
ROUTING = :platform
|
|
39
|
+
PREFIX = '/lol/tournament-stub/v5'
|
|
40
|
+
|
|
41
|
+
def code(tournament_code, region: nil)
|
|
42
|
+
get("#{PREFIX}/codes/by-code/#{escape(tournament_code)}", region)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def create_codes(tournament_id:, count: nil, body: {}, region: nil)
|
|
46
|
+
params = { 'tournamentId' => tournament_id }
|
|
47
|
+
params['count'] = count if count
|
|
48
|
+
post("#{PREFIX}/codes", region, params: params, body: body)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def update_code(tournament_code, body: {}, region: nil)
|
|
52
|
+
put("#{PREFIX}/codes/by-code/#{escape(tournament_code)}", region, body: body)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def lobby_events(tournament_code, region: nil)
|
|
56
|
+
get("#{PREFIX}/lobby-events/by-code/#{escape(tournament_code)}", region)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def create_provider(body:, region: nil)
|
|
60
|
+
post("#{PREFIX}/providers", region, body: body)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def create_tournament(body:, region: nil)
|
|
64
|
+
post("#{PREFIX}/tournaments", region, body: body)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Lor
|
|
6
|
+
class MatchV1 < Base
|
|
7
|
+
ROUTING = :regional
|
|
8
|
+
|
|
9
|
+
def ids_by_puuid(puuid, region: nil)
|
|
10
|
+
get("/lor/match/v1/matches/by-puuid/#{escape(puuid)}/ids", region)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def by_id(match_id, region: nil)
|
|
14
|
+
get("/lor/match/v1/matches/#{escape(match_id)}", region)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class RankedV1 < Base
|
|
19
|
+
ROUTING = :regional
|
|
20
|
+
|
|
21
|
+
def leaderboards(region: nil)
|
|
22
|
+
get('/lor/ranked/v1/leaderboards', region)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class StatusV1 < Base
|
|
27
|
+
ROUTING = :regional
|
|
28
|
+
|
|
29
|
+
def platform_data(region: nil)
|
|
30
|
+
get('/lor/status/v1/platform-data', region)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class DeckV1 < Base
|
|
35
|
+
ROUTING = :regional
|
|
36
|
+
|
|
37
|
+
# Requires the client to be configured with a bearer_token (RSO).
|
|
38
|
+
def my_decks(region: nil)
|
|
39
|
+
get('/lor/deck/v1/decks/me', region)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def deck(deck_id, region: nil)
|
|
43
|
+
get("/lor/deck/v1/decks/me/#{escape(deck_id)}", region)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class InventoryV1 < Base
|
|
48
|
+
ROUTING = :regional
|
|
49
|
+
|
|
50
|
+
# Requires the client to be configured with a bearer_token (RSO).
|
|
51
|
+
def my_cards(region: nil)
|
|
52
|
+
get('/lor/inventory/v1/cards/me', region)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class Riftbound < Base
|
|
58
|
+
ROUTING = :regional
|
|
59
|
+
|
|
60
|
+
def contents(locale: nil, region: nil)
|
|
61
|
+
params = {}
|
|
62
|
+
params['locale'] = locale if locale
|
|
63
|
+
get('/riftbound/content/v1/contents', region, params: params)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
module Lor
|
|
69
|
+
class Api
|
|
70
|
+
def initialize(client)
|
|
71
|
+
@client = client
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def matches
|
|
75
|
+
Endpoints::Lor::MatchV1.new(@client)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def ranked
|
|
79
|
+
Endpoints::Lor::RankedV1.new(@client)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def status
|
|
83
|
+
Endpoints::Lor::StatusV1.new(@client)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def decks
|
|
87
|
+
Endpoints::Lor::DeckV1.new(@client)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def inventory
|
|
91
|
+
Endpoints::Lor::InventoryV1.new(@client)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Tft
|
|
6
|
+
class SummonerV1 < Base
|
|
7
|
+
ROUTING = :platform
|
|
8
|
+
PREFIX = '/tft/summoner/v1/summoners'
|
|
9
|
+
|
|
10
|
+
def by_puuid(puuid, region: nil)
|
|
11
|
+
Models::Summoner.from_api(get("#{PREFIX}/by-puuid/#{escape(puuid)}", region))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def by_account_id(account_id, region: nil)
|
|
15
|
+
Models::Summoner.from_api(get("#{PREFIX}/by-account/#{escape(account_id)}", region))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def by_summoner_id(summoner_id, region: nil)
|
|
19
|
+
Models::Summoner.from_api(get("#{PREFIX}/#{escape(summoner_id)}", region))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Requires the client to be configured with a bearer_token (RSO).
|
|
23
|
+
def me(region: nil)
|
|
24
|
+
Models::Summoner.from_api(get("#{PREFIX}/me", region))
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class LeagueV1 < Base
|
|
29
|
+
ROUTING = :regional
|
|
30
|
+
|
|
31
|
+
def entries_by_summoner_id(summoner_id, queue: nil, tier: nil, division: nil, region: nil)
|
|
32
|
+
params = {}
|
|
33
|
+
params['queue'] = queue if queue
|
|
34
|
+
params['tier'] = tier if tier
|
|
35
|
+
params['division'] = division if division
|
|
36
|
+
get("/tft/league/v1/entries/by-summoner/#{escape(summoner_id)}", region, params: params)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def challenger(region: nil)
|
|
40
|
+
get('/tft/league/v1/challenger', region)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def grandmaster(region: nil)
|
|
44
|
+
get('/tft/league/v1/grandmaster', region)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def master(region: nil)
|
|
48
|
+
get('/tft/league/v1/master', region)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def rated_ladder_top(queue, region: nil)
|
|
52
|
+
get("/tft/league/v1/rated-ladders/#{escape(queue)}/top", region)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class MatchV1 < Base
|
|
57
|
+
ROUTING = :regional
|
|
58
|
+
|
|
59
|
+
def ids_by_puuid(puuid, count: nil, start: nil, region: nil)
|
|
60
|
+
params = {}
|
|
61
|
+
params['count'] = count if count
|
|
62
|
+
params['start'] = start if start
|
|
63
|
+
get("/tft/match/v1/matches/by-puuid/#{escape(puuid)}/ids", region, params: params)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def by_id(match_id, region: nil)
|
|
67
|
+
get("/tft/match/v1/matches/#{escape(match_id)}", region)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class StatusV1 < Base
|
|
72
|
+
ROUTING = :platform
|
|
73
|
+
|
|
74
|
+
def platform_data(region: nil)
|
|
75
|
+
get('/tft/status/v1/platform-data', region)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class SpectatorTftV5 < Base
|
|
80
|
+
ROUTING = :platform
|
|
81
|
+
|
|
82
|
+
# Returns nil when the summoner is not in an active game (HTTP 404).
|
|
83
|
+
def active_game(summoner_id, region: nil)
|
|
84
|
+
get("/tft/spectator/v5/active-games/by-summoner-id/#{escape(summoner_id)}", region)
|
|
85
|
+
rescue NotFound
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def featured_games(region: nil)
|
|
90
|
+
get('/tft/spectator/v5/featured-games', region)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
module Tft
|
|
97
|
+
class Api
|
|
98
|
+
def initialize(client)
|
|
99
|
+
@client = client
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def summoner
|
|
103
|
+
Endpoints::Tft::SummonerV1.new(@client)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def leagues
|
|
107
|
+
Endpoints::Tft::LeagueV1.new(@client)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def matches
|
|
111
|
+
Endpoints::Tft::MatchV1.new(@client)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def status
|
|
115
|
+
Endpoints::Tft::StatusV1.new(@client)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def spectator
|
|
119
|
+
Endpoints::Tft::SpectatorTftV5.new(@client)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Valorant
|
|
6
|
+
class ContentV1 < Base
|
|
7
|
+
ROUTING = :valorant_platform
|
|
8
|
+
|
|
9
|
+
def contents(locale: nil, region: nil)
|
|
10
|
+
params = {}
|
|
11
|
+
params['locale'] = locale if locale
|
|
12
|
+
get('/val/content/v1/contents', region, params: params)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class MatchV1 < Base
|
|
17
|
+
ROUTING = :valorant_platform
|
|
18
|
+
|
|
19
|
+
def by_id(match_id, region: nil)
|
|
20
|
+
get("/val/match/v1/matches/#{escape(match_id)}", region)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def ids_by_puuid(puuid, region: nil)
|
|
24
|
+
get("/val/match/v1/matchlists/by-puuid/#{escape(puuid)}", region)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def recent_by_queue(queue, region: nil)
|
|
28
|
+
get("/val/match/v1/recent-matches/by-queue/#{escape(queue)}", region)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class ConsoleMatchV1 < Base
|
|
33
|
+
ROUTING = :valorant_console_platform
|
|
34
|
+
|
|
35
|
+
def by_id(match_id, region: nil)
|
|
36
|
+
get("/val/console/match/v1/matches/#{escape(match_id)}", region)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def ids_by_puuid(puuid, region: nil)
|
|
40
|
+
get("/val/console/match/v1/matchlists/by-puuid/#{escape(puuid)}", region)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class RankedV1 < Base
|
|
45
|
+
ROUTING = :valorant_platform
|
|
46
|
+
|
|
47
|
+
def leaderboard(act_id, size: nil, start_index: nil, region: nil)
|
|
48
|
+
params = {}
|
|
49
|
+
params['size'] = size if size
|
|
50
|
+
params['startIndex'] = start_index if start_index
|
|
51
|
+
get("/val/ranked/v1/leaderboards/by-act/#{escape(act_id)}", region, params: params)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class StatusV1 < Base
|
|
56
|
+
ROUTING = :valorant_platform
|
|
57
|
+
|
|
58
|
+
def platform_data(region: nil)
|
|
59
|
+
get('/val/status/v1/platform-data', region)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
module Val
|
|
66
|
+
class Api
|
|
67
|
+
def initialize(client)
|
|
68
|
+
@client = client
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def content
|
|
72
|
+
Endpoints::Valorant::ContentV1.new(@client)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def matches
|
|
76
|
+
Endpoints::Valorant::MatchV1.new(@client)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def console_matches
|
|
80
|
+
Endpoints::Valorant::ConsoleMatchV1.new(@client)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def ranked
|
|
84
|
+
Endpoints::Valorant::RankedV1.new(@client)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def status
|
|
88
|
+
Endpoints::Valorant::StatusV1.new(@client)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/rito/errors.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
attr_reader :response, :status
|
|
6
|
+
|
|
7
|
+
def initialize(message = nil, response: nil)
|
|
8
|
+
@response = response
|
|
9
|
+
@status = response&.status
|
|
10
|
+
super(message || default_message)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def default_message
|
|
16
|
+
base = self.class.name.split('::').last
|
|
17
|
+
detail = @status ? " (HTTP #{@status})" : ''
|
|
18
|
+
"#{base}#{detail}"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class ConfigError < Error; end
|
|
23
|
+
|
|
24
|
+
class BadRequest < Error; end
|
|
25
|
+
class Unauthorized < Error; end
|
|
26
|
+
class Forbidden < Error; end
|
|
27
|
+
class NotFound < Error; end
|
|
28
|
+
class MethodNotAllowed < Error; end
|
|
29
|
+
class UnsupportedMediaType < Error; end
|
|
30
|
+
|
|
31
|
+
class RateLimited < Error
|
|
32
|
+
attr_reader :retry_after, :limit_type
|
|
33
|
+
|
|
34
|
+
def initialize(message = nil, retry_after: nil, limit_type: nil, response: nil)
|
|
35
|
+
@retry_after = retry_after
|
|
36
|
+
@limit_type = limit_type
|
|
37
|
+
super(message, response: response)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class ServerError < Error; end
|
|
42
|
+
class ServiceUnavailable < ServerError; end
|
|
43
|
+
|
|
44
|
+
class ConnectionError < Error
|
|
45
|
+
attr_reader :wrapped
|
|
46
|
+
|
|
47
|
+
def initialize(message = nil, wrapped: nil)
|
|
48
|
+
@wrapped = wrapped
|
|
49
|
+
super(message)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class TimeoutError < ConnectionError; end
|
|
54
|
+
class SSLError < ConnectionError; end
|
|
55
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
# Emits `request.rito` events via ActiveSupport::Notifications when
|
|
5
|
+
# ActiveSupport is available; a no-op otherwise.
|
|
6
|
+
module Instrumentation
|
|
7
|
+
EVENT = 'request.rito'
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def emit(payload)
|
|
11
|
+
return unless defined?(ActiveSupport::Notifications)
|
|
12
|
+
|
|
13
|
+
ActiveSupport::Notifications.instrument(EVENT, payload)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Middleware
|
|
5
|
+
class Authentication < Faraday::Middleware
|
|
6
|
+
def initialize(app, client:)
|
|
7
|
+
super(app)
|
|
8
|
+
@client = client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(env)
|
|
12
|
+
if @client.api_key
|
|
13
|
+
env.request_headers['X-Riot-Token'] = @client.api_key
|
|
14
|
+
elsif @client.bearer_token
|
|
15
|
+
env.request_headers['Authorization'] = "Bearer #{@client.bearer_token}"
|
|
16
|
+
end
|
|
17
|
+
@app.call(env)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Middleware
|
|
5
|
+
# First response middleware: syncs limiter state from rate limit headers
|
|
6
|
+
# before RiotErrors raises, so blocks are in place before any retry.
|
|
7
|
+
class RateLimitObserve < Faraday::Middleware
|
|
8
|
+
def initialize(app, client:)
|
|
9
|
+
super(app)
|
|
10
|
+
@client = client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def on_complete(env)
|
|
14
|
+
limiter = @client.limiter
|
|
15
|
+
return if limiter.nil? || @client.api_key.nil?
|
|
16
|
+
|
|
17
|
+
limiter.observe!(Faraday::Response.new(env), key: @client.api_key,
|
|
18
|
+
region: region_from(env), bucket: bucket_from(env))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def region_from(env)
|
|
24
|
+
env.url.host.to_s.split('.').first
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def bucket_from(env)
|
|
28
|
+
env.url.path.split('/').first(2).join('/')
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rito
|
|
4
|
+
module Middleware
|
|
5
|
+
# Runs before the network call: blocks until the adaptive limiter allows
|
|
6
|
+
# the request, and retries RateLimited responses until max_retries is
|
|
7
|
+
# exhausted (the limiter blocks the bucket before the rescue, so the retry
|
|
8
|
+
# honors Retry-After automatically). Region and endpoint bucket are derived
|
|
9
|
+
# from the request URL.
|
|
10
|
+
class RateLimitThrottle < Faraday::Middleware
|
|
11
|
+
def initialize(app, client:)
|
|
12
|
+
super(app)
|
|
13
|
+
@client = client
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(env)
|
|
17
|
+
limiter = @client.limiter
|
|
18
|
+
key = @client.api_key
|
|
19
|
+
return @app.call(env) if limiter.nil? || key.nil?
|
|
20
|
+
|
|
21
|
+
attempts = 0
|
|
22
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
23
|
+
begin
|
|
24
|
+
limiter.acquire!(key: key, region: region_from(env), bucket: bucket_from(env))
|
|
25
|
+
response = @app.call(env)
|
|
26
|
+
emit(env, response.status, started, attempts)
|
|
27
|
+
response
|
|
28
|
+
rescue Rito::RateLimited => e
|
|
29
|
+
emit(env, e.status, started, attempts, error: e.class.name)
|
|
30
|
+
attempts += 1
|
|
31
|
+
retry if attempts <= @client.max_retries
|
|
32
|
+
raise
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def emit(env, status, started, attempts, error: nil)
|
|
39
|
+
Instrumentation.emit(
|
|
40
|
+
http_method: env.method,
|
|
41
|
+
url: env.url.to_s,
|
|
42
|
+
status: status,
|
|
43
|
+
attempts: attempts,
|
|
44
|
+
duration_ms: ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) * 1000).round(2),
|
|
45
|
+
error: error
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def region_from(env)
|
|
50
|
+
env.url.host.to_s.split('.').first
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def bucket_from(env)
|
|
54
|
+
env.url.path.split('/').first(2).join('/')
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|