forked from mirror/Riven
Compare commits
224 Commits
Author | SHA1 | Date |
---|---|---|
Mingwei Samuel | 886ed4032d | |
Mingwei Samuel | 5e235e3c51 | |
Guillermo Lloret Talavera | 0f5fae1193 | |
Mingwei Samuel | 416140c41d | |
Mingwei Samuel | 4039a355d3 | |
Mingwei Samuel | b52ddcd5df | |
Mingwei Samuel | 4cc241baac | |
Mingwei Samuel | 8d810ccca8 | |
Mingwei Samuel | 33ef298039 | |
Mingwei Samuel | 655d04f8df | |
Mingwei Samuel | 25f41388f0 | |
Mingwei Samuel | 83d7fc3001 | |
Mingwei Samuel | 25b5d9fd22 | |
Mingwei Samuel | 4067b0cec7 | |
Mingwei Samuel | 406df5188f | |
Mingwei Samuel | af759728e3 | |
Mingwei Samuel | 1e136feaf3 | |
Mingwei Samuel | 172bc84620 | |
Mingwei Samuel | 2c284d860c | |
Mingwei Samuel | ceac9d203b | |
Mingwei Samuel | 4265d7e3c9 | |
Mingwei Samuel | 4391d214aa | |
Mingwei Samuel | 13a06b224a | |
Mingwei Samuel | b4398e4e98 | |
Mingwei Samuel | 904a6b083f | |
Mingwei Samuel | ce2cd7c7a2 | |
Mingwei Samuel | c3982c390e | |
Mingwei Samuel | c7f5c59495 | |
Mingwei Samuel | ac12a8bfc2 | |
Mingwei Samuel | 09f0f2562b | |
Mingwei Samuel | dd77ffa6dd | |
Mingwei Samuel | a9a0990d5c | |
Mingwei Samuel | c449a4fc52 | |
Mingwei Samuel | dc73fcbf46 | |
Mingwei Samuel | fb95601526 | |
Mingwei Samuel | bd76b24b2b | |
Mingwei Samuel | 56b75ace18 | |
Mingwei Samuel | 2661acf5e9 | |
Mingwei Samuel | c4a9993ac2 | |
Mingwei Samuel | 512a7e9020 | |
Mingwei Samuel | daf18c476c | |
Mingwei Samuel | e67ffff627 | |
Mingwei Samuel | 9b08e058b7 | |
Mingwei Samuel | f79f2edab3 | |
Mingwei Samuel | 1ec4c18ae4 | |
Mingwei Samuel | c58da65298 | |
Mingwei Samuel | ac76a87367 | |
Mingwei Samuel | fc42abcbe2 | |
Mingwei Samuel | 1fc947a040 | |
Mingwei Samuel | 8e3e081944 | |
Mingwei Samuel | 41a00d68f6 | |
Mingwei Samuel | 800f9c83f4 | |
Mingwei Samuel | 7c0fff55ae | |
Mingwei Samuel | 908d0d64ed | |
Mingwei Samuel | 2aa493abfe | |
Mingwei Samuel | d15d43797e | |
Mingwei Samuel | 08bb2afd68 | |
Mingwei Samuel | 9a2e5277c1 | |
Mingwei Samuel | 3253fa4ec2 | |
Mingwei Samuel | bf51ba0a83 | |
Mingwei Samuel | 3136f84680 | |
Mingwei Samuel | 3070216709 | |
Mingwei Samuel | 78f187570f | |
Mingwei Samuel | ad08632089 | |
Mingwei Samuel | 4b3afbd8ab | |
Mingwei Samuel | cb5bd58784 | |
Mingwei Samuel | e53d78c807 | |
Mingwei Samuel | 3d1188b5c9 | |
Mingwei Samuel | 150566983e | |
Mingwei Samuel | 7e78abb7e5 | |
Mingwei Samuel | 1567bae16e | |
Mingwei Samuel | 112f6bceae | |
Mingwei Samuel | 94e0a0d1d3 | |
Mingwei Samuel | 975b954f3f | |
Mingwei Samuel | 0ea966a105 | |
Mingwei Samuel | 642ed27fa2 | |
Mingwei Samuel | 26adf882fb | |
Mingwei Samuel | ac46e74de4 | |
Mingwei Samuel | 0a1d5537cc | |
Mingwei Samuel | e5803092bf | |
Mingwei Samuel | fa3e7a4bab | |
Mingwei Samuel | 520fad7e7e | |
Mingwei Samuel | 728aa0f99a | |
Mingwei Samuel | 32fe532f07 | |
Mingwei Samuel | d05b068bb8 | |
Mingwei Samuel | d2c661b9ab | |
Mingwei Samuel | 599f88ec73 | |
Mingwei Samuel | 7f0b63bf92 | |
Mingwei Samuel | 563f9e84a7 | |
Mingwei Samuel | 9f5b12cd1b | |
Mingwei Samuel | 129002bf23 | |
Mingwei Samuel | a11b6874f6 | |
Mingwei Samuel | f51a67c1fc | |
sir naji | 6512866c26 | |
Mingwei Samuel | 97b6017b42 | |
Mingwei Samuel | d7330fcd84 | |
Mingwei Samuel | d2a213c287 | |
Mingwei Samuel | 4f5d115831 | |
Mingwei Samuel | 871044974c | |
Mingwei Samuel | 6c5af2bfd6 | |
Mingwei Samuel | 08e276a558 | |
Mingwei Samuel | 0753fe1b34 | |
Mingwei Samuel | c9bca855f2 | |
Mingwei Samuel | 342c87e682 | |
dependabot[bot] | d05c3a11d4 | |
Mingwei Samuel | 6c41bd03e4 | |
Mingwei Samuel | 998eb4ec46 | |
Mingwei Samuel | f3f34b6ff5 | |
tomchandler | 1af0539a84 | |
Mingwei Samuel | 036f818457 | |
Mingwei Samuel | 7d545b55af | |
Mingwei Samuel | a2699a695f | |
djazouli | 2f4aae6635 | |
Mingwei Samuel | 650380cdf9 | |
Mingwei Samuel | 76a48963e5 | |
Mingwei Samuel | 3ae9f36888 | |
Mingwei Samuel | 713995491e | |
Mingwei Samuel | bfa9bdc36e | |
Mingwei Samuel | 2e52b03c63 | |
Mingwei Samuel | b24fdcb765 | |
Mingwei Samuel | 104db04d9d | |
Mingwei Samuel | 20863c5bcc | |
Mingwei Samuel | b82a1725c1 | |
Mingwei Samuel | 056bacf67c | |
Mingwei Samuel | b1f7e8e605 | |
Mingwei Samuel | 8e6073aa63 | |
Mingwei Samuel | f0f4af13af | |
Mingwei Samuel | b821314492 | |
Mingwei Samuel | 03a053d142 | |
Mingwei Samuel | 061ce5bd39 | |
Mingwei Samuel | 7661c534cb | |
Mingwei Samuel | 295ccc4daf | |
Mingwei Samuel | 8c458c000a | |
Mingwei Samuel | 43d0578ab7 | |
dependabot[bot] | 1b62208c78 | |
Mingwei Samuel | 8d48956324 | |
Mingwei Samuel | f37e7a5cbb | |
Mingwei Samuel | 386d41c208 | |
Mingwei Samuel | 038e5eb493 | |
Mingwei Samuel | ede1247bf8 | |
Mingwei Samuel | 9f5935a3f5 | |
Mingwei Samuel | 62f323da88 | |
Mingwei Samuel | 1d70c479cf | |
Mingwei Samuel | 7865a439e1 | |
Mingwei Samuel | cb145bea0f | |
Mingwei Samuel | 9478e3f95e | |
Mingwei Samuel | d05f89c0d4 | |
Mingwei Samuel | 2f4d1dc65c | |
Mingwei Samuel | de1e5cdbca | |
Mingwei Samuel | c07efb1087 | |
Mingwei Samuel | 44b78abca9 | |
Mingwei Samuel | 40edebf78b | |
Mingwei Samuel | fd892b1451 | |
aPinat | 260bf8250a | |
Mingwei Samuel | 2be19f05ca | |
Mingwei Samuel | 7e5bccd039 | |
Mingwei Samuel | b981adbffc | |
Mingwei Samuel | 0b16c2a385 | |
Mingwei Samuel | 0e22623056 | |
Mingwei Samuel | fda0836d27 | |
Mingwei Samuel | 324016f33d | |
Mingwei Samuel | 9f759369f8 | |
Mingwei Samuel | 4c618eb939 | |
Mingwei Samuel | ad1b4fd630 | |
Mingwei Samuel | fed9c07e7f | |
Mingwei Samuel | 8d2bcfe694 | |
Mingwei Samuel | 3cd41bce4c | |
Mingwei Samuel | 9c13d126a6 | |
Mingwei Samuel | f43795c5a3 | |
Mingwei Samuel | 1a0ae872f5 | |
Mingwei Samuel | 8f5d7327d1 | |
Mingwei Samuel | c9465eee32 | |
Mingwei Samuel | 32e70437ab | |
Mingwei Samuel | 394e6c72e2 | |
Mingwei Samuel | f1a51693fa | |
Mingwei Samuel | 74eb5fa045 | |
Mingwei Samuel | 5c26ddd211 | |
Mingwei Samuel | 9dcbc626c7 | |
Mingwei Samuel | dafc181bbe | |
Mingwei Samuel | 44320bdeeb | |
Mingwei Samuel | e1bf531235 | |
Mingwei Samuel | 00e520b7af | |
Mingwei Samuel | 8dbcca434d | |
Thijs Molendijk | 7f3fa7f59e | |
Thijs Molendijk | 3c4f3c967a | |
Thijs Molendijk | 032d3ac97d | |
Mingwei Samuel | f182191680 | |
Mingwei Samuel | 75ab41aca2 | |
Mingwei Samuel | c10275831f | |
Mingwei Samuel | 56973ae26e | |
Mingwei Samuel | 27e694a61c | |
Mingwei Samuel | 23645a9319 | |
Mingwei Samuel | 9c4679fb88 | |
Mingwei Samuel | 2cb8db61f5 | |
Mingwei Samuel | a1bca52576 | |
Mingwei Samuel | 62e2001457 | |
Mingwei Samuel | 4b23f9afed | |
Mingwei Samuel | 35f826b198 | |
Mingwei Samuel | 2d80f97170 | |
Mingwei Samuel | 88124ecb3a | |
Mingwei Samuel | 5daeab990a | |
Mingwei Samuel | 07348a5c53 | |
Mingwei Samuel | 6307a0aa13 | |
Mingwei Samuel | 2989c4483e | |
Mingwei Samuel | 569a5bbc1b | |
Mingwei Samuel | b6cf9217d8 | |
Mingwei Samuel | 4b0cb11197 | |
Mingwei Samuel | c38afa7753 | |
Mingwei Samuel | c813228603 | |
Mingwei Samuel | 2b1f109427 | |
Mingwei Samuel | 1c4cadb6d9 | |
Mingwei Samuel | 20d5196f13 | |
Mingwei Samuel | ad5b1c70ea | |
Mingwei Samuel | e7e40a3cc4 | |
Mingwei Samuel | dded4d5644 | |
Mingwei Samuel | d4cf56f496 | |
Mingwei Samuel | 9c8313c604 | |
Mingwei Samuel | 39af464170 | |
Mingwei Samuel | 4e0d45e59d | |
Mingwei Samuel | 40d49f837d | |
Mingwei Samuel | 53230dd8df | |
Mingwei Samuel | 8934790c1c | |
Mingwei Samuel | 7f046e99f7 | |
Mingwei Samuel | 869216aab5 |
|
@ -0,0 +1,120 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [v/2.x.x]
|
||||||
|
pull_request:
|
||||||
|
branches: [v/2.x.x]
|
||||||
|
schedule:
|
||||||
|
- cron: "24 05 * * *" # Daily at 10:24 PM PDT, 9:24 PM PST.
|
||||||
|
workflow_dispatch:
|
||||||
|
# TODO: generate nightly releases
|
||||||
|
# inputs:
|
||||||
|
# should_bench:
|
||||||
|
# description: "Should Benchmark? (`true`)"
|
||||||
|
# required: true
|
||||||
|
# default: "false"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pre_job:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||||
|
steps:
|
||||||
|
- id: skip_check
|
||||||
|
uses: fkirc/skip-duplicate-actions@v3.4.0
|
||||||
|
with:
|
||||||
|
cancel_others: "true"
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Check & Test
|
||||||
|
needs: pre_job
|
||||||
|
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
|
||||||
|
- name: Install Node dependencies
|
||||||
|
run: npm ci --prefix riven/srcgen
|
||||||
|
|
||||||
|
- name: Run codegen
|
||||||
|
run: node riven/srcgen
|
||||||
|
|
||||||
|
- name: Install Rust nightly toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
components: rustfmt
|
||||||
|
|
||||||
|
- name: Run `cargo +stable check --all-targets`
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
command: check
|
||||||
|
args: --all-targets
|
||||||
|
|
||||||
|
- name: Run `cargo +stable check --all-targets --features tracing`
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
command: check
|
||||||
|
args: --all-targets --features tracing
|
||||||
|
|
||||||
|
- name: Run `cargo check --all-targets --features nightly,tracing`
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: check
|
||||||
|
args: --all-targets --features nightly,tracing
|
||||||
|
|
||||||
|
- name: Run `cargo build --all-targets --features nightly,deny-unknown`
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
args: --all-targets --features nightly,deny-unknown
|
||||||
|
|
||||||
|
- name: Run `cargo test --features nightly,deny-unknown`
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
args: --no-fail-fast --features nightly,deny-unknown
|
||||||
|
env:
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
RUSTLOG: riven=trace
|
||||||
|
RGAPI_KEY: ${{ secrets.RGAPI_KEY }}
|
||||||
|
|
||||||
|
lints:
|
||||||
|
name: Lints
|
||||||
|
needs: pre_job
|
||||||
|
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install nightly toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Run cargo fmt
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: --all -- --check
|
||||||
|
|
||||||
|
- name: Run cargo clippy
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: clippy
|
||||||
|
args: -- -D warnings
|
|
@ -4,6 +4,3 @@
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
apikey.txt
|
apikey.txt
|
||||||
|
|
||||||
/srcgen/node_modules/
|
|
||||||
/srcgen/.*.json
|
|
||||||
|
|
||||||
|
|
10
.travis.yml
10
.travis.yml
|
@ -1,10 +0,0 @@
|
||||||
language: rust
|
|
||||||
matrix:
|
|
||||||
fast_finish: true
|
|
||||||
include:
|
|
||||||
- rust: stable
|
|
||||||
- rust: nightly
|
|
||||||
env: FEATURES="--features nightly"
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- RUST_BACKTRACE=1
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"rust-analyzer.runnableEnv": [
|
||||||
|
{
|
||||||
|
// Set output levels for `tracing` logging.
|
||||||
|
"env": {
|
||||||
|
"RUST_BACKTRACE": "1",
|
||||||
|
"RUST_LOG": "riven=debug"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
47
Cargo.toml
47
Cargo.toml
|
@ -1,43 +1,4 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "riven"
|
members = [
|
||||||
version = "1.12.1"
|
"riven",
|
||||||
authors = ["Mingwei Samuel <mingwei.samuel@gmail.com>"]
|
]
|
||||||
repository = "https://github.com/MingweiSamuel/Riven"
|
|
||||||
description = "Riot Games API Library"
|
|
||||||
readme = "README.md"
|
|
||||||
license = "MIT"
|
|
||||||
edition = "2018"
|
|
||||||
include = [ "src/**", "/README.md" ]
|
|
||||||
keywords = [ "riot-games", "riot", "league", "league-of-legends" ]
|
|
||||||
categories = [ "api-bindings", "web-programming::http-client" ]
|
|
||||||
|
|
||||||
#[badges]
|
|
||||||
#travis-ci = { repository = "MingweiSamuel/Riven" }
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
features = [ "nightly" ]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
nightly = [ "parking_lot/nightly" ]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
lazy_static = "1.4"
|
|
||||||
log = "0.4"
|
|
||||||
num_enum = "0.5"
|
|
||||||
parking_lot = "0.11"
|
|
||||||
reqwest = { version = "0.11", features = [ "gzip", "json" ] }
|
|
||||||
scan_fmt = { version = "0.2", default-features = false }
|
|
||||||
serde = { version = "1.0", features = [ "derive" ] }
|
|
||||||
serde_repr = "0.1"
|
|
||||||
strum = "0.20"
|
|
||||||
strum_macros = "0.20"
|
|
||||||
tokio = { version = "1", default-features = false, features = [ "time" ] }
|
|
||||||
url = "2"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
colored = "2"
|
|
||||||
env_logger = "0.8"
|
|
||||||
fake_instant = "0.4"
|
|
||||||
tokio = { version = "1", default-features = false, features = [ "rt-multi-thread" ] }
|
|
||||||
|
|
79
README.md
79
README.md
|
@ -5,7 +5,7 @@
|
||||||
<a href="https://github.com/MingweiSamuel/Riven/"><img src="https://cdn.communitydragon.org/latest/champion/Riven/square" width="20" height="20" alt="Riven Github"></a>
|
<a href="https://github.com/MingweiSamuel/Riven/"><img src="https://cdn.communitydragon.org/latest/champion/Riven/square" width="20" height="20" alt="Riven Github"></a>
|
||||||
<a href="https://crates.io/crates/riven"><img src="https://img.shields.io/crates/v/riven?style=flat-square&logo=rust" alt="Crates.io"></a>
|
<a href="https://crates.io/crates/riven"><img src="https://img.shields.io/crates/v/riven?style=flat-square&logo=rust" alt="Crates.io"></a>
|
||||||
<a href="https://docs.rs/riven/"><img src="https://img.shields.io/badge/docs.rs-Riven-blue?style=flat-square&logo=read-the-docs&logoColor=white" alt="Docs.rs"></a>
|
<a href="https://docs.rs/riven/"><img src="https://img.shields.io/badge/docs.rs-Riven-blue?style=flat-square&logo=read-the-docs&logoColor=white" alt="Docs.rs"></a>
|
||||||
<a href="https://travis-ci.com/MingweiSamuel/Riven"><img src="https://img.shields.io/travis/com/mingweisamuel/riven?style=flat-square" alt="Travis CI"></a>
|
<!--<a href="https://travis-ci.com/MingweiSamuel/Riven"><img src="https://img.shields.io/travis/com/mingweisamuel/riven?style=flat-square" alt="Travis CI"></a>-->
|
||||||
<a href="https://github.com/rust-secure-code/safety-dance/"><img src="https://img.shields.io/badge/unsafe-forbidden-green.svg?style=flat-square" alt="unsafe forbidden"></a>
|
<a href="https://github.com/rust-secure-code/safety-dance/"><img src="https://img.shields.io/badge/unsafe-forbidden-green.svg?style=flat-square" alt="unsafe forbidden"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -15,29 +15,28 @@ Riven's goals are _speed_, _reliability_, and _maintainability_. Riven handles r
|
||||||
Data structs and endpoints are automatically generated from the
|
Data structs and endpoints are automatically generated from the
|
||||||
[Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)).
|
[Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)).
|
||||||
|
|
||||||
## Design
|
# Design
|
||||||
|
|
||||||
* Fast, asynchronous, thread-safe.
|
* Fast, asynchronous, thread-safe.
|
||||||
* Automatically retries failed requests.
|
* Automatically retries failed requests.
|
||||||
* TFT API Support.
|
* Supports all endpoints, kept up-to-date using [riotapi-schema](https://github.com/MingweiSamuel/riotapi-schema).
|
||||||
|
|
||||||
## Usage
|
# Usage
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use riven::RiotApi;
|
use riven::RiotApi;
|
||||||
use riven::consts::Region;
|
use riven::consts::PlatformRoute;
|
||||||
|
|
||||||
// Enter tokio async runtime.
|
// Enter tokio async runtime.
|
||||||
let mut rt = tokio::runtime::Runtime::new().unwrap();
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
// Create RiotApi instance from key string.
|
// Create RiotApi instance from key string.
|
||||||
let api_key = "RGAPI-01234567-89ab-cdef-0123-456789abcdef";
|
let api_key = std::env!("RGAPI_KEY"); // "RGAPI-01234567-89ab-cdef-0123-456789abcdef";
|
||||||
# /* (doc testing) */ let api_key = std::env!("RGAPI_KEY");
|
let riot_api = RiotApi::new(api_key);
|
||||||
let riot_api = RiotApi::with_key(api_key);
|
|
||||||
|
|
||||||
// Get summoner data.
|
// Get summoner data.
|
||||||
let summoner = riot_api.summoner_v4()
|
let summoner = riot_api.summoner_v4()
|
||||||
.get_by_summoner_name(Region::NA, "잘 못").await
|
.get_by_summoner_name(PlatformRoute::NA1, "잘 못").await
|
||||||
.expect("Get summoner failed.")
|
.expect("Get summoner failed.")
|
||||||
.expect("There is no summoner with that name.");
|
.expect("There is no summoner with that name.");
|
||||||
|
|
||||||
|
@ -46,13 +45,13 @@ rt.block_on(async {
|
||||||
|
|
||||||
// Get champion mastery data.
|
// Get champion mastery data.
|
||||||
let masteries = riot_api.champion_mastery_v4()
|
let masteries = riot_api.champion_mastery_v4()
|
||||||
.get_all_champion_masteries(Region::NA, &summoner.id).await
|
.get_all_champion_masteries_by_puuid(PlatformRoute::NA1, &summoner.puuid).await
|
||||||
.expect("Get champion masteries failed.");
|
.expect("Get champion masteries failed.");
|
||||||
|
|
||||||
// Print champioon masteries.
|
// Print champion masteries.
|
||||||
for (i, mastery) in masteries.iter().take(10).enumerate() {
|
for (i, mastery) in masteries.iter().take(10).enumerate() {
|
||||||
println!("{: >2}) {: <9} {: >7} ({})", i + 1,
|
println!("{: >2}) {: <9} {: >7} ({})", i + 1,
|
||||||
mastery.champion_id.to_string(),
|
mastery.champion_id.name().unwrap_or("UNKNOWN"),
|
||||||
mastery.champion_points, mastery.champion_level);
|
mastery.champion_points, mastery.champion_level);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -60,29 +59,50 @@ rt.block_on(async {
|
||||||
Output:
|
Output:
|
||||||
```text
|
```text
|
||||||
잘 못 Champion Masteries:
|
잘 못 Champion Masteries:
|
||||||
1) Riven 1219895 (7)
|
1) Riven 1236866 (7)
|
||||||
2) Fiora 229714 (5)
|
2) Fiora 230679 (5)
|
||||||
3) Katarina 175985 (5)
|
3) Katarina 175985 (5)
|
||||||
4) Lee Sin 150546 (7)
|
4) Lee Sin 156070 (7)
|
||||||
5) Jax 100509 (5)
|
5) Jax 102662 (5)
|
||||||
6) Gnar 76373 (6)
|
6) Gnar 76373 (6)
|
||||||
7) Kai'Sa 64271 (5)
|
7) Kai'Sa 64271 (5)
|
||||||
8) Caitlyn 46479 (5)
|
8) Caitlyn 46614 (5)
|
||||||
9) Irelia 46465 (5)
|
9) Irelia 46465 (5)
|
||||||
10) Vladimir 37176 (5)
|
10) Vladimir 37176 (5)
|
||||||
```
|
```
|
||||||
|
The [`RiotApi` struct documentation](https://docs.rs/riven/latest/riven/struct.RiotApi.html)
|
||||||
|
contains additional usage information. The [tests](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/tests)
|
||||||
|
and [example proxy](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/examples/proxy)
|
||||||
|
provide more example usage.
|
||||||
|
|
||||||
|
## Feature Flags
|
||||||
|
|
||||||
### Nightly vs Stable
|
### Nightly vs Stable
|
||||||
|
|
||||||
Enable the `nightly` feature to use nightly-only functionality. Mainly enables
|
Enable the `nightly` feature to use nightly-only functionality. This enables
|
||||||
[nightly optimizations in the `parking_lot` crate](https://github.com/Amanieu/parking_lot#nightly-vs-stable).
|
[nightly optimizations in the `parking_lot` crate](https://github.com/Amanieu/parking_lot#nightly-vs-stable).
|
||||||
Also required for running async integration tests.
|
|
||||||
|
|
||||||
### Docs
|
```toml
|
||||||
|
riven = { version = "...", features = [ "nightly" ] }
|
||||||
|
```
|
||||||
|
|
||||||
|
### rustls
|
||||||
|
|
||||||
|
Riven uses [reqwest](https://github.com/seanmonstar/reqwest) for making requests. By default, reqwest uses the native TLS library.
|
||||||
|
If you prefer using [rustls](https://github.com/ctz/rustls) you can do so by turning off the Riven default features
|
||||||
|
and specifying the `rustls-tls` feature:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
riven = { version = "...", default-features = false, features = [ "rustls-tls" ] }
|
||||||
|
```
|
||||||
|
|
||||||
|
Riven is additionally able to produce [tracing](https://docs.rs/tracing) spans for requests if the `tracing` feature is enabled. This feature is disabled by default.
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
[On docs.rs](https://docs.rs/riven/).
|
[On docs.rs](https://docs.rs/riven/).
|
||||||
|
|
||||||
### Error Handling
|
## Error Handling
|
||||||
|
|
||||||
Riven returns either `Result<T>` or `Result<Option<T>>` within futures.
|
Riven returns either `Result<T>` or `Result<Option<T>>` within futures.
|
||||||
|
|
||||||
|
@ -101,12 +121,12 @@ diagnostic information, such as the source Reqwest error, the number of retries
|
||||||
attempted, and the Reqwest `Response` object.
|
attempted, and the Reqwest `Response` object.
|
||||||
|
|
||||||
You can configure the number of time Riven retries using
|
You can configure the number of time Riven retries using
|
||||||
`RiotApiConfig::set_retries(...)` and the `RiotApi::with_config(config)`
|
`RiotApiConfig::set_retries(...)` and the `RiotApi::from_config(config)`
|
||||||
constructor. By default, Riven retries up to 3 times (4 requests total).
|
constructor. By default, Riven retries up to 3 times (4 requests total).
|
||||||
Some errors, such as 400 client errors, are not retried as they would
|
Some errors, such as 400 client errors, are not retried as they would
|
||||||
inevitably fail again.
|
inevitably fail again.
|
||||||
|
|
||||||
### Semantic Versioning
|
## Semantic Versioning
|
||||||
|
|
||||||
This package follows semantic versioning to an extent. However, the Riot API
|
This package follows semantic versioning to an extent. However, the Riot API
|
||||||
itself changes often and does not follow semantic versioning, which makes
|
itself changes often and does not follow semantic versioning, which makes
|
||||||
|
@ -122,21 +142,22 @@ not the major version.
|
||||||
Parts of Riven that do not depend on Riot API changes do follow semantic
|
Parts of Riven that do not depend on Riot API changes do follow semantic
|
||||||
versioning.
|
versioning.
|
||||||
|
|
||||||
### Additional Help
|
## Additional Help
|
||||||
|
|
||||||
Feel free to [make an issue](https://github.com/MingweiSamuel/Riven/issues/new)
|
Feel free to [make an issue](https://github.com/MingweiSamuel/Riven/issues/new)
|
||||||
if you are have any questions or trouble with Riven.
|
if you are have any questions or trouble with Riven.
|
||||||
|
|
||||||
## Development
|
# Development
|
||||||
|
|
||||||
NodeJS is used to generate code for Riven. The
|
NodeJS is used to generate code for Riven. The
|
||||||
[`srcgen/`](https://github.com/MingweiSamuel/Riven/tree/master/srcgen)
|
[`riven/srcgen`](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/srcgen)
|
||||||
folder contains the code and [doT.js](https://olado.github.io/doT/index.html)
|
folder contains the code and [doT.js](https://olado.github.io/doT/index.html)
|
||||||
templates. `index.js` lists the JSON files downloaded and used to generate the
|
templates. `index.js` lists the JSON files downloaded and used to generate the
|
||||||
code.
|
code.
|
||||||
|
|
||||||
To set up the srcgen, you will first need to install NodeJS. Then enter the
|
To set up the srcgen, you will first need to install NodeJS. Then enter the
|
||||||
srcgen folder and run `npm ci` (or `npm install`) to install dependencies.
|
`riven/srcgen` folder and run `npm ci` (or `npm install`) to install
|
||||||
|
dependencies.
|
||||||
|
|
||||||
To run the srcgen use `node srcgen` from the main folder.
|
To run the srcgen use `node riven/srcgen` from the repository root.
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
target
|
|
|
@ -1,12 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "riven_example_proxy"
|
|
||||||
version = "0.0.0"
|
|
||||||
authors = [ "Mingwei Samuel <mingwei.samuel@gmail.com>" ]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
riven = { path = '..' }
|
|
||||||
|
|
||||||
hyper = "0.13"
|
|
||||||
lazy_static = "1.4"
|
|
||||||
tokio = { version = "0.2", features = [ "full" ] }
|
|
|
@ -1,20 +0,0 @@
|
||||||
# Riven Example Proxy
|
|
||||||
|
|
||||||
This is a simple example implementation of a Riot API proxy server using `hyper`. This adds the API key and forwards
|
|
||||||
requests to the Riot API, then returns and forwards responses back to the requester. It handles error cases but only
|
|
||||||
provides minimal failure information. HTTP requests will wait to complete when Riven is waiting on rate limits.
|
|
||||||
|
|
||||||
Set `RGAPI_KEY` env var then run:
|
|
||||||
```bash
|
|
||||||
export RGAPI_KEY=RGAPI-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
|
|
||||||
cargo run
|
|
||||||
```
|
|
||||||
|
|
||||||
Test in your browser or using `curl`. The first path segment specifies the region:
|
|
||||||
```json
|
|
||||||
$ curl http://localhost:3000/na1/lol/summoner/v4/summoners/by-name/LugnutsK
|
|
||||||
{"id":"...","accountId":"...","puuid":"...","name":"LugnutsK","profileIconId":4540,"revisionDate":1589704662000,"summonerLevel":111}
|
|
||||||
|
|
||||||
$ curl http://localhost:3000/na1/valorant/v4/players/by-name/LugnutsK # not yet :)
|
|
||||||
{"error":"Riot API endpoint method not found."}
|
|
||||||
```
|
|
|
@ -1,149 +0,0 @@
|
||||||
// #![deny(warnings)]
|
|
||||||
|
|
||||||
use std::convert::Infallible;
|
|
||||||
|
|
||||||
use hyper::service::{ make_service_fn, service_fn };
|
|
||||||
use hyper::{ Body, Method, Request, Response, Server, StatusCode };
|
|
||||||
use hyper::header::HeaderValue;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
use riven::{ RiotApi, RiotApiConfig };
|
|
||||||
use riven::consts::Region;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
/// Create lazy static RiotApi instance.
|
|
||||||
/// Easier than passing it around.
|
|
||||||
pub static ref RIOT_API: RiotApi = {
|
|
||||||
let api_key = std::env::var("RGAPI_KEY").ok()
|
|
||||||
.or_else(|| std::fs::read_to_string("../apikey.txt").ok())
|
|
||||||
.expect("Failed to find RGAPI_KEY env var or apikey.txt.");
|
|
||||||
RiotApi::with_config(RiotApiConfig::with_key(api_key.trim())
|
|
||||||
.preconfig_burst())
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper to create JSON error responses.
|
|
||||||
fn create_json_response(body: &'static str, status: StatusCode) -> Response<Body> {
|
|
||||||
let mut resp = Response::new(Body::from(body));
|
|
||||||
*resp.status_mut() = status;
|
|
||||||
resp.headers_mut().insert(hyper::header::CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Main request handler service.
|
|
||||||
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
|
||||||
|
|
||||||
if Method::GET != req.method() {
|
|
||||||
return Ok(create_json_response(r#"{"error":"HTTP method must be GET."}"#, StatusCode::METHOD_NOT_ALLOWED));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle path.
|
|
||||||
let path_data_opt = parse_path(req.uri().path());
|
|
||||||
let ( region, method_id, req_path ) = match path_data_opt {
|
|
||||||
None => return Ok(create_json_response(
|
|
||||||
r#"{"error":"Riot API endpoint method not found."}"#, StatusCode::NOT_FOUND)),
|
|
||||||
Some(path_data) => path_data,
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("Request to region {} path {}:\n\t{} {}", region, method_id, region, req_path);
|
|
||||||
|
|
||||||
// Send request to Riot API.
|
|
||||||
let query = req.uri().query().map(|s| s.to_owned());
|
|
||||||
let resp_result = RIOT_API.get_raw_response(method_id, region.into(), req_path.to_owned(), query).await;
|
|
||||||
let resp_info = match resp_result {
|
|
||||||
Err(_err) => return Ok(create_json_response(
|
|
||||||
r#"{"error":"Riot API request failed."}"#, StatusCode::INTERNAL_SERVER_ERROR)),
|
|
||||||
Ok(resp_info) => resp_info,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write output.
|
|
||||||
let api_response = resp_info.response;
|
|
||||||
let mut out_response = Response::default();
|
|
||||||
*out_response.headers_mut() = api_response.headers().clone();
|
|
||||||
|
|
||||||
// If None, write "null" to body to be extra nice.
|
|
||||||
if resp_info.status_none {
|
|
||||||
*out_response.body_mut() = Body::from("null");
|
|
||||||
}
|
|
||||||
// Otherwise copy body.
|
|
||||||
else {
|
|
||||||
*out_response.status_mut() = api_response.status();
|
|
||||||
|
|
||||||
// Using streams would be faster.
|
|
||||||
let bytes_result = api_response.bytes().await;
|
|
||||||
let bytes = match bytes_result {
|
|
||||||
Err(_err) => return Ok(create_json_response(
|
|
||||||
r#"{"error":"Failed to get body from Riot API response."}"#, StatusCode::INTERNAL_SERVER_ERROR)),
|
|
||||||
Ok(bytes) => bytes,
|
|
||||||
};
|
|
||||||
*out_response.body_mut() = Body::from((&bytes[..]).to_vec());
|
|
||||||
}
|
|
||||||
return Ok(out_response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the region, method_id, and Riot API path based on the given path.
|
|
||||||
fn parse_path<'a>(req_path: &'a str) -> Option<( Region, &'static str, &'a str )> {
|
|
||||||
|
|
||||||
// Split URI into region and rest of path.
|
|
||||||
let req_path = req_path.trim_start_matches('/');
|
|
||||||
let ( region, req_path ) = req_path.split_at(req_path.find('/')?);
|
|
||||||
let region: Region = region.to_uppercase().parse().ok()?;
|
|
||||||
|
|
||||||
// Find method_id for given path.
|
|
||||||
let method_id = find_matching_method_id(req_path)?;
|
|
||||||
|
|
||||||
return Some(( region, method_id, req_path ))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds the method_id given the request path.
|
|
||||||
fn find_matching_method_id(req_path: &str) -> Option<&'static str> {
|
|
||||||
for ( ref_path, method_id ) in &*riven::meta::ENDPOINT_PATH_METHODID {
|
|
||||||
if paths_match(ref_path, req_path) {
|
|
||||||
return Some(method_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if the request path (req_path) matches the reference path (ref_path).
|
|
||||||
fn paths_match(ref_path: &str, req_path: &str) -> bool {
|
|
||||||
let mut ref_iter = ref_path.split('/');
|
|
||||||
let mut req_iter = req_path.split('/');
|
|
||||||
loop {
|
|
||||||
let ref_seg_opt = ref_iter.next();
|
|
||||||
let req_seg_opt = req_iter.next();
|
|
||||||
if ref_seg_opt.is_none() != req_seg_opt.is_none() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if let Some(ref_seg) = ref_seg_opt {
|
|
||||||
if let Some(req_seg) = req_seg_opt {
|
|
||||||
if ref_seg.starts_with('{') || ref_seg == req_seg {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
// For every connection, we must make a `Service` to handle all
|
|
||||||
// incoming HTTP requests on said connection.
|
|
||||||
let make_svc = make_service_fn(|_conn| {
|
|
||||||
// This is the `Service` that will handle the connection.
|
|
||||||
// `service_fn` is a helper to convert a function that
|
|
||||||
// returns a Response into a `Service`.
|
|
||||||
async { Ok::<_, Infallible>(service_fn(handle_request)) }
|
|
||||||
});
|
|
||||||
|
|
||||||
let addr = ([ 127, 0, 0, 1 ], 3000).into();
|
|
||||||
|
|
||||||
let server = Server::bind(&addr).serve(make_svc);
|
|
||||||
|
|
||||||
println!("Listening on http://{}", addr);
|
|
||||||
|
|
||||||
server.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
[package]
|
||||||
|
name = "riven"
|
||||||
|
version = "2.32.0"
|
||||||
|
authors = ["Mingwei Samuel <mingwei.samuel@gmail.com>"]
|
||||||
|
repository = "https://github.com/MingweiSamuel/Riven"
|
||||||
|
description = "Riot Games API Library"
|
||||||
|
readme = "../README.md"
|
||||||
|
license = "MIT"
|
||||||
|
edition = "2018"
|
||||||
|
include = [ "src/**", "../README.md" ]
|
||||||
|
keywords = [ "riot-games", "riot", "league", "league-of-legends" ]
|
||||||
|
categories = [ "api-bindings", "web-programming::http-client" ]
|
||||||
|
|
||||||
|
#[badges]
|
||||||
|
#travis-ci = { repository = "MingweiSamuel/Riven" }
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
features = [ "nightly" ]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = [ "default-tls" ]
|
||||||
|
|
||||||
|
nightly = [ "parking_lot/nightly" ]
|
||||||
|
|
||||||
|
default-tls = [ "reqwest/default-tls" ]
|
||||||
|
native-tls = [ "reqwest/native-tls" ]
|
||||||
|
rustls-tls = [ "reqwest/rustls-tls" ]
|
||||||
|
|
||||||
|
deny-unknown = [ "deny-unknown-fields", "deny-unknown-enum-variants" ]
|
||||||
|
# If enabled, extra unknown fields encountered during deserialization will
|
||||||
|
# cause an error instead of being ignored.
|
||||||
|
deny-unknown-fields = []
|
||||||
|
# If enabled, deserialization of unknown enum variants will cause an error
|
||||||
|
# instead of being deserialized to `UNKNOWN` or other integer variants.
|
||||||
|
deny-unknown-enum-variants = [ "deny-unknown-enum-variants-strings", "deny-unknown-enum-variants-integers" ]
|
||||||
|
deny-unknown-enum-variants-strings = []
|
||||||
|
deny-unknown-enum-variants-integers = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
lazy_static = "1.4"
|
||||||
|
log = "0.4"
|
||||||
|
num_enum = "0.5"
|
||||||
|
parking_lot = "0.12"
|
||||||
|
reqwest = { version = "0.11", default-features = false, features = [ "gzip", "json" ] }
|
||||||
|
scan_fmt = { version = "0.2", default-features = false }
|
||||||
|
serde = { version = "1.0", features = [ "derive" ] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde_repr = "0.1"
|
||||||
|
strum = "0.20"
|
||||||
|
strum_macros = "0.20"
|
||||||
|
tokio = { version = "1", default-features = false, features = [ "time", "macros", "parking_lot" ] }
|
||||||
|
tracing = { version = "0.1", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
colored = "2"
|
||||||
|
env_logger = "0.10.0"
|
||||||
|
fake_instant = "0.5.0"
|
||||||
|
futures = "0.3"
|
||||||
|
hyper = { version = "0.14", features = [ "server" ] }
|
||||||
|
tokio = { version = "1", features = [ "full" ] }
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = "0.2"
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Riven Example Proxy
|
||||||
|
|
||||||
|
This is a simple example implementation of a Riot API proxy server using
|
||||||
|
[`hyper`](https://github.com/hyperium/hyper). This adds the API key and
|
||||||
|
forwards requests to the Riot API, then returns and forwards responses back to
|
||||||
|
the requester.
|
||||||
|
|
||||||
|
This can handle not just GET endpoints but also POST and PUT (and any others)
|
||||||
|
with bodies, however it does not handle RSO endpoints which require an
|
||||||
|
`Authorization` header parameter. This handles errors but provides only minimal
|
||||||
|
failure information. Rate limits are enforced, so requests will wait to complete
|
||||||
|
when Riven is at the rate limit.
|
||||||
|
|
||||||
|
Set `RGAPI_KEY` env var then run:
|
||||||
|
```bash
|
||||||
|
export RGAPI_KEY=RGAPI-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
|
||||||
|
cargo run --example proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
Test in your browser or using `curl`. The first path segment specifies the region:
|
||||||
|
```json
|
||||||
|
$ curl http://localhost:3000/na1/lol/summoner/v4/summoners/by-name/LugnutsK
|
||||||
|
{"id":"...","accountId":"...","puuid":"...","name":"LugnutsK","profileIconId":4540,"revisionDate":1589704662000,"summonerLevel":111}
|
||||||
|
|
||||||
|
$ curl http://localhost:3000/eu/val/status/v1/platform-data
|
||||||
|
{"id": "EU", "name": "Europe", "locales": ["..."], "maintenances": [], "incidents": []}
|
||||||
|
|
||||||
|
$ curl http://localhost:3000/americas/lol/tournament-stub/v5/providers -H "Content-Type: application/json" -d '{"region":"JP","url":"https://github.com/MingweiSamuel/Riven"}'
|
||||||
|
1
|
||||||
|
|
||||||
|
$ curl http://localhost:3000/na1/unknown/endpoint
|
||||||
|
{"error":"Riot API endpoint method not found."}
|
||||||
|
```
|
|
@ -0,0 +1,193 @@
|
||||||
|
// #![deny(warnings)]
|
||||||
|
|
||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
|
use http::{Method, Request, Response, StatusCode};
|
||||||
|
use hyper::header::HeaderValue;
|
||||||
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
|
use hyper::{http, Body, Server};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use riven::consts::Route;
|
||||||
|
use riven::{RiotApi, RiotApiConfig};
|
||||||
|
use tracing as log;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
/// Create lazy static RiotApi instance.
|
||||||
|
/// Easier than passing it around.
|
||||||
|
pub static ref RIOT_API: RiotApi = {
|
||||||
|
let api_key = std::env::var("RGAPI_KEY").ok()
|
||||||
|
.or_else(|| {
|
||||||
|
let path: std::path::PathBuf = [ env!("CARGO_MANIFEST_DIR"), "../../apikey.txt" ].iter().collect();
|
||||||
|
std::fs::read_to_string(path).ok()
|
||||||
|
})
|
||||||
|
.expect("Failed to find RGAPI_KEY env var or apikey.txt.");
|
||||||
|
RiotApi::new(RiotApiConfig::with_key(api_key.trim()).preconfig_burst())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to create JSON error responses.
|
||||||
|
fn create_json_response(body: &'static str, status: StatusCode) -> Response<Body> {
|
||||||
|
let mut resp = Response::new(Body::from(body));
|
||||||
|
*resp.status_mut() = status;
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
hyper::header::CONTENT_TYPE,
|
||||||
|
HeaderValue::from_static("application/json"),
|
||||||
|
);
|
||||||
|
resp
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main request handler service.
|
||||||
|
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||||
|
let (parts, body) = req.into_parts();
|
||||||
|
let http::request::Parts { method, uri, .. } = parts;
|
||||||
|
|
||||||
|
// Handle path.
|
||||||
|
let path_data_opt = parse_path(&method, uri.path());
|
||||||
|
let (route, method_id, req_path) = match path_data_opt {
|
||||||
|
None => {
|
||||||
|
return Ok(create_json_response(
|
||||||
|
r#"{"error":"Riot API endpoint method not found."}"#,
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Some(path_data) => path_data,
|
||||||
|
};
|
||||||
|
|
||||||
|
log::debug!(
|
||||||
|
"Request to route {:?}, method ID {:?}: {} {:?}.",
|
||||||
|
route,
|
||||||
|
method_id,
|
||||||
|
method,
|
||||||
|
req_path
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert http:request::Parts from hyper to reqwest's RequestBuilder.
|
||||||
|
let body = match hyper::body::to_bytes(body).await {
|
||||||
|
Err(err) => {
|
||||||
|
log::info!("Error handling request body: {:#?}", err);
|
||||||
|
return Ok(create_json_response(
|
||||||
|
r#"{"error":"Failed to handle request body."}"#,
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(bytes) => bytes,
|
||||||
|
};
|
||||||
|
|
||||||
|
let req_builder = RIOT_API.request(method, route.into(), req_path).body(body);
|
||||||
|
|
||||||
|
// Send request to Riot API.
|
||||||
|
let resp_result = RIOT_API
|
||||||
|
.execute_raw(method_id, route.into(), req_builder)
|
||||||
|
.await;
|
||||||
|
let resp_info = match resp_result {
|
||||||
|
Err(err) => {
|
||||||
|
log::info!("Riot API error: {:#?}", err.source_reqwest_error());
|
||||||
|
return Ok(create_json_response(
|
||||||
|
r#"{"error":"Riot API request failed."}"#,
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(resp_info) => resp_info,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write output.
|
||||||
|
let api_response = resp_info.response;
|
||||||
|
let mut out_response = Response::default();
|
||||||
|
*out_response.headers_mut() = api_response.headers().clone();
|
||||||
|
|
||||||
|
// If None, write "null" to body to be extra nice.
|
||||||
|
if resp_info.status_none {
|
||||||
|
*out_response.body_mut() = Body::from("null");
|
||||||
|
}
|
||||||
|
// Otherwise copy body.
|
||||||
|
else {
|
||||||
|
*out_response.status_mut() = api_response.status();
|
||||||
|
|
||||||
|
// Using streams would be faster.
|
||||||
|
let bytes_result = api_response.bytes().await;
|
||||||
|
let bytes = match bytes_result {
|
||||||
|
Err(_err) => {
|
||||||
|
return Ok(create_json_response(
|
||||||
|
r#"{"error":"Failed to get body from Riot API response."}"#,
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Ok(bytes) => bytes,
|
||||||
|
};
|
||||||
|
*out_response.body_mut() = Body::from(bytes);
|
||||||
|
}
|
||||||
|
Ok(out_response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the region, method_id, and Riot API path based on the given http method and path.
|
||||||
|
fn parse_path<'a>(
|
||||||
|
http_method: &Method,
|
||||||
|
req_path: &'a str,
|
||||||
|
) -> Option<(Route, &'static str, &'a str)> {
|
||||||
|
// Split URI into region and rest of path.
|
||||||
|
let req_path = req_path.trim_start_matches('/');
|
||||||
|
let (route, req_path) = req_path.split_at(req_path.find('/')?);
|
||||||
|
let route: Route = route.to_uppercase().parse().ok()?;
|
||||||
|
|
||||||
|
// Find method_id for given path.
|
||||||
|
let method_id = find_matching_method_id(http_method, req_path)?;
|
||||||
|
|
||||||
|
Some((route, method_id, req_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the method_id given the request path.
|
||||||
|
fn find_matching_method_id(http_method: &Method, req_path: &str) -> Option<&'static str> {
|
||||||
|
for (endpoint_http_method, ref_path, method_id) in &riven::meta::ALL_ENDPOINTS {
|
||||||
|
if http_method == endpoint_http_method && paths_match(ref_path, req_path) {
|
||||||
|
return Some(method_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the request path (req_path) matches the reference path (ref_path).
|
||||||
|
fn paths_match(ref_path: &str, req_path: &str) -> bool {
|
||||||
|
let mut ref_iter = ref_path.split('/');
|
||||||
|
let mut req_iter = req_path.split('/');
|
||||||
|
loop {
|
||||||
|
match (ref_iter.next(), req_iter.next()) {
|
||||||
|
(None, None) => return true,
|
||||||
|
(Some(ref_seg), Some(req_seg)) => {
|
||||||
|
if ref_seg.starts_with('{') || ref_seg == req_seg {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
// Setup loggers.
|
||||||
|
// env_logger::init();
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
// Trigger lazy_static.
|
||||||
|
let _ = &*RIOT_API;
|
||||||
|
|
||||||
|
// For every connection, we must make a `Service` to handle all
|
||||||
|
// incoming HTTP requests on said connection.
|
||||||
|
let make_svc = make_service_fn(|_conn| {
|
||||||
|
// This is the `Service` that will handle the connection.
|
||||||
|
// `service_fn` is a helper to convert a function that
|
||||||
|
// returns a Response into a `Service`.
|
||||||
|
async { Ok::<_, Infallible>(service_fn(handle_request)) }
|
||||||
|
});
|
||||||
|
|
||||||
|
let addr = ([127, 0, 0, 1], 3000).into();
|
||||||
|
|
||||||
|
let server = Server::bind(&addr).serve(make_svc);
|
||||||
|
|
||||||
|
log::info!("Listening on http://{}", addr);
|
||||||
|
|
||||||
|
server.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,304 @@
|
||||||
|
//! Configuration of RiotApi.
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use reqwest::header::{HeaderMap, HeaderValue};
|
||||||
|
use reqwest::ClientBuilder;
|
||||||
|
|
||||||
|
/// Configuration for instantiating RiotApi.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RiotApiConfig {
|
||||||
|
pub(crate) base_url: String,
|
||||||
|
pub(crate) retries: u8,
|
||||||
|
pub(crate) app_rate_usage_factor: f32,
|
||||||
|
pub(crate) method_rate_usage_factor: f32,
|
||||||
|
pub(crate) burst_factor: f32,
|
||||||
|
pub(crate) duration_overhead: Duration,
|
||||||
|
pub(crate) client_builder: Option<ClientBuilder>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RiotApiConfig {
|
||||||
|
/// Request header name for the Riot API key, `"X-Riot-Token"`.
|
||||||
|
///
|
||||||
|
/// When using `set_client_builder`, the supplied builder should include
|
||||||
|
/// this default header with the Riot API key as the value.
|
||||||
|
pub const RIOT_KEY_HEADER: &'static str = "X-Riot-Token";
|
||||||
|
|
||||||
|
/// `"https://{}.api.riotgames.com"`
|
||||||
|
///
|
||||||
|
/// Default base URL, including `{}` placeholder for region platform.
|
||||||
|
pub const DEFAULT_BASE_URL: &'static str = "https://{}.api.riotgames.com";
|
||||||
|
|
||||||
|
/// `3`
|
||||||
|
///
|
||||||
|
/// Default number of retries.
|
||||||
|
pub const DEFAULT_RETRIES: u8 = 3;
|
||||||
|
|
||||||
|
/// `1.0`
|
||||||
|
///
|
||||||
|
/// Default rate limit usage factor.
|
||||||
|
pub const DEFAULT_RATE_USAGE_FACTOR: f32 = 1.0;
|
||||||
|
|
||||||
|
/// `0.99`
|
||||||
|
///
|
||||||
|
/// Default `burst_factor`, also used by `preconfig_burst`.
|
||||||
|
pub const PRECONFIG_BURST_BURST_FACTOR: f32 = 0.99;
|
||||||
|
/// `989` ms
|
||||||
|
///
|
||||||
|
/// Default `duration_overhead`, also used by `preconfig_burst`.
|
||||||
|
pub const PRECONFIG_BURST_DURATION_OVERHEAD: Duration = Duration::from_millis(989);
|
||||||
|
|
||||||
|
/// `0.47`
|
||||||
|
///
|
||||||
|
/// `burst_factor` used by `preconfig_throughput`.
|
||||||
|
pub const PRECONFIG_THROUGHPUT_BURST_FACTOR: f32 = 0.47;
|
||||||
|
/// `10` ms.
|
||||||
|
///
|
||||||
|
/// `duration_overhead` used by `preconfig_throughput`.
|
||||||
|
pub const PRECONFIG_THROUGHPUT_DURATION_OVERHEAD: Duration = Duration::from_millis(10);
|
||||||
|
|
||||||
|
/// Creates a new `RiotApiConfig` with the given `api_key` with the following
|
||||||
|
/// configuration:
|
||||||
|
///
|
||||||
|
/// * `retries = 3` (`RiotApiConfig::DEFAULT_RETRIES`).
|
||||||
|
/// * `burst_factor = 0.99` (`preconfig_burst`).
|
||||||
|
/// * `duration_overhead = 989 ms` (`preconfig_burst`).
|
||||||
|
///
|
||||||
|
/// `api_key` should be a Riot Games API key from
|
||||||
|
/// [https://developer.riotgames.com/](https://developer.riotgames.com/),
|
||||||
|
/// and should look like `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`.
|
||||||
|
pub fn with_key(api_key: impl AsRef<[u8]>) -> Self {
|
||||||
|
let mut default_headers = HeaderMap::new();
|
||||||
|
default_headers.insert(
|
||||||
|
Self::RIOT_KEY_HEADER,
|
||||||
|
HeaderValue::from_bytes(api_key.as_ref()).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
base_url: Self::DEFAULT_BASE_URL.into(),
|
||||||
|
retries: Self::DEFAULT_RETRIES,
|
||||||
|
app_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
|
||||||
|
method_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
|
||||||
|
burst_factor: Self::PRECONFIG_BURST_BURST_FACTOR,
|
||||||
|
duration_overhead: Self::PRECONFIG_BURST_DURATION_OVERHEAD,
|
||||||
|
client_builder: Some(ClientBuilder::new().default_headers(default_headers)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `RiotApiConfig` with the given client builder.
|
||||||
|
///
|
||||||
|
/// The client builder default headers should include a value for
|
||||||
|
/// [`RiotApiConfig::RIOT_KEY_HEADER`] (`"X-Riot-Token"`), otherwise authentication will fail.
|
||||||
|
///
|
||||||
|
/// * `retries = 3` (`RiotApiConfig::DEFAULT_RETRIES`).
|
||||||
|
/// * `burst_factor = 0.99` (`preconfig_burst`).
|
||||||
|
/// * `duration_overhead = 989 ms` (`preconfig_burst`).
|
||||||
|
pub fn with_client_builder(client_builder: ClientBuilder) -> Self {
|
||||||
|
Self {
|
||||||
|
base_url: Self::DEFAULT_BASE_URL.to_owned(),
|
||||||
|
retries: Self::DEFAULT_RETRIES,
|
||||||
|
app_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
|
||||||
|
method_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
|
||||||
|
burst_factor: Self::PRECONFIG_BURST_BURST_FACTOR,
|
||||||
|
duration_overhead: Self::PRECONFIG_BURST_DURATION_OVERHEAD,
|
||||||
|
client_builder: Some(client_builder),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets rate limiting settings to preconfigured values optimized for burst,
|
||||||
|
/// low latency:
|
||||||
|
///
|
||||||
|
/// * `burst_factor = 0.99` (`PRECONFIG_BURST_BURST_FACTOR`).
|
||||||
|
/// * `duration_overhead = 989 ms` (`PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS`).
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `self`, for chaining.
|
||||||
|
pub fn preconfig_burst(mut self) -> Self {
|
||||||
|
self.burst_factor = Self::PRECONFIG_BURST_BURST_FACTOR;
|
||||||
|
self.duration_overhead = Self::PRECONFIG_BURST_DURATION_OVERHEAD;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the rate limiting settings to preconfigured values optimized for
|
||||||
|
/// high throughput:
|
||||||
|
///
|
||||||
|
/// * `burst_factor = 0.47` (`PRECONFIG_THROUGHPUT_BURST_FACTOR`).
|
||||||
|
/// * `duration_overhead = 10 ms` (`PRECONFIG_THROUGHPUT_DURATION_OVERHEAD_MILLIS`).
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `self`, for chaining.
|
||||||
|
pub fn preconfig_throughput(mut self) -> Self {
|
||||||
|
self.burst_factor = Self::PRECONFIG_THROUGHPUT_BURST_FACTOR;
|
||||||
|
self.duration_overhead = Self::PRECONFIG_THROUGHPUT_DURATION_OVERHEAD;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the base url for requests. The string should contain a `"{}"`
|
||||||
|
/// literal which will be replaced with the region platform name. (However
|
||||||
|
/// multiple or zero `"{}"`s may be included if needed).
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `self`, for chaining.
|
||||||
|
pub fn set_base_url(mut self, base_url: impl Into<String>) -> Self {
|
||||||
|
self.base_url = base_url.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set number of times to retry requests. Naturally, only retryable requests
|
||||||
|
/// will be retried: responses with status codes 5xx or 429 (after waiting
|
||||||
|
/// for retry-after headers). A value of `0` means one request will be sent
|
||||||
|
/// and it will not be retried if it fails.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `self`, for chaining.
|
||||||
|
pub fn set_retries(mut self, retries: u8) -> Self {
|
||||||
|
self.retries = retries;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The rate limit usage percentage controls how much of the API key's rate
|
||||||
|
/// limit will be used. The default value of `1.0` means the entirety of
|
||||||
|
/// the rate limit may be used if it is needed. This applies to both the
|
||||||
|
/// API key's rate limit (per route) _and_ to endpoint method rate limits.
|
||||||
|
///
|
||||||
|
/// Setting a value lower than `1.0` can be useful if you are running
|
||||||
|
/// multiple API instances on the same API key.
|
||||||
|
///
|
||||||
|
/// For example, four instances, possibly running on different machines,
|
||||||
|
/// could each have a value of `0.25` to share an API key's rate limit
|
||||||
|
/// evenly.
|
||||||
|
///
|
||||||
|
/// Note that if you have multiple instances hitting _different_ methods,
|
||||||
|
/// you should use [Self::set_app_rate_usage_factor()] and [Self::set_method_rate_usage_factor()]
|
||||||
|
/// separately, as this sets both.
|
||||||
|
///
|
||||||
|
/// This also can be used to reduce the chance of hitting 429s, although
|
||||||
|
/// 429s should be rare even with this set to `1.0`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If `rate_usage_factor` is not in range (0, 1].
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `self`, for chaining.
|
||||||
|
pub fn set_rate_usage_factor(mut self, rate_usage_factor: f32) -> Self {
|
||||||
|
// Use inverted check to handle NaN.
|
||||||
|
if 0.0 < rate_usage_factor && rate_usage_factor <= 1.0 {
|
||||||
|
self.app_rate_usage_factor = rate_usage_factor;
|
||||||
|
self.method_rate_usage_factor = rate_usage_factor;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
panic!(
|
||||||
|
"rate_usage_factor \"{}\" not in range (0, 1].",
|
||||||
|
rate_usage_factor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [Self::set_rate_usage_factor]. Setting this is useful if you have multiple
|
||||||
|
/// instances sharing the app rate limit, but are hitting distinct methods
|
||||||
|
/// and therefore do not need their method usage decreased.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If `app_rate_usage_factor` is not in range (0, 1\].
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `self`, for chaining.
|
||||||
|
pub fn set_app_rate_usage_factor(mut self, app_rate_usage_factor: f32) -> Self {
|
||||||
|
// Use inverted check to handle NaN.
|
||||||
|
if 0.0 < app_rate_usage_factor && app_rate_usage_factor <= 1.0 {
|
||||||
|
self.app_rate_usage_factor = app_rate_usage_factor;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
panic!(
|
||||||
|
"app_rate_usage_factor \"{}\" not in range (0, 1].",
|
||||||
|
app_rate_usage_factor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [Self::set_rate_usage_factor] and [Self::set_app_rate_usage_factor].
|
||||||
|
/// This method is mainly provided for completeness, though it may be
|
||||||
|
/// useful in advanced use cases.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If `method_rate_usage_factor` is not in range (0, 1\].
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `self`, for chaining.
|
||||||
|
pub fn set_method_rate_usage_factor(mut self, method_rate_usage_factor: f32) -> Self {
|
||||||
|
// Use inverted check to handle NaN.
|
||||||
|
if 0.0 < method_rate_usage_factor && method_rate_usage_factor <= 1.0 {
|
||||||
|
self.method_rate_usage_factor = method_rate_usage_factor;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
panic!(
|
||||||
|
"method_rate_usage_factor \"{}\" not in range (0, 1].",
|
||||||
|
method_rate_usage_factor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Burst percentage controls how many burst requests are allowed and
|
||||||
|
/// therefore how requests are spread out. Higher equals more burst,
|
||||||
|
/// less spread. Lower equals less burst, more spread.
|
||||||
|
///
|
||||||
|
/// The value must be in the range (0, 1];
|
||||||
|
/// Between 0, exclusive, and 1, inclusive. However values should generally
|
||||||
|
/// be larger than 0.25.
|
||||||
|
///
|
||||||
|
/// Burst percentage behaves as follows:<br>
|
||||||
|
/// A burst percentage of x% means, for each token bucket, "x% of the
|
||||||
|
/// tokens can be used in x% of the bucket duration." So, for example, if x
|
||||||
|
/// is 90%, a bucket would allow 90% of the requests to be made without
|
||||||
|
/// any delay. Then, after waiting 90% of the bucket's duration, the
|
||||||
|
/// remaining 10% of requests could be made.
|
||||||
|
///
|
||||||
|
/// A burst percentage of 100% results in no request spreading, which would
|
||||||
|
/// allow for the largest bursts and lowest latency, but could result in
|
||||||
|
/// 429s as bucket boundaries occur.
|
||||||
|
///
|
||||||
|
/// A burst percentage of near 0% results in high spreading causing
|
||||||
|
/// temporally equidistant requests. This prevents 429s but has the highest
|
||||||
|
/// latency. Additionally, if the number of tokens is high, this may lower
|
||||||
|
/// the overall throughput due to the rate at which requests can be
|
||||||
|
/// scheduled.
|
||||||
|
///
|
||||||
|
/// Therefore, for interactive applications like summoner & match history
|
||||||
|
/// lookup, a higher percentage may be better. For data-collection apps
|
||||||
|
/// like champion winrate aggregation, a medium-low percentage may be
|
||||||
|
/// better.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If `burst_factor` is not in range (0, 1\].
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `self`, for chaining.
|
||||||
|
pub fn set_burst_factor(mut self, burst_factor: f32) -> Self {
|
||||||
|
// Use inverted check to handle NaN.
|
||||||
|
if 0.0 < burst_factor && burst_factor <= 1.0 {
|
||||||
|
self.burst_factor = burst_factor;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
panic!("burst_factor \"{}\" not in range (0, 1].", burst_factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the additional bucket duration to consider when rate limiting.
|
||||||
|
/// Increasing this value will decrease the chances of 429s, but will lower
|
||||||
|
/// the overall throughput.
|
||||||
|
///
|
||||||
|
/// In a sense, the `duration_overhead` is how much to "widen" the temporal
|
||||||
|
/// width of buckets.
|
||||||
|
///
|
||||||
|
/// Given a particular Riot Game API rate limit bucket that allows N requests
|
||||||
|
/// per D duration, when counting requests this library will consider requests
|
||||||
|
/// sent in the past `D + duration_overhead` duration.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// `self`, for chaining.
|
||||||
|
pub fn set_duration_overhead(mut self, duration_overhead: Duration) -> Self {
|
||||||
|
self.duration_overhead = duration_overhead;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<[u8]>> From<T> for RiotApiConfig {
|
||||||
|
fn from(api_key: T) -> Self {
|
||||||
|
Self::with_key(api_key)
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,9 @@
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||||
|
|
||||||
/// LoL and TFT rank divisions, I, II, III, IV, and (deprecated) V.
|
/// LoL and TFT rank divisions, I, II, III, IV, and (deprecated) V.
|
||||||
///
|
///
|
||||||
|
@ -11,29 +12,45 @@ use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
/// Repr'd as equivalent numeric values, (1, 2, 3, 4, 5).
|
/// Repr'd as equivalent numeric values, (1, 2, 3, 4, 5).
|
||||||
///
|
///
|
||||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator). Iterator excludes deprecated `Division::V`.
|
/// Implements [IntoEnumIterator](super::IntoEnumIterator). Iterator excludes deprecated `Division::V`.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
Debug,
|
||||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
Copy,
|
||||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
Clone,
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
Hash,
|
||||||
|
EnumString,
|
||||||
|
Display,
|
||||||
|
AsRefStr,
|
||||||
|
IntoStaticStr,
|
||||||
|
IntoPrimitive,
|
||||||
|
TryFromPrimitive,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Division {
|
pub enum Division {
|
||||||
|
/// Division 1, the best/highest division in a [`Tier`](crate::consts::Tier), or the only division in
|
||||||
|
/// [apex tiers](crate::consts::Tier::is_apex).
|
||||||
I = 1,
|
I = 1,
|
||||||
|
/// Division 2, the second highest division.
|
||||||
II = 2,
|
II = 2,
|
||||||
|
/// Division 3, the third highest division.
|
||||||
III = 3,
|
III = 3,
|
||||||
|
/// Division 4, the fourth and lowest division since 2019.
|
||||||
IV = 4,
|
IV = 4,
|
||||||
#[deprecated(note="Removed for 2019.")]
|
/// Division 5, the lowest division, only used before 2019.
|
||||||
|
#[deprecated(note = "Removed for 2019.")]
|
||||||
V = 5,
|
V = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
serde_string!(Division);
|
|
||||||
|
|
||||||
/// Returns a DoubleEndedIterator of I, II, III, IV.
|
/// Returns a DoubleEndedIterator of I, II, III, IV.
|
||||||
/// Ordered from high rank (I) to low (IV).
|
/// Ordered from high rank (I) to low (IV).
|
||||||
/// Excludes V, which is deprecated.
|
/// Excludes V, which is deprecated.
|
||||||
impl IntoEnumIterator for Division {
|
impl IntoEnumIterator for Division {
|
||||||
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
||||||
fn iter() -> Self::Iterator {
|
fn iter() -> Self::Iterator {
|
||||||
[ Self::I, Self::II, Self::III, Self::IV ].iter().copied()
|
[Self::I, Self::II, Self::III, Self::IV].iter().copied()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
///////////////////////////////////////////////
|
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
|
///////////////////////////////////////////////
|
||||||
// //
|
// //
|
||||||
// ! //
|
// ! //
|
||||||
// This file is automatically generated! //
|
// This file is automatically generated! //
|
||||||
|
@ -6,16 +7,20 @@
|
||||||
// //
|
// //
|
||||||
///////////////////////////////////////////////
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
use strum_macros::{ EnumString, EnumVariantNames, IntoStaticStr };
|
||||||
|
|
||||||
/// League of Legends game mode, such as Classic,
|
/// League of Legends game mode, such as Classic,
|
||||||
/// ARAM, URF, One For All, Ascension, etc.
|
/// ARAM, URF, One For All, Ascension, etc.
|
||||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
#[derive(EnumString, EnumVariantNames, IntoStaticStr)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum GameMode {
|
pub enum GameMode {
|
||||||
|
/// Catch-all variant for new, unknown game modes.
|
||||||
|
#[strum(default)]
|
||||||
|
UNKNOWN(String),
|
||||||
|
|
||||||
/// ARAM games
|
/// ARAM games
|
||||||
ARAM,
|
ARAM,
|
||||||
/// All Random Summoner's Rift games
|
/// All Random Summoner's Rift games
|
||||||
|
@ -24,6 +29,8 @@ pub enum GameMode {
|
||||||
ASCENSION,
|
ASCENSION,
|
||||||
/// Blood Hunt Assassin games
|
/// Blood Hunt Assassin games
|
||||||
ASSASSINATE,
|
ASSASSINATE,
|
||||||
|
/// 2v2v2v2
|
||||||
|
CHERRY,
|
||||||
/// Classic Summoner's Rift and Twisted Treeline games
|
/// Classic Summoner's Rift and Twisted Treeline games
|
||||||
CLASSIC,
|
CLASSIC,
|
||||||
/// Dark Star: Singularity games
|
/// Dark Star: Singularity games
|
||||||
|
@ -32,11 +39,11 @@ pub enum GameMode {
|
||||||
DOOMBOTSTEEMO,
|
DOOMBOTSTEEMO,
|
||||||
/// Snowdown Showdown games
|
/// Snowdown Showdown games
|
||||||
FIRSTBLOOD,
|
FIRSTBLOOD,
|
||||||
/// Nexus Blitz games, deprecated in patch 9.2 in favor of gameMode NEXUSBLITZ.
|
/// Nexus Blitz games
|
||||||
GAMEMODEX,
|
GAMEMODEX,
|
||||||
/// Legend of the Poro King games
|
/// Legend of the Poro King games
|
||||||
KINGPORO,
|
KINGPORO,
|
||||||
/// Nexus Blitz games.
|
/// Nexus Blitz games
|
||||||
NEXUSBLITZ,
|
NEXUSBLITZ,
|
||||||
/// Dominion/Crystal Scar games
|
/// Dominion/Crystal Scar games
|
||||||
ODIN,
|
ODIN,
|
||||||
|
@ -44,12 +51,16 @@ pub enum GameMode {
|
||||||
ODYSSEY,
|
ODYSSEY,
|
||||||
/// One for All games
|
/// One for All games
|
||||||
ONEFORALL,
|
ONEFORALL,
|
||||||
|
/// Practice tool training games.
|
||||||
|
PRACTICETOOL,
|
||||||
/// PROJECT: Hunters games
|
/// PROJECT: Hunters games
|
||||||
PROJECT,
|
PROJECT,
|
||||||
/// Nexus Siege games
|
/// Nexus Siege games
|
||||||
SIEGE,
|
SIEGE,
|
||||||
/// Star Guardian Invasion games
|
/// Star Guardian Invasion games
|
||||||
STARGUARDIAN,
|
STARGUARDIAN,
|
||||||
|
/// Teamfight Tactics, used in `spectator-v4` endpoints.
|
||||||
|
TFT,
|
||||||
/// Tutorial games
|
/// Tutorial games
|
||||||
TUTORIAL,
|
TUTORIAL,
|
||||||
/// Tutorial: Welcome to League.
|
/// Tutorial: Welcome to League.
|
||||||
|
@ -58,8 +69,10 @@ pub enum GameMode {
|
||||||
TUTORIAL_MODULE_2,
|
TUTORIAL_MODULE_2,
|
||||||
/// Tutorial: Shop for Gear.
|
/// Tutorial: Shop for Gear.
|
||||||
TUTORIAL_MODULE_3,
|
TUTORIAL_MODULE_3,
|
||||||
|
/// Ultimate Spellbook games
|
||||||
|
ULTBOOK,
|
||||||
/// URF games
|
/// URF games
|
||||||
URF,
|
URF,
|
||||||
}
|
}
|
||||||
|
|
||||||
serde_string!(GameMode);
|
serde_strum_unknown!(GameMode);
|
|
@ -1,4 +1,5 @@
|
||||||
///////////////////////////////////////////////
|
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
|
///////////////////////////////////////////////
|
||||||
// //
|
// //
|
||||||
// ! //
|
// ! //
|
||||||
// This file is automatically generated! //
|
// This file is automatically generated! //
|
||||||
|
@ -6,20 +7,29 @@
|
||||||
// //
|
// //
|
||||||
///////////////////////////////////////////////
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
use serde::{ Serialize, Deserialize };
|
||||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
|
|
||||||
/// League of Legends game type: matched game, custom game, or tutorial game.
|
/// League of Legends game type: matched game, custom game, or tutorial game.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum GameType {
|
pub enum GameType {
|
||||||
/// Custom games
|
/// Custom games
|
||||||
|
#[strum(to_string = "CUSTOM_GAME", serialize = "CUSTOM")]
|
||||||
|
#[serde(alias = "CUSTOM")]
|
||||||
CUSTOM_GAME,
|
CUSTOM_GAME,
|
||||||
/// all other games
|
/// all other games
|
||||||
|
#[strum(to_string = "MATCHED_GAME", serialize = "MATCHED")]
|
||||||
|
#[serde(alias = "MATCHED")]
|
||||||
MATCHED_GAME,
|
MATCHED_GAME,
|
||||||
/// Tutorial games
|
/// Tutorial games
|
||||||
|
#[strum(to_string = "TUTORIAL_GAME", serialize = "TUTORIAL")]
|
||||||
|
#[serde(alias = "TUTORIAL")]
|
||||||
TUTORIAL_GAME,
|
TUTORIAL_GAME,
|
||||||
}
|
}
|
||||||
|
|
||||||
serde_string!(GameType);
|
#[cfg(test)]
|
||||||
|
mod test;
|
|
@ -0,0 +1,24 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_as_ref() {
|
||||||
|
assert_eq!("MATCHED_GAME", GameType::MATCHED_GAME.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_to_string() {
|
||||||
|
assert_eq!("MATCHED_GAME", GameType::MATCHED_GAME.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_from_string() {
|
||||||
|
assert_eq!(Ok(GameType::MATCHED_GAME), "MATCHED_GAME".parse());
|
||||||
|
assert_eq!(Ok(GameType::MATCHED_GAME), "MATCHED".parse());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_serialize() {
|
||||||
|
assert_eq!(Some("\"MATCHED_GAME\""),
|
||||||
|
serde_json::to_string(&GameType::MATCHED_GAME)
|
||||||
|
.ok().as_deref());
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
#![macro_use]
|
||||||
|
|
||||||
|
/// Macro for deriving `Serialize` and `Deserialize` for string enums with an
|
||||||
|
/// `UNKNOWN(String)` variant.
|
||||||
|
///
|
||||||
|
/// Enum should have `#[derive(EnumString, EnumVariantNames, IntoStaticStr)]` included.
|
||||||
|
///
|
||||||
|
/// Also implements `AsRef<str>`, `Display`, and `From<&str>`.
|
||||||
|
macro_rules! serde_strum_unknown {
|
||||||
|
($name:ident) => {
|
||||||
|
impl AsRef<str> for $name {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::UNKNOWN(string) => &*string,
|
||||||
|
known => known.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for $name {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
|
self.as_ref().fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl serde::ser::Serialize for $name {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::ser::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(self.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for $name {
|
||||||
|
fn from(item: &str) -> Self {
|
||||||
|
item.parse().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'de> serde::de::Deserialize<'de> for $name {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "deny-unknown-enum-variants-strings"))]
|
||||||
|
{
|
||||||
|
<&str>::deserialize(deserializer).map(Into::into)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "deny-unknown-enum-variants-strings")]
|
||||||
|
{
|
||||||
|
<&str>::deserialize(deserializer)
|
||||||
|
.map(Into::into)
|
||||||
|
.and_then(|item| match item {
|
||||||
|
Self::UNKNOWN(unknown) => Err(serde::de::Error::unknown_variant(
|
||||||
|
&*unknown,
|
||||||
|
<Self as strum::VariantNames>::VARIANTS,
|
||||||
|
)),
|
||||||
|
other => Ok(other),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! arr {
|
||||||
|
(
|
||||||
|
$( #[$attr:meta] )*
|
||||||
|
$v:vis $id:ident $name:ident: [$ty:ty; _] = $value:expr
|
||||||
|
) => {
|
||||||
|
$( #[$attr] )*
|
||||||
|
$v $id $name: [$ty; $value.len()] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro for newtype "enums" with integer values.
|
||||||
|
///
|
||||||
|
/// For serde, use the following:
|
||||||
|
/// ```ignore
|
||||||
|
/// #[derive(Serialize, Deserialize)]
|
||||||
|
/// #[serde(from = "$repr", into = "$repr")]
|
||||||
|
/// ```
|
||||||
|
macro_rules! newtype_enum {
|
||||||
|
{
|
||||||
|
$( #[$attr:meta] )*
|
||||||
|
$v:vis newtype_enum $name:ident($repr:ty) {
|
||||||
|
$(
|
||||||
|
$( #[$var_attr:meta] )*
|
||||||
|
$var_name:ident = $var_val:expr,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
} => {
|
||||||
|
$( #[$attr] )*
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
$v struct $name($v $repr);
|
||||||
|
impl $name {
|
||||||
|
$(
|
||||||
|
$( #[$var_attr] )*
|
||||||
|
$v const $var_name: Self = Self($var_val);
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $name {
|
||||||
|
arr!{
|
||||||
|
#[doc = "Array containing all known variants."]
|
||||||
|
pub const ALL_KNOWN: [Self; _] = [
|
||||||
|
$( Self::$var_name, )*
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "If this is one of the known variants."]
|
||||||
|
$v const fn is_known(self) -> bool {
|
||||||
|
match self {
|
||||||
|
$(
|
||||||
|
Self::$var_name => true,
|
||||||
|
)*
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<$name> for $repr {
|
||||||
|
fn from(value: $name ) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl serde::ser::Serialize for $name {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::ser::Serializer,
|
||||||
|
{
|
||||||
|
<$repr>::serialize(&self.0, serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<$repr> for $name {
|
||||||
|
fn from(value: $repr ) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'de> serde::de::Deserialize<'de> for $name {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::de::Deserializer<'de>
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "deny-unknown-enum-variants-integers"))]
|
||||||
|
{
|
||||||
|
<$repr>::deserialize(deserializer).map(Into::into)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "deny-unknown-enum-variants-integers")]
|
||||||
|
{
|
||||||
|
<$repr>::deserialize(deserializer).map(Into::into)
|
||||||
|
.and_then(|item: Self| {
|
||||||
|
if !item.is_known() {
|
||||||
|
Err(serde::de::Error::custom(format!(
|
||||||
|
"Unknown integer enum variant: {} (\"deny-unknown-enum-variants-integers\" feature is enabled).\nExpected one of the following: {:?}",
|
||||||
|
item, Self::ALL_KNOWN
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Ok(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for $name {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Debug for $name {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}({}{})", stringify!($name), self.0, if self.is_known() { "" } else { "?" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// ! //
|
||||||
|
// This file is automatically generated! //
|
||||||
|
// Do not directly edit! //
|
||||||
|
// //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
newtype_enum! {
|
||||||
|
/// A League of Legends map.
|
||||||
|
pub newtype_enum Map(u8) {
|
||||||
|
/// `1`.
|
||||||
|
/// Summoner's Rift
|
||||||
|
/// Original Summer variant
|
||||||
|
SUMMONERS_RIFT_ORIGINAL_SUMMER_VARIANT = 1,
|
||||||
|
/// `2`.
|
||||||
|
/// Summoner's Rift
|
||||||
|
/// Original Autumn variant
|
||||||
|
SUMMONERS_RIFT_ORIGINAL_AUTUMN_VARIANT = 2,
|
||||||
|
/// `3`.
|
||||||
|
/// The Proving Grounds
|
||||||
|
/// Tutorial Map
|
||||||
|
THE_PROVING_GROUNDS = 3,
|
||||||
|
/// `4`.
|
||||||
|
/// Twisted Treeline
|
||||||
|
/// Original Version
|
||||||
|
TWISTED_TREELINE_ORIGINAL_VERSION = 4,
|
||||||
|
/// `8`.
|
||||||
|
/// The Crystal Scar
|
||||||
|
/// Dominion map
|
||||||
|
THE_CRYSTAL_SCAR = 8,
|
||||||
|
/// `10`.
|
||||||
|
/// Twisted Treeline
|
||||||
|
/// Last TT map
|
||||||
|
TWISTED_TREELINE = 10,
|
||||||
|
/// `11`.
|
||||||
|
/// Summoner's Rift
|
||||||
|
/// Current Version
|
||||||
|
SUMMONERS_RIFT = 11,
|
||||||
|
/// `12`.
|
||||||
|
/// Howling Abyss
|
||||||
|
/// ARAM map
|
||||||
|
HOWLING_ABYSS = 12,
|
||||||
|
/// `14`.
|
||||||
|
/// Butcher's Bridge
|
||||||
|
/// Alternate ARAM map
|
||||||
|
BUTCHERS_BRIDGE = 14,
|
||||||
|
/// `16`.
|
||||||
|
/// Cosmic Ruins
|
||||||
|
/// Dark Star: Singularity map
|
||||||
|
COSMIC_RUINS = 16,
|
||||||
|
/// `18`.
|
||||||
|
/// Valoran City Park
|
||||||
|
/// Star Guardian Invasion map
|
||||||
|
VALORAN_CITY_PARK = 18,
|
||||||
|
/// `19`.
|
||||||
|
/// Substructure 43
|
||||||
|
/// PROJECT: Hunters map
|
||||||
|
SUBSTRUCTURE_43 = 19,
|
||||||
|
/// `20`.
|
||||||
|
/// Crash Site
|
||||||
|
/// Odyssey: Extraction map
|
||||||
|
CRASH_SITE = 20,
|
||||||
|
/// `21`.
|
||||||
|
/// Nexus Blitz
|
||||||
|
/// Nexus Blitz map
|
||||||
|
NEXUS_BLITZ = 21,
|
||||||
|
/// `22`.
|
||||||
|
/// Convergence
|
||||||
|
/// Teamfight Tactics map
|
||||||
|
CONVERGENCE = 22,
|
||||||
|
/// `30`.
|
||||||
|
/// Arena
|
||||||
|
/// Map for 2v2v2v2 (`CHERRY`). Team up with a friend or venture solo in this new game mode. Face against multiple teams in chaotic battles across diverse arenas
|
||||||
|
ARENA = 30,
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,41 +6,49 @@
|
||||||
#![allow(deprecated)]
|
#![allow(deprecated)]
|
||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
mod macro_serde_string;
|
mod macros;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod champion;
|
mod champion;
|
||||||
pub use champion::*;
|
pub use champion::*;
|
||||||
|
|
||||||
mod division;
|
mod division;
|
||||||
pub use division::*;
|
pub use division::*;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod game_mode;
|
mod game_mode;
|
||||||
pub use game_mode::*;
|
pub use game_mode::*;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod game_type;
|
mod game_type;
|
||||||
pub use game_type::*;
|
pub use game_type::*;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod map;
|
mod map;
|
||||||
pub use map::*;
|
pub use map::*;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod queue_type;
|
mod queue_type;
|
||||||
pub use queue_type::*;
|
pub use queue_type::*;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod queue;
|
mod queue;
|
||||||
pub use queue::*;
|
pub use queue::*;
|
||||||
|
|
||||||
pub mod ranks;
|
pub mod ranks;
|
||||||
|
|
||||||
mod region;
|
#[rustfmt::skip]
|
||||||
pub use region::*;
|
mod route;
|
||||||
|
pub use route::*;
|
||||||
|
|
||||||
|
mod route_ext;
|
||||||
|
pub use route_ext::*;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
mod season;
|
mod season;
|
||||||
pub use season::*;
|
pub use season::*;
|
||||||
|
|
||||||
/// Trait allowing iteration of enum types, implemented by several enums in this module.
|
/// Trait allowing iteration of enum types, implemented by several enums in this module.
|
||||||
/// Re-exported from strum.
|
/// Re-exported from strum.
|
||||||
///
|
|
||||||
///
|
|
||||||
pub use strum::IntoEnumIterator;
|
pub use strum::IntoEnumIterator;
|
||||||
|
|
||||||
mod team;
|
mod team;
|
|
@ -0,0 +1,397 @@
|
||||||
|
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// ! //
|
||||||
|
// This file is automatically generated! //
|
||||||
|
// Do not directly edit! //
|
||||||
|
// //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
newtype_enum! {
|
||||||
|
/// A League of Legends matchmaking queue.
|
||||||
|
pub newtype_enum Queue(u16) {
|
||||||
|
/// `0`.
|
||||||
|
/// Games on Custom games
|
||||||
|
CUSTOM = 0,
|
||||||
|
/// `2`.
|
||||||
|
/// 5v5 Blind Pick games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 430
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 430")]
|
||||||
|
SUMMONERS_RIFT_5V5_BLIND_PICK_DEPRECATED_2 = 2,
|
||||||
|
/// `4`.
|
||||||
|
/// 5v5 Ranked Solo games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Deprecated in favor of queueId 420
|
||||||
|
#[deprecated(note="Deprecated in favor of queueId 420")]
|
||||||
|
SUMMONERS_RIFT_5V5_RANKED_SOLO_DEPRECATED_4 = 4,
|
||||||
|
/// `6`.
|
||||||
|
/// 5v5 Ranked Premade games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
SUMMONERS_RIFT_5V5_RANKED_PREMADE = 6,
|
||||||
|
/// `7`.
|
||||||
|
/// Co-op vs AI games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Deprecated in favor of queueId 32 and 33
|
||||||
|
#[deprecated(note="Deprecated in favor of queueId 32 and 33")]
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI = 7,
|
||||||
|
/// `8`.
|
||||||
|
/// 3v3 Normal games on Twisted Treeline
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 460
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 460")]
|
||||||
|
TWISTED_TREELINE_3V3_NORMAL = 8,
|
||||||
|
/// `9`.
|
||||||
|
/// 3v3 Ranked Flex games on Twisted Treeline
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 470
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 470")]
|
||||||
|
TWISTED_TREELINE_3V3_RANKED_FLEX_DEPRECATED_9 = 9,
|
||||||
|
/// `14`.
|
||||||
|
/// 5v5 Draft Pick games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Deprecated in favor of queueId 400
|
||||||
|
#[deprecated(note="Deprecated in favor of queueId 400")]
|
||||||
|
SUMMONERS_RIFT_5V5_DRAFT_PICK_DEPRECATED_14 = 14,
|
||||||
|
/// `16`.
|
||||||
|
/// 5v5 Dominion Blind Pick games on Crystal Scar
|
||||||
|
///
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
CRYSTAL_SCAR_5V5_DOMINION_BLIND_PICK = 16,
|
||||||
|
/// `17`.
|
||||||
|
/// 5v5 Dominion Draft Pick games on Crystal Scar
|
||||||
|
///
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
CRYSTAL_SCAR_5V5_DOMINION_DRAFT_PICK = 17,
|
||||||
|
/// `25`.
|
||||||
|
/// Dominion Co-op vs AI games on Crystal Scar
|
||||||
|
///
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
CRYSTAL_SCAR_DOMINION_CO_OP_VS_AI = 25,
|
||||||
|
/// `31`.
|
||||||
|
/// Co-op vs AI Intro Bot games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 830
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 830")]
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_INTRO_BOT_DEPRECATED_31 = 31,
|
||||||
|
/// `32`.
|
||||||
|
/// Co-op vs AI Beginner Bot games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 840
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 840")]
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_BEGINNER_BOT_DEPRECATED_32 = 32,
|
||||||
|
/// `33`.
|
||||||
|
/// Co-op vs AI Intermediate Bot games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 850
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 850")]
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_INTERMEDIATE_BOT_DEPRECATED_33 = 33,
|
||||||
|
/// `41`.
|
||||||
|
/// 3v3 Ranked Team games on Twisted Treeline
|
||||||
|
///
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
TWISTED_TREELINE_3V3_RANKED_TEAM = 41,
|
||||||
|
/// `42`.
|
||||||
|
/// 5v5 Ranked Team games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
SUMMONERS_RIFT_5V5_RANKED_TEAM = 42,
|
||||||
|
/// `52`.
|
||||||
|
/// Co-op vs AI games on Twisted Treeline
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 800
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 800")]
|
||||||
|
TWISTED_TREELINE_CO_OP_VS_AI = 52,
|
||||||
|
/// `61`.
|
||||||
|
/// 5v5 Team Builder games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
SUMMONERS_RIFT_5V5_TEAM_BUILDER = 61,
|
||||||
|
/// `65`.
|
||||||
|
/// 5v5 ARAM games on Howling Abyss
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 450
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 450")]
|
||||||
|
HOWLING_ABYSS_5V5_ARAM_DEPRECATED_65 = 65,
|
||||||
|
/// `67`.
|
||||||
|
/// ARAM Co-op vs AI games on Howling Abyss
|
||||||
|
///
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
HOWLING_ABYSS_ARAM_CO_OP_VS_AI = 67,
|
||||||
|
/// `70`.
|
||||||
|
/// One for All games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 8.6 in favor of queueId 1020
|
||||||
|
#[deprecated(note="Deprecated in patch 8.6 in favor of queueId 1020")]
|
||||||
|
SUMMONERS_RIFT_ONE_FOR_ALL_DEPRECATED_70 = 70,
|
||||||
|
/// `72`.
|
||||||
|
/// 1v1 Snowdown Showdown games on Howling Abyss
|
||||||
|
HOWLING_ABYSS_1V1_SNOWDOWN_SHOWDOWN = 72,
|
||||||
|
/// `73`.
|
||||||
|
/// 2v2 Snowdown Showdown games on Howling Abyss
|
||||||
|
HOWLING_ABYSS_2V2_SNOWDOWN_SHOWDOWN = 73,
|
||||||
|
/// `75`.
|
||||||
|
/// 6v6 Hexakill games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_6V6_HEXAKILL = 75,
|
||||||
|
/// `76`.
|
||||||
|
/// Ultra Rapid Fire games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_ULTRA_RAPID_FIRE = 76,
|
||||||
|
/// `78`.
|
||||||
|
/// One For All: Mirror Mode games on Howling Abyss
|
||||||
|
HOWLING_ABYSS_ONE_FOR_ALL_MIRROR_MODE = 78,
|
||||||
|
/// `83`.
|
||||||
|
/// Co-op vs AI Ultra Rapid Fire games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_ULTRA_RAPID_FIRE = 83,
|
||||||
|
/// `91`.
|
||||||
|
/// Doom Bots Rank 1 games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 950
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
||||||
|
SUMMONERS_RIFT_DOOM_BOTS_RANK_1 = 91,
|
||||||
|
/// `92`.
|
||||||
|
/// Doom Bots Rank 2 games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 950
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
||||||
|
SUMMONERS_RIFT_DOOM_BOTS_RANK_2 = 92,
|
||||||
|
/// `93`.
|
||||||
|
/// Doom Bots Rank 5 games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 950
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
||||||
|
SUMMONERS_RIFT_DOOM_BOTS_RANK_5 = 93,
|
||||||
|
/// `96`.
|
||||||
|
/// Ascension games on Crystal Scar
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 910
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 910")]
|
||||||
|
CRYSTAL_SCAR_ASCENSION_DEPRECATED_96 = 96,
|
||||||
|
/// `98`.
|
||||||
|
/// 6v6 Hexakill games on Twisted Treeline
|
||||||
|
TWISTED_TREELINE_6V6_HEXAKILL = 98,
|
||||||
|
/// `100`.
|
||||||
|
/// 5v5 ARAM games on Butcher's Bridge
|
||||||
|
BUTCHERS_BRIDGE_5V5_ARAM = 100,
|
||||||
|
/// `300`.
|
||||||
|
/// Legend of the Poro King games on Howling Abyss
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 920
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 920")]
|
||||||
|
HOWLING_ABYSS_LEGEND_OF_THE_PORO_KING_DEPRECATED_300 = 300,
|
||||||
|
/// `310`.
|
||||||
|
/// Nemesis games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_NEMESIS = 310,
|
||||||
|
/// `313`.
|
||||||
|
/// Black Market Brawlers games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_BLACK_MARKET_BRAWLERS = 313,
|
||||||
|
/// `315`.
|
||||||
|
/// Nexus Siege games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 940
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 940")]
|
||||||
|
SUMMONERS_RIFT_NEXUS_SIEGE_DEPRECATED_315 = 315,
|
||||||
|
/// `317`.
|
||||||
|
/// Definitely Not Dominion games on Crystal Scar
|
||||||
|
CRYSTAL_SCAR_DEFINITELY_NOT_DOMINION = 317,
|
||||||
|
/// `318`.
|
||||||
|
/// ARURF games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 900
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 900")]
|
||||||
|
SUMMONERS_RIFT_ARURF_DEPRECATED_318 = 318,
|
||||||
|
/// `325`.
|
||||||
|
/// All Random games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_ALL_RANDOM = 325,
|
||||||
|
/// `400`.
|
||||||
|
/// 5v5 Draft Pick games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_5V5_DRAFT_PICK = 400,
|
||||||
|
/// `410`.
|
||||||
|
/// 5v5 Ranked Dynamic games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// Game mode deprecated in patch 6.22
|
||||||
|
#[deprecated(note="Game mode deprecated in patch 6.22")]
|
||||||
|
SUMMONERS_RIFT_5V5_RANKED_DYNAMIC = 410,
|
||||||
|
/// `420`.
|
||||||
|
/// 5v5 Ranked Solo games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_5V5_RANKED_SOLO = 420,
|
||||||
|
/// `430`.
|
||||||
|
/// 5v5 Blind Pick games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_5V5_BLIND_PICK = 430,
|
||||||
|
/// `440`.
|
||||||
|
/// 5v5 Ranked Flex games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_5V5_RANKED_FLEX = 440,
|
||||||
|
/// `450`.
|
||||||
|
/// 5v5 ARAM games on Howling Abyss
|
||||||
|
HOWLING_ABYSS_5V5_ARAM = 450,
|
||||||
|
/// `460`.
|
||||||
|
/// 3v3 Blind Pick games on Twisted Treeline
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 9.23
|
||||||
|
#[deprecated(note="Deprecated in patch 9.23")]
|
||||||
|
TWISTED_TREELINE_3V3_BLIND_PICK = 460,
|
||||||
|
/// `470`.
|
||||||
|
/// 3v3 Ranked Flex games on Twisted Treeline
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 9.23
|
||||||
|
#[deprecated(note="Deprecated in patch 9.23")]
|
||||||
|
TWISTED_TREELINE_3V3_RANKED_FLEX_DEPRECATED_470 = 470,
|
||||||
|
/// `490`.
|
||||||
|
/// Normal (Quickplay) games on Summoner's Rift
|
||||||
|
///
|
||||||
|
/// https://github.com/RiotGames/developer-relations/issues/846
|
||||||
|
SUMMONERS_RIFT_NORMAL_QUICKPLAY_ = 490,
|
||||||
|
/// `600`.
|
||||||
|
/// Blood Hunt Assassin games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_BLOOD_HUNT_ASSASSIN = 600,
|
||||||
|
/// `610`.
|
||||||
|
/// Dark Star: Singularity games on Cosmic Ruins
|
||||||
|
COSMIC_RUINS_DARK_STAR_SINGULARITY = 610,
|
||||||
|
/// `700`.
|
||||||
|
/// Summoner's Rift Clash games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_CLASH = 700,
|
||||||
|
/// `720`.
|
||||||
|
/// ARAM Clash games on Howling Abyss
|
||||||
|
HOWLING_ABYSS_ARAM_CLASH = 720,
|
||||||
|
/// `800`.
|
||||||
|
/// Co-op vs. AI Intermediate Bot games on Twisted Treeline
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 9.23
|
||||||
|
#[deprecated(note="Deprecated in patch 9.23")]
|
||||||
|
TWISTED_TREELINE_CO_OP_VS_AI_INTERMEDIATE_BOT = 800,
|
||||||
|
/// `810`.
|
||||||
|
/// Co-op vs. AI Intro Bot games on Twisted Treeline
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 9.23
|
||||||
|
#[deprecated(note="Deprecated in patch 9.23")]
|
||||||
|
TWISTED_TREELINE_CO_OP_VS_AI_INTRO_BOT = 810,
|
||||||
|
/// `820`.
|
||||||
|
/// Co-op vs. AI Beginner Bot games on Twisted Treeline
|
||||||
|
TWISTED_TREELINE_CO_OP_VS_AI_BEGINNER_BOT = 820,
|
||||||
|
/// `830`.
|
||||||
|
/// Co-op vs. AI Intro Bot games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_INTRO_BOT = 830,
|
||||||
|
/// `840`.
|
||||||
|
/// Co-op vs. AI Beginner Bot games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_BEGINNER_BOT = 840,
|
||||||
|
/// `850`.
|
||||||
|
/// Co-op vs. AI Intermediate Bot games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_INTERMEDIATE_BOT = 850,
|
||||||
|
/// `900`.
|
||||||
|
/// ARURF games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_ARURF = 900,
|
||||||
|
/// `910`.
|
||||||
|
/// Ascension games on Crystal Scar
|
||||||
|
CRYSTAL_SCAR_ASCENSION = 910,
|
||||||
|
/// `920`.
|
||||||
|
/// Legend of the Poro King games on Howling Abyss
|
||||||
|
HOWLING_ABYSS_LEGEND_OF_THE_PORO_KING = 920,
|
||||||
|
/// `940`.
|
||||||
|
/// Nexus Siege games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_NEXUS_SIEGE = 940,
|
||||||
|
/// `950`.
|
||||||
|
/// Doom Bots Voting games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_DOOM_BOTS_VOTING = 950,
|
||||||
|
/// `960`.
|
||||||
|
/// Doom Bots Standard games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_DOOM_BOTS_STANDARD = 960,
|
||||||
|
/// `980`.
|
||||||
|
/// Star Guardian Invasion: Normal games on Valoran City Park
|
||||||
|
VALORAN_CITY_PARK_STAR_GUARDIAN_INVASION_NORMAL = 980,
|
||||||
|
/// `990`.
|
||||||
|
/// Star Guardian Invasion: Onslaught games on Valoran City Park
|
||||||
|
VALORAN_CITY_PARK_STAR_GUARDIAN_INVASION_ONSLAUGHT = 990,
|
||||||
|
/// `1000`.
|
||||||
|
/// PROJECT: Hunters games on Overcharge
|
||||||
|
OVERCHARGE_PROJECT_HUNTERS = 1000,
|
||||||
|
/// `1010`.
|
||||||
|
/// Snow ARURF games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_SNOW_ARURF = 1010,
|
||||||
|
/// `1020`.
|
||||||
|
/// One for All games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_ONE_FOR_ALL = 1020,
|
||||||
|
/// `1030`.
|
||||||
|
/// Odyssey Extraction: Intro games on Crash Site
|
||||||
|
CRASH_SITE_ODYSSEY_EXTRACTION_INTRO = 1030,
|
||||||
|
/// `1040`.
|
||||||
|
/// Odyssey Extraction: Cadet games on Crash Site
|
||||||
|
CRASH_SITE_ODYSSEY_EXTRACTION_CADET = 1040,
|
||||||
|
/// `1050`.
|
||||||
|
/// Odyssey Extraction: Crewmember games on Crash Site
|
||||||
|
CRASH_SITE_ODYSSEY_EXTRACTION_CREWMEMBER = 1050,
|
||||||
|
/// `1060`.
|
||||||
|
/// Odyssey Extraction: Captain games on Crash Site
|
||||||
|
CRASH_SITE_ODYSSEY_EXTRACTION_CAPTAIN = 1060,
|
||||||
|
/// `1070`.
|
||||||
|
/// Odyssey Extraction: Onslaught games on Crash Site
|
||||||
|
CRASH_SITE_ODYSSEY_EXTRACTION_ONSLAUGHT = 1070,
|
||||||
|
/// `1090`.
|
||||||
|
/// Teamfight Tactics games on Convergence
|
||||||
|
CONVERGENCE_TEAMFIGHT_TACTICS = 1090,
|
||||||
|
/// `1091`.
|
||||||
|
/// Teamfight Tactics 1v0 games on Convergence
|
||||||
|
CONVERGENCE_TEAMFIGHT_TACTICS_1V0 = 1091,
|
||||||
|
/// `1092`.
|
||||||
|
/// Teamfight Tactics 2v0 games on Convergence
|
||||||
|
CONVERGENCE_TEAMFIGHT_TACTICS_2V0 = 1092,
|
||||||
|
/// `1100`.
|
||||||
|
/// Ranked Teamfight Tactics games on Convergence
|
||||||
|
CONVERGENCE_RANKED_TEAMFIGHT_TACTICS = 1100,
|
||||||
|
/// `1110`.
|
||||||
|
/// Teamfight Tactics Tutorial games on Convergence
|
||||||
|
CONVERGENCE_TEAMFIGHT_TACTICS_TUTORIAL = 1110,
|
||||||
|
/// `1111`.
|
||||||
|
/// Teamfight Tactics Simluation games on Convergence
|
||||||
|
CONVERGENCE_TEAMFIGHT_TACTICS_SIMLUATION = 1111,
|
||||||
|
/// `1130`.
|
||||||
|
/// Ranked Teamfight Tactics (Hyper Roll) games on Convergence
|
||||||
|
CONVERGENCE_RANKED_TEAMFIGHT_TACTICS_HYPER_ROLL_ = 1130,
|
||||||
|
/// `1150`.
|
||||||
|
/// Ranked Teamfight Tactics (Double Up Workshop) games on Convergence
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 12.11 in favor of queueId 1160
|
||||||
|
#[deprecated(note="Deprecated in patch 12.11 in favor of queueId 1160")]
|
||||||
|
CONVERGENCE_RANKED_TEAMFIGHT_TACTICS_DOUBLE_UP_WORKSHOP__DEPRECATED_1150 = 1150,
|
||||||
|
/// `1160`.
|
||||||
|
/// Ranked Teamfight Tactics (Double Up Workshop) games on Convergence
|
||||||
|
CONVERGENCE_RANKED_TEAMFIGHT_TACTICS_DOUBLE_UP_WORKSHOP_ = 1160,
|
||||||
|
/// `1200`.
|
||||||
|
/// Nexus Blitz games on Nexus Blitz
|
||||||
|
///
|
||||||
|
/// Deprecated in patch 9.2 in favor of queueId 1300
|
||||||
|
#[deprecated(note="Deprecated in patch 9.2 in favor of queueId 1300")]
|
||||||
|
NEXUS_BLITZ_DEPRECATED_1200 = 1200,
|
||||||
|
/// `1300`.
|
||||||
|
/// Nexus Blitz games on Nexus Blitz
|
||||||
|
NEXUS_BLITZ = 1300,
|
||||||
|
/// `1400`.
|
||||||
|
/// Ultimate Spellbook games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_ULTIMATE_SPELLBOOK = 1400,
|
||||||
|
/// `1700`.
|
||||||
|
/// 2v2v2v2 `CHERRY` games on Arena
|
||||||
|
ARENA_2V2V2V2_CHERRY_ = 1700,
|
||||||
|
/// `1900`.
|
||||||
|
/// Pick URF games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_PICK_URF = 1900,
|
||||||
|
/// `2000`.
|
||||||
|
/// Tutorial 1 games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_TUTORIAL_1 = 2000,
|
||||||
|
/// `2010`.
|
||||||
|
/// Tutorial 2 games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_TUTORIAL_2 = 2010,
|
||||||
|
/// `2020`.
|
||||||
|
/// Tutorial 3 games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_TUTORIAL_3 = 2020,
|
||||||
|
/// `6000`.
|
||||||
|
/// Teamfight Tactics Set 3.5 Revival games on Convergence
|
||||||
|
CONVERGENCE_TEAMFIGHT_TACTICS_SET_3_5_REVIVAL = 6000,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// ! //
|
||||||
|
// This file is automatically generated! //
|
||||||
|
// Do not directly edit! //
|
||||||
|
// //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
use strum_macros::{ EnumString, EnumVariantNames, IntoStaticStr };
|
||||||
|
|
||||||
|
/// LoL or TFT ranked queue types.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
|
#[derive(EnumString, EnumVariantNames, IntoStaticStr)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum QueueType {
|
||||||
|
/// Catch-all variant for new, unknown queue types.
|
||||||
|
#[strum(default)]
|
||||||
|
UNKNOWN(String),
|
||||||
|
|
||||||
|
/// 5v5 Ranked Solo games
|
||||||
|
RANKED_SOLO_5x5,
|
||||||
|
/// 5v5 Ranked Flex games
|
||||||
|
RANKED_FLEX_SR,
|
||||||
|
/// 3v3 Ranked Flex games
|
||||||
|
/// Deprecated in patch 9.23
|
||||||
|
#[deprecated(note="Deprecated in patch 9.23")]
|
||||||
|
RANKED_FLEX_TT,
|
||||||
|
/// Ranked Teamfight Tactics games
|
||||||
|
RANKED_TFT,
|
||||||
|
/// Ranked Teamfight Tactics (Hyper Roll) games
|
||||||
|
RANKED_TFT_TURBO,
|
||||||
|
/// Ranked Teamfight Tactics (Double Up Workshop) games
|
||||||
|
/// Deprecated in patch 12.11 in favor of queueId 1160 (`RANKED_TFT_DOUBLE_UP`)
|
||||||
|
#[deprecated(note="Deprecated in patch 12.11 in favor of queueId 1160 (`RANKED_TFT_DOUBLE_UP`)")]
|
||||||
|
RANKED_TFT_PAIRS,
|
||||||
|
/// Ranked Teamfight Tactics (Double Up Workshop) games
|
||||||
|
RANKED_TFT_DOUBLE_UP,
|
||||||
|
/// 2v2v2v2 "Arena" games
|
||||||
|
CHERRY,
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_strum_unknown!(QueueType);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
|
@ -0,0 +1,52 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_as_ref() {
|
||||||
|
assert_eq!("RANKED_SOLO_5x5", QueueType::RANKED_SOLO_5x5.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_to_string() {
|
||||||
|
assert_eq!("RANKED_SOLO_5x5", QueueType::RANKED_SOLO_5x5.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_from_string() {
|
||||||
|
assert_eq!(QueueType::RANKED_SOLO_5x5, "RANKED_SOLO_5x5".into());
|
||||||
|
assert_eq!(QueueType::UNKNOWN("RANKED_MYSTERY_UNKNOWN".to_owned()), "RANKED_MYSTERY_UNKNOWN".into());
|
||||||
|
assert_eq!("RANKED_MYSTERY_UNKNOWN", QueueType::UNKNOWN("RANKED_MYSTERY_UNKNOWN".to_owned()).as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_serialize() {
|
||||||
|
assert_eq!(Some("\"RANKED_TFT_DOUBLE_UP\""),
|
||||||
|
serde_json::to_string(&QueueType::RANKED_TFT_DOUBLE_UP)
|
||||||
|
.ok().as_deref());
|
||||||
|
assert_eq!(Some("\"RANKED_MYSTERY_UNKNOWN\""),
|
||||||
|
serde_json::to_string(&QueueType::UNKNOWN("RANKED_MYSTERY_UNKNOWN".to_owned()))
|
||||||
|
.ok().as_deref());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Note: this test is often not run due to this condition below.
|
||||||
|
#[cfg(not(feature = "deny-unknown-enum-variants-strings"))]
|
||||||
|
fn check_deserialize() {
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
let dict: BTreeMap<usize, QueueType> = serde_json::from_str(
|
||||||
|
r#"{
|
||||||
|
"100": "RANKED_SOLO_5x5",
|
||||||
|
"200": "RANKED_TFT_TURBO",
|
||||||
|
"210": "RANKED_TFT_DOUBLE_UP",
|
||||||
|
"211": "RANKED_TFT_PAIRS",
|
||||||
|
"900": "RANKED_MYSTERY_UNKNOWN"
|
||||||
|
}"#
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(Some(&QueueType::RANKED_SOLO_5x5), dict.get(&100));
|
||||||
|
assert_eq!(Some(&QueueType::RANKED_TFT_TURBO), dict.get(&200));
|
||||||
|
assert_eq!(Some(&QueueType::RANKED_TFT_DOUBLE_UP), dict.get(&210));
|
||||||
|
assert_eq!(Some(&QueueType::RANKED_TFT_PAIRS), dict.get(&211));
|
||||||
|
assert_eq!(Some(&QueueType::UNKNOWN("RANKED_MYSTERY_UNKNOWN".to_owned())), dict.get(&900));
|
||||||
|
|
||||||
|
}
|
|
@ -4,10 +4,10 @@ use std::iter::Peekable;
|
||||||
|
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use super::{ Tier, Division };
|
use super::{Division, Tier};
|
||||||
|
|
||||||
/// (Tier, Division) tuple representing a rank.
|
/// (Tier, Division) tuple representing a rank.
|
||||||
pub type Rank = ( Tier, Division );
|
pub type Rank = (Tier, Division);
|
||||||
|
|
||||||
/// Iterator for iterating `(Tier, Division)` rank tuples.
|
/// Iterator for iterating `(Tier, Division)` rank tuples.
|
||||||
pub struct Iter {
|
pub struct Iter {
|
||||||
|
@ -20,8 +20,7 @@ impl Iterator for Iter {
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
// First find the tier (innermost loop).
|
// First find the tier (innermost loop).
|
||||||
// If none found, we go to next tier (in unwrap_or_else case).
|
// If none found, we go to next tier (in unwrap_or_else case).
|
||||||
let div = self.div_iter.next()
|
let div = self.div_iter.next().unwrap_or_else(|| {
|
||||||
.unwrap_or_else(|| {
|
|
||||||
// If no divisions available, go to next tier, reset the divisions, and return I.
|
// If no divisions available, go to next tier, reset the divisions, and return I.
|
||||||
self.tier_iter.next();
|
self.tier_iter.next();
|
||||||
self.div_iter = Division::iter();
|
self.div_iter = Division::iter();
|
||||||
|
@ -36,7 +35,7 @@ impl Iterator for Iter {
|
||||||
self.div_iter = Division::iter();
|
self.div_iter = Division::iter();
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(( tier, div ))
|
Some((tier, div))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,32 +58,31 @@ pub fn non_apex_iter() -> Iter {
|
||||||
tier_iter.next();
|
tier_iter.next();
|
||||||
}
|
}
|
||||||
Iter {
|
Iter {
|
||||||
tier_iter: tier_iter,
|
tier_iter,
|
||||||
div_iter: Division::iter(),
|
div_iter: Division::iter(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{ Tier, Division };
|
use super::{Division, Tier};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn iter() {
|
fn iter() {
|
||||||
let mut it = super::iter();
|
let mut it = super::iter();
|
||||||
assert_eq!(Some(( Tier::CHALLENGER, Division::I )), it.next());
|
assert_eq!(Some((Tier::CHALLENGER, Division::I)), it.next());
|
||||||
assert_eq!(Some(( Tier::GRANDMASTER, Division::I )), it.next());
|
assert_eq!(Some((Tier::GRANDMASTER, Division::I)), it.next());
|
||||||
assert_eq!(Some(( Tier::MASTER, Division::I )), it.next());
|
assert_eq!(Some((Tier::MASTER, Division::I)), it.next());
|
||||||
assert_eq!(Some(( Tier::DIAMOND, Division::I )), it.next());
|
assert_eq!(Some((Tier::DIAMOND, Division::I)), it.next());
|
||||||
assert_eq!(Some(( Tier::DIAMOND, Division::II )), it.next());
|
assert_eq!(Some((Tier::DIAMOND, Division::II)), it.next());
|
||||||
let mut last = None;
|
let mut last = None;
|
||||||
for next in &mut it {
|
for next in &mut it {
|
||||||
last = Some(next);
|
last = Some(next);
|
||||||
}
|
}
|
||||||
assert_eq!(Some(( Tier::IRON, Division::IV )), last);
|
assert_eq!(Some((Tier::IRON, Division::IV)), last);
|
||||||
assert_eq!(None, it.next());
|
assert_eq!(None, it.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn non_apex_iter() {
|
fn non_apex_iter() {
|
||||||
let mut it = super::non_apex_iter();
|
let mut it = super::non_apex_iter();
|
||||||
|
@ -92,12 +90,12 @@ mod tests {
|
||||||
assert_eq!(Some((Tier::DIAMOND, Division::II)), it.next());
|
assert_eq!(Some((Tier::DIAMOND, Division::II)), it.next());
|
||||||
assert_eq!(Some((Tier::DIAMOND, Division::III)), it.next());
|
assert_eq!(Some((Tier::DIAMOND, Division::III)), it.next());
|
||||||
assert_eq!(Some((Tier::DIAMOND, Division::IV)), it.next());
|
assert_eq!(Some((Tier::DIAMOND, Division::IV)), it.next());
|
||||||
|
assert_eq!(Some((Tier::EMERALD, Division::I)), it.next());
|
||||||
|
assert_eq!(Some((Tier::EMERALD, Division::II)), it.next());
|
||||||
|
assert_eq!(Some((Tier::EMERALD, Division::III)), it.next());
|
||||||
|
assert_eq!(Some((Tier::EMERALD, Division::IV)), it.next());
|
||||||
assert_eq!(Some((Tier::PLATINUM, Division::I)), it.next());
|
assert_eq!(Some((Tier::PLATINUM, Division::I)), it.next());
|
||||||
let mut last = None;
|
let last = it.last();
|
||||||
for next in &mut it {
|
|
||||||
last = Some(next);
|
|
||||||
}
|
|
||||||
assert_eq!(Some((Tier::IRON, Division::IV)), last);
|
assert_eq!(Some((Tier::IRON, Division::IV)), last);
|
||||||
assert_eq!(None, it.next());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,345 @@
|
||||||
|
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// ! //
|
||||||
|
// This file is automatically generated! //
|
||||||
|
// Do not directly edit! //
|
||||||
|
// //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||||
|
use strum_macros::{ EnumString, EnumIter, Display, IntoStaticStr };
|
||||||
|
|
||||||
|
/// Regional routes, used in tournament services, Legends of Runeterra (LoR), and other some endpoints.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
|
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum RegionalRoute {
|
||||||
|
/// North and South America.
|
||||||
|
///
|
||||||
|
/// `1` (riotapi-schema ID/repr)
|
||||||
|
AMERICAS = 1,
|
||||||
|
|
||||||
|
/// Asia, used for LoL matches (`match-v5`) and TFT matches (`tft-match-v1`).
|
||||||
|
///
|
||||||
|
/// `2` (riotapi-schema ID/repr)
|
||||||
|
ASIA = 2,
|
||||||
|
|
||||||
|
/// Europe.
|
||||||
|
///
|
||||||
|
/// `3` (riotapi-schema ID/repr)
|
||||||
|
EUROPE = 3,
|
||||||
|
|
||||||
|
/// South East Asia, used for LoR, LoL matches (`match-v5`), and TFT matches (`tft-match-v1`).
|
||||||
|
///
|
||||||
|
/// `4` (riotapi-schema ID/repr)
|
||||||
|
SEA = 4,
|
||||||
|
|
||||||
|
/// Asia-Pacific, deprecated, for some old matches in `lor-match-v1`.
|
||||||
|
///
|
||||||
|
/// `10` (riotapi-schema ID/repr)
|
||||||
|
#[deprecated]
|
||||||
|
APAC = 10,
|
||||||
|
|
||||||
|
/// Special esports platform for `account-v1`. Do not confuse with the `esports` Valorant platform route.
|
||||||
|
///
|
||||||
|
/// `11` (riotapi-schema ID/repr)
|
||||||
|
ESPORTS = 11,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Platform routes for League of Legends (LoL), Teamfight Tactics (TFT), and Legends of Runeterra (LoR).
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
|
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
// Note: strum(serialize = ...) actually specifies extra DEserialization values.
|
||||||
|
pub enum PlatformRoute {
|
||||||
|
/// Brazil.
|
||||||
|
///
|
||||||
|
/// `16` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="BR1", serialize="BR")]
|
||||||
|
BR1 = 16,
|
||||||
|
|
||||||
|
/// Europe, Northeast.
|
||||||
|
///
|
||||||
|
/// `17` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="EUN1", serialize="EUNE")]
|
||||||
|
EUN1 = 17,
|
||||||
|
|
||||||
|
/// Europe, West.
|
||||||
|
///
|
||||||
|
/// `18` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="EUW1", serialize="EUW")]
|
||||||
|
EUW1 = 18,
|
||||||
|
|
||||||
|
/// Japan.
|
||||||
|
///
|
||||||
|
/// `19` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="JP1", serialize="JP")]
|
||||||
|
JP1 = 19,
|
||||||
|
|
||||||
|
/// Korea.
|
||||||
|
///
|
||||||
|
/// `20` (riotapi-schema ID/repr)
|
||||||
|
KR = 20,
|
||||||
|
|
||||||
|
/// Latin America, North.
|
||||||
|
///
|
||||||
|
/// `21` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="LA1", serialize="LAN")]
|
||||||
|
LA1 = 21,
|
||||||
|
|
||||||
|
/// Latin America, South.
|
||||||
|
///
|
||||||
|
/// `22` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="LA2", serialize="LAS")]
|
||||||
|
LA2 = 22,
|
||||||
|
|
||||||
|
/// North America.
|
||||||
|
///
|
||||||
|
/// `23` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="NA1", serialize="NA")]
|
||||||
|
NA1 = 23,
|
||||||
|
|
||||||
|
/// Oceania.
|
||||||
|
///
|
||||||
|
/// `24` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="OC1", serialize="OCE")]
|
||||||
|
OC1 = 24,
|
||||||
|
|
||||||
|
/// Philippines
|
||||||
|
///
|
||||||
|
/// `32` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="PH2", serialize="PH")]
|
||||||
|
PH2 = 32,
|
||||||
|
|
||||||
|
/// Russia
|
||||||
|
///
|
||||||
|
/// `25` (riotapi-schema ID/repr)
|
||||||
|
RU = 25,
|
||||||
|
|
||||||
|
/// Singapore
|
||||||
|
///
|
||||||
|
/// `33` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="SG2", serialize="SG")]
|
||||||
|
SG2 = 33,
|
||||||
|
|
||||||
|
/// Thailand
|
||||||
|
///
|
||||||
|
/// `34` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="TH2", serialize="TH")]
|
||||||
|
TH2 = 34,
|
||||||
|
|
||||||
|
/// Turkey
|
||||||
|
///
|
||||||
|
/// `26` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="TR1", serialize="TR")]
|
||||||
|
TR1 = 26,
|
||||||
|
|
||||||
|
/// Taiwan
|
||||||
|
///
|
||||||
|
/// `35` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="TW2", serialize="TW")]
|
||||||
|
TW2 = 35,
|
||||||
|
|
||||||
|
/// Vietnam
|
||||||
|
///
|
||||||
|
/// `36` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="VN2", serialize="VN")]
|
||||||
|
VN2 = 36,
|
||||||
|
|
||||||
|
/// Public Beta Environment, special beta testing platform. Located in North America.
|
||||||
|
///
|
||||||
|
/// `31` (riotapi-schema ID/repr)
|
||||||
|
#[strum(to_string="PBE1", serialize="PBE")]
|
||||||
|
PBE1 = 31,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlatformRoute {
|
||||||
|
/// Converts this [`PlatformRoute`] into its corresponding
|
||||||
|
/// [`RegionalRoute`] for LoL and TFT match endpoints.
|
||||||
|
/// For example, [`match-v5`](crate::endpoints::MatchV5).
|
||||||
|
pub fn to_regional(self) -> RegionalRoute {
|
||||||
|
match self {
|
||||||
|
Self::BR1 => RegionalRoute::AMERICAS,
|
||||||
|
Self::EUN1 => RegionalRoute::EUROPE,
|
||||||
|
Self::EUW1 => RegionalRoute::EUROPE,
|
||||||
|
Self::JP1 => RegionalRoute::ASIA,
|
||||||
|
Self::KR => RegionalRoute::ASIA,
|
||||||
|
Self::LA1 => RegionalRoute::AMERICAS,
|
||||||
|
Self::LA2 => RegionalRoute::AMERICAS,
|
||||||
|
Self::NA1 => RegionalRoute::AMERICAS,
|
||||||
|
Self::OC1 => RegionalRoute::SEA,
|
||||||
|
Self::PH2 => RegionalRoute::SEA,
|
||||||
|
Self::RU => RegionalRoute::EUROPE,
|
||||||
|
Self::SG2 => RegionalRoute::SEA,
|
||||||
|
Self::TH2 => RegionalRoute::SEA,
|
||||||
|
Self::TR1 => RegionalRoute::EUROPE,
|
||||||
|
Self::TW2 => RegionalRoute::SEA,
|
||||||
|
Self::VN2 => RegionalRoute::SEA,
|
||||||
|
Self::PBE1 => RegionalRoute::AMERICAS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this [`PlatformRoute`] into its corresponding
|
||||||
|
/// [`RegionalRoute`] for LoR endpoints.
|
||||||
|
/// For example, [`lor-match-v1`](crate::endpoints::LorMatchV1).
|
||||||
|
pub fn to_regional_lor(self) -> RegionalRoute {
|
||||||
|
match self {
|
||||||
|
Self::BR1 => RegionalRoute::AMERICAS,
|
||||||
|
Self::EUN1 => RegionalRoute::EUROPE,
|
||||||
|
Self::EUW1 => RegionalRoute::EUROPE,
|
||||||
|
Self::JP1 => RegionalRoute::ASIA,
|
||||||
|
Self::KR => RegionalRoute::ASIA,
|
||||||
|
Self::LA1 => RegionalRoute::AMERICAS,
|
||||||
|
Self::LA2 => RegionalRoute::AMERICAS,
|
||||||
|
Self::NA1 => RegionalRoute::AMERICAS,
|
||||||
|
Self::OC1 => RegionalRoute::SEA,
|
||||||
|
Self::PH2 => RegionalRoute::SEA,
|
||||||
|
Self::RU => RegionalRoute::SEA,
|
||||||
|
Self::SG2 => RegionalRoute::SEA,
|
||||||
|
Self::TH2 => RegionalRoute::SEA,
|
||||||
|
Self::TR1 => RegionalRoute::SEA,
|
||||||
|
Self::TW2 => RegionalRoute::SEA,
|
||||||
|
Self::VN2 => RegionalRoute::SEA,
|
||||||
|
Self::PBE1 => RegionalRoute::AMERICAS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used in the LoL Tournament API. Specifically
|
||||||
|
/// [`tournament-stub-v4.registerProviderData`](crate::endpoints::TournamentStubV4::register_provider_data)
|
||||||
|
/// and [`tournament-v4.registerProviderData`](crate::endpoints::TournamentV4::register_provider_data).
|
||||||
|
pub fn to_tournament_region(self) -> Option<TournamentRegion> {
|
||||||
|
match self {
|
||||||
|
Self::BR1 => Some(TournamentRegion::BR),
|
||||||
|
Self::EUN1 => Some(TournamentRegion::EUNE),
|
||||||
|
Self::EUW1 => Some(TournamentRegion::EUW),
|
||||||
|
Self::JP1 => Some(TournamentRegion::JP),
|
||||||
|
Self::LA1 => Some(TournamentRegion::LAN),
|
||||||
|
Self::LA2 => Some(TournamentRegion::LAS),
|
||||||
|
Self::NA1 => Some(TournamentRegion::NA),
|
||||||
|
Self::OC1 => Some(TournamentRegion::OCE),
|
||||||
|
Self::TR1 => Some(TournamentRegion::TR),
|
||||||
|
Self::PBE1 => Some(TournamentRegion::PBE),
|
||||||
|
_other => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the slightly more human-friendly alternate name for this `PlatformRoute`. Specifically
|
||||||
|
/// excludes any trailing numbers and appends extra N(orth), S(outh), E(ast), and/or W(est)
|
||||||
|
/// suffixes to some names. Some of these are old region names which are often still used as
|
||||||
|
/// user-facing names, e.g. on op.gg.
|
||||||
|
///
|
||||||
|
/// Note these strings *are* handled by the `FromStr` implementation, if you wish to parse them
|
||||||
|
/// back into `PlatformRoute`s.
|
||||||
|
pub fn as_region_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::BR1 => "BR",
|
||||||
|
Self::EUN1 => "EUNE",
|
||||||
|
Self::EUW1 => "EUW",
|
||||||
|
Self::JP1 => "JP",
|
||||||
|
Self::LA1 => "LAN",
|
||||||
|
Self::LA2 => "LAS",
|
||||||
|
Self::NA1 => "NA",
|
||||||
|
Self::OC1 => "OCE",
|
||||||
|
Self::PH2 => "PH",
|
||||||
|
Self::SG2 => "SG",
|
||||||
|
Self::TH2 => "TH",
|
||||||
|
Self::TR1 => "TR",
|
||||||
|
Self::TW2 => "TW",
|
||||||
|
Self::VN2 => "VN",
|
||||||
|
Self::PBE1 => "PBE",
|
||||||
|
other => other.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Platform routes for Valorant.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
|
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ValPlatformRoute {
|
||||||
|
/// Asia-Pacific.
|
||||||
|
///
|
||||||
|
/// `64` (riotapi-schema ID/repr)
|
||||||
|
AP = 64,
|
||||||
|
|
||||||
|
/// Brazil.
|
||||||
|
///
|
||||||
|
/// `65` (riotapi-schema ID/repr)
|
||||||
|
BR = 65,
|
||||||
|
|
||||||
|
/// Europe.
|
||||||
|
///
|
||||||
|
/// `66` (riotapi-schema ID/repr)
|
||||||
|
EU = 66,
|
||||||
|
|
||||||
|
/// Korea.
|
||||||
|
///
|
||||||
|
/// `70` (riotapi-schema ID/repr)
|
||||||
|
KR = 70,
|
||||||
|
|
||||||
|
/// Latin America.
|
||||||
|
///
|
||||||
|
/// `68` (riotapi-schema ID/repr)
|
||||||
|
LATAM = 68,
|
||||||
|
|
||||||
|
/// North America.
|
||||||
|
///
|
||||||
|
/// `69` (riotapi-schema ID/repr)
|
||||||
|
NA = 69,
|
||||||
|
|
||||||
|
/// Special esports platform.
|
||||||
|
///
|
||||||
|
/// `95` (riotapi-schema ID/repr)
|
||||||
|
ESPORTS = 95,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tournament regions for League of Legends (LoL) used in
|
||||||
|
/// [`tournament-stub-v4.registerProviderData`](crate::endpoints::TournamentStubV4::register_provider_data)
|
||||||
|
/// and [`tournament-v4.registerProviderData`](crate::endpoints::TournamentV4::register_provider_data).
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
|
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
// Note: strum(serialize = ...) actually specifies extra DEserialization values.
|
||||||
|
pub enum TournamentRegion {
|
||||||
|
/// Brazil.
|
||||||
|
BR = 16,
|
||||||
|
/// Europe, Northeast.
|
||||||
|
EUNE = 17,
|
||||||
|
/// Europe, West.
|
||||||
|
EUW = 18,
|
||||||
|
/// Japan.
|
||||||
|
JP = 19,
|
||||||
|
/// Latin America, North.
|
||||||
|
LAN = 21,
|
||||||
|
/// Latin America, South.
|
||||||
|
LAS = 22,
|
||||||
|
/// North America.
|
||||||
|
NA = 23,
|
||||||
|
/// Oceania.
|
||||||
|
OCE = 24,
|
||||||
|
/// Turkey
|
||||||
|
TR = 26,
|
||||||
|
/// Public Beta Environment, special beta testing platform. Located in North America.
|
||||||
|
PBE = 31,
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
use super::{PlatformRoute, RegionalRoute, ValPlatformRoute};
|
||||||
|
|
||||||
|
/// Utility enum containing all routing variants.
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Route {
|
||||||
|
/// Sub-variant for [`RegionalRoute`]s.
|
||||||
|
Regional(RegionalRoute),
|
||||||
|
/// Sub-variant for [`PlatformRoute`]s.
|
||||||
|
Platform(PlatformRoute),
|
||||||
|
/// Sub-variant for [`ValPlatformRoute`]s.
|
||||||
|
ValPlatform(ValPlatformRoute),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Route> for &'static str {
|
||||||
|
fn from(route: Route) -> Self {
|
||||||
|
match route {
|
||||||
|
Route::Regional(r) => r.into(),
|
||||||
|
Route::Platform(r) => r.into(),
|
||||||
|
Route::ValPlatform(r) => r.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Route> for u8 {
|
||||||
|
fn from(route: Route) -> Self {
|
||||||
|
match route {
|
||||||
|
Route::Regional(r) => r.into(),
|
||||||
|
Route::Platform(r) => r.into(),
|
||||||
|
Route::ValPlatform(r) => r.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl num_enum::TryFromPrimitive for Route {
|
||||||
|
type Primitive = u8;
|
||||||
|
|
||||||
|
const NAME: &'static str = stringify!(Route);
|
||||||
|
|
||||||
|
fn try_from_primitive(
|
||||||
|
number: Self::Primitive,
|
||||||
|
) -> Result<Self, num_enum::TryFromPrimitiveError<Self>> {
|
||||||
|
RegionalRoute::try_from_primitive(number)
|
||||||
|
.map(Route::Regional)
|
||||||
|
.or_else(|_| PlatformRoute::try_from_primitive(number).map(Route::Platform))
|
||||||
|
.or_else(|_| ValPlatformRoute::try_from_primitive(number).map(Route::ValPlatform))
|
||||||
|
.map_err(|_| num_enum::TryFromPrimitiveError { number })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::TryFrom<u8> for Route {
|
||||||
|
type Error = num_enum::TryFromPrimitiveError<Self>;
|
||||||
|
fn try_from(number: u8) -> Result<Self, num_enum::TryFromPrimitiveError<Self>> {
|
||||||
|
<Self as num_enum::TryFromPrimitive>::try_from_primitive(number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Route {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Regional(r) => r.fmt(f),
|
||||||
|
Self::Platform(r) => r.fmt(f),
|
||||||
|
Self::ValPlatform(r) => r.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for Route {
|
||||||
|
type Err = strum::ParseError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
RegionalRoute::from_str(s)
|
||||||
|
.map(Self::Regional)
|
||||||
|
.or_else(|_| PlatformRoute::from_str(s).map(Self::Platform))
|
||||||
|
.or_else(|_| ValPlatformRoute::from_str(s).map(Self::ValPlatform))
|
||||||
|
.map_err(|_| strum::ParseError::VariantNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Route {
|
||||||
|
/// Returns an iterator over all routes. Starts with [`Self::Regional`],
|
||||||
|
/// then [`Self::Platform`], and finally [`Self::ValPlatform`].
|
||||||
|
pub fn iter() -> impl Iterator<Item = Self> {
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
let regional = RegionalRoute::iter().map(Self::Regional);
|
||||||
|
let platform = PlatformRoute::iter().map(Self::Platform);
|
||||||
|
let val_platform = ValPlatformRoute::iter().map(Self::ValPlatform);
|
||||||
|
|
||||||
|
regional.chain(platform).chain(val_platform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_route_tostring() {
|
||||||
|
assert_eq!(
|
||||||
|
"AMERICAS",
|
||||||
|
Into::<&'static str>::into(Route::Regional(RegionalRoute::AMERICAS))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"KR",
|
||||||
|
Into::<&'static str>::into(Route::Platform(PlatformRoute::KR))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"KR",
|
||||||
|
Into::<&'static str>::into(Route::ValPlatform(ValPlatformRoute::KR))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_route_iter() {
|
||||||
|
for (i, route) in Route::iter().enumerate() {
|
||||||
|
println!("{:>2} {:<10} {:>3}", i, route, u8::from(route));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_route_tryfrom() {
|
||||||
|
for x in u8::MIN..=u8::MAX {
|
||||||
|
if let Ok(route) = std::convert::TryInto::<Route>::try_into(x) {
|
||||||
|
println!("{:>3} {:<8}", x, route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_regional_tostring() {
|
||||||
|
assert_eq!("AMERICAS", RegionalRoute::AMERICAS.to_string());
|
||||||
|
assert_eq!("SEA", RegionalRoute::SEA.to_string());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"AMERICAS",
|
||||||
|
Into::<&'static str>::into(RegionalRoute::AMERICAS)
|
||||||
|
);
|
||||||
|
assert_eq!("SEA", Into::<&'static str>::into(RegionalRoute::SEA));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_regional_parse() {
|
||||||
|
assert_eq!(Ok(RegionalRoute::AMERICAS), "AMERICAS".parse());
|
||||||
|
assert_eq!(Ok(RegionalRoute::SEA), "SEA".parse());
|
||||||
|
assert!("NA".parse::<RegionalRoute>().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_platform_tostring() {
|
||||||
|
assert_eq!("BR1", PlatformRoute::BR1.to_string());
|
||||||
|
assert_eq!("KR", PlatformRoute::KR.to_string());
|
||||||
|
|
||||||
|
assert_eq!("BR1", Into::<&'static str>::into(PlatformRoute::BR1));
|
||||||
|
assert_eq!("KR", Into::<&'static str>::into(PlatformRoute::KR));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_platform_parse() {
|
||||||
|
assert_eq!(Ok(PlatformRoute::BR1), "BR1".parse());
|
||||||
|
assert_eq!(Ok(PlatformRoute::KR), "KR".parse());
|
||||||
|
assert_eq!(Ok(PlatformRoute::JP1), "JP1".parse());
|
||||||
|
assert_eq!(Ok(PlatformRoute::JP1), "JP".parse());
|
||||||
|
assert_eq!(Ok(PlatformRoute::NA1), "NA1".parse());
|
||||||
|
assert_eq!(Ok(PlatformRoute::NA1), "NA".parse());
|
||||||
|
assert!("LA".parse::<PlatformRoute>().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valplatform_tostring() {
|
||||||
|
assert_eq!("AP", ValPlatformRoute::AP.to_string());
|
||||||
|
assert_eq!("KR", ValPlatformRoute::KR.to_string());
|
||||||
|
assert_eq!("ESPORTS", ValPlatformRoute::ESPORTS.to_string());
|
||||||
|
|
||||||
|
assert_eq!("AP", Into::<&'static str>::into(ValPlatformRoute::AP));
|
||||||
|
assert_eq!("KR", Into::<&'static str>::into(ValPlatformRoute::KR));
|
||||||
|
assert_eq!(
|
||||||
|
"ESPORTS",
|
||||||
|
Into::<&'static str>::into(ValPlatformRoute::ESPORTS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valplatform_parse() {
|
||||||
|
assert_eq!(Ok(ValPlatformRoute::AP), "AP".parse());
|
||||||
|
assert_eq!(Ok(ValPlatformRoute::KR), "KR".parse());
|
||||||
|
assert_eq!(Ok(ValPlatformRoute::ESPORTS), "ESPORTS".parse());
|
||||||
|
assert!("SEA".parse::<ValPlatformRoute>().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tournament_region_serde() {
|
||||||
|
use crate::consts::TournamentRegion;
|
||||||
|
let json = serde_json::to_string(&TournamentRegion::EUNE);
|
||||||
|
assert!(json.is_ok());
|
||||||
|
assert_eq!("\"EUNE\"", &*json.unwrap());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// ! //
|
||||||
|
// This file is automatically generated! //
|
||||||
|
// Do not directly edit! //
|
||||||
|
// //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
newtype_enum! {
|
||||||
|
/// A League of Legends season for competitive matchmaking.
|
||||||
|
pub newtype_enum Season(u8) {
|
||||||
|
/// `0`.
|
||||||
|
PRESEASON_3 = 0,
|
||||||
|
/// `1`.
|
||||||
|
SEASON_3 = 1,
|
||||||
|
/// `2`.
|
||||||
|
PRESEASON_2014 = 2,
|
||||||
|
/// `3`.
|
||||||
|
SEASON_2014 = 3,
|
||||||
|
/// `4`.
|
||||||
|
PRESEASON_2015 = 4,
|
||||||
|
/// `5`.
|
||||||
|
SEASON_2015 = 5,
|
||||||
|
/// `6`.
|
||||||
|
PRESEASON_2016 = 6,
|
||||||
|
/// `7`.
|
||||||
|
SEASON_2016 = 7,
|
||||||
|
/// `8`.
|
||||||
|
PRESEASON_2017 = 8,
|
||||||
|
/// `9`.
|
||||||
|
SEASON_2017 = 9,
|
||||||
|
/// `10`.
|
||||||
|
PRESEASON_2018 = 10,
|
||||||
|
/// `11`.
|
||||||
|
SEASON_2018 = 11,
|
||||||
|
/// `12`.
|
||||||
|
PRESEASON_2019 = 12,
|
||||||
|
/// `13`.
|
||||||
|
SEASON_2019 = 13,
|
||||||
|
/// `14`.
|
||||||
|
PRESEASON_2020 = 14,
|
||||||
|
/// `15`.
|
||||||
|
SEASON_2020 = 15,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
|
|
||||||
|
/// League of Legends team.
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
Hash,
|
||||||
|
Ord,
|
||||||
|
PartialOrd,
|
||||||
|
Serialize_repr,
|
||||||
|
Deserialize_repr,
|
||||||
|
IntoPrimitive,
|
||||||
|
TryFromPrimitive,
|
||||||
|
)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum Team {
|
||||||
|
/// Team ID zero for 2v2v2v2 Arena `CHERRY` game mode. (TODO: SUBJECT TO CHANGE?)
|
||||||
|
ZERO = 0,
|
||||||
|
|
||||||
|
/// Blue team (bottom left on Summoner's Rift).
|
||||||
|
BLUE = 100,
|
||||||
|
/// Red team (top right on Summoner's Rift).
|
||||||
|
RED = 200,
|
||||||
|
|
||||||
|
/// "killerTeamId" when Baron Nashor spawns and kills Rift Herald.
|
||||||
|
OTHER = 300,
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
|
||||||
|
|
||||||
/// LoL and TFT ranked tiers, such as gold, diamond, challenger, etc.
|
/// LoL and TFT ranked tiers, such as gold, diamond, challenger, etc.
|
||||||
///
|
///
|
||||||
|
@ -9,10 +10,24 @@ use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
/// Repr'd as arbitrary `u8` values.
|
/// Repr'd as arbitrary `u8` values.
|
||||||
///
|
///
|
||||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator).
|
/// Implements [IntoEnumIterator](super::IntoEnumIterator).
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(
|
||||||
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
Debug,
|
||||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
Copy,
|
||||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
Clone,
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
Hash,
|
||||||
|
PartialOrd,
|
||||||
|
Ord,
|
||||||
|
IntoPrimitive,
|
||||||
|
TryFromPrimitive,
|
||||||
|
EnumString,
|
||||||
|
Display,
|
||||||
|
AsRefStr,
|
||||||
|
IntoStaticStr,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Tier {
|
pub enum Tier {
|
||||||
/// Challenger, the highest tier, an apex tier. Repr: `220_u8`.
|
/// Challenger, the highest tier, an apex tier. Repr: `220_u8`.
|
||||||
|
@ -23,6 +38,8 @@ pub enum Tier {
|
||||||
MASTER = 180,
|
MASTER = 180,
|
||||||
/// Diamond, the higest non-apex tier. Repr: `140_u8`.
|
/// Diamond, the higest non-apex tier. Repr: `140_u8`.
|
||||||
DIAMOND = 140,
|
DIAMOND = 140,
|
||||||
|
/// Emerald. Added in 2023. Repr: `130_u8`.
|
||||||
|
EMERALD = 130,
|
||||||
/// Platinum. Repr: `120_u8`.
|
/// Platinum. Repr: `120_u8`.
|
||||||
PLATINUM = 120,
|
PLATINUM = 120,
|
||||||
/// Gold. Repr: `100_u8`.
|
/// Gold. Repr: `100_u8`.
|
||||||
|
@ -35,14 +52,14 @@ pub enum Tier {
|
||||||
IRON = 40,
|
IRON = 40,
|
||||||
|
|
||||||
/// Unranked, no tier. Repr: `0_u8`.
|
/// Unranked, no tier. Repr: `0_u8`.
|
||||||
|
/// Also deserializes from "NONE" returned by `lol-challenges-v1.getChallengePercentiles`.
|
||||||
|
#[serde(alias = "NONE")]
|
||||||
UNRANKED = 0,
|
UNRANKED = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
serde_string!(Tier);
|
|
||||||
|
|
||||||
impl Tier {
|
impl Tier {
|
||||||
/// If this tier is an apex tier: master, grandmaster, or challenger.
|
/// If this tier is an apex tier: [`Self::MASTER`], [`Self::GRANDMASTER`],
|
||||||
/// Returns false for unranked.
|
/// or [`Self::CHALLENGER`]. Returns false for [`Self::UNRANKED`].
|
||||||
///
|
///
|
||||||
/// These tiers are NOT queryable by LeagueV4Endpoints::get_league_entries(...).
|
/// These tiers are NOT queryable by LeagueV4Endpoints::get_league_entries(...).
|
||||||
pub const fn is_apex(self) -> bool {
|
pub const fn is_apex(self) -> bool {
|
||||||
|
@ -53,7 +70,7 @@ impl Tier {
|
||||||
/// If this tier is a "standard" tier: iron through diamond.
|
/// If this tier is a "standard" tier: iron through diamond.
|
||||||
/// Returns false for unranked.
|
/// Returns false for unranked.
|
||||||
///
|
///
|
||||||
/// ONLY these tiers are queryable by LeagueV4Endpoints::get_league_entries(...).
|
/// ONLY these tiers are queryable by [`LeagueV4::get_league_entries(...)`](crate::endpoints::LeagueV4::get_league_entries).
|
||||||
pub fn is_standard(self) -> bool {
|
pub fn is_standard(self) -> bool {
|
||||||
// Casts needed for const.
|
// Casts needed for const.
|
||||||
((Self::UNRANKED as u8) < (self as u8)) && ((self as u8) < (Self::MASTER as u8))
|
((Self::UNRANKED as u8) < (self as u8)) && ((self as u8) < (Self::MASTER as u8))
|
||||||
|
@ -75,7 +92,11 @@ impl Tier {
|
||||||
|
|
||||||
/// Converts UNRANKED to None and all ranked tiers to Some(...).
|
/// Converts UNRANKED to None and all ranked tiers to Some(...).
|
||||||
pub fn to_ranked(self) -> Option<Self> {
|
pub fn to_ranked(self) -> Option<Self> {
|
||||||
if self.is_unranked() { None } else { Some(self) }
|
if self.is_unranked() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,10 +107,19 @@ impl IntoEnumIterator for Tier {
|
||||||
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
||||||
fn iter() -> Self::Iterator {
|
fn iter() -> Self::Iterator {
|
||||||
[
|
[
|
||||||
Self::CHALLENGER, Self::GRANDMASTER, Self::MASTER,
|
Self::CHALLENGER,
|
||||||
Self::DIAMOND, Self::PLATINUM, Self::GOLD,
|
Self::GRANDMASTER,
|
||||||
Self::SILVER, Self::BRONZE, Self::IRON
|
Self::MASTER,
|
||||||
].iter().copied()
|
Self::DIAMOND,
|
||||||
|
Self::EMERALD,
|
||||||
|
Self::PLATINUM,
|
||||||
|
Self::GOLD,
|
||||||
|
Self::SILVER,
|
||||||
|
Self::BRONZE,
|
||||||
|
Self::IRON,
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,6 +191,7 @@ mod tests {
|
||||||
iter.next();
|
iter.next();
|
||||||
iter.next();
|
iter.next();
|
||||||
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
||||||
|
assert_eq!(Some(Tier::EMERALD), iter.next());
|
||||||
iter.next();
|
iter.next();
|
||||||
iter.next();
|
iter.next();
|
||||||
iter.next();
|
iter.next();
|
||||||
|
@ -175,6 +206,7 @@ mod tests {
|
||||||
iter.next();
|
iter.next();
|
||||||
iter.next();
|
iter.next();
|
||||||
iter.next();
|
iter.next();
|
||||||
|
assert_eq!(Some(Tier::EMERALD), iter.next());
|
||||||
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
||||||
iter.next();
|
iter.next();
|
||||||
iter.next();
|
iter.next();
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,6 @@
|
||||||
use std::error::Error as StdError;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use reqwest::{ Error, Response, StatusCode };
|
use reqwest::{Error, Response, StatusCode};
|
||||||
|
|
||||||
/// Result containing RiotApiError on failure.
|
/// Result containing RiotApiError on failure.
|
||||||
pub type Result<T> = std::result::Result<T, RiotApiError>;
|
pub type Result<T> = std::result::Result<T, RiotApiError>;
|
||||||
|
@ -18,12 +17,17 @@ pub struct RiotApiError {
|
||||||
status_code: Option<StatusCode>,
|
status_code: Option<StatusCode>,
|
||||||
}
|
}
|
||||||
impl RiotApiError {
|
impl RiotApiError {
|
||||||
pub(crate) fn new(reqwest_error: Error, retries: u8, response: Option<Response>, status_code: Option<StatusCode>) -> Self {
|
pub(crate) fn new(
|
||||||
|
reqwest_error: Error,
|
||||||
|
retries: u8,
|
||||||
|
response: Option<Response>,
|
||||||
|
status_code: Option<StatusCode>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
reqwest_error: reqwest_error,
|
reqwest_error,
|
||||||
retries: retries,
|
retries,
|
||||||
response: response,
|
response,
|
||||||
status_code: status_code,
|
status_code,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// The reqwest::Error for the final failed request.
|
/// The reqwest::Error for the final failed request.
|
||||||
|
@ -35,11 +39,17 @@ impl RiotApiError {
|
||||||
self.retries
|
self.retries
|
||||||
}
|
}
|
||||||
/// The failed response.
|
/// The failed response.
|
||||||
/// `Some(reqwest::Response)` if the request was sent and failed.
|
/// `Some(&reqwest::Response)` if the request was sent and failed.
|
||||||
/// `None` if the request was not sent, OR if parsing the response JSON failed.
|
/// `None` if the request was not sent, OR if parsing the response JSON failed.
|
||||||
pub fn response(&self) -> Option<&Response> {
|
pub fn response(&self) -> Option<&Response> {
|
||||||
self.response.as_ref()
|
self.response.as_ref()
|
||||||
}
|
}
|
||||||
|
/// The failed response.
|
||||||
|
/// `Some(reqwest::Response)` if the request was sent and failed.
|
||||||
|
/// `None` if the request was not sent, OR if parsing the response JSON failed.
|
||||||
|
pub fn take_response(&mut self) -> Option<Response> {
|
||||||
|
self.response.take()
|
||||||
|
}
|
||||||
/// The failed response's HTTP status code.
|
/// The failed response's HTTP status code.
|
||||||
/// `Some(reqwest::StatusCode)` if the request was sent and failed, OR if parsing the response JSON failed.
|
/// `Some(reqwest::StatusCode)` if the request was sent and failed, OR if parsing the response JSON failed.
|
||||||
/// `None` if the request was not sent.
|
/// `None` if the request was not sent.
|
||||||
|
@ -52,8 +62,8 @@ impl fmt::Display for RiotApiError {
|
||||||
write!(f, "{:#?}", self)
|
write!(f, "{:#?}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl StdError for RiotApiError {
|
impl std::error::Error for RiotApiError {
|
||||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
Some(&self.reqwest_error)
|
Some(&self.reqwest_error)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,206 @@
|
||||||
|
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// ! //
|
||||||
|
// This file is automatically generated! //
|
||||||
|
// Do not directly edit! //
|
||||||
|
// //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
//! <h1 align="center">
|
||||||
|
//! Riven<br>
|
||||||
|
//! </h1>
|
||||||
|
//! <p align="center">
|
||||||
|
//! <a href="https://github.com/MingweiSamuel/Riven/"><img src="https://cdn.communitydragon.org/latest/champion/Riven/square" width="20" height="20" alt="Riven Github"></a>
|
||||||
|
//! <a href="https://crates.io/crates/riven"><img src="https://img.shields.io/crates/v/riven?style=flat-square&logo=rust" alt="Crates.io"></a>
|
||||||
|
//! <a href="https://docs.rs/riven/"><img src="https://img.shields.io/badge/docs.rs-Riven-blue?style=flat-square&logo=read-the-docs&logoColor=white" alt="Docs.rs"></a>
|
||||||
|
//! <!--<a href="https://travis-ci.com/MingweiSamuel/Riven"><img src="https://img.shields.io/travis/com/mingweisamuel/riven?style=flat-square" alt="Travis CI"></a>-->
|
||||||
|
//! <a href="https://github.com/rust-secure-code/safety-dance/"><img src="https://img.shields.io/badge/unsafe-forbidden-green.svg?style=flat-square" alt="unsafe forbidden"></a>
|
||||||
|
//! </p>
|
||||||
|
//!
|
||||||
|
//! Rust Library for the [Riot Games API](https://developer.riotgames.com/).
|
||||||
|
//!
|
||||||
|
//! Riven's goals are _speed_, _reliability_, and _maintainability_. Riven handles rate limits and large requests with ease.
|
||||||
|
//! Data structs and endpoints are automatically generated from the
|
||||||
|
//! [Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)).
|
||||||
|
//!
|
||||||
|
//! # Design
|
||||||
|
//!
|
||||||
|
//! * Fast, asynchronous, thread-safe.
|
||||||
|
//! * Automatically retries failed requests.
|
||||||
|
//! * Supports all endpoints, kept up-to-date using [riotapi-schema](https://github.com/MingweiSamuel/riotapi-schema).
|
||||||
|
//!
|
||||||
|
//! # Usage
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use riven::RiotApi;
|
||||||
|
//! use riven::consts::PlatformRoute;
|
||||||
|
//!
|
||||||
|
//! // Enter tokio async runtime.
|
||||||
|
//! let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
//! rt.block_on(async {
|
||||||
|
//! // Create RiotApi instance from key string.
|
||||||
|
//! let api_key = std::env!("RGAPI_KEY"); // "RGAPI-01234567-89ab-cdef-0123-456789abcdef";
|
||||||
|
//! let riot_api = RiotApi::new(api_key);
|
||||||
|
//!
|
||||||
|
//! // Get summoner data.
|
||||||
|
//! let summoner = riot_api.summoner_v4()
|
||||||
|
//! .get_by_summoner_name(PlatformRoute::NA1, "잘 못").await
|
||||||
|
//! .expect("Get summoner failed.")
|
||||||
|
//! .expect("There is no summoner with that name.");
|
||||||
|
//!
|
||||||
|
//! // Print summoner name.
|
||||||
|
//! println!("{} Champion Masteries:", summoner.name);
|
||||||
|
//!
|
||||||
|
//! // Get champion mastery data.
|
||||||
|
//! let masteries = riot_api.champion_mastery_v4()
|
||||||
|
//! .get_all_champion_masteries_by_puuid(PlatformRoute::NA1, &summoner.puuid).await
|
||||||
|
//! .expect("Get champion masteries failed.");
|
||||||
|
//!
|
||||||
|
//! // Print champion masteries.
|
||||||
|
//! for (i, mastery) in masteries.iter().take(10).enumerate() {
|
||||||
|
//! println!("{: >2}) {: <9} {: >7} ({})", i + 1,
|
||||||
|
//! mastery.champion_id.name().unwrap_or("UNKNOWN"),
|
||||||
|
//! mastery.champion_points, mastery.champion_level);
|
||||||
|
//! }
|
||||||
|
//! });
|
||||||
|
//! ```
|
||||||
|
//! Output:
|
||||||
|
//! ```text
|
||||||
|
//! 잘 못 Champion Masteries:
|
||||||
|
//! 1) Riven 1236866 (7)
|
||||||
|
//! 2) Fiora 230679 (5)
|
||||||
|
//! 3) Katarina 175985 (5)
|
||||||
|
//! 4) Lee Sin 156070 (7)
|
||||||
|
//! 5) Jax 102662 (5)
|
||||||
|
//! 6) Gnar 76373 (6)
|
||||||
|
//! 7) Kai'Sa 64271 (5)
|
||||||
|
//! 8) Caitlyn 46614 (5)
|
||||||
|
//! 9) Irelia 46465 (5)
|
||||||
|
//! 10) Vladimir 37176 (5)
|
||||||
|
//! ```
|
||||||
|
//! The [`RiotApi` struct documentation](https://docs.rs/riven/latest/riven/struct.RiotApi.html)
|
||||||
|
//! contains additional usage information. The [tests](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/tests)
|
||||||
|
//! and [example proxy](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/examples/proxy)
|
||||||
|
//! provide more example usage.
|
||||||
|
//!
|
||||||
|
//! ## Feature Flags
|
||||||
|
//!
|
||||||
|
//! ### Nightly vs Stable
|
||||||
|
//!
|
||||||
|
//! Enable the `nightly` feature to use nightly-only functionality. This enables
|
||||||
|
//! [nightly optimizations in the `parking_lot` crate](https://github.com/Amanieu/parking_lot#nightly-vs-stable).
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! riven = { version = "...", features = [ "nightly" ] }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### rustls
|
||||||
|
//!
|
||||||
|
//! Riven uses [reqwest](https://github.com/seanmonstar/reqwest) for making requests. By default, reqwest uses the native TLS library.
|
||||||
|
//! If you prefer using [rustls](https://github.com/ctz/rustls) you can do so by turning off the Riven default features
|
||||||
|
//! and specifying the `rustls-tls` feature:
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! riven = { version = "...", default-features = false, features = [ "rustls-tls" ] }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Riven is additionally able to produce [tracing](https://docs.rs/tracing) spans for requests if the `tracing` feature is enabled. This feature is disabled by default.
|
||||||
|
//!
|
||||||
|
//! ## Docs
|
||||||
|
//!
|
||||||
|
//! [On docs.rs](https://docs.rs/riven/).
|
||||||
|
//!
|
||||||
|
//! ## Error Handling
|
||||||
|
//!
|
||||||
|
//! Riven returns either `Result<T>` or `Result<Option<T>>` within futures.
|
||||||
|
//!
|
||||||
|
//! If the `Result` is errored, this indicates that the API request failed to
|
||||||
|
//! complete successfully, which may be due to bad user input, Riot server errors,
|
||||||
|
//! incorrect API key, etc.
|
||||||
|
//!
|
||||||
|
//! If the `Option` is `None`, this indicates that the request completed
|
||||||
|
//! successfully but no data was returned. This happens in several situations, such
|
||||||
|
//! as getting a summoner (by name) or match (by id) that doesn't exist, or getting
|
||||||
|
//! spectator data for a summoner who is not in-game.
|
||||||
|
//! Specifically, the API returned a 404 HTTP status code in this situation.
|
||||||
|
//!
|
||||||
|
//! The error type used by Riven is `riven::RiotApiError`. It provides some basic
|
||||||
|
//! diagnostic information, such as the source Reqwest error, the number of retries
|
||||||
|
//! attempted, and the Reqwest `Response` object.
|
||||||
|
//!
|
||||||
|
//! You can configure the number of time Riven retries using
|
||||||
|
//! `RiotApiConfig::set_retries(...)` and the `RiotApi::from_config(config)`
|
||||||
|
//! constructor. By default, Riven retries up to 3 times (4 requests total).
|
||||||
|
//! Some errors, such as 400 client errors, are not retried as they would
|
||||||
|
//! inevitably fail again.
|
||||||
|
//!
|
||||||
|
//! ## Semantic Versioning
|
||||||
|
//!
|
||||||
|
//! This package follows semantic versioning to an extent. However, the Riot API
|
||||||
|
//! itself changes often and does not follow semantic versioning, which makes
|
||||||
|
//! things difficult. Out-of-date versions will slowly partially cease to work due
|
||||||
|
//! to this.
|
||||||
|
//!
|
||||||
|
//! When the API changes, this may result in breaking changes in the `models`
|
||||||
|
//! module, `endpoints` module, and some of the `consts` module. "Handle accessor"
|
||||||
|
//! methods may be removed from `RiotApi` if the corresponding endpoint is removed
|
||||||
|
//! from the Riot API. These breaking changes will increment the **MINOR** version,
|
||||||
|
//! not the major version.
|
||||||
|
//!
|
||||||
|
//! Parts of Riven that do not depend on Riot API changes do follow semantic
|
||||||
|
//! versioning.
|
||||||
|
//!
|
||||||
|
//! ## Additional Help
|
||||||
|
//!
|
||||||
|
//! Feel free to [make an issue](https://github.com/MingweiSamuel/Riven/issues/new)
|
||||||
|
//! if you are have any questions or trouble with Riven.
|
||||||
|
//!
|
||||||
|
//! # Development
|
||||||
|
//!
|
||||||
|
//! NodeJS is used to generate code for Riven. The
|
||||||
|
//! [`riven/srcgen`](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/srcgen)
|
||||||
|
//! folder contains the code and [doT.js](https://olado.github.io/doT/index.html)
|
||||||
|
//! templates. `index.js` lists the JSON files downloaded and used to generate the
|
||||||
|
//! code.
|
||||||
|
//!
|
||||||
|
//! To set up the srcgen, you will first need to install NodeJS. Then enter the
|
||||||
|
//! `riven/srcgen` folder and run `npm ci` (or `npm install`) to install
|
||||||
|
//! dependencies.
|
||||||
|
//!
|
||||||
|
//! To run the srcgen use `node riven/srcgen` from the repository root.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
|
||||||
|
// Re-exported reqwest types.
|
||||||
|
pub use reqwest;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
pub use config::RiotApiConfig;
|
||||||
|
|
||||||
|
pub mod consts;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub mod endpoints;
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
pub use error::*;
|
||||||
|
|
||||||
|
pub mod meta;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub mod models;
|
||||||
|
mod models_impls;
|
||||||
|
|
||||||
|
mod req;
|
||||||
|
|
||||||
|
mod response_info;
|
||||||
|
pub use response_info::*;
|
||||||
|
|
||||||
|
mod riot_api;
|
||||||
|
pub use riot_api::*;
|
||||||
|
|
||||||
|
mod util;
|
|
@ -0,0 +1,100 @@
|
||||||
|
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// ! //
|
||||||
|
// This file is automatically generated! //
|
||||||
|
// Do not directly edit! //
|
||||||
|
// //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
||||||
|
// Version d4f02b20da80dd2c869da349ba774ef6eddc22fa
|
||||||
|
|
||||||
|
//! Metadata about the Riot API and Riven.
|
||||||
|
//!
|
||||||
|
//! Note: this modules is automatically generated.
|
||||||
|
|
||||||
|
/// Metadata for endpoints. Each tuple corresponds to one endpoint and contains
|
||||||
|
/// the HTTP [`Method`](reqwest::Method), `str` path, and the method's `str` ID.
|
||||||
|
pub static ALL_ENDPOINTS: [(reqwest::Method, &str, &str); 80] = [
|
||||||
|
(reqwest::Method::GET, "/riot/account/v1/accounts/by-puuid/{puuid}", "account-v1.getByPuuid"),
|
||||||
|
(reqwest::Method::GET, "/riot/account/v1/accounts/by-riot-id/{gameName}/{tagLine}", "account-v1.getByRiotId"),
|
||||||
|
(reqwest::Method::GET, "/riot/account/v1/accounts/me", "account-v1.getByAccessToken"),
|
||||||
|
(reqwest::Method::GET, "/riot/account/v1/active-shards/by-game/{game}/by-puuid/{puuid}", "account-v1.getActiveShard"),
|
||||||
|
(reqwest::Method::GET, "/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}", "champion-mastery-v4.getAllChampionMasteriesByPUUID"),
|
||||||
|
(reqwest::Method::GET, "/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}/by-champion/{championId}", "champion-mastery-v4.getChampionMasteryByPUUID"),
|
||||||
|
(reqwest::Method::GET, "/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}/top", "champion-mastery-v4.getTopChampionMasteriesByPUUID"),
|
||||||
|
(reqwest::Method::GET, "/lol/champion-mastery/v4/scores/by-puuid/{encryptedPUUID}", "champion-mastery-v4.getChampionMasteryScoreByPUUID"),
|
||||||
|
(reqwest::Method::GET, "/lol/platform/v3/champion-rotations", "champion-v3.getChampionInfo"),
|
||||||
|
(reqwest::Method::GET, "/lol/clash/v1/players/by-summoner/{summonerId}", "clash-v1.getPlayersBySummoner"),
|
||||||
|
(reqwest::Method::GET, "/lol/clash/v1/teams/{teamId}", "clash-v1.getTeamById"),
|
||||||
|
(reqwest::Method::GET, "/lol/clash/v1/tournaments", "clash-v1.getTournaments"),
|
||||||
|
(reqwest::Method::GET, "/lol/clash/v1/tournaments/by-team/{teamId}", "clash-v1.getTournamentByTeam"),
|
||||||
|
(reqwest::Method::GET, "/lol/clash/v1/tournaments/{tournamentId}", "clash-v1.getTournamentById"),
|
||||||
|
(reqwest::Method::GET, "/lol/league-exp/v4/entries/{queue}/{tier}/{division}", "league-exp-v4.getLeagueEntries"),
|
||||||
|
(reqwest::Method::GET, "/lol/league/v4/challengerleagues/by-queue/{queue}", "league-v4.getChallengerLeague"),
|
||||||
|
(reqwest::Method::GET, "/lol/league/v4/entries/by-summoner/{encryptedSummonerId}", "league-v4.getLeagueEntriesForSummoner"),
|
||||||
|
(reqwest::Method::GET, "/lol/league/v4/entries/{queue}/{tier}/{division}", "league-v4.getLeagueEntries"),
|
||||||
|
(reqwest::Method::GET, "/lol/league/v4/grandmasterleagues/by-queue/{queue}", "league-v4.getGrandmasterLeague"),
|
||||||
|
(reqwest::Method::GET, "/lol/league/v4/leagues/{leagueId}", "league-v4.getLeagueById"),
|
||||||
|
(reqwest::Method::GET, "/lol/league/v4/masterleagues/by-queue/{queue}", "league-v4.getMasterLeague"),
|
||||||
|
(reqwest::Method::GET, "/lol/challenges/v1/challenges/config", "lol-challenges-v1.getAllChallengeConfigs"),
|
||||||
|
(reqwest::Method::GET, "/lol/challenges/v1/challenges/percentiles", "lol-challenges-v1.getAllChallengePercentiles"),
|
||||||
|
(reqwest::Method::GET, "/lol/challenges/v1/challenges/{challengeId}/config", "lol-challenges-v1.getChallengeConfigs"),
|
||||||
|
(reqwest::Method::GET, "/lol/challenges/v1/challenges/{challengeId}/leaderboards/by-level/{level}", "lol-challenges-v1.getChallengeLeaderboards"),
|
||||||
|
(reqwest::Method::GET, "/lol/challenges/v1/challenges/{challengeId}/percentiles", "lol-challenges-v1.getChallengePercentiles"),
|
||||||
|
(reqwest::Method::GET, "/lol/challenges/v1/player-data/{puuid}", "lol-challenges-v1.getPlayerData"),
|
||||||
|
(reqwest::Method::GET, "/lol/status/v3/shard-data", "lol-status-v3.getShardData"),
|
||||||
|
(reqwest::Method::GET, "/lol/status/v4/platform-data", "lol-status-v4.getPlatformData"),
|
||||||
|
(reqwest::Method::GET, "/lor/deck/v1/decks/me", "lor-deck-v1.getDecks"),
|
||||||
|
(reqwest::Method::POST, "/lor/deck/v1/decks/me", "lor-deck-v1.createDeck"),
|
||||||
|
(reqwest::Method::GET, "/lor/inventory/v1/cards/me", "lor-inventory-v1.getCards"),
|
||||||
|
(reqwest::Method::GET, "/lor/match/v1/matches/by-puuid/{puuid}/ids", "lor-match-v1.getMatchIdsByPUUID"),
|
||||||
|
(reqwest::Method::GET, "/lor/match/v1/matches/{matchId}", "lor-match-v1.getMatch"),
|
||||||
|
(reqwest::Method::GET, "/lor/ranked/v1/leaderboards", "lor-ranked-v1.getLeaderboards"),
|
||||||
|
(reqwest::Method::GET, "/lor/status/v1/platform-data", "lor-status-v1.getPlatformData"),
|
||||||
|
(reqwest::Method::GET, "/lol/match/v5/matches/by-puuid/{puuid}/ids", "match-v5.getMatchIdsByPUUID"),
|
||||||
|
(reqwest::Method::GET, "/lol/match/v5/matches/{matchId}", "match-v5.getMatch"),
|
||||||
|
(reqwest::Method::GET, "/lol/match/v5/matches/{matchId}/timeline", "match-v5.getTimeline"),
|
||||||
|
(reqwest::Method::GET, "/lol/spectator/v4/active-games/by-summoner/{encryptedSummonerId}", "spectator-v4.getCurrentGameInfoBySummoner"),
|
||||||
|
(reqwest::Method::GET, "/lol/spectator/v4/featured-games", "spectator-v4.getFeaturedGames"),
|
||||||
|
(reqwest::Method::GET, "/fulfillment/v1/summoners/by-puuid/{rsoPUUID}", "summoner-v4.getByRSOPUUID"),
|
||||||
|
(reqwest::Method::GET, "/lol/summoner/v4/summoners/by-account/{encryptedAccountId}", "summoner-v4.getByAccountId"),
|
||||||
|
(reqwest::Method::GET, "/lol/summoner/v4/summoners/by-name/{summonerName}", "summoner-v4.getBySummonerName"),
|
||||||
|
(reqwest::Method::GET, "/lol/summoner/v4/summoners/by-puuid/{encryptedPUUID}", "summoner-v4.getByPUUID"),
|
||||||
|
(reqwest::Method::GET, "/lol/summoner/v4/summoners/me", "summoner-v4.getByAccessToken"),
|
||||||
|
(reqwest::Method::GET, "/lol/summoner/v4/summoners/{encryptedSummonerId}", "summoner-v4.getBySummonerId"),
|
||||||
|
(reqwest::Method::GET, "/tft/league/v1/challenger", "tft-league-v1.getChallengerLeague"),
|
||||||
|
(reqwest::Method::GET, "/tft/league/v1/entries/by-summoner/{summonerId}", "tft-league-v1.getLeagueEntriesForSummoner"),
|
||||||
|
(reqwest::Method::GET, "/tft/league/v1/entries/{tier}/{division}", "tft-league-v1.getLeagueEntries"),
|
||||||
|
(reqwest::Method::GET, "/tft/league/v1/grandmaster", "tft-league-v1.getGrandmasterLeague"),
|
||||||
|
(reqwest::Method::GET, "/tft/league/v1/leagues/{leagueId}", "tft-league-v1.getLeagueById"),
|
||||||
|
(reqwest::Method::GET, "/tft/league/v1/master", "tft-league-v1.getMasterLeague"),
|
||||||
|
(reqwest::Method::GET, "/tft/league/v1/rated-ladders/{queue}/top", "tft-league-v1.getTopRatedLadder"),
|
||||||
|
(reqwest::Method::GET, "/tft/match/v1/matches/by-puuid/{puuid}/ids", "tft-match-v1.getMatchIdsByPUUID"),
|
||||||
|
(reqwest::Method::GET, "/tft/match/v1/matches/{matchId}", "tft-match-v1.getMatch"),
|
||||||
|
(reqwest::Method::GET, "/tft/status/v1/platform-data", "tft-status-v1.getPlatformData"),
|
||||||
|
(reqwest::Method::GET, "/tft/summoner/v1/summoners/by-account/{encryptedAccountId}", "tft-summoner-v1.getByAccountId"),
|
||||||
|
(reqwest::Method::GET, "/tft/summoner/v1/summoners/by-name/{summonerName}", "tft-summoner-v1.getBySummonerName"),
|
||||||
|
(reqwest::Method::GET, "/tft/summoner/v1/summoners/by-puuid/{encryptedPUUID}", "tft-summoner-v1.getByPUUID"),
|
||||||
|
(reqwest::Method::GET, "/tft/summoner/v1/summoners/me", "tft-summoner-v1.getByAccessToken"),
|
||||||
|
(reqwest::Method::GET, "/tft/summoner/v1/summoners/{encryptedSummonerId}", "tft-summoner-v1.getBySummonerId"),
|
||||||
|
(reqwest::Method::POST, "/lol/tournament-stub/v5/codes", "tournament-stub-v5.createTournamentCode"),
|
||||||
|
(reqwest::Method::GET, "/lol/tournament-stub/v5/codes/{tournamentCode}", "tournament-stub-v5.getTournamentCode"),
|
||||||
|
(reqwest::Method::GET, "/lol/tournament-stub/v5/lobby-events/by-code/{tournamentCode}", "tournament-stub-v5.getLobbyEventsByCode"),
|
||||||
|
(reqwest::Method::POST, "/lol/tournament-stub/v5/providers", "tournament-stub-v5.registerProviderData"),
|
||||||
|
(reqwest::Method::POST, "/lol/tournament-stub/v5/tournaments", "tournament-stub-v5.registerTournament"),
|
||||||
|
(reqwest::Method::POST, "/lol/tournament/v5/codes", "tournament-v5.createTournamentCode"),
|
||||||
|
(reqwest::Method::GET, "/lol/tournament/v5/codes/{tournamentCode}", "tournament-v5.getTournamentCode"),
|
||||||
|
(reqwest::Method::PUT, "/lol/tournament/v5/codes/{tournamentCode}", "tournament-v5.updateCode"),
|
||||||
|
(reqwest::Method::GET, "/lol/tournament/v5/games/by-code/{tournamentCode}", "tournament-v5.getGames"),
|
||||||
|
(reqwest::Method::GET, "/lol/tournament/v5/lobby-events/by-code/{tournamentCode}", "tournament-v5.getLobbyEventsByCode"),
|
||||||
|
(reqwest::Method::POST, "/lol/tournament/v5/providers", "tournament-v5.registerProviderData"),
|
||||||
|
(reqwest::Method::POST, "/lol/tournament/v5/tournaments", "tournament-v5.registerTournament"),
|
||||||
|
(reqwest::Method::GET, "/val/content/v1/contents", "val-content-v1.getContent"),
|
||||||
|
(reqwest::Method::GET, "/val/match/v1/matches/{matchId}", "val-match-v1.getMatch"),
|
||||||
|
(reqwest::Method::GET, "/val/match/v1/matchlists/by-puuid/{puuid}", "val-match-v1.getMatchlist"),
|
||||||
|
(reqwest::Method::GET, "/val/match/v1/recent-matches/by-queue/{queue}", "val-match-v1.getRecent"),
|
||||||
|
(reqwest::Method::GET, "/val/ranked/v1/leaderboards/by-act/{actId}", "val-ranked-v1.getLeaderboard"),
|
||||||
|
(reqwest::Method::GET, "/val/status/v1/platform-data", "val-status-v1.getPlatformData"),
|
||||||
|
];
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,21 @@
|
||||||
|
use crate::consts::Champion;
|
||||||
|
use crate::models::match_v5::Participant;
|
||||||
|
|
||||||
|
impl Participant {
|
||||||
|
/// This method takes the [`Self::champion_id`] field if it is valid
|
||||||
|
/// (`Ok`), otherwise it attempts to parse [`Self::champion_name`] and
|
||||||
|
/// returns the `Result`.
|
||||||
|
///
|
||||||
|
/// This is needed because some of Riot's [`Self::champion_id`] data is
|
||||||
|
/// corrupted, as they describe in the docs:
|
||||||
|
///
|
||||||
|
/// > Prior to patch 11.4, on Feb 18th, 2021, this field returned invalid
|
||||||
|
/// > championIds. We recommend determining the champion based on the
|
||||||
|
/// > championName field for matches played prior to patch 11.4.
|
||||||
|
///
|
||||||
|
/// This issue is reported here: <https://github.com/RiotGames/developer-relations/issues/553>.
|
||||||
|
pub fn champion(&self) -> Result<Champion, <Champion as std::str::FromStr>::Err> {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
self.champion_id.or_else(|_| self.champion_name.parse())
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,9 +4,9 @@ mod rate_limit;
|
||||||
pub use rate_limit::*;
|
pub use rate_limit::*;
|
||||||
|
|
||||||
mod rate_limit_type;
|
mod rate_limit_type;
|
||||||
pub use rate_limit_type::*;
|
use std::time::Instant;
|
||||||
|
|
||||||
use std::time::Instant; // Hack for token_bucket_test.rs.
|
pub use rate_limit_type::*; // Hack for token_bucket_test.rs.
|
||||||
mod token_bucket;
|
mod token_bucket;
|
||||||
pub use token_bucket::*;
|
pub use token_bucket::*;
|
||||||
|
|
|
@ -0,0 +1,307 @@
|
||||||
|
use std::cmp;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||||
|
use reqwest::{Response, StatusCode};
|
||||||
|
use scan_fmt::scan_fmt;
|
||||||
|
use tokio::sync::Notify;
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
use tracing as log;
|
||||||
|
|
||||||
|
use super::{RateLimitType, TokenBucket, VectorTokenBucket};
|
||||||
|
use crate::RiotApiConfig;
|
||||||
|
|
||||||
|
pub struct RateLimit {
|
||||||
|
rate_limit_type: RateLimitType,
|
||||||
|
// Buckets for this rate limit (synchronized).
|
||||||
|
// Almost always read, written only when rate limit rates are updated
|
||||||
|
// from API response.
|
||||||
|
buckets: RwLock<Vec<VectorTokenBucket>>,
|
||||||
|
// Set to when we can retry if a retry-after header is received.
|
||||||
|
retry_after: RwLock<Option<Instant>>,
|
||||||
|
// Notifies waiters when rate limits are updated.
|
||||||
|
update_notify: Notify,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RateLimit {
|
||||||
|
/// Header specifying which RateLimitType caused a 429.
|
||||||
|
/// This header specifies which rate limit is violated in a 429 (if any).
|
||||||
|
/// There are three possible values, see [HEADER_XRATELIMITTYPE_APPLICATION],
|
||||||
|
/// [HEADER_XRATELIMITTYPE_METHOD], and [HEADER_XRATELIMITTYPE_SERVICE].
|
||||||
|
const HEADER_XRATELIMITTYPE: &'static str = "X-Rate-Limit-Type";
|
||||||
|
|
||||||
|
/// `"application"` - Entire app/key is rate limited due to violation.
|
||||||
|
const HEADER_XRATELIMITTYPE_APPLICATION: &'static str = "application";
|
||||||
|
/// `"method"` - App/key is rate limited on a specific method due to violation.
|
||||||
|
const HEADER_XRATELIMITTYPE_METHOD: &'static str = "method";
|
||||||
|
/// `"service"` - Service backend is rate-limiting (no violation).
|
||||||
|
const HEADER_XRATELIMITTYPE_SERVICE: &'static str = "service";
|
||||||
|
|
||||||
|
pub fn new(rate_limit_type: RateLimitType) -> Self {
|
||||||
|
let initial_bucket =
|
||||||
|
VectorTokenBucket::new(Duration::from_secs(1), 1, Duration::new(0, 0), 1.0, 1.0);
|
||||||
|
RateLimit {
|
||||||
|
rate_limit_type,
|
||||||
|
// Rate limit before getting from response: 1/s.
|
||||||
|
buckets: RwLock::new(vec![initial_bucket]),
|
||||||
|
retry_after: RwLock::new(None),
|
||||||
|
update_notify: Notify::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn acquire_both(app_rate_limit: &Self, method_rate_limit: &Self) {
|
||||||
|
while let Some(delay) = Self::acquire_both_or_duration(app_rate_limit, method_rate_limit) {
|
||||||
|
tokio::select! {
|
||||||
|
biased;
|
||||||
|
_ = tokio::time::sleep(delay) => { continue }
|
||||||
|
_ = app_rate_limit.update_notify.notified() => {}
|
||||||
|
_ = method_rate_limit.update_notify.notified() => {}
|
||||||
|
};
|
||||||
|
log::trace!("Task awoken due to rate limit update.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acquire_both_or_duration(
|
||||||
|
app_rate_limit: &Self,
|
||||||
|
method_rate_limit: &Self,
|
||||||
|
) -> Option<Duration> {
|
||||||
|
// Check retry after.
|
||||||
|
{
|
||||||
|
let retry_after_delay = app_rate_limit.get_retry_after_delay().and_then(|a| {
|
||||||
|
method_rate_limit
|
||||||
|
.get_retry_after_delay()
|
||||||
|
.map(|m| cmp::max(a, m))
|
||||||
|
});
|
||||||
|
if retry_after_delay.is_some() {
|
||||||
|
return retry_after_delay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check buckets.
|
||||||
|
let app_buckets = app_rate_limit.buckets.read();
|
||||||
|
let method_buckets = method_rate_limit.buckets.read();
|
||||||
|
for bucket in app_buckets.iter().chain(method_buckets.iter()) {
|
||||||
|
let delay = bucket.get_delay();
|
||||||
|
if delay.is_some() {
|
||||||
|
return delay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Success.
|
||||||
|
for bucket in app_buckets.iter().chain(method_buckets.iter()) {
|
||||||
|
bucket.get_tokens(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::trace!(
|
||||||
|
"Tokens obtained, buckets: APP {:?} METHOD {:?}",
|
||||||
|
app_buckets,
|
||||||
|
method_buckets
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_retry_after_delay(&self) -> Option<Duration> {
|
||||||
|
self.retry_after
|
||||||
|
.read()
|
||||||
|
.and_then(|i| Instant::now().checked_duration_since(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update retry-after and rate limits based on an API response.
|
||||||
|
/// Returns the retry-after delay if set.
|
||||||
|
pub fn on_response(&self, config: &RiotApiConfig, response: &Response) -> Option<Duration> {
|
||||||
|
let retry_after = self.on_response_retry_after(response);
|
||||||
|
self.on_response_rate_limits(config, response);
|
||||||
|
retry_after
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `on_response` helper for retry after check.
|
||||||
|
/// Returns the retry-after delay if set.
|
||||||
|
#[inline]
|
||||||
|
fn on_response_retry_after(&self, response: &Response) -> Option<Duration> {
|
||||||
|
// Only care about 429s.
|
||||||
|
if StatusCode::TOO_MANY_REQUESTS != response.status() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Get the X-Rate-Limit-Type header, `Some("application" | "method" | "service")` or `None`.
|
||||||
|
let header_opt = response.headers()
|
||||||
|
.get(Self::HEADER_XRATELIMITTYPE)
|
||||||
|
.or_else(|| {
|
||||||
|
log::info!("429 response missing {} header.", Self::HEADER_XRATELIMITTYPE);
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.and_then(|header_value| header_value.to_str()
|
||||||
|
.map_err(|e| log::info!("429 response, error parsing '{}' header as string: {}. Header value: {:#?}",
|
||||||
|
Self::HEADER_XRATELIMITTYPE, e, header_value))
|
||||||
|
.ok());
|
||||||
|
|
||||||
|
// This match checks the valid possibilities. Otherwise returns none to ignore.
|
||||||
|
// `Application` handles "application", `Method` handles all other values.
|
||||||
|
let is_responsible = match header_opt {
|
||||||
|
Some(Self::HEADER_XRATELIMITTYPE_APPLICATION) => {
|
||||||
|
self.rate_limit_type == RateLimitType::Application
|
||||||
|
}
|
||||||
|
Some(Self::HEADER_XRATELIMITTYPE_METHOD | Self::HEADER_XRATELIMITTYPE_SERVICE) => {
|
||||||
|
self.rate_limit_type == RateLimitType::Method
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
// RateLimitType::Method handles unknown values.
|
||||||
|
if self.rate_limit_type == RateLimitType::Method {
|
||||||
|
log::warn!(
|
||||||
|
"429 response has None (missing or invalid) or unknown {} header value {:?}, {:?} rate limit obeying retry-after.",
|
||||||
|
Self::HEADER_XRATELIMITTYPE, other, self.rate_limit_type);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !is_responsible {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retry after header. Only care if it exists.
|
||||||
|
let retry_after_header = response
|
||||||
|
.headers()
|
||||||
|
.get(reqwest::header::RETRY_AFTER)
|
||||||
|
.and_then(|h| {
|
||||||
|
h.to_str()
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!(
|
||||||
|
"Failed to read retry-after header as visible ASCII string: {:?}.",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"429 response, rate limit {:?}, retry-after {} secs.",
|
||||||
|
self.rate_limit_type,
|
||||||
|
retry_after_header
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header currently only returns ints, but float is more general. Can be zero.
|
||||||
|
let retry_after_secs = retry_after_header
|
||||||
|
.parse::<f32>()
|
||||||
|
.map_err(|e| log::error!("Failed to parse read-after header as f32: {:?}.", e))
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
// Add 0.5 seconds to account for rounding, cases when response is zero.
|
||||||
|
let delay = Duration::from_secs_f32(0.5 + retry_after_secs);
|
||||||
|
|
||||||
|
// Set `self.retry_after`.
|
||||||
|
*self.retry_after.write() = Some(Instant::now() + delay);
|
||||||
|
Some(delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn on_response_rate_limits(&self, config: &RiotApiConfig, response: &Response) {
|
||||||
|
// Check if rate limits changed.
|
||||||
|
let headers = response.headers();
|
||||||
|
let limit_header_opt = headers
|
||||||
|
.get(self.rate_limit_type.limit_header())
|
||||||
|
.and_then(|h| {
|
||||||
|
h.to_str()
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!(
|
||||||
|
"Failed to read limit header as visible ASCII string: {:?}.",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
});
|
||||||
|
let count_header_opt = headers
|
||||||
|
.get(self.rate_limit_type.count_header())
|
||||||
|
.and_then(|h| {
|
||||||
|
h.to_str()
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!(
|
||||||
|
"Failed to read count header as visible ASCII string: {:?}.",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
});
|
||||||
|
|
||||||
|
if let (Some(limit_header), Some(count_header)) = (limit_header_opt, count_header_opt) {
|
||||||
|
{
|
||||||
|
let buckets = self.buckets.upgradable_read();
|
||||||
|
if !buckets_require_updating(limit_header, &buckets) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buckets require updating. Upgrade to write lock.
|
||||||
|
let mut buckets = RwLockUpgradableReadGuard::upgrade(buckets);
|
||||||
|
*buckets =
|
||||||
|
buckets_from_header(config, limit_header, count_header, self.rate_limit_type);
|
||||||
|
}
|
||||||
|
// Notify waiters that buckets have updated (after unlocking).
|
||||||
|
self.update_notify.notify_waiters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buckets_require_updating(limit_header: &str, buckets: &[VectorTokenBucket]) -> bool {
|
||||||
|
if buckets.len() != limit_header.split(',').count() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (limit_header_entry, bucket) in limit_header.split(',').zip(buckets) {
|
||||||
|
// limit_header_entry "100:60" means 100 req per 60 sec.
|
||||||
|
let bucket_entry = format!(
|
||||||
|
"{}:{}",
|
||||||
|
bucket.get_total_limit(),
|
||||||
|
bucket.get_bucket_duration().as_secs()
|
||||||
|
);
|
||||||
|
if limit_header_entry != bucket_entry {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buckets_from_header(
|
||||||
|
config: &RiotApiConfig,
|
||||||
|
limit_header: &str,
|
||||||
|
count_header: &str,
|
||||||
|
rate_limit_type: RateLimitType,
|
||||||
|
) -> Vec<VectorTokenBucket> {
|
||||||
|
// Limits: "20000:10,1200000:600"
|
||||||
|
// Counts: "7:10,58:600"
|
||||||
|
let size = limit_header.split(',').count();
|
||||||
|
debug_assert!(size == count_header.split(',').count());
|
||||||
|
let mut out = Vec::with_capacity(size);
|
||||||
|
|
||||||
|
for (limit_entry, count_entry) in limit_header.split(',').zip(count_header.split(',')) {
|
||||||
|
let (limit, limit_secs) = scan_fmt!(limit_entry, "{d}:{d}", usize, u64)
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to parse limit entry \"{}\".", limit_entry));
|
||||||
|
let (count, count_secs) = scan_fmt!(count_entry, "{d}:{d}", usize, u64)
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to parse count entry \"{}\".", count_entry));
|
||||||
|
debug_assert!(limit_secs == count_secs);
|
||||||
|
|
||||||
|
let rate_usage_factor = if RateLimitType::Application == rate_limit_type {
|
||||||
|
config.app_rate_usage_factor
|
||||||
|
} else {
|
||||||
|
config.method_rate_usage_factor
|
||||||
|
};
|
||||||
|
|
||||||
|
let limit_f32 = limit as f32;
|
||||||
|
let scaled_burst_factor = config.burst_factor * limit_f32 / (limit_f32 + 1.0);
|
||||||
|
|
||||||
|
let bucket = VectorTokenBucket::new(
|
||||||
|
Duration::from_secs(limit_secs),
|
||||||
|
limit,
|
||||||
|
config.duration_overhead,
|
||||||
|
scaled_burst_factor,
|
||||||
|
rate_usage_factor,
|
||||||
|
);
|
||||||
|
bucket.get_tokens(count);
|
||||||
|
out.push(bucket);
|
||||||
|
}
|
||||||
|
log::debug!(
|
||||||
|
"Set buckets to {} limit, {} count.",
|
||||||
|
limit_header,
|
||||||
|
count_header
|
||||||
|
);
|
||||||
|
out
|
||||||
|
}
|
|
@ -1,25 +1,21 @@
|
||||||
#[derive(Copy, Clone)]
|
/// The type for a [RateLimit](super::RateLimit). Either a rate limit for the
|
||||||
|
/// entire app (`Application`) or for a specific method (`Method`).
|
||||||
|
/// Method rate limit will handle service violations.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum RateLimitType {
|
pub enum RateLimitType {
|
||||||
Application,
|
Application,
|
||||||
Method,
|
Method,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RateLimitType {
|
impl RateLimitType {
|
||||||
pub fn type_name(self) -> &'static str {
|
pub const fn limit_header(self) -> &'static str {
|
||||||
match self {
|
|
||||||
Self::Application => "application",
|
|
||||||
Self::Method => "method",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn limit_header(self) -> &'static str {
|
|
||||||
match self {
|
match self {
|
||||||
Self::Application => "X-App-Rate-Limit",
|
Self::Application => "X-App-Rate-Limit",
|
||||||
Self::Method => "X-Method-Rate-Limit",
|
Self::Method => "X-Method-Rate-Limit",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_header(self) -> &'static str {
|
pub const fn count_header(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Application => "X-App-Rate-Limit-Count",
|
Self::Application => "X-App-Rate-Limit-Count",
|
||||||
Self::Method => "X-Method-Rate-Limit-Count",
|
Self::Method => "X-Method-Rate-Limit-Count",
|
|
@ -0,0 +1,136 @@
|
||||||
|
use std::future::Future;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use reqwest::{RequestBuilder, StatusCode};
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
use tracing as log;
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
use tracing::Instrument;
|
||||||
|
|
||||||
|
use super::{RateLimit, RateLimitType};
|
||||||
|
use crate::util::InsertOnlyCHashMap;
|
||||||
|
use crate::{ResponseInfo, Result, RiotApiConfig, RiotApiError};
|
||||||
|
|
||||||
|
pub struct RegionalRequester {
|
||||||
|
/// The app rate limit.
|
||||||
|
app_rate_limit: RateLimit,
|
||||||
|
/// Method rate limits.
|
||||||
|
method_rate_limits: InsertOnlyCHashMap<&'static str, RateLimit>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegionalRequester {
|
||||||
|
/// HTTP status codes which are considered a success but will results in `None`.
|
||||||
|
const NONE_STATUS_CODES: [StatusCode; 2] = [
|
||||||
|
StatusCode::NO_CONTENT, // 204
|
||||||
|
StatusCode::NOT_FOUND, // 404
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
app_rate_limit: RateLimit::new(RateLimitType::Application),
|
||||||
|
method_rate_limits: InsertOnlyCHashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute<'a>(
|
||||||
|
self: Arc<Self>,
|
||||||
|
config: &'a RiotApiConfig,
|
||||||
|
method_id: &'static str,
|
||||||
|
request: RequestBuilder,
|
||||||
|
) -> impl Future<Output = Result<ResponseInfo>> + 'a {
|
||||||
|
async move {
|
||||||
|
let mut retries: u8 = 0;
|
||||||
|
loop {
|
||||||
|
let method_rate_limit: Arc<RateLimit> = self
|
||||||
|
.method_rate_limits
|
||||||
|
.get_or_insert_with(method_id, || RateLimit::new(RateLimitType::Method));
|
||||||
|
|
||||||
|
// Rate limit.
|
||||||
|
let rate_limit = RateLimit::acquire_both(&self.app_rate_limit, &method_rate_limit);
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
let rate_limit = rate_limit.instrument(tracing::info_span!("rate_limit"));
|
||||||
|
rate_limit.await;
|
||||||
|
|
||||||
|
// Send request.
|
||||||
|
let request_clone = request
|
||||||
|
.try_clone()
|
||||||
|
.expect("Failed to clone request.")
|
||||||
|
.send();
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
let request_clone = request_clone.instrument(tracing::info_span!("request"));
|
||||||
|
let response = request_clone
|
||||||
|
.await
|
||||||
|
.map_err(|e| RiotApiError::new(e, retries, None, None))?;
|
||||||
|
|
||||||
|
// Maybe update rate limits (based on response headers).
|
||||||
|
// Use single bar for no short circuiting.
|
||||||
|
let retry_after_app = self.app_rate_limit.on_response(config, &response);
|
||||||
|
let retry_after_method = method_rate_limit.on_response(config, &response);
|
||||||
|
let retry_after = retry_after_app.or(retry_after_method); // Note: Edge case if both are Some(_) not handled.
|
||||||
|
|
||||||
|
let status = response.status();
|
||||||
|
// Handle normal success / failure cases.
|
||||||
|
let status_none = Self::NONE_STATUS_CODES.contains(&status);
|
||||||
|
// Success case.
|
||||||
|
if status.is_success() || status_none {
|
||||||
|
log::trace!(
|
||||||
|
"Response {} (retried {} times), success, returning result.",
|
||||||
|
status,
|
||||||
|
retries
|
||||||
|
);
|
||||||
|
break Ok(ResponseInfo {
|
||||||
|
response,
|
||||||
|
retries,
|
||||||
|
status_none,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let err = response.error_for_status_ref().err().unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"Unhandlable response status code, neither success nor failure: {}.",
|
||||||
|
status
|
||||||
|
)
|
||||||
|
});
|
||||||
|
// Failure, may or may not be retryable.
|
||||||
|
// Not-retryable: no more retries or 4xx or ? (3xx, redirects exceeded).
|
||||||
|
// Retryable: retries remaining, and 429 or 5xx.
|
||||||
|
if retries >= config.retries
|
||||||
|
|| (StatusCode::TOO_MANY_REQUESTS != status && !status.is_server_error())
|
||||||
|
{
|
||||||
|
log::debug!(
|
||||||
|
"Response {} (retried {} times), failure, returning error.",
|
||||||
|
status,
|
||||||
|
retries
|
||||||
|
);
|
||||||
|
break Err(RiotApiError::new(
|
||||||
|
err,
|
||||||
|
retries,
|
||||||
|
Some(response),
|
||||||
|
Some(status),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is retryable, do exponential backoff if retry-after wasn't specified.
|
||||||
|
// 1 sec, 2 sec, 4 sec, 8 sec.
|
||||||
|
match retry_after {
|
||||||
|
None => {
|
||||||
|
let delay = std::time::Duration::from_secs(2_u64.pow(retries as u32));
|
||||||
|
log::debug!("Response {} (retried {} times), NO `retry-after`, using exponential backoff, retrying after {:?}.", status, retries, delay);
|
||||||
|
let backoff = tokio::time::sleep(delay);
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
let backoff = backoff.instrument(tracing::info_span!("backoff"));
|
||||||
|
backoff.await;
|
||||||
|
}
|
||||||
|
Some(delay) => {
|
||||||
|
log::debug!("Response {} (retried {} times), `retry-after` set, retrying after {:?}.", status, retries, delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retries += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
// use super::*;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use std::fmt;
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::fmt;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use parking_lot::{Mutex, MutexGuard};
|
use parking_lot::{Mutex, MutexGuard};
|
||||||
|
@ -39,10 +39,16 @@ pub trait TokenBucket {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VectorTokenBucket {
|
pub struct VectorTokenBucket {
|
||||||
|
/// The total limit supplied to the constructor, unadjusted by the [rate_usage_factor].
|
||||||
|
_given_total_limit: usize,
|
||||||
|
/// Additional factor to reduce rate limit usage, in range (0, 1\].
|
||||||
|
_rate_usage_factor: f32,
|
||||||
|
|
||||||
/// Duration of this TokenBucket.
|
/// Duration of this TokenBucket.
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
// Total tokens available from this TokenBucket.
|
// Total tokens available from this TokenBucket.
|
||||||
total_limit: usize,
|
total_limit: usize,
|
||||||
|
|
||||||
/// Extra duration to be considered on top of `duration`, to account for
|
/// Extra duration to be considered on top of `duration`, to account for
|
||||||
/// varying network latency.
|
/// varying network latency.
|
||||||
duration_overhead: Duration,
|
duration_overhead: Duration,
|
||||||
|
@ -52,36 +58,53 @@ pub struct VectorTokenBucket {
|
||||||
/// Limit allowed per burst_duration, for burst factor.
|
/// Limit allowed per burst_duration, for burst factor.
|
||||||
burst_limit: usize,
|
burst_limit: usize,
|
||||||
|
|
||||||
|
|
||||||
/// Record of timestamps (synchronized).
|
/// Record of timestamps (synchronized).
|
||||||
timestamps: Mutex<VecDeque<Instant>>,
|
timestamps: Mutex<VecDeque<Instant>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VectorTokenBucket {
|
impl VectorTokenBucket {
|
||||||
pub fn new(duration: Duration, total_limit: usize,
|
pub fn new(
|
||||||
duration_overhead: Duration, burst_pct: f32) -> Self
|
duration: Duration,
|
||||||
{
|
given_total_limit: usize,
|
||||||
debug_assert!(0.0 < burst_pct && burst_pct <= 1.0,
|
duration_overhead: Duration,
|
||||||
"BAD burst_pct {}.", burst_pct);
|
burst_factor: f32,
|
||||||
|
rate_usage_factor: f32,
|
||||||
|
) -> Self {
|
||||||
|
debug_assert!(
|
||||||
|
0.0 < rate_usage_factor && rate_usage_factor <= 1.0,
|
||||||
|
"BAD rate_usage_factor {}.",
|
||||||
|
rate_usage_factor
|
||||||
|
);
|
||||||
|
debug_assert!(
|
||||||
|
0.0 < burst_factor && burst_factor <= 1.0,
|
||||||
|
"BAD burst_factor {}.",
|
||||||
|
burst_factor
|
||||||
|
);
|
||||||
// Float ops may lose precision, but nothing should be that precise.
|
// Float ops may lose precision, but nothing should be that precise.
|
||||||
// API always uses round numbers, burst_pct is frac of 256.
|
// API always uses round numbers, burst_factor is frac of 256.
|
||||||
|
|
||||||
|
// Adjust everything by rate_usage_factor.
|
||||||
|
let total_limit = std::cmp::max(
|
||||||
|
1,
|
||||||
|
(given_total_limit as f32 * rate_usage_factor).floor() as usize,
|
||||||
|
);
|
||||||
|
|
||||||
// Effective duration.
|
// Effective duration.
|
||||||
let d_eff = duration + duration_overhead;
|
let d_eff = duration + duration_overhead;
|
||||||
let burst_duration = Duration::new(
|
let burst_duration = d_eff.mul_f32(burst_factor);
|
||||||
(d_eff.as_secs() as f32 * burst_pct).ceil() as u64,
|
let burst_limit = std::cmp::max(1, (total_limit as f32 * burst_factor).floor() as usize);
|
||||||
(d_eff.subsec_nanos() as f32 * burst_pct).ceil() as u32);
|
|
||||||
let burst_limit = std::cmp::max(1,
|
|
||||||
(total_limit as f32 * burst_pct).floor() as usize);
|
|
||||||
debug_assert!(burst_limit <= total_limit);
|
debug_assert!(burst_limit <= total_limit);
|
||||||
|
|
||||||
VectorTokenBucket {
|
VectorTokenBucket {
|
||||||
duration: duration,
|
_given_total_limit: given_total_limit,
|
||||||
total_limit: total_limit,
|
_rate_usage_factor: rate_usage_factor,
|
||||||
duration_overhead: duration_overhead,
|
|
||||||
|
|
||||||
burst_duration: burst_duration,
|
duration,
|
||||||
burst_limit: burst_limit,
|
total_limit,
|
||||||
|
|
||||||
|
duration_overhead,
|
||||||
|
burst_duration,
|
||||||
|
burst_limit,
|
||||||
|
|
||||||
timestamps: Mutex::new(VecDeque::with_capacity(total_limit)),
|
timestamps: Mutex::new(VecDeque::with_capacity(total_limit)),
|
||||||
}
|
}
|
||||||
|
@ -90,36 +113,32 @@ impl VectorTokenBucket {
|
||||||
fn update_get_timestamps(&self) -> MutexGuard<VecDeque<Instant>> {
|
fn update_get_timestamps(&self) -> MutexGuard<VecDeque<Instant>> {
|
||||||
let mut timestamps = self.timestamps.lock();
|
let mut timestamps = self.timestamps.lock();
|
||||||
let cutoff = Instant::now() - self.duration - self.duration_overhead;
|
let cutoff = Instant::now() - self.duration - self.duration_overhead;
|
||||||
// We only need to trim the end of the queue to not leak memory.
|
// Pop off timestamps that are beyound the bucket duration.
|
||||||
// We could do it lazily somehow if we wanted to be really fancy.
|
|
||||||
while timestamps.back().map_or(false, |ts| *ts < cutoff) {
|
while timestamps.back().map_or(false, |ts| *ts < cutoff) {
|
||||||
timestamps.pop_back();
|
timestamps.pop_back();
|
||||||
}
|
}
|
||||||
return timestamps;
|
timestamps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TokenBucket for VectorTokenBucket {
|
impl TokenBucket for VectorTokenBucket {
|
||||||
|
|
||||||
fn get_delay(&self) -> Option<Duration> {
|
fn get_delay(&self) -> Option<Duration> {
|
||||||
let timestamps = self.update_get_timestamps();
|
let timestamps = self.update_get_timestamps();
|
||||||
|
|
||||||
// The "?" means:
|
|
||||||
// `if timestamps.len() < self.total_limit { return None }`
|
|
||||||
// Timestamp that needs to be popped before
|
|
||||||
// we can enter another timestamp.
|
|
||||||
|
|
||||||
// Full rate limit.
|
// Full rate limit.
|
||||||
if let Some(ts) = timestamps.get(self.total_limit - 1) {
|
if let Some(ts) = timestamps.get(self.total_limit - 1) {
|
||||||
// Return amount of time needed for timestamp `ts` to go away.
|
// Return amount of time needed for timestamp `ts` to go away.
|
||||||
Instant::now().checked_duration_since(*ts)
|
Instant::now()
|
||||||
.and_then(|passed_dur| (self.duration + self.duration_overhead)
|
.checked_duration_since(*ts)
|
||||||
.checked_sub(passed_dur))
|
.and_then(|passed_dur| {
|
||||||
|
(self.duration + self.duration_overhead).checked_sub(passed_dur)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
// Otherwise burst rate limit.
|
// Otherwise burst rate limit.
|
||||||
else if let Some(ts) = timestamps.get(self.burst_limit - 1) {
|
else if let Some(ts) = timestamps.get(self.burst_limit - 1) {
|
||||||
// Return amount of time needed for timestamp `ts` to go away.
|
// Return amount of time needed for timestamp `ts` to go away.
|
||||||
Instant::now().checked_duration_since(*ts)
|
Instant::now()
|
||||||
|
.checked_duration_since(*ts)
|
||||||
.and_then(|passed_dur| self.burst_duration.checked_sub(passed_dur))
|
.and_then(|passed_dur| self.burst_duration.checked_sub(passed_dur))
|
||||||
}
|
}
|
||||||
// No delay needed.
|
// No delay needed.
|
||||||
|
@ -137,7 +156,21 @@ impl TokenBucket for VectorTokenBucket {
|
||||||
for _ in 0..n {
|
for _ in 0..n {
|
||||||
timestamps.push_front(now);
|
timestamps.push_front(now);
|
||||||
}
|
}
|
||||||
timestamps.len() <= self.total_limit
|
|
||||||
|
// Check total limit.
|
||||||
|
if self.total_limit < timestamps.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check burst limit.
|
||||||
|
if let Some(burst_time) = timestamps.get(self.burst_limit) {
|
||||||
|
let duration_since = now.duration_since(*burst_time); // `now` before `burst_time` will panic.
|
||||||
|
if duration_since < self.burst_duration {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bucket_duration(&self) -> Duration {
|
fn get_bucket_duration(&self) -> Duration {
|
||||||
|
@ -151,6 +184,12 @@ impl TokenBucket for VectorTokenBucket {
|
||||||
|
|
||||||
impl fmt::Debug for VectorTokenBucket {
|
impl fmt::Debug for VectorTokenBucket {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "({}/{}:{})", self.timestamps.lock().len(), self.total_limit, self.duration.as_secs())
|
write!(
|
||||||
|
f,
|
||||||
|
"({}/{}:{})",
|
||||||
|
self.timestamps.lock().len(),
|
||||||
|
self.total_limit,
|
||||||
|
self.duration.as_secs()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,200 @@
|
||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
use fake_instant::FakeInstant as Instant;
|
||||||
|
|
||||||
|
/// This is a hack to test token bucket, substituting `FakeInstant` in place of `Instant`.
|
||||||
|
mod token_bucket {
|
||||||
|
include!("token_bucket.rs");
|
||||||
|
|
||||||
|
mod tests {
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref ZERO: Duration = Duration::new(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic() {
|
||||||
|
Instant::set_time(50_000);
|
||||||
|
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 0.95, 1.0);
|
||||||
|
assert!(bucket.get_tokens(50), "Should have not violated limit.");
|
||||||
|
assert_eq!(None, bucket.get_delay(), "Can get stuff.");
|
||||||
|
assert!(!bucket.get_tokens(51), "Should have violated limit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_internal_constructor() {
|
||||||
|
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 1.0, 1.0);
|
||||||
|
assert_eq!(100, bucket.burst_limit);
|
||||||
|
|
||||||
|
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 1e-6, 1.0);
|
||||||
|
assert_eq!(1, bucket.burst_limit);
|
||||||
|
|
||||||
|
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 1.0, 1e-6);
|
||||||
|
assert_eq!(1, bucket.total_limit);
|
||||||
|
assert_eq!(1, bucket.burst_limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_saturated_100_burst() {
|
||||||
|
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 1.00, 1.0);
|
||||||
|
|
||||||
|
Instant::set_time(50_000);
|
||||||
|
assert!(
|
||||||
|
bucket.get_tokens(100),
|
||||||
|
"All tokens should be immediately available."
|
||||||
|
);
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
|
||||||
|
Instant::advance_time(1001); // Extra buffer for Duration(0).
|
||||||
|
assert!(
|
||||||
|
bucket.get_tokens(100),
|
||||||
|
"All tokens should be available after a bucket duration."
|
||||||
|
);
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_saturated_95_burst() {
|
||||||
|
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 0.95, 1.0);
|
||||||
|
|
||||||
|
Instant::set_time(50_000);
|
||||||
|
assert!(
|
||||||
|
bucket.get_tokens(95),
|
||||||
|
"95 tokens should be immediately available."
|
||||||
|
);
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
|
||||||
|
Instant::advance_time(475);
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
Instant::advance_time(476); // Total 951. Extra buffer for Duration(0).
|
||||||
|
|
||||||
|
assert!(bucket.get_tokens(5), "Last 5 tokens should be available.");
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
|
||||||
|
Instant::advance_time(51); // Total 1002.
|
||||||
|
assert!(bucket.get_tokens(90), "90 tokens should be available.");
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
|
||||||
|
Instant::advance_time(951);
|
||||||
|
assert!(bucket.get_tokens(10), "Last 10 tokens should be available.");
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_violated_50_burst() {
|
||||||
|
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 0.50, 1.0);
|
||||||
|
|
||||||
|
Instant::set_time(50_000);
|
||||||
|
assert!(!bucket.get_tokens(90), "Burst should be violated.");
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_saturated_50_burst() {
|
||||||
|
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 0.50, 1.0);
|
||||||
|
|
||||||
|
Instant::set_time(50_000);
|
||||||
|
assert!(
|
||||||
|
bucket.get_tokens(50),
|
||||||
|
"Half the tokens should be immediately available."
|
||||||
|
);
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
|
||||||
|
Instant::advance_time(501); // Extra buffer for Duration(0).
|
||||||
|
assert!(
|
||||||
|
bucket.get_tokens(50),
|
||||||
|
"Half the tokens should be available after a half bucket duration."
|
||||||
|
);
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
|
||||||
|
Instant::advance_time(501);
|
||||||
|
assert!(
|
||||||
|
bucket.get_tokens(50),
|
||||||
|
"Half the tokens should be available after a full bucket duration."
|
||||||
|
);
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
|
||||||
|
Instant::advance_time(501);
|
||||||
|
assert!(bucket.get_tokens(50));
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_saturated_90_burst_rate_usage_factor_50() {
|
||||||
|
let rate_usage_factor = 0.5;
|
||||||
|
let bucket = VectorTokenBucket::new(
|
||||||
|
Duration::from_millis(1000),
|
||||||
|
100,
|
||||||
|
*ZERO,
|
||||||
|
0.90,
|
||||||
|
rate_usage_factor,
|
||||||
|
);
|
||||||
|
|
||||||
|
Instant::set_time(50_000);
|
||||||
|
assert!(
|
||||||
|
bucket.get_tokens(45),
|
||||||
|
"45 tokens should be immediately available."
|
||||||
|
);
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
|
||||||
|
Instant::advance_time(475);
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
Instant::advance_time(476); // Total 951. Extra buffer for Duration(0).
|
||||||
|
|
||||||
|
assert!(bucket.get_tokens(5), "Last 5 tokens should be available.");
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
|
||||||
|
Instant::advance_time(51); // Total 1002.
|
||||||
|
assert!(bucket.get_tokens(40), "45 tokens should be available.");
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
|
||||||
|
Instant::advance_time(951);
|
||||||
|
assert!(bucket.get_tokens(10), "Last 10 tokens should be available.");
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_many() {
|
||||||
|
Instant::set_time(50_000);
|
||||||
|
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 0.5, 1.0);
|
||||||
|
assert!(
|
||||||
|
bucket.get_tokens(50),
|
||||||
|
"Should have not violated limit. i=-1."
|
||||||
|
);
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay. i=-1.");
|
||||||
|
for i in 0..20_000 {
|
||||||
|
Instant::advance_time(501);
|
||||||
|
assert!(
|
||||||
|
bucket.get_tokens(50),
|
||||||
|
"Should have not violated limit. i={}.",
|
||||||
|
i
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
None,
|
||||||
|
bucket.get_delay(),
|
||||||
|
"Bucket should have delay. i={}.",
|
||||||
|
i
|
||||||
|
);
|
||||||
|
Instant::advance_time(501);
|
||||||
|
assert!(
|
||||||
|
bucket.get_tokens(50),
|
||||||
|
"Should have not violated limit. i={}.",
|
||||||
|
i
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
None,
|
||||||
|
bucket.get_delay(),
|
||||||
|
"Bucket should have delay. i={}.",
|
||||||
|
i
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
bucket.timestamps.lock().len() < 110,
|
||||||
|
"Should not memory leak."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
use std::future::Future;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use reqwest::{Client, Method, RequestBuilder};
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
use tracing as log;
|
||||||
|
|
||||||
|
use crate::req::RegionalRequester;
|
||||||
|
use crate::util::InsertOnlyCHashMap;
|
||||||
|
use crate::{ResponseInfo, Result, RiotApiConfig, RiotApiError};
|
||||||
|
|
||||||
|
/// For retrieving data from the Riot Games API.
|
||||||
|
///
|
||||||
|
/// # Usage
|
||||||
|
///
|
||||||
|
/// Construct an instance using [`RiotApi::new(api_key or config)`](RiotApi::new).
|
||||||
|
/// The parameter may be a Riot API key string or a [`RiotApiConfig`]. Riot API
|
||||||
|
/// keys are obtained from the [Riot Developer Portal](https://developer.riotgames.com/)
|
||||||
|
/// and look like `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`.
|
||||||
|
///
|
||||||
|
/// An instance provides access to "endpoint handles" which in turn provide
|
||||||
|
/// access to individual API method calls. For example, to get a summoner by
|
||||||
|
/// name we first access the [`summoner_v4()`](RiotApi::summoner_v4) endpoints
|
||||||
|
/// then call the [`get_by_summoner_name()`](crate::endpoints::SummonerV4::get_by_summoner_name)
|
||||||
|
/// method:
|
||||||
|
/// ```ignore
|
||||||
|
/// riot_api.summoner_v4().get_by_summoner_name(Region::NA, "LugnutsK")
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Rate Limiting
|
||||||
|
///
|
||||||
|
/// The Riot Game API enforces _dynamic_ rate limiting, meaning that rate limits are
|
||||||
|
/// specified in response headers and can change at any time.
|
||||||
|
/// Riven keeps track of changing rate limits seamlessly, preventing you from
|
||||||
|
/// getting blacklisted.
|
||||||
|
///
|
||||||
|
/// Riven's rate limiting is highly efficient; it can use the full throughput
|
||||||
|
/// of your rate limit without triggering 429 errors.
|
||||||
|
///
|
||||||
|
/// To adjust rate limiting, see [RiotApiConfig] and use
|
||||||
|
/// [`RiotApi::new(config)`](RiotApi::new) to construct an instance.
|
||||||
|
pub struct RiotApi {
|
||||||
|
/// Configuration settings.
|
||||||
|
config: RiotApiConfig,
|
||||||
|
/// Client for making requests.
|
||||||
|
client: Client,
|
||||||
|
|
||||||
|
/// Per-region requesters.
|
||||||
|
regional_requesters: InsertOnlyCHashMap<&'static str, RegionalRequester>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RiotApi {
|
||||||
|
/// Constructs a new instance from an API key (e.g. `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`) or a [RiotApiConfig].
|
||||||
|
pub fn new(config: impl Into<RiotApiConfig>) -> Self {
|
||||||
|
let mut config = config.into();
|
||||||
|
let client_builder = config
|
||||||
|
.client_builder
|
||||||
|
.take()
|
||||||
|
.expect("CLIENT_BUILDER IN CONFIG SHOULD NOT BE NONE.");
|
||||||
|
Self {
|
||||||
|
config,
|
||||||
|
client: client_builder
|
||||||
|
.build()
|
||||||
|
.expect("Failed to create client from builder."),
|
||||||
|
regional_requesters: InsertOnlyCHashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||||
|
///
|
||||||
|
/// Creates a `RequestBuilder` instance with the given parameters, for use with the `execute*()` methods.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// * `method` - The HTTP method for this request.
|
||||||
|
/// * `region_platform` - The stringified platform, used to create the base URL.
|
||||||
|
/// * `path` - The URL path, appended to the base URL.
|
||||||
|
pub fn request(&self, method: Method, region_platform: &str, path: &str) -> RequestBuilder {
|
||||||
|
let base_url_platform = self.config.base_url.replace("{}", region_platform);
|
||||||
|
self.client
|
||||||
|
.request(method, format!("{}{}", base_url_platform, path))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||||
|
///
|
||||||
|
/// This sends a request based on the given parameters and returns a parsed result.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
|
||||||
|
/// * `region_platform` - The stringified platform, used in rate limiting.
|
||||||
|
/// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A future resolving to a `Result` containg either a `T` (success) or a `RiotApiError` (failure).
|
||||||
|
pub async fn execute_val<'a, T: serde::de::DeserializeOwned + 'a>(
|
||||||
|
&'a self,
|
||||||
|
method_id: &'static str,
|
||||||
|
region_platform: &'static str,
|
||||||
|
request: RequestBuilder,
|
||||||
|
) -> Result<T> {
|
||||||
|
let rinfo = self
|
||||||
|
.execute_raw(method_id, region_platform, request)
|
||||||
|
.await?;
|
||||||
|
let retries = rinfo.retries;
|
||||||
|
let status = rinfo.response.status();
|
||||||
|
let value = rinfo.response.json::<T>().await;
|
||||||
|
value.map_err(|e| RiotApiError::new(e, retries, None, Some(status)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||||
|
///
|
||||||
|
/// This sends a request based on the given parameters and returns an optional parsed result.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
|
||||||
|
/// * `region_platform` - The stringified platform, used in rate limiting.
|
||||||
|
/// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A future resolving to a `Result` containg either an `Option<T>` (success) or a `RiotApiError` (failure).
|
||||||
|
pub async fn execute_opt<'a, T: serde::de::DeserializeOwned + 'a>(
|
||||||
|
&'a self,
|
||||||
|
method_id: &'static str,
|
||||||
|
region_platform: &'static str,
|
||||||
|
request: RequestBuilder,
|
||||||
|
) -> Result<Option<T>> {
|
||||||
|
let rinfo = self
|
||||||
|
.execute_raw(method_id, region_platform, request)
|
||||||
|
.await?;
|
||||||
|
if rinfo.status_none {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let retries = rinfo.retries;
|
||||||
|
let status = rinfo.response.status();
|
||||||
|
let value = rinfo.response.json::<Option<T>>().await;
|
||||||
|
value.map_err(|e| RiotApiError::new(e, retries, None, Some(status)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||||
|
///
|
||||||
|
/// This sends a request based on the given parameters but does not deserialize any response body.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
|
||||||
|
/// * `region_platform` - The stringified platform, used in rate limiting.
|
||||||
|
/// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A future resolving to a `Result` containg either `()` (success) or a `RiotApiError` (failure).
|
||||||
|
pub async fn execute(
|
||||||
|
&self,
|
||||||
|
method_id: &'static str,
|
||||||
|
region_platform: &'static str,
|
||||||
|
request: RequestBuilder,
|
||||||
|
) -> Result<()> {
|
||||||
|
let rinfo = self
|
||||||
|
.execute_raw(method_id, region_platform, request)
|
||||||
|
.await?;
|
||||||
|
let retries = rinfo.retries;
|
||||||
|
let status = rinfo.response.status();
|
||||||
|
rinfo
|
||||||
|
.response
|
||||||
|
.error_for_status()
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|e| RiotApiError::new(e, retries, None, Some(status)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||||
|
///
|
||||||
|
/// This sends a request based on the given parameters and returns a raw `ResponseInfo`.
|
||||||
|
///
|
||||||
|
/// This can be used to implement a Riot API proxy without needing to deserialize and reserialize JSON responses.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
|
||||||
|
/// * `region_platform` - The stringified platform, used in rate limiting.
|
||||||
|
/// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A future resolving to a `Result` containg either a `ResponseInfo` (success) or a `RiotApiError` (failure).
|
||||||
|
pub fn execute_raw(
|
||||||
|
&self,
|
||||||
|
method_id: &'static str,
|
||||||
|
region_platform: &'static str,
|
||||||
|
request: RequestBuilder,
|
||||||
|
) -> impl Future<Output = Result<ResponseInfo>> + '_ {
|
||||||
|
self.regional_requester(region_platform)
|
||||||
|
.execute(&self.config, method_id, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get or create the RegionalRequester for the given region.
|
||||||
|
fn regional_requester(&self, region_platform: &'static str) -> Arc<RegionalRequester> {
|
||||||
|
self.regional_requesters
|
||||||
|
.get_or_insert_with(region_platform, || {
|
||||||
|
log::debug!(
|
||||||
|
"Creating requester for region platform {}.",
|
||||||
|
region_platform
|
||||||
|
);
|
||||||
|
RegionalRequester::new()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ impl<K: Hash + Eq, V> InsertOnlyCHashMap<K, V> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
base: Mutex::new(HashMap::new())
|
base: Mutex::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,10 +27,12 @@ impl<K: Hash + Eq, V> InsertOnlyCHashMap<K, V> {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_or_insert_with<F: FnOnce() -> V>(&self, key: K, default: F) -> Arc<V>
|
pub fn get_or_insert_with<F: FnOnce() -> V>(&self, key: K, default: F) -> Arc<V> {
|
||||||
{
|
Arc::clone(
|
||||||
Arc::clone(self.base.lock()
|
self.base
|
||||||
|
.lock()
|
||||||
.entry(key)
|
.entry(key)
|
||||||
.or_insert_with(|| Arc::new(default())))
|
.or_insert_with(|| Arc::new(default())),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules/
|
||||||
|
.*.json
|
|
@ -0,0 +1,166 @@
|
||||||
|
{{
|
||||||
|
const dotUtils = require('./dotUtils.js');
|
||||||
|
const champions = require('./.champion.json')
|
||||||
|
.filter(({ id }) => id > 0)
|
||||||
|
.sortBy(({ name }) => name);
|
||||||
|
|
||||||
|
const constName = name => dotUtils.changeCase.constantCase(name).replace(/[^_A-Z0-9]+/g, '');
|
||||||
|
const constNamePad = 12;
|
||||||
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
|
newtype_enum! {
|
||||||
|
/// A League of Legends champion.
|
||||||
|
///
|
||||||
|
/// This newtype acts as a C-like enum; each variant corresponds to an
|
||||||
|
/// integer value. Using a newtype allows _unknown_ variants to be
|
||||||
|
/// represented. This is important when Riot adds new champions.
|
||||||
|
///
|
||||||
|
/// Field | Name | Identifier | Id
|
||||||
|
/// ---|---|---|---
|
||||||
|
/// `NONE` | None (no ban) | | -1
|
||||||
|
{{
|
||||||
|
for (const { id, alias, name } of champions) {
|
||||||
|
}}
|
||||||
|
/// `{{= constName(name) }}` | "{{= name }}" | "{{= alias }}" | {{= id }}
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
pub newtype_enum Champion(i16) {
|
||||||
|
/// `-1`, none. Appears when a champion ban is not used in champ select.
|
||||||
|
NONE = -1,
|
||||||
|
|
||||||
|
{{
|
||||||
|
for (const { id, alias, name } of champions) {
|
||||||
|
}}
|
||||||
|
/// `{{= id }}`.
|
||||||
|
{{= constName(name) }} = {{= id }},
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Champion {
|
||||||
|
/// The champion's name (`en_US` localization).
|
||||||
|
pub const fn name(self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
{{
|
||||||
|
for (const { name } of champions) {
|
||||||
|
}}
|
||||||
|
Self::{{= constName(name).padEnd(constNamePad) }} => Some("{{= name }}"),
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The champion's identifier key. Somtimes called "key", "identifier", or "alias".
|
||||||
|
/// This is mainly used in DDragon paths.
|
||||||
|
///
|
||||||
|
/// This is generally the `en_US` name with spaces and punctuation removed,
|
||||||
|
/// capitalization preserved, however the follow are exceptions:
|
||||||
|
///
|
||||||
|
/// Field | Name | Identifier | Id
|
||||||
|
/// ---|---|---|---
|
||||||
|
{{
|
||||||
|
for (const { id, alias, name } of champions) {
|
||||||
|
if (name.replace(/[^a-zA-Z0-9]+/, '') !== alias) {
|
||||||
|
}}
|
||||||
|
/// `{{= constName(name) }}` | "{{= name }}" | "{{= alias }}" | {{= id }}
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
pub const fn identifier(self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
{{
|
||||||
|
for (const { name, alias } of champions) {
|
||||||
|
}}
|
||||||
|
Self::{{= constName(name).padEnd(constNamePad) }} => Some("{{= alias }}"),
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// https://github.com/MingweiSamuel/Riven/issues/36
|
||||||
|
pub(crate) fn serialize_result<S>(
|
||||||
|
val: &Result<Self, std::num::TryFromIntError>,
|
||||||
|
serializer: S,
|
||||||
|
) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::ser::Serializer,
|
||||||
|
{
|
||||||
|
use serde::ser::Serialize;
|
||||||
|
val.unwrap_or(Champion(-1)).serialize(serializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// https://github.com/MingweiSamuel/Riven/issues/36
|
||||||
|
pub(crate) fn deserialize_result<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<Result<Self, std::num::TryFromIntError>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
use std::convert::TryInto;
|
||||||
|
<i64 as serde::de::Deserialize>::deserialize(deserializer).map(|id| id.try_into().map(Self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error used for failures in [`Champion`]'s
|
||||||
|
/// [`FromStr`](std::str::FromStr) implementation.
|
||||||
|
///
|
||||||
|
/// Currently only internally stores the four characters used to parse the
|
||||||
|
/// champion, but may change in the future.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ParseChampionError([char; 4]);
|
||||||
|
impl std::fmt::Display for ParseChampionError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
let s: String = self.0.iter().copied().take_while(|&c| '\0' != c).collect();
|
||||||
|
write!(f, "Failed to parse unknown champion prefix: {:?}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for ParseChampionError {}
|
||||||
|
|
||||||
|
impl std::str::FromStr for Champion {
|
||||||
|
type Err = ParseChampionError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut chars = ['\0'; 4];
|
||||||
|
s.chars()
|
||||||
|
.take(4)
|
||||||
|
.filter(|c| c.is_ascii_alphanumeric())
|
||||||
|
.map(|c| c.to_ascii_uppercase())
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, c)| chars[i] = c);
|
||||||
|
match chars {
|
||||||
|
{{
|
||||||
|
const keyStrings = (name, alias) => new Set([].concat(...[ name, alias ].map(s => s.toUpperCase())
|
||||||
|
.map(s => [
|
||||||
|
s.replace(/[^A-Z0-9]+/, '').substring(0, 4),
|
||||||
|
s.split(/[^A-Z0-9]/, 1)[0].substring(0, 4),
|
||||||
|
s.split(/[^A-Z]/, 1)[0].substring(0, 4),
|
||||||
|
])));
|
||||||
|
for (const { id, alias, name } of champions) {
|
||||||
|
for (const prefix of keyStrings(name, alias)) {
|
||||||
|
const chars = Object.assign(Array(4).fill('\\0'), Array.from(prefix))
|
||||||
|
.map(c => `'${c}'`)
|
||||||
|
.map(c => c.padStart(4));
|
||||||
|
}}
|
||||||
|
/* {{= prefix.padEnd(4) }} */ [{{= chars.join(', ') }}] => Ok(Champion::{{= constName(name) }}),
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
unknown => Err(ParseChampionError(unknown)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::TryFrom<&str> for Champion {
|
||||||
|
type Error = <Self as std::str::FromStr>::Err;
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
<Self as std::str::FromStr>::from_str(value)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,22 @@
|
||||||
{{
|
{{
|
||||||
const dotUtils = require('./dotUtils.js');
|
const dotUtils = require('./dotUtils.js');
|
||||||
const gameModes = require('./.gameModes.json');
|
const gameModes = require('./.gameModes.json');
|
||||||
}}{{= dotUtils.preamble() }}
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
use strum_macros::{ EnumString, EnumVariantNames, IntoStaticStr };
|
||||||
|
|
||||||
/// League of Legends game mode, such as Classic,
|
/// League of Legends game mode, such as Classic,
|
||||||
/// ARAM, URF, One For All, Ascension, etc.
|
/// ARAM, URF, One For All, Ascension, etc.
|
||||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
#[derive(EnumString, EnumVariantNames, IntoStaticStr)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum GameMode {
|
pub enum GameMode {
|
||||||
|
/// Catch-all variant for new, unknown game modes.
|
||||||
|
#[strum(default)]
|
||||||
|
UNKNOWN(String),
|
||||||
|
|
||||||
{{
|
{{
|
||||||
for (const e of gameModes) {
|
for (const e of gameModes) {
|
||||||
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||||
|
@ -26,4 +30,4 @@ pub enum GameMode {
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
serde_string!(GameMode);
|
serde_strum_unknown!(GameMode);
|
|
@ -1,27 +1,33 @@
|
||||||
{{
|
{{
|
||||||
const dotUtils = require('./dotUtils.js');
|
const dotUtils = require('./dotUtils.js');
|
||||||
const gameTypes = require('./.gameTypes.json');
|
const gameTypes = require('./.gameTypes.json');
|
||||||
}}{{= dotUtils.preamble() }}
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
|
use serde::{ Serialize, Deserialize };
|
||||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
|
|
||||||
/// League of Legends game type: matched game, custom game, or tutorial game.
|
/// League of Legends game type: matched game, custom game, or tutorial game.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum GameType {
|
pub enum GameType {
|
||||||
{{
|
{{
|
||||||
for (const e of gameTypes) {
|
for (const e of gameTypes) {
|
||||||
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||||
|
const nameNoGame = e['x-name'].replace(/_GAME$/, "");
|
||||||
}}
|
}}
|
||||||
{{~ desc :line }}
|
{{~ desc :line }}
|
||||||
/// {{= line }}
|
/// {{= line }}
|
||||||
{{~}}
|
{{~}}
|
||||||
|
#[strum(to_string = "{{= e['x-name'] }}", serialize = "{{= nameNoGame }}")]
|
||||||
|
#[serde(alias = "{{= nameNoGame }}")]
|
||||||
{{= e['x-name'] }},
|
{{= e['x-name'] }},
|
||||||
{{
|
{{
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
serde_string!(GameType);
|
#[cfg(test)]
|
||||||
|
mod test;
|
|
@ -0,0 +1,22 @@
|
||||||
|
{{
|
||||||
|
const dotUtils = require('./dotUtils.js');
|
||||||
|
const maps = require('./.maps.json');
|
||||||
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
|
newtype_enum! {
|
||||||
|
/// A League of Legends map.
|
||||||
|
pub newtype_enum Map(u8) {
|
||||||
|
{{
|
||||||
|
for (const e of maps) {
|
||||||
|
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||||
|
}}
|
||||||
|
/// `{{= e['x-value'] }}`.
|
||||||
|
{{~ desc :line }}
|
||||||
|
/// {{= line }}
|
||||||
|
{{~}}
|
||||||
|
{{= e['x-name'] }} = {{= e['x-value'] }},
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
{{
|
||||||
|
const dotUtils = require('./dotUtils.js');
|
||||||
|
const queues = require('./.queues.json');
|
||||||
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
|
newtype_enum! {
|
||||||
|
/// A League of Legends matchmaking queue.
|
||||||
|
pub newtype_enum Queue(u16) {
|
||||||
|
{{
|
||||||
|
for (const e of queues) {
|
||||||
|
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||||
|
}}
|
||||||
|
/// `{{= e['x-value'] }}`.
|
||||||
|
{{~ desc :line }}
|
||||||
|
/// {{= line }}
|
||||||
|
{{~}}
|
||||||
|
{{? e.notes }}
|
||||||
|
///
|
||||||
|
/// {{= e.notes }}
|
||||||
|
{{?}}
|
||||||
|
{{? e['x-deprecated'] }}
|
||||||
|
#[deprecated(note="{{= e.notes }}")]
|
||||||
|
{{?}}
|
||||||
|
{{= e['x-name'] }} = {{= e['x-value'] }},
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
{{
|
||||||
|
const dotUtils = require('./dotUtils.js');
|
||||||
|
const queueTypes = require('./.queueTypes.json');
|
||||||
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
|
use strum_macros::{ EnumString, EnumVariantNames, IntoStaticStr };
|
||||||
|
|
||||||
|
/// LoL or TFT ranked queue types.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
|
#[derive(EnumString, EnumVariantNames, IntoStaticStr)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum QueueType {
|
||||||
|
/// Catch-all variant for new, unknown queue types.
|
||||||
|
#[strum(default)]
|
||||||
|
UNKNOWN(String),
|
||||||
|
|
||||||
|
{{
|
||||||
|
for (const e of queueTypes) {
|
||||||
|
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||||
|
}}
|
||||||
|
{{~ desc :line }}
|
||||||
|
/// {{= line }}
|
||||||
|
{{~}}
|
||||||
|
{{? e['x-deprecated'] }}
|
||||||
|
/// {{= e.notes }}
|
||||||
|
#[deprecated(note="{{= e.notes }}")]
|
||||||
|
{{?}}
|
||||||
|
{{= e['x-name'] }},
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_strum_unknown!(QueueType);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
|
@ -0,0 +1,196 @@
|
||||||
|
{{
|
||||||
|
const dotUtils = require('./dotUtils.js');
|
||||||
|
const routesTable = require('./.routesTable.json');
|
||||||
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
|
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||||
|
use strum_macros::{ EnumString, EnumIter, Display, IntoStaticStr };
|
||||||
|
|
||||||
|
/// Regional routes, used in tournament services, Legends of Runeterra (LoR), and other some endpoints.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
|
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum RegionalRoute {
|
||||||
|
{{
|
||||||
|
for (const [ name, { id, description, deprecated } ] of Object.entries(routesTable['regional'])) {
|
||||||
|
const desc = description.split('\n');
|
||||||
|
}}
|
||||||
|
{{~ desc :line }}
|
||||||
|
/// {{= line }}
|
||||||
|
{{~}}
|
||||||
|
///
|
||||||
|
/// `{{= id }}` (riotapi-schema ID/repr)
|
||||||
|
{{? deprecated }}
|
||||||
|
#[deprecated]
|
||||||
|
{{?}}
|
||||||
|
{{= name.toUpperCase() }} = {{= id }},
|
||||||
|
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Platform routes for League of Legends (LoL), Teamfight Tactics (TFT), and Legends of Runeterra (LoR).
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
|
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
// Note: strum(serialize = ...) actually specifies extra DEserialization values.
|
||||||
|
pub enum PlatformRoute {
|
||||||
|
{{
|
||||||
|
for (const [ name, { id, description, altName, deprecated } ] of Object.entries(routesTable['platform'])) {
|
||||||
|
const desc = description.split('\n');
|
||||||
|
}}
|
||||||
|
{{~ desc :line }}
|
||||||
|
/// {{= line }}
|
||||||
|
{{~}}
|
||||||
|
///
|
||||||
|
/// `{{= id }}` (riotapi-schema ID/repr)
|
||||||
|
{{? deprecated }}
|
||||||
|
#[deprecated]
|
||||||
|
{{?}}
|
||||||
|
{{? altName }}
|
||||||
|
#[strum(to_string="{{= name.toUpperCase() }}", serialize="{{= altName }}")]
|
||||||
|
{{?}}
|
||||||
|
{{= name.toUpperCase() }} = {{= id }},
|
||||||
|
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlatformRoute {
|
||||||
|
/// Converts this [`PlatformRoute`] into its corresponding
|
||||||
|
/// [`RegionalRoute`] for LoL and TFT match endpoints.
|
||||||
|
/// For example, [`match-v5`](crate::endpoints::MatchV5).
|
||||||
|
pub fn to_regional(self) -> RegionalRoute {
|
||||||
|
match self {
|
||||||
|
{{
|
||||||
|
for (const [ name, { regionalRoute } ] of Object.entries(routesTable['platform'])) {
|
||||||
|
}}
|
||||||
|
Self::{{= name.toUpperCase() }} => RegionalRoute::{{= regionalRoute.toUpperCase() }},
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts this [`PlatformRoute`] into its corresponding
|
||||||
|
/// [`RegionalRoute`] for LoR endpoints.
|
||||||
|
/// For example, [`lor-match-v1`](crate::endpoints::LorMatchV1).
|
||||||
|
pub fn to_regional_lor(self) -> RegionalRoute {
|
||||||
|
match self {
|
||||||
|
{{
|
||||||
|
for (const [ name, { regionalRouteLor } ] of Object.entries(routesTable['platform'])) {
|
||||||
|
}}
|
||||||
|
Self::{{= name.toUpperCase() }} => RegionalRoute::{{= regionalRouteLor.toUpperCase() }},
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used in the LoL Tournament API. Specifically
|
||||||
|
/// [`tournament-stub-v4.registerProviderData`](crate::endpoints::TournamentStubV4::register_provider_data)
|
||||||
|
/// and [`tournament-v4.registerProviderData`](crate::endpoints::TournamentV4::register_provider_data).
|
||||||
|
pub fn to_tournament_region(self) -> Option<TournamentRegion> {
|
||||||
|
match self {
|
||||||
|
{{
|
||||||
|
for (const [ name, { tournamentRegion } ] of Object.entries(routesTable['platform'])) {
|
||||||
|
if (!tournamentRegion) continue;
|
||||||
|
}}
|
||||||
|
Self::{{= name.toUpperCase() }} => Some(TournamentRegion::{{= tournamentRegion }}),
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
_other => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the slightly more human-friendly alternate name for this `PlatformRoute`. Specifically
|
||||||
|
/// excludes any trailing numbers and appends extra N(orth), S(outh), E(ast), and/or W(est)
|
||||||
|
/// suffixes to some names. Some of these are old region names which are often still used as
|
||||||
|
/// user-facing names, e.g. on op.gg.
|
||||||
|
///
|
||||||
|
/// Note these strings *are* handled by the `FromStr` implementation, if you wish to parse them
|
||||||
|
/// back into `PlatformRoute`s.
|
||||||
|
pub fn as_region_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
{{
|
||||||
|
for (const [ name, { altName } ] of Object.entries(routesTable['platform'])) {
|
||||||
|
if (!altName) continue;
|
||||||
|
}}
|
||||||
|
Self::{{= name.toUpperCase() }} => "{{= altName }}",
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
other => other.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Platform routes for Valorant.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
|
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ValPlatformRoute {
|
||||||
|
{{
|
||||||
|
for (const [ name, { id, description, deprecated } ] of Object.entries(routesTable['val-platform'])) {
|
||||||
|
const desc = description.split('\n');
|
||||||
|
}}
|
||||||
|
{{~ desc :line }}
|
||||||
|
/// {{= line }}
|
||||||
|
{{~}}
|
||||||
|
///
|
||||||
|
/// `{{= id }}` (riotapi-schema ID/repr)
|
||||||
|
{{? deprecated }}
|
||||||
|
#[deprecated]
|
||||||
|
{{?}}
|
||||||
|
{{= name.toUpperCase() }} = {{= id }},
|
||||||
|
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tournament regions for League of Legends (LoL) used in
|
||||||
|
/// [`tournament-stub-v4.registerProviderData`](crate::endpoints::TournamentStubV4::register_provider_data)
|
||||||
|
/// and [`tournament-v4.registerProviderData`](crate::endpoints::TournamentV4::register_provider_data).
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
|
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
// Note: strum(serialize = ...) actually specifies extra DEserialization values.
|
||||||
|
pub enum TournamentRegion {
|
||||||
|
{{
|
||||||
|
for (const [ name, { id, description, tournamentRegion, deprecated } ] of Object.entries(routesTable['platform'])) {
|
||||||
|
if (tournamentRegion) {
|
||||||
|
const desc = description.split('\n');
|
||||||
|
}}
|
||||||
|
{{~ desc :line }}
|
||||||
|
/// {{= line }}
|
||||||
|
{{~}}
|
||||||
|
{{? deprecated }}
|
||||||
|
#[deprecated]
|
||||||
|
{{?}}
|
||||||
|
{{= tournamentRegion }} = {{= id }},
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
{{
|
||||||
|
const dotUtils = require('./dotUtils.js');
|
||||||
|
const seasons = require('./.seasons.json');
|
||||||
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
|
newtype_enum! {
|
||||||
|
/// A League of Legends season for competitive matchmaking.
|
||||||
|
pub newtype_enum Season(u8) {
|
||||||
|
{{
|
||||||
|
for (const e of seasons) {
|
||||||
|
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||||
|
}}
|
||||||
|
/// `{{= e['x-value'] }}`.
|
||||||
|
{{~ desc :line }}
|
||||||
|
/// {{= line }}
|
||||||
|
{{~}}
|
||||||
|
{{= e['x-name'] }} = {{= e['x-value'] }},
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,5 @@
|
||||||
const changeCase = require('change-case');
|
const changeCase = require('change-case');
|
||||||
|
|
||||||
const enumTypeLookup = {
|
|
||||||
champion: 'i16',
|
|
||||||
gameMode: 'u8',
|
|
||||||
gameType: 'u8',
|
|
||||||
map: 'u8',
|
|
||||||
queue: 'u16',
|
|
||||||
season: 'u8',
|
|
||||||
};
|
|
||||||
|
|
||||||
// flatMap: https://gist.github.com/samgiles/762ee337dff48623e729
|
// flatMap: https://gist.github.com/samgiles/762ee337dff48623e729
|
||||||
// [B](f: (A) ⇒ [B]): [B] ; Although the types in the arrays aren't strict (:
|
// [B](f: (A) ⇒ [B]): [B] ; Although the types in the arrays aren't strict (:
|
||||||
Array.prototype.flatMap = function(lambda) {
|
Array.prototype.flatMap = function(lambda) {
|
||||||
|
@ -37,6 +28,7 @@ Array.prototype.sortBy = function(lambda) {
|
||||||
|
|
||||||
function preamble() {
|
function preamble() {
|
||||||
return `\
|
return `\
|
||||||
|
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
///////////////////////////////////////////////
|
///////////////////////////////////////////////
|
||||||
// //
|
// //
|
||||||
// ! //
|
// ! //
|
||||||
|
@ -65,7 +57,9 @@ function normalizeArgName(name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePropName(propName) {
|
function normalizePropName(propName) {
|
||||||
const out = changeCase.snakeCase(propName);
|
let out = changeCase.snakeCase(propName);
|
||||||
|
if (/^\d/.test(out)) // No leading digits.
|
||||||
|
out = 'x' + out;
|
||||||
if ('type' === out)
|
if ('type' === out)
|
||||||
return 'r#' + out;
|
return 'r#' + out;
|
||||||
return out;
|
return out;
|
||||||
|
@ -107,47 +101,48 @@ function formatJsonProperty(name) {
|
||||||
return `#[serde(rename = "${name}")]`;
|
return `#[serde(rename = "${name}")]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatQueryParamStringify(name, prop, useOwned = false) {
|
function formatAddQueryParam(param) {
|
||||||
const own = useOwned ? '' : '&*';
|
const k = `"${param.name}"`;
|
||||||
if (prop['x-enum']) {
|
const name = normalizePropName(param.name);
|
||||||
|
const condStart = param.required ? '' : `if let Some(${name}) = ${name} { `;
|
||||||
|
const condEnd = param.required ? '' : ' } else { request }'
|
||||||
|
const prop = param.schema;
|
||||||
switch (prop.type) {
|
switch (prop.type) {
|
||||||
case 'integer':
|
case 'array': return `let request = ${condStart}request.query(&*${name}.iter()`
|
||||||
return `${own}Into::<${enumTypeLookup[prop['x-enum']]}>::into(*${name}).to_string()`;
|
+ `.map(|w| ( ${k}, w )).collect::<Vec<_>>())${condEnd};`;
|
||||||
default: throw new Error(`Enum not supported: ${JSON.stringify(prop)}.`)
|
case 'object':
|
||||||
}
|
throw 'unsupported';
|
||||||
}
|
default:
|
||||||
switch (prop.type) {
|
return `let request = ${condStart}request.query(&[ (${k}, ${name}) ])${condEnd};`;
|
||||||
case 'array': throw new Error(`Cannot formart array: ${JSON.stringify(prop)}.`);
|
|
||||||
case 'boolean': return `${name} ? "true" : "false"`;
|
|
||||||
case 'string': return name;
|
|
||||||
default: return `${own}${name}.to_string()`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatAddQueryParam(param) {
|
function formatAddHeaderParam(param) {
|
||||||
let k = `"${param.name}"`;
|
const k = `"${param.name}"`;
|
||||||
let name = changeCase.snakeCase(param.name);
|
const name = changeCase.snakeCase(param.name);
|
||||||
let nc = param.required ? '' : `if let Some(${name}) = ${name} `;
|
const condStart = param.required ? '' : `mut request = request; if let Some(${name}) = ${name} { `;
|
||||||
let prop = param.schema;
|
const condEnd = param.required ? '' : ' }'
|
||||||
|
const prop = param.schema;
|
||||||
switch (prop.type) {
|
switch (prop.type) {
|
||||||
case 'array': return `${nc}{ query_params.extend_pairs(${name}.iter()`
|
case 'string':
|
||||||
+ `.map(|w| (${k}, ${formatQueryParamStringify("w", prop.items, true)}))); }`;
|
return `let ${condStart}request = request.header(${k}, ${name});${condEnd}`;
|
||||||
case 'object': throw 'unsupported';
|
case 'object':
|
||||||
|
throw 'unsupported';
|
||||||
default:
|
default:
|
||||||
return `${nc}{ query_params.append_pair(${k}, ${formatQueryParamStringify(name, prop)}); }`;
|
return `let ${condStart}request = request.header(${k}, ${name}.to_string());${condEnd}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatRouteArgument(route, pathParams = []) {
|
function formatRouteArgument(route, pathParams = []) {
|
||||||
if (!pathParams.length)
|
if (!pathParams.length)
|
||||||
return `"${route}".to_owned()`;
|
return `"${route}"`;
|
||||||
|
|
||||||
route = route.replace(/\{\S+?\}/g, '{}');
|
route = route.replace(/\{\S+?\}/g, '{}');
|
||||||
const args = pathParams
|
const args = pathParams
|
||||||
.map(({name}) => name)
|
.map(({name}) => name)
|
||||||
.map(changeCase.snakeCase)
|
.map(changeCase.snakeCase)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
return `format!("${route}", ${args})`;
|
return `&format!("${route}", ${args})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -161,5 +156,6 @@ module.exports = {
|
||||||
stringifyType,
|
stringifyType,
|
||||||
formatJsonProperty,
|
formatJsonProperty,
|
||||||
formatAddQueryParam,
|
formatAddQueryParam,
|
||||||
|
formatAddHeaderParam,
|
||||||
formatRouteArgument,
|
formatRouteArgument,
|
||||||
};
|
};
|
|
@ -0,0 +1,216 @@
|
||||||
|
{{
|
||||||
|
const spec = require('./.spec.json');
|
||||||
|
const dotUtils = require('./dotUtils.js');
|
||||||
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
|
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
||||||
|
// Version {{= spec.info.version }}
|
||||||
|
|
||||||
|
//! Automatically generated endpoint handles.
|
||||||
|
#![allow(clippy::let_and_return, clippy::too_many_arguments)]
|
||||||
|
|
||||||
|
use crate::models::*;
|
||||||
|
|
||||||
|
use std::future::Future;
|
||||||
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
#[cfg(feature="tracing")]
|
||||||
|
use tracing::Instrument;
|
||||||
|
use reqwest::Method;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
use crate::consts::{ RegionalRoute, PlatformRoute, ValPlatformRoute };
|
||||||
|
use crate::riot_api::RiotApi;
|
||||||
|
|
||||||
|
{{
|
||||||
|
const endpointGroups = {};
|
||||||
|
for (let path of Object.entries(spec.paths)) {
|
||||||
|
let ep = path[1]['x-endpoint'];
|
||||||
|
endpointGroups[ep] = endpointGroups[ep] || [];
|
||||||
|
endpointGroups[ep].push(path);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
impl RiotApi {
|
||||||
|
{{
|
||||||
|
for (const endpointName of Object.keys(endpointGroups)) {
|
||||||
|
const method = dotUtils.changeCase.snakeCase(endpointName);
|
||||||
|
const type = dotUtils.changeCase.pascalCase(endpointName);
|
||||||
|
}}
|
||||||
|
/// Returns a handle for accessing [{{= type }}](crate::endpoints::{{= type }}) endpoints.
|
||||||
|
/// # Riot Developer API Reference
|
||||||
|
/// <a href="https://developer.riotgames.com/apis#{{= endpointName }}" target="_blank">`{{= endpointName }}`</a>
|
||||||
|
///
|
||||||
|
/// Note: this method is automatically generated.
|
||||||
|
#[inline]
|
||||||
|
pub fn {{= method }}(&self) -> {{= type }} {
|
||||||
|
{{= type }} { base: self }
|
||||||
|
}
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
{{
|
||||||
|
for (let [ endpointName, endpointMethods ] of Object.entries(endpointGroups))
|
||||||
|
{
|
||||||
|
let endpoint = dotUtils.changeCase.pascalCase(endpointName);
|
||||||
|
const endpoint_snake_case = dotUtils.changeCase.snakeCase(endpointName);
|
||||||
|
}}
|
||||||
|
|
||||||
|
/// {{= endpoint }} endpoints handle, accessed by calling [`{{= endpoint_snake_case }}()`](crate::RiotApi::{{= endpoint_snake_case }}) on a [`RiotApi`](crate::RiotApi) instance.
|
||||||
|
/// # Riot Developer API Reference
|
||||||
|
/// <a href="https://developer.riotgames.com/apis#{{= endpointName }}" target="_blank">`{{= endpointName }}`</a>
|
||||||
|
///
|
||||||
|
/// Note: this struct is automatically generated.
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct {{= endpoint }}<'a> {
|
||||||
|
base: &'a RiotApi,
|
||||||
|
}
|
||||||
|
impl<'a> {{= endpoint }}<'a> {
|
||||||
|
{{
|
||||||
|
for (const [ route, path ] of endpointMethods)
|
||||||
|
{
|
||||||
|
for (const [ verb, operation ] of Object.entries(path))
|
||||||
|
{
|
||||||
|
if (verb.startsWith('x-')) continue;
|
||||||
|
|
||||||
|
const operationId = operation.operationId;
|
||||||
|
const method = dotUtils.changeCase.snakeCase(operationId.slice(operationId.indexOf('.') + 1));
|
||||||
|
|
||||||
|
const resp200 = operation.responses['200'];
|
||||||
|
|
||||||
|
/* Return type checks. */
|
||||||
|
let hasReturn = false;
|
||||||
|
let returnType = '()';
|
||||||
|
let returnTypeTurbofish = '';
|
||||||
|
let returnOptional = false;
|
||||||
|
if (resp200 && resp200.content)
|
||||||
|
{
|
||||||
|
hasReturn = true;
|
||||||
|
const jsonInfo = resp200.content['application/json'];
|
||||||
|
|
||||||
|
const parseType = dotUtils.stringifyType(jsonInfo.schema, { endpoint, fullpath: false });
|
||||||
|
returnTypeTurbofish = `::<${parseType}>`;
|
||||||
|
returnOptional = !!operation['x-nullable-404'];
|
||||||
|
returnType = returnOptional ? `Option<${parseType}>` : parseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body content checks. */
|
||||||
|
let bodyType = null;
|
||||||
|
if (operation.requestBody)
|
||||||
|
{
|
||||||
|
const jsonInfo = operation.requestBody.content['application/json'];
|
||||||
|
bodyType = dotUtils.stringifyType(jsonInfo.schema, { endpoint, fullpath: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Description processing. */
|
||||||
|
let descArr = operation.description.split('\n');
|
||||||
|
|
||||||
|
/* Build argument comment & string. */
|
||||||
|
const argBuilder = [
|
||||||
|
'route: ', dotUtils.changeCase.pascalCase(operation['x-route-enum']), 'Route'
|
||||||
|
];
|
||||||
|
|
||||||
|
/* Add body params before path/query. */
|
||||||
|
if (bodyType) {
|
||||||
|
argBuilder.push(', body: &', bodyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Path and query params. */
|
||||||
|
const allParams = operation.parameters;
|
||||||
|
let queryParams = [];
|
||||||
|
let headerParams = [];
|
||||||
|
let routeArgument;
|
||||||
|
if (allParams && allParams.length)
|
||||||
|
{
|
||||||
|
const pathParams = allParams.filter(p => 'path' === p.in)
|
||||||
|
.sortBy(({ name }) => route.indexOf(name));
|
||||||
|
|
||||||
|
const reqQueryParams = allParams.filter(p => 'query' === p.in && p.required);
|
||||||
|
const optQueryParams = allParams.filter(p => 'query' === p.in && !p.required)
|
||||||
|
.sortBy(({ name }) => {
|
||||||
|
let match = /(^[a-z]+|[A-Z]+(?![a-z])|[A-Z][a-z]+)/.exec(name);
|
||||||
|
return match.slice(1).reverse().join('');
|
||||||
|
});
|
||||||
|
queryParams = reqQueryParams.concat(optQueryParams);
|
||||||
|
|
||||||
|
headerParams = allParams.filter(p => 'header' === p.in);
|
||||||
|
|
||||||
|
for (let paramList of [ pathParams, reqQueryParams, optQueryParams, headerParams ])
|
||||||
|
{
|
||||||
|
const required = paramList === pathParams;
|
||||||
|
for (const param of paramList)
|
||||||
|
{
|
||||||
|
argBuilder.push(', ', dotUtils.normalizePropName(param.name), ': ',
|
||||||
|
dotUtils.stringifyType(param.schema, { endpoint, optional: !(required || param.required), owned: false }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
routeArgument = dotUtils.formatRouteArgument(route, pathParams);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
routeArgument = dotUtils.formatRouteArgument(route);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var descLine of descArr)
|
||||||
|
{
|
||||||
|
}}
|
||||||
|
///{{= descLine ? ' ' + descLine : '' }}
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/// # Parameters
|
||||||
|
/// * `route` - Route to query.
|
||||||
|
{{
|
||||||
|
if (allParams)
|
||||||
|
{
|
||||||
|
for (let param of allParams)
|
||||||
|
{
|
||||||
|
}}
|
||||||
|
/// * `{{= dotUtils.changeCase.snakeCase(param.name) }}` ({{= param.required ? 'required' : 'optional' }}, in {{= param.in }}){{= param.description ? ' - ' + param.description : ''}}
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/// # Riot Developer API Reference
|
||||||
|
/// <a href="{{= operation.externalDocs.url }}" target="_blank">`{{= operationId }}`</a>
|
||||||
|
///
|
||||||
|
/// Note: this method is automatically generated.
|
||||||
|
pub fn {{= method }}(&self, {{= argBuilder.join('') }})
|
||||||
|
-> impl Future<Output = Result<{{= returnType }}>> + 'a
|
||||||
|
{
|
||||||
|
let route_str = route.into();
|
||||||
|
let request = self.base.request(Method::{{= verb.toUpperCase() }}, route_str, {{= routeArgument }});
|
||||||
|
{{
|
||||||
|
for (let queryParam of queryParams)
|
||||||
|
{
|
||||||
|
}}
|
||||||
|
{{= dotUtils.formatAddQueryParam(queryParam) }}
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{{
|
||||||
|
for (const headerParam of headerParams)
|
||||||
|
{
|
||||||
|
}}
|
||||||
|
{{= dotUtils.formatAddHeaderParam(headerParam) }}
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{{? bodyType }}
|
||||||
|
let request = request.body(serde_json::ser::to_vec(body).unwrap());
|
||||||
|
{{?}}
|
||||||
|
let future = self.base.execute{{= hasReturn ? (returnOptional ? '_opt' : '_val') : '' }}{{= returnTypeTurbofish }}("{{= operationId }}", route_str, request);
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
let future = future.instrument(tracing::info_span!("{{= operationId }}"));
|
||||||
|
future
|
||||||
|
}
|
||||||
|
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
|
@ -23,6 +23,10 @@ const files = [
|
||||||
'http://www.mingweisamuel.com/riotapi-schema/enums/queues.json',
|
'http://www.mingweisamuel.com/riotapi-schema/enums/queues.json',
|
||||||
'.queues.json'
|
'.queues.json'
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'http://www.mingweisamuel.com/riotapi-schema/enums/queueTypes.json',
|
||||||
|
'.queueTypes.json'
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'http://www.mingweisamuel.com/riotapi-schema/enums/gameTypes.json',
|
'http://www.mingweisamuel.com/riotapi-schema/enums/gameTypes.json',
|
||||||
'.gameTypes.json'
|
'.gameTypes.json'
|
||||||
|
@ -34,8 +38,12 @@ const files = [
|
||||||
[
|
[
|
||||||
'http://www.mingweisamuel.com/riotapi-schema/enums/maps.json',
|
'http://www.mingweisamuel.com/riotapi-schema/enums/maps.json',
|
||||||
'.maps.json'
|
'.maps.json'
|
||||||
]
|
],
|
||||||
]
|
[
|
||||||
|
'http://www.mingweisamuel.com/riotapi-schema/routesTable.json',
|
||||||
|
'.routesTable.json'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
const downloadFilesPromise = Promise.all(files.map(([url, file]) => req(url)
|
const downloadFilesPromise = Promise.all(files.map(([url, file]) => req(url)
|
||||||
.then(body => fs.writeFileAsync(file, body, "utf8"))));
|
.then(body => fs.writeFileAsync(file, body, "utf8"))));
|
||||||
|
@ -72,6 +80,7 @@ downloadFilesPromise.then(() => glob.promise("**/*" + suffix, { ignore: ["**/nod
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error(`Error thrown while running "${file}":`, e);
|
console.error(`Error thrown while running "${file}":`, e);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(output => fs.writeFileAsync("../src/" + file.slice(0, -suffix.length), output, "utf8"))
|
.then(output => fs.writeFileAsync("../src/" + file.slice(0, -suffix.length), output, "utf8"))
|
|
@ -1,20 +1,24 @@
|
||||||
#![cfg_attr(feature = "nightly", feature(non_exhaustive))]
|
{{
|
||||||
#![cfg_attr(feature = "nightly", feature(external_doc))]
|
const dotUtils = require('./dotUtils.js');
|
||||||
|
const readme = require('fs').readFileSync('../../README.md', 'utf-8').split(/\r?\n/);
|
||||||
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
#![cfg_attr(feature = "nightly", doc(include = "../README.md"))]
|
{{~ readme :line }}
|
||||||
#![cfg_attr(not(feature = "nightly"), doc = "See [README.md](https://github.com/MingweiSamuel/Riven#readme).")]
|
//!{{= line ? (' ' + line) : '' }}
|
||||||
|
{{~}}
|
||||||
|
|
||||||
// Re-exported reqwest types.
|
// Re-exported reqwest types.
|
||||||
pub use reqwest;
|
pub use reqwest;
|
||||||
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
pub use config::RiotApiConfig;
|
pub use config::RiotApiConfig;
|
||||||
|
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
pub mod endpoints;
|
pub mod endpoints;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
|
@ -22,7 +26,9 @@ pub use error::*;
|
||||||
|
|
||||||
pub mod meta;
|
pub mod meta;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
mod models_impls;
|
||||||
|
|
||||||
mod req;
|
mod req;
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
{{
|
||||||
|
const spec = require('./.spec.json');
|
||||||
|
const dotUtils = require('./dotUtils.js');
|
||||||
|
|
||||||
|
const operations = [];
|
||||||
|
for (const [ route, path ] of Object.entries(spec.paths)) {
|
||||||
|
for (const [ method, operation ] of Object.entries(path)) {
|
||||||
|
if (method.startsWith('x-')) continue;
|
||||||
|
operations.push({ route, method, operation });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
|
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
||||||
|
// Version {{= spec.info.version }}
|
||||||
|
|
||||||
|
//! Metadata about the Riot API and Riven.
|
||||||
|
//!
|
||||||
|
//! Note: this modules is automatically generated.
|
||||||
|
|
||||||
|
/// Metadata for endpoints. Each tuple corresponds to one endpoint and contains
|
||||||
|
/// the HTTP [`Method`](reqwest::Method), `str` path, and the method's `str` ID.
|
||||||
|
pub static ALL_ENDPOINTS: [(reqwest::Method, &str, &str); {{= operations.length }}] = [
|
||||||
|
{{
|
||||||
|
for (const { route, method, operation } of operations) {
|
||||||
|
}}
|
||||||
|
(reqwest::Method::{{= method.toUpperCase() }}, "{{= route }}", "{{= operation.operationId }}"),
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
];
|
|
@ -6,6 +6,8 @@
|
||||||
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
||||||
// Version {{= spec.info.version }}
|
// Version {{= spec.info.version }}
|
||||||
|
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
//! Data transfer structs.
|
//! Data transfer structs.
|
||||||
//!
|
//!
|
||||||
//! Separated into separate modules for each endpoint.
|
//! Separated into separate modules for each endpoint.
|
||||||
|
@ -38,24 +40,37 @@ pub mod {{= dotUtils.changeCase.snakeCase(endpoint) }} {
|
||||||
/// {{= schemaName }} data object.
|
/// {{= schemaName }} data object.
|
||||||
{{? schema.description }}
|
{{? schema.description }}
|
||||||
/// # Description
|
/// # Description
|
||||||
/// {{= schema.description }}
|
/// {{= schema.description.split('\n').map(x => x.trim()).join('<br>\r\n /// ') }}
|
||||||
///
|
///
|
||||||
/// Note: This struct is automatically generated
|
/// Note: This struct is automatically generated
|
||||||
{{?}}
|
{{?}}
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
#[cfg_attr(feature = "deny-unknown-fields", serde(deny_unknown_fields))]
|
||||||
pub struct {{= schemaName }} {
|
pub struct {{= schemaName }} {
|
||||||
{{
|
{{
|
||||||
for (let [ propKey, prop ] of Object.entries(props))
|
for (let [ propKey, prop ] of Object.entries(props))
|
||||||
{
|
{
|
||||||
const name = dotUtils.normalizePropName(propKey);
|
const name = dotUtils.normalizePropName(propKey);
|
||||||
const required = requiredSet.has(propKey);
|
const optional = !requiredSet.has(propKey);
|
||||||
}}
|
}}
|
||||||
{{? prop.description }}
|
{{? prop.description }}
|
||||||
/// {{= prop.description.split('\n').map(x => x.trim()).join('<br>\r\n /// ') }}
|
/// {{= prop.description.split('\n').map(x => x.trim()).join('<br>\r\n /// ') }}
|
||||||
{{?}}
|
{{?}}
|
||||||
{{= dotUtils.formatJsonProperty(propKey) }}
|
{{= dotUtils.formatJsonProperty(propKey) }}
|
||||||
pub {{= name }}: {{= dotUtils.stringifyType(prop, { optional: !required }) }},
|
{{? optional }}
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
{{?}}
|
||||||
|
{{? 'championId' === propKey && (prop.description || '').includes('this field returned invalid championIds') }}
|
||||||
|
///
|
||||||
|
/// Instead use [`Self::champion()`] which checks this field then parses [`Self::champion_name`].
|
||||||
|
#[deprecated(since = "2.5.0", note = "Use `Participant.champion()` instead. Riot sometimes returns corrupted data for this field: https://github.com/RiotGames/developer-relations/issues/553")]
|
||||||
|
#[serde(serialize_with = "crate::consts::Champion::serialize_result")]
|
||||||
|
#[serde(deserialize_with = "crate::consts::Champion::deserialize_result")]
|
||||||
|
pub {{= name }}: Result<crate::consts::Champion, std::num::TryFromIntError>,
|
||||||
|
{{??}}
|
||||||
|
pub {{= name }}: {{= dotUtils.stringifyType(prop, { optional }) }},
|
||||||
|
{{?}}
|
||||||
{{
|
{{
|
||||||
}
|
}
|
||||||
}}
|
}}
|
File diff suppressed because it is too large
Load Diff
|
@ -65,7 +65,7 @@ macro_rules! rassert {
|
||||||
};
|
};
|
||||||
( $x:expr, $format:expr $(, $arg:expr)* ) => {
|
( $x:expr, $format:expr $(, $arg:expr)* ) => {
|
||||||
{
|
{
|
||||||
if $x { Ok(()) } else { Err( format!($format, $( $arg )* ) ) }?
|
if $x { Ok(()) } else { Err( format!($format $(, $arg )* ) ) }?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||||
|
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||||
|
|
||||||
|
mod async_tests;
|
||||||
|
mod testutils;
|
||||||
|
use colored::*;
|
||||||
|
use riven::consts::*;
|
||||||
|
use testutils::*;
|
||||||
|
|
||||||
|
const ROUTE: PlatformRoute = PlatformRoute::RU;
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
|
my_runner {
|
||||||
|
summoner_leagues: async {
|
||||||
|
let sum = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "d3atomiz3d");
|
||||||
|
let sum = sum.await
|
||||||
|
.map_err(|e| format!("Error getting summoner: {}", e))?
|
||||||
|
.ok_or_else(|| "Failed to find summoner".to_owned())?;
|
||||||
|
|
||||||
|
let p = RIOT_API.league_v4().get_league_entries_for_summoner(ROUTE, &sum.id);
|
||||||
|
let s = p.await.map_err(|e| format!("Error getting league entries: {}", e))?;
|
||||||
|
let _ = s;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||||
|
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||||
|
|
||||||
|
mod async_tests;
|
||||||
|
mod testutils;
|
||||||
|
use colored::*;
|
||||||
|
use riven::consts::*;
|
||||||
|
use riven::models::tournament_stub_v5::*;
|
||||||
|
use testutils::*;
|
||||||
|
|
||||||
|
const ROUTE: RegionalRoute = RegionalRoute::AMERICAS;
|
||||||
|
|
||||||
|
static MATCHES: &[&str] = &[
|
||||||
|
// New games with `match-v5.ParticipantDto.challenges` field.
|
||||||
|
"NA1_4209556127",
|
||||||
|
"NA1_4212715433",
|
||||||
|
"NA1_4265913704", // `match-v5.ParticipantDto.challenges.mejaisFullStackInTime`
|
||||||
|
];
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
|
my_runner {
|
||||||
|
// Account-v1
|
||||||
|
account_v1_getbyriotid_getbypuuid: async {
|
||||||
|
// Game name is case and whitespace insensitive.
|
||||||
|
// But tag cannot have spaces. (Is it case sensitive?).
|
||||||
|
let account_tag = RIOT_API.account_v1().get_by_riot_id(ROUTE, "Lug nuts K", "000")
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to get account by riot ID: {}", e))?
|
||||||
|
.ok_or("Riot account not found!".to_owned())?;
|
||||||
|
|
||||||
|
let account_puuid = RIOT_API.account_v1().get_by_puuid(ROUTE, &account_tag.puuid)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to get account by PUUID: {}", e))?;
|
||||||
|
|
||||||
|
let _ = account_puuid;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tournament stub test.
|
||||||
|
tournamentstub: async {
|
||||||
|
let ts = RIOT_API.tournament_stub_v5();
|
||||||
|
let provider_id = ts.register_provider_data(ROUTE, &ProviderRegistrationParametersV5 {
|
||||||
|
region: PlatformRoute::NA1.as_region_str().to_owned(),
|
||||||
|
url: "https://github.com/MingweiSamuel/Riven".to_owned(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
println!("provider_id: {}", provider_id);
|
||||||
|
|
||||||
|
let tournament_id = ts.register_tournament(ROUTE, &TournamentRegistrationParametersV5 {
|
||||||
|
name: Some("Riven Tourney :)".to_owned()),
|
||||||
|
provider_id,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
println!("tournament_id: {}", tournament_id);
|
||||||
|
|
||||||
|
let codes_result = ts.create_tournament_code(ROUTE, &TournamentCodeParametersV5 {
|
||||||
|
map_type: "SUMMONERS_RIFT".to_owned(),
|
||||||
|
metadata: Some("eW91IGZvdW5kIHRoZSBzZWNyZXQgbWVzc2FnZQ==".to_owned()),
|
||||||
|
pick_type: "TOURNAMENT_DRAFT".to_owned(),
|
||||||
|
spectator_type: "ALL".to_owned(),
|
||||||
|
team_size: 5,
|
||||||
|
allowed_participants: None,
|
||||||
|
enough_players: false,
|
||||||
|
}, tournament_id as i64, Some(300))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match codes_result {
|
||||||
|
Ok(codes) => {
|
||||||
|
rassert_eq!(300, codes.len());
|
||||||
|
println!("codes: {}", codes.join(", "));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(mut e) => {
|
||||||
|
if let Some(response) = e.take_response() {
|
||||||
|
eprintln!("{:?}", response.text().await);
|
||||||
|
}
|
||||||
|
Err(e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
match_v5_get: async {
|
||||||
|
match_v5_get(ROUTE, MATCHES).await
|
||||||
|
},
|
||||||
|
match_v5_get_timeline: async {
|
||||||
|
match_v5_get_timeline(ROUTE, MATCHES).await
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||||
|
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||||
|
|
||||||
|
mod async_tests;
|
||||||
|
mod testutils;
|
||||||
|
use colored::*;
|
||||||
|
use riven::consts::*;
|
||||||
|
use testutils::*;
|
||||||
|
|
||||||
|
const ROUTE: PlatformRoute = PlatformRoute::JP1;
|
||||||
|
|
||||||
|
static MATCHES: &[&str] = &[
|
||||||
|
// Only has participant IDs for blue team.
|
||||||
|
"JP1_391732436",
|
||||||
|
// New field `ParticipantChallenges` `twoWardsOneSweeperCount`
|
||||||
|
"JP1_397348569",
|
||||||
|
// New fields:
|
||||||
|
// `match-v5.ParticipantDto.playerAugment[1234],playerSubteamId,subteamPlacement`
|
||||||
|
"JP1_400700181",
|
||||||
|
// New field: `match-v5.ParticipantDto.placement`
|
||||||
|
"JP1_405073638",
|
||||||
|
// New ARENA 2v2v2v2 game mode, broken `subteamPlacement`
|
||||||
|
"KR_6604607115",
|
||||||
|
// New field: `match-v5.ParticipantDto.missions`
|
||||||
|
"JP1_417935351",
|
||||||
|
// New field: `match-v5.ParticipantDto.riotIdGameName`
|
||||||
|
"JP1_419115017",
|
||||||
|
];
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
|
my_runner {
|
||||||
|
// Summoner tests.
|
||||||
|
summoner_get_kanjikana: async {
|
||||||
|
let p = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "私の 頭が かたい");
|
||||||
|
let s = p.await.map_err(|e| e.to_string())?.ok_or_else(|| "Failed to get myheadhard".to_owned())?;
|
||||||
|
rassert_eq!("私の頭がかたい", s.name);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
|
||||||
|
// Failure cases.
|
||||||
|
// // Make sure get_raw_response(...) with invalid path fails as expected.
|
||||||
|
// raw_response_invalid: async {
|
||||||
|
// let p = RIOT_API.get_raw_response("summoner-v4.getBySummonerName", Region::JP.into(), "INVALID/PATH".to_owned(), None);
|
||||||
|
// let r = p.await;
|
||||||
|
// rassert!(r.is_err());
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
// summoner_v4().get_by_summoner_name(...) normally returns an option.
|
||||||
|
// If we use `get` (instead of `get_optional`) make sure it errors.
|
||||||
|
get_nonoptional_invalid: async {
|
||||||
|
let path_string = format!("/lol/summoner/v4/summoners/by-name/{}", "SUMMONER THAT DOES NOT EXIST");
|
||||||
|
let request = RIOT_API.request(reqwest::Method::GET, ROUTE.into(), &path_string);
|
||||||
|
let p = RIOT_API.execute_val::<riven::models::summoner_v4::Summoner>(
|
||||||
|
"summoner-v4.getBySummonerName", ROUTE.into(), request);
|
||||||
|
let r = p.await;
|
||||||
|
rassert!(r.is_err());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
// Make sure 403 is handled as expected.
|
||||||
|
tournament_forbidden: async {
|
||||||
|
let p = RIOT_API.tournament_v5().get_tournament_code(ROUTE.to_regional(), "INVALID_CODE");
|
||||||
|
let r = p.await;
|
||||||
|
rassert!(r.is_err());
|
||||||
|
rassert_eq!(Some(reqwest::StatusCode::FORBIDDEN), r.unwrap_err().status_code());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
|
||||||
|
// Disabled: Caihonbbt no longer ranked.
|
||||||
|
// // tft-league-v1.getLeagueEntriesForSummoner
|
||||||
|
// // https://github.com/MingweiSamuel/Riven/issues/25
|
||||||
|
// tft_league_getleagueentriesforsummoner: async {
|
||||||
|
// let sp = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "Caihonbbt");
|
||||||
|
// let sr = sp.await.map_err(|e| e.to_string())?.ok_or_else(|| "Failed to get \"Caihonbbt\"".to_owned())?;
|
||||||
|
// let lp = RIOT_API.tft_league_v1().get_league_entries_for_summoner(ROUTE, &sr.id);
|
||||||
|
// let lr = lp.await.map_err(|e| e.to_string())?;
|
||||||
|
// rassert!(!lr.is_empty());
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
// tft-league-v1.getTopRatedLadder
|
||||||
|
// https://github.com/MingweiSamuel/Riven/issues/24
|
||||||
|
tft_league_gettopratedladder: async {
|
||||||
|
let lp = RIOT_API.tft_league_v1().get_top_rated_ladder(ROUTE, QueueType::RANKED_TFT_TURBO);
|
||||||
|
let lr = lp.await.map_err(|e| e.to_string())?;
|
||||||
|
rassert!(!lr.is_empty());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
|
||||||
|
// ASIA regional tests
|
||||||
|
league_v4_match_v5_latest_combo: async {
|
||||||
|
league_v4_match_v5_latest_combo(ROUTE).await
|
||||||
|
},
|
||||||
|
match_v5_get: async {
|
||||||
|
match_v5_get(ROUTE.to_regional(), MATCHES).await
|
||||||
|
},
|
||||||
|
match_v5_get_timeline: async {
|
||||||
|
match_v5_get_timeline(ROUTE.to_regional(), MATCHES).await
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||||
|
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||||
|
|
||||||
|
mod async_tests;
|
||||||
|
mod testutils;
|
||||||
|
use colored::*;
|
||||||
|
use riven::consts::*;
|
||||||
|
use testutils::*;
|
||||||
|
|
||||||
|
const ROUTE: RegionalRoute = RegionalRoute::EUROPE;
|
||||||
|
|
||||||
|
// Archived 2023-08-17
|
||||||
|
// // Illegal big `championId`s. https://github.com/RiotGames/developer-relations/issues/553
|
||||||
|
// "EUW1_5097684633",
|
||||||
|
// "EUW1_5097963383",
|
||||||
|
// "EUW1_5102203800", // https://github.com/MingweiSamuel/Riven/issues/36
|
||||||
|
// "EUW1_5765650307", // https://gist.github.com/MingweiSamuel/d5f9dc40cc5a80a9255e488f27705c56?permalink_comment_id=4088256#gistcomment-4088256
|
||||||
|
|
||||||
|
static MATCHES: &[&str] = &[
|
||||||
|
// New ARENA 2v2v2v2 game mode
|
||||||
|
"EUW1_6511808246", // https://github.com/MingweiSamuel/Camille/issues/99
|
||||||
|
// Added 2023-08-27
|
||||||
|
"EUW1_6569580003",
|
||||||
|
"EUW1_6569417645",
|
||||||
|
"EUW1_6568707352",
|
||||||
|
"EUW1_6568635198",
|
||||||
|
"EUW1_6568537080",
|
||||||
|
];
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
|
my_runner {
|
||||||
|
match_v5_get: async {
|
||||||
|
match_v5_get(ROUTE, MATCHES).await
|
||||||
|
},
|
||||||
|
match_v5_get_timeline: async {
|
||||||
|
match_v5_get_timeline(ROUTE, MATCHES).await
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||||
|
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||||
|
|
||||||
|
mod async_tests;
|
||||||
|
mod testutils;
|
||||||
|
use colored::*;
|
||||||
|
use riven::consts::*;
|
||||||
|
use testutils::*;
|
||||||
|
|
||||||
|
const ROUTE: PlatformRoute = PlatformRoute::EUW1;
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
|
my_runner {
|
||||||
|
// Champion Mastery tests.
|
||||||
|
// SUMMONER ID ENDPOINT BROKEN: https://github.com/RiotGames/developer-relations/issues/830
|
||||||
|
// championmastery_getscore_ma5tery: async {
|
||||||
|
// let sum = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "ma5tery");
|
||||||
|
// let sum = sum.await
|
||||||
|
// .map_err(|e| format!("Error getting summoner: {}", e))?
|
||||||
|
// .ok_or_else(|| "Failed to find summoner".to_owned())?;
|
||||||
|
|
||||||
|
// let p = RIOT_API.champion_mastery_v4().get_champion_mastery_score(ROUTE, &sum.id);
|
||||||
|
// let s = p.await.map_err(|e| format!("Error getting champion mastery score: {}", e))?;
|
||||||
|
// rassert!((969..=1000).contains(&s), "Unexpected ma5tery score: {}.", s);
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
championmastery_getscore_ma5tery: async {
|
||||||
|
let sum = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "ma5tery");
|
||||||
|
let sum = sum.await
|
||||||
|
.map_err(|e| format!("Error getting summoner: {}", e))?
|
||||||
|
.ok_or_else(|| "Failed to find summoner".to_owned())?;
|
||||||
|
|
||||||
|
let p = RIOT_API.champion_mastery_v4().get_champion_mastery_score_by_puuid(ROUTE, &sum.puuid);
|
||||||
|
let s = p.await.map_err(|e| format!("Error getting champion mastery score: {}", e))?;
|
||||||
|
rassert!((969..=1000).contains(&s), "Unexpected ma5tery score: {}.", s);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
championmastery_getall_ma5tery: async {
|
||||||
|
let sum = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "ma5tery");
|
||||||
|
let sum = sum.await
|
||||||
|
.map_err(|e| format!("Error getting summoner: {}", e))?
|
||||||
|
.ok_or_else(|| "Failed to find summoner".to_owned())?;
|
||||||
|
|
||||||
|
let p = RIOT_API.champion_mastery_v4().get_all_champion_masteries_by_puuid(ROUTE, &sum.puuid);
|
||||||
|
let s = p.await.map_err(|e| format!("Error getting all champion masteries: {}", e))?;
|
||||||
|
rassert!(s.len() >= 142, "Expected masteries: {}.", s.len());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://github.com/RiotGames/developer-relations/issues/602
|
||||||
|
spectator_combo: async {
|
||||||
|
let featured_p = RIOT_API.spectator_v4().get_featured_games(ROUTE);
|
||||||
|
let featured = featured_p.await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
rassert!(!featured.game_list.is_empty());
|
||||||
|
|
||||||
|
// let summoner_name = &featured.game_list[0].participants[0].summoner_name;
|
||||||
|
// let summoner_p = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, summoner_name);
|
||||||
|
// let summoner = summoner_p.await.map_err(|e| e.to_string())?.ok_or_else(|| "Failed to find summoner".to_owned())?;
|
||||||
|
|
||||||
|
let featured_game = &featured.game_list[0];
|
||||||
|
let participant = &featured_game.participants[0];
|
||||||
|
let summoner_id = participant.summoner_id.as_ref()
|
||||||
|
.ok_or_else(|| format!("Summoner in spectator featured game missing summoner ID: {}", &participant.summoner_name))?;
|
||||||
|
|
||||||
|
let livegame_p = RIOT_API.spectator_v4().get_current_game_info_by_summoner(ROUTE, &summoner_id);
|
||||||
|
let livegame_o = livegame_p.await.map_err(|e| e.to_string())?;
|
||||||
|
if let Some(livegame) = livegame_o {
|
||||||
|
let participant_match = livegame.participants.iter().find(|p| p.summoner_name == participant.summoner_name);
|
||||||
|
rassert!(participant_match.is_some(), "Failed to find summoner in match: {}.", &participant.summoner_name);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||||
|
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||||
|
|
||||||
|
mod async_tests;
|
||||||
|
mod testutils;
|
||||||
|
use colored::*;
|
||||||
|
use riven::consts::*;
|
||||||
|
use testutils::*;
|
||||||
|
|
||||||
|
const ROUTE: PlatformRoute = PlatformRoute::EUW1;
|
||||||
|
|
||||||
|
static TFT_MATCHES: &[&str] = &[
|
||||||
|
"EUW1_6307427444", // https://github.com/MingweiSamuel/Riven/issues/50
|
||||||
|
"EUW1_6307262798",
|
||||||
|
// https://github.com/MingweiSamuel/Riven/pull/62
|
||||||
|
// https://github.com/MingweiSamuel/riotapi-schema/pull/43
|
||||||
|
"EUW1_6786745342",
|
||||||
|
];
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
|
my_runner {
|
||||||
|
tftmatchv1_get_list: async {
|
||||||
|
tft_match_v1_get(ROUTE.to_regional(), TFT_MATCHES).await
|
||||||
|
},
|
||||||
|
|
||||||
|
// // Don't have acecess to tft-status-v1.
|
||||||
|
// tftstatusv1_getplatformdata: async {
|
||||||
|
// let p = RIOT_API.tft_status_v1().get_platform_data(ROUTE);
|
||||||
|
// let _s = p.await.map_err(|e| e.to_string())?;
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
tftleaguev1_gettopratedladder: async {
|
||||||
|
let p = RIOT_API.tft_league_v1().get_top_rated_ladder(ROUTE, QueueType::RANKED_TFT_TURBO);
|
||||||
|
let l = p.await.map_err(|e| e.to_string())?;
|
||||||
|
rassert!(l.len() > 10, "Expected a few ranked players, got: {}.", l.len());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
tftmatchv1_getmatch: async {
|
||||||
|
let p = RIOT_API.tft_match_v1().get_match(ROUTE.to_regional(), "EUW1_6455483163");
|
||||||
|
let _m = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get TFT match.".to_owned())?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
tftsummonerv1_getbyname: async {
|
||||||
|
let p = RIOT_API.tft_summoner_v1().get_by_summoner_name(ROUTE, "相当猥琐");
|
||||||
|
let _s = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get TFT summoner.".to_owned())?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
tftsummonerv1_getbyname_none: async {
|
||||||
|
let p = RIOT_API.tft_summoner_v1().get_by_summoner_name(ROUTE, "this summoner does not exist");
|
||||||
|
rassert!(p.await.map_err(|e| e.to_string())?.is_none());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
// Get top rated player, get some of their matches.
|
||||||
|
tft_combo: async {
|
||||||
|
let top_players = RIOT_API.tft_league_v1().get_top_rated_ladder(ROUTE, QueueType::RANKED_TFT_TURBO);
|
||||||
|
let top_players = top_players.await.map_err(|e| e.to_string())?;
|
||||||
|
rassert!(!top_players.is_empty());
|
||||||
|
let top_player_entry = &top_players[0];
|
||||||
|
let top_player = RIOT_API.tft_summoner_v1().get_by_summoner_id(ROUTE, &top_player_entry.summoner_id);
|
||||||
|
let top_player = top_player.await.map_err(|e| e.to_string())?;
|
||||||
|
println!("Top player is {} with `puuid` {}.", top_player.name, top_player.puuid);
|
||||||
|
let match_ids = RIOT_API.tft_match_v1().get_match_ids_by_puuid(
|
||||||
|
ROUTE.to_regional(), &top_player.puuid, Some(10), None, None, None);
|
||||||
|
let match_ids = match_ids.await.map_err(|e| e.to_string())?;
|
||||||
|
tft_match_v1_get(ROUTE.to_regional(), &*match_ids).await?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,7 +36,7 @@ async_tests!{
|
||||||
REGION, &leagueentry.summoner_id);
|
REGION, &leagueentry.summoner_id);
|
||||||
summonerfuture.await
|
summonerfuture.await
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
.ok_or(format!("Failed to get summoner_id {}.",
|
.ok_or(format!("Failed to find summoner_id {}.",
|
||||||
leagueentry.summoner_id))
|
leagueentry.summoner_id))
|
||||||
});
|
});
|
||||||
future::join_all(summoners).await
|
future::join_all(summoners).await
|
|
@ -0,0 +1,88 @@
|
||||||
|
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||||
|
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||||
|
|
||||||
|
mod async_tests;
|
||||||
|
mod testutils;
|
||||||
|
use colored::*;
|
||||||
|
use riven::consts::*;
|
||||||
|
use testutils::RIOT_API;
|
||||||
|
|
||||||
|
const ROUTE: PlatformRoute = PlatformRoute::LA1;
|
||||||
|
|
||||||
|
/// en_US description: "As a laner, get kills before 10 minutes outside your lane (anyone but your lane opponent)"
|
||||||
|
const CHALLENGE_ID__ARAM_1K_DPM: i64 = 101101;
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
|
my_runner {
|
||||||
|
// /lol/challenges/v1/challenges/{challengeId}/leaderboards/by-level/{level}
|
||||||
|
// /lol/challenges/v1/player-data/{puuid}
|
||||||
|
lol_challenges_v1_leaderboards_playerdata: async {
|
||||||
|
let challenge_id = CHALLENGE_ID__ARAM_1K_DPM;
|
||||||
|
let leaderboard = RIOT_API.lol_challenges_v1()
|
||||||
|
.get_challenge_leaderboards(ROUTE, challenge_id, Tier::GRANDMASTER, None)
|
||||||
|
.await.map_err(|e| e.to_string())?
|
||||||
|
.ok_or_else(|| format!("Challenge leaderboard with id {} returned 404", challenge_id))?;
|
||||||
|
|
||||||
|
{
|
||||||
|
rassert!(!leaderboard.is_empty());
|
||||||
|
let start = leaderboard[0].position;
|
||||||
|
// Commented out: leaderboard is not monotonic for some reason.
|
||||||
|
// let mut val = leaderboard[0].value;
|
||||||
|
for (n, entry) in leaderboard.iter().enumerate() {
|
||||||
|
rassert_eq!(start + (n as i32), entry.position);
|
||||||
|
// rassert!(entry.val <= val);
|
||||||
|
// val = etnry.val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spot check 10% for `player-data`.
|
||||||
|
for entry in leaderboard.iter().step_by(10)
|
||||||
|
{
|
||||||
|
let _player_data = RIOT_API.lol_challenges_v1().get_player_data(ROUTE, &entry.puuid)
|
||||||
|
.await.map_err(|e| format!("Failed to get player data PUUID {}: {}", entry.puuid, e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
|
||||||
|
// /lol/challenges/v1/challenges/config
|
||||||
|
// /lol/challenges/v1/challenges/{challengeId}/config
|
||||||
|
lol_challenges_v1_check_configs: async {
|
||||||
|
let challenges = RIOT_API.lol_challenges_v1().get_all_challenge_configs(ROUTE)
|
||||||
|
.await.map_err(|e| e.to_string())?;
|
||||||
|
rassert!(!challenges.is_empty());
|
||||||
|
|
||||||
|
for challenge in challenges.iter() {
|
||||||
|
rassert!(!challenge.localized_names.is_empty());
|
||||||
|
rassert!(!challenge.thresholds.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spot-check 10% of the challenge IDs.
|
||||||
|
for challenge in challenges.iter().step_by(10) {
|
||||||
|
RIOT_API.lol_challenges_v1().get_challenge_configs(ROUTE, challenge.id)
|
||||||
|
.await.map_err(|e| format!("Failed to get challenge config with id {}\n{}", challenge.id, e))?
|
||||||
|
.ok_or_else(|| format!("Challenge config with id {} returned 404", challenge.id))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
|
||||||
|
// /lol/challenges/v1/challenges/percentiles
|
||||||
|
// /lol/challenges/v1/challenges/{challengeId}/percentiles
|
||||||
|
lol_challenges_v1_check_percentiles: async {
|
||||||
|
// Check all percentiles.
|
||||||
|
let percentiles = RIOT_API.lol_challenges_v1().get_all_challenge_percentiles(ROUTE)
|
||||||
|
.await.map_err(|e| e.to_string())?;
|
||||||
|
rassert!(!percentiles.is_empty());
|
||||||
|
|
||||||
|
// Spot-check 10% of the challenge IDs.
|
||||||
|
for &challenge_id in percentiles.keys().step_by(10) {
|
||||||
|
RIOT_API.lol_challenges_v1().get_challenge_percentiles(ROUTE, challenge_id)
|
||||||
|
.await.map_err(|e| format!("Failed to get challenge percentile with id {}\n{}", challenge_id, e))?
|
||||||
|
.ok_or_else(|| format!("Challenge percentile with id {} returned 404", challenge_id))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||||
|
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||||
|
|
||||||
|
mod async_tests;
|
||||||
|
mod testutils;
|
||||||
|
use colored::*;
|
||||||
|
use riven::consts::*;
|
||||||
|
use riven::models::summoner_v4::*;
|
||||||
|
use testutils::*;
|
||||||
|
|
||||||
|
fn validate_summoners(s1: Summoner, s2: Summoner) -> Result<(), String> {
|
||||||
|
rassert_eq!(s1.name, s2.name, "Names didn't match {}.", "");
|
||||||
|
rassert_eq!(s1.id, s2.id, "SummonerId didn't match {}.", "");
|
||||||
|
rassert_eq!(
|
||||||
|
s1.account_id,
|
||||||
|
s2.account_id,
|
||||||
|
"AccountId didn't match {}.",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const ROUTE: PlatformRoute = PlatformRoute::NA1;
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
|
my_runner {
|
||||||
|
// Summoner tests.
|
||||||
|
summoner_double: async {
|
||||||
|
let l1p = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "lug nuts k");
|
||||||
|
let l2p = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "lugnuts k");
|
||||||
|
let l1 = l1p.await.map_err(|e| e.to_string())?.ok_or_else(|| "'lug nuts k' not found!".to_owned())?;
|
||||||
|
let l2 = l2p.await.map_err(|e| e.to_string())?.ok_or_else(|| "'lugnuts k' not found!".to_owned())?;
|
||||||
|
validate_summoners(l1, l2)?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
champion_getrotation: async {
|
||||||
|
let p = RIOT_API.champion_v3().get_champion_info(ROUTE);
|
||||||
|
let d = p.await.map_err(|e| e.to_string())?;
|
||||||
|
let new_len = d.free_champion_ids_for_new_players.len();
|
||||||
|
let free_len = d.free_champion_ids.len();
|
||||||
|
let level = d.max_new_player_level;
|
||||||
|
rassert!(new_len >= 10, "New len: {}", new_len);
|
||||||
|
rassert!(free_len >= 15, "Free len: {}", free_len);
|
||||||
|
rassert_eq!(10, level, "New player level: {}", level);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
leagueexp_get: async {
|
||||||
|
let p = RIOT_API.league_exp_v4().get_league_entries(ROUTE, QueueType::RANKED_SOLO_5x5, Tier::CHALLENGER, Division::I, None);
|
||||||
|
let d = p.await.map_err(|e| e.to_string())?;
|
||||||
|
if d.is_empty() {
|
||||||
|
eprintln!("Off-season, challenger league is empty.");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
champion_mastery_v4: async {
|
||||||
|
let summoner = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "LugnutsK");
|
||||||
|
let summoner = summoner.await.map_err(|e| e.to_string())?.ok_or_else(|| "'LugnutsK' not found!".to_owned())?;
|
||||||
|
let masteries = RIOT_API.champion_mastery_v4().get_all_champion_masteries_by_puuid(ROUTE, &summoner.puuid);
|
||||||
|
let masteries = masteries.await.map_err(|e| e.to_string())?;
|
||||||
|
rassert!(74 <= masteries.len());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO: MATCH-V4 REMOVED.
|
||||||
|
// matchlist_get: async {
|
||||||
|
// let sp = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "haha yes");
|
||||||
|
// let s = sp.await.map_err(|e| e.to_string())?.ok_or("Failed to get \"haha yes\"".to_owned())?;
|
||||||
|
// let mp = RIOT_API.match_v4().get_matchlist(ROUTE, &s.account_id, None, Some(2500), None, None, Some(2600), None, None);
|
||||||
|
// let m = mp.await.map_err(|e| e.to_string())?.ok_or("Failed to get matchlist".to_owned())?;
|
||||||
|
// rassert!(m.matches.len() > 0, "Matchlist should not be empty");
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
// matchlist_get2: async {
|
||||||
|
// let sp = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "haha yes");
|
||||||
|
// let s = sp.await.map_err(|e| e.to_string())?.ok_or("Failed to get \"haha yes\"".to_owned())?;
|
||||||
|
// let mp = RIOT_API.match_v4().get_matchlist(ROUTE, &s.account_id, None, None, Some(&[ Champion::SION, Champion::SIVIR, Champion::CASSIOPEIA ]), None, None, None, None);
|
||||||
|
// let m = mp.await.map_err(|e| e.to_string())?.ok_or("Failed to get matchlist".to_owned())?;
|
||||||
|
// rassert!(m.matches.len() > 0, "Matchlist should not be empty");
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
|
||||||
|
// match_get: async {
|
||||||
|
// let p = RIOT_API.match_v4().get_match(ROUTE, 3190191338);
|
||||||
|
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Match not found.".to_owned())?;
|
||||||
|
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
// match_get_bots: async {
|
||||||
|
// let p = RIOT_API.match_v4().get_match(ROUTE, 3251803350);
|
||||||
|
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Match not found.".to_owned())?;
|
||||||
|
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
// match_get_odyssey: async {
|
||||||
|
// let p = RIOT_API.match_v4().get_match(ROUTE, 2881976826);
|
||||||
|
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Match not found.".to_owned())?;
|
||||||
|
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
// match_get_aram: async {
|
||||||
|
// let p = RIOT_API.match_v4().get_match(ROUTE, 2961635718);
|
||||||
|
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get match.".to_owned())?;
|
||||||
|
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
// match_get_aram2: async {
|
||||||
|
// let p = RIOT_API.match_v4().get_match(ROUTE, 3596184782);
|
||||||
|
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Match not found.".to_owned())?;
|
||||||
|
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
// match_get_urf900: async {
|
||||||
|
// let p = RIOT_API.match_v4().get_match(ROUTE, 2963663381);
|
||||||
|
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get match.".to_owned())?;
|
||||||
|
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
// match_get_tutorial1: async {
|
||||||
|
// let p = RIOT_API.match_v4().get_match(ROUTE, 3432145099);
|
||||||
|
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get match.".to_owned())?;
|
||||||
|
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
// match_get_tutorial2: async {
|
||||||
|
// let p = RIOT_API.match_v4().get_match(ROUTE, 3432116214);
|
||||||
|
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get match.".to_owned())?;
|
||||||
|
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
// match_get_tutorial3: async {
|
||||||
|
// let p = RIOT_API.match_v4().get_match(ROUTE, 3432156790);
|
||||||
|
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get match.".to_owned())?;
|
||||||
|
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
|
||||||
|
// match_gettimeline: async {
|
||||||
|
// let p = RIOT_API.match_v4().get_match_timeline(ROUTE, 3190191338);
|
||||||
|
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Match timeline not found.".to_owned())?;
|
||||||
|
// rassert!(!m.frames.is_empty(), "Match timeline should have frames.");
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
|
||||||
|
// Commented out, requires special API key.
|
||||||
|
// // LOR
|
||||||
|
// lor_ranked_get_leaderboards: async {
|
||||||
|
// let future = RIOT_API.lor_ranked_v1().get_leaderboards(Region::AMERICAS);
|
||||||
|
// let _leaderboard = future.await.map_err(|e| e.to_string())?;
|
||||||
|
// Ok(())
|
||||||
|
// },
|
||||||
|
// CLASH
|
||||||
|
clash_get_tournaments: async {
|
||||||
|
let p = RIOT_API.clash_v1().get_tournaments(ROUTE);
|
||||||
|
let tours = p.await.map_err(|e| e.to_string())?;
|
||||||
|
if let Some(tour0) = tours.first() {
|
||||||
|
let p = RIOT_API.clash_v1().get_tournament_by_id(ROUTE, tour0.id);
|
||||||
|
let tour1 = p.await.map_err(|e| e.to_string())?;
|
||||||
|
assert_eq!(Some(tour0.id), tour1.map(|t| t.id));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
clash_get_team_by_id: async {
|
||||||
|
let p = RIOT_API.clash_v1().get_team_by_id(ROUTE, "00000000-0000-0000-0000-000000000000");
|
||||||
|
let team = p.await.map_err(|e| e.to_string())?;
|
||||||
|
assert!(team.is_none());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
|
||||||
|
status: async {
|
||||||
|
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||||
|
let status = p.await.map_err(|e| e.to_string())?;
|
||||||
|
println!("{:?}", status);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||||
|
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||||
|
|
||||||
|
mod async_tests;
|
||||||
|
mod testutils;
|
||||||
|
use colored::*;
|
||||||
|
use riven::consts::*;
|
||||||
|
use testutils::*;
|
||||||
|
|
||||||
|
const ROUTE: PlatformRoute = PlatformRoute::PH2;
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
|
my_runner {
|
||||||
|
status: async {
|
||||||
|
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||||
|
let status = p.await.map_err(|e| e.to_string())?;
|
||||||
|
println!("{:?}", status);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||||
|
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||||
|
|
||||||
|
mod async_tests;
|
||||||
|
mod testutils;
|
||||||
|
use colored::*;
|
||||||
|
use riven::consts::*;
|
||||||
|
use testutils::*;
|
||||||
|
|
||||||
|
const ROUTE: PlatformRoute = PlatformRoute::SG2;
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
|
my_runner {
|
||||||
|
status: async {
|
||||||
|
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||||
|
let status = p.await.map_err(|e| e.to_string())?;
|
||||||
|
println!("{:?}", status);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||||
|
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||||
|
|
||||||
|
mod async_tests;
|
||||||
|
mod testutils;
|
||||||
|
use colored::*;
|
||||||
|
use riven::consts::*;
|
||||||
|
use testutils::*;
|
||||||
|
|
||||||
|
const ROUTE: PlatformRoute = PlatformRoute::TH2;
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
|
my_runner {
|
||||||
|
status: async {
|
||||||
|
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||||
|
let status = p.await.map_err(|e| e.to_string())?;
|
||||||
|
println!("{:?}", status);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,27 +3,24 @@
|
||||||
|
|
||||||
mod async_tests;
|
mod async_tests;
|
||||||
mod testutils;
|
mod testutils;
|
||||||
use testutils::RIOT_API;
|
|
||||||
|
|
||||||
use colored::*;
|
use colored::*;
|
||||||
|
|
||||||
use riven::consts::*;
|
use riven::consts::*;
|
||||||
use riven::models::summoner_v4::Summoner;
|
use riven::models::summoner_v4::Summoner;
|
||||||
|
use testutils::RIOT_API;
|
||||||
|
|
||||||
const REGION: Region = Region::TR;
|
const ROUTE: PlatformRoute = PlatformRoute::TR1;
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
async_tests!{
|
|
||||||
my_runner {
|
my_runner {
|
||||||
league_summoner_bulk_test: async {
|
league_summoner_bulk_test: async {
|
||||||
let p = RIOT_API.league_v4().get_challenger_league(REGION, QueueType::RANKED_SOLO_5x5);
|
let p = RIOT_API.league_v4().get_challenger_league(ROUTE, QueueType::RANKED_SOLO_5x5);
|
||||||
// let p = future_start(p);
|
// let p = future_start(p);
|
||||||
let ll = p.await.map_err(|e| e.to_string())?;
|
let ll = p.await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
println!("{:?} Challenger {} entries.", REGION, ll.entries.len());
|
println!("{:?} Challenger {} entries.", ROUTE, ll.entries.len());
|
||||||
|
|
||||||
let sl = ll.entries.iter().take(50)
|
let sl = ll.entries.iter().take(50)
|
||||||
.map(|entry| RIOT_API.summoner_v4().get_by_summoner_id(REGION, &entry.summoner_id))
|
.map(|entry| RIOT_API.summoner_v4().get_by_summoner_id(ROUTE, &entry.summoner_id))
|
||||||
.map(tokio::spawn)
|
.map(tokio::spawn)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||||
|
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||||
|
|
||||||
|
mod async_tests;
|
||||||
|
mod testutils;
|
||||||
|
use testutils::*;
|
||||||
|
|
||||||
|
use colored::*;
|
||||||
|
|
||||||
|
use riven::consts::*;
|
||||||
|
|
||||||
|
const ROUTE: PlatformRoute = PlatformRoute::TW2;
|
||||||
|
|
||||||
|
async_tests!{
|
||||||
|
my_runner {
|
||||||
|
// TODO: for some reason status is not available on TW2...
|
||||||
|
// https://developer.riotgames.com/apis#lol-status-v4/GET_getPlatformData
|
||||||
|
status: async {
|
||||||
|
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||||
|
let status = p.await.map_err(|e| e.to_string())?;
|
||||||
|
println!("{:?}", status);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,25 +3,25 @@
|
||||||
|
|
||||||
mod async_tests;
|
mod async_tests;
|
||||||
mod testutils;
|
mod testutils;
|
||||||
|
use colored::*;
|
||||||
|
use riven::consts::*;
|
||||||
use testutils::RIOT_API;
|
use testutils::RIOT_API;
|
||||||
|
|
||||||
use colored::*;
|
const ROUTE: ValPlatformRoute = ValPlatformRoute::LATAM;
|
||||||
|
|
||||||
use riven::consts::*;
|
async_tests! {
|
||||||
|
|
||||||
const REGION: Region = Region::VAL_LATAM;
|
|
||||||
|
|
||||||
|
|
||||||
async_tests!{
|
|
||||||
my_runner {
|
my_runner {
|
||||||
val_content_ranked_test: async {
|
val_content_ranked_test: async {
|
||||||
let p = RIOT_API.val_content_v1().get_content(REGION, Some("zh-CN"));
|
let p = RIOT_API.val_content_v1().get_content(ROUTE, Some("zh-CN"));
|
||||||
let contents = p.await.map_err(|e| e.to_string())?;
|
let contents = p.await.map_err(|e| format!("Failed to get content: {}", e))?;
|
||||||
|
|
||||||
let act = contents.acts.iter().find(|act| act.is_active)
|
// Find the LAST active act, via `.rev().find(...)`.
|
||||||
|
// Added filter when parent id is 0000... as there are multiple that are active, the last active seems to be episode 5
|
||||||
|
// Not sure if this a bandaid fix
|
||||||
|
let act = contents.acts.iter().rev().find(|act| act.is_active && act.parent_id != Some("00000000-0000-0000-0000-000000000000".to_string()))
|
||||||
.ok_or(format!("No active acts of {} found.", contents.acts.len()))?;
|
.ok_or(format!("No active acts of {} found.", contents.acts.len()))?;
|
||||||
|
|
||||||
let p = RIOT_API.val_ranked_v1().get_leaderboard(REGION, &act.id, None, None);
|
let p = RIOT_API.val_ranked_v1().get_leaderboard(ROUTE, &act.id, None, None);
|
||||||
let leaderboard = p.await.map_err(|e| e.to_string())?
|
let leaderboard = p.await.map_err(|e| e.to_string())?
|
||||||
.ok_or(format!("Failed to get act leaderboard {} {}.", act.id, act.name))?;
|
.ok_or(format!("Failed to get act leaderboard {} {}.", act.id, act.name))?;
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||||
|
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||||
|
|
||||||
|
mod async_tests;
|
||||||
|
mod testutils;
|
||||||
|
use colored::*;
|
||||||
|
use riven::consts::*;
|
||||||
|
use testutils::*;
|
||||||
|
|
||||||
|
const ROUTE: PlatformRoute = PlatformRoute::VN2;
|
||||||
|
|
||||||
|
async_tests! {
|
||||||
|
my_runner {
|
||||||
|
status: async {
|
||||||
|
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||||
|
let status = p.await.map_err(|e| e.to_string())?;
|
||||||
|
println!("{:?}", status);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,208 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use riven::consts::{PlatformRoute, QueueType, RegionalRoute};
|
||||||
|
use riven::{RiotApi, RiotApiConfig};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref RIOT_API: RiotApi = {
|
||||||
|
let api_key = std::env::var("RGAPI_KEY")
|
||||||
|
.ok()
|
||||||
|
.or_else(|| std::fs::read_to_string("apikey.txt").ok())
|
||||||
|
.expect("Failed to find RGAPI_KEY env var or apikey.txt.");
|
||||||
|
RiotApi::new(RiotApiConfig::with_key(api_key.trim()).preconfig_burst())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn league_v4_match_v5_latest_combo(route: PlatformRoute) -> Result<(), String> {
|
||||||
|
const NUM_MATCHES: usize = 10;
|
||||||
|
|
||||||
|
let challenger_future = RIOT_API
|
||||||
|
.league_v4()
|
||||||
|
.get_challenger_league(route, QueueType::RANKED_SOLO_5x5);
|
||||||
|
let challenger_league = challenger_future
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to get challenger league: {}", e))?;
|
||||||
|
|
||||||
|
let Some(queue) = challenger_league.queue else {
|
||||||
|
assert!(challenger_league.entries.is_empty());
|
||||||
|
eprintln!("Off-season, challenger league is empty.");
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if QueueType::RANKED_SOLO_5x5 != queue {
|
||||||
|
return Err(format!("Unexpected `queue`: {:?}", queue));
|
||||||
|
}
|
||||||
|
if challenger_league.entries.is_empty() {
|
||||||
|
return Err("Challenger league is unexpectedly empty!".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
let match_ids_futures = challenger_league
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.take(5)
|
||||||
|
.map(|entry| async move {
|
||||||
|
let summoner_future = RIOT_API
|
||||||
|
.summoner_v4()
|
||||||
|
.get_by_summoner_id(route, &entry.summoner_id);
|
||||||
|
let summoner_info = summoner_future
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to find summoner info: {}", e))?;
|
||||||
|
|
||||||
|
let match_ids_future = RIOT_API.match_v5().get_match_ids_by_puuid(
|
||||||
|
route.to_regional(),
|
||||||
|
&summoner_info.puuid,
|
||||||
|
Some(5),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let match_ids = match_ids_future
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to find summoner match IDs: {}", e))?;
|
||||||
|
Ok(match_ids) as Result<_, String>
|
||||||
|
});
|
||||||
|
|
||||||
|
let match_ids = futures::future::try_join_all(match_ids_futures).await?;
|
||||||
|
|
||||||
|
let mut match_ids: Vec<String> = match_ids.into_iter().flatten().collect();
|
||||||
|
match_ids.sort_unstable_by(|a, b| a.cmp(b).reverse()); // Sort descending, so latest are first.
|
||||||
|
|
||||||
|
let _ = tokio::try_join!(
|
||||||
|
match_v5_get(route.to_regional(), match_ids.iter().take(NUM_MATCHES)),
|
||||||
|
match_v5_get_timeline(route.to_regional(), match_ids.iter().take(NUM_MATCHES)),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn tft_match_v1_get(
|
||||||
|
route: RegionalRoute,
|
||||||
|
matches: impl IntoIterator<Item = impl AsRef<str>>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let futures = matches.into_iter().map(|matche| async move {
|
||||||
|
let matche = matche.as_ref();
|
||||||
|
let p = RIOT_API.tft_match_v1().get_match(route, matche);
|
||||||
|
let m = p
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to get match {}: {:?}", matche, e))?
|
||||||
|
.ok_or(format!("Match {} not found.", matche))?;
|
||||||
|
|
||||||
|
if matche != &*m.metadata.match_id {
|
||||||
|
return Err(format!(
|
||||||
|
"Bad match id? Sent {}, received {}.",
|
||||||
|
matche, m.metadata.match_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if m.metadata.participants.is_empty() {
|
||||||
|
return Err(format!(
|
||||||
|
"Match {} should have participants (metadata).",
|
||||||
|
matche
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if m.metadata.participants.len() != m.info.participants.len() {
|
||||||
|
return Err(format!(
|
||||||
|
"Match {} participants do not line up with participant UUIDs.",
|
||||||
|
matche
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if m.info.participants.is_empty() {
|
||||||
|
return Err(format!("Match {} should have participants (info).", matche));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
futures::future::try_join_all(futures).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn match_v5_get(
|
||||||
|
route: RegionalRoute,
|
||||||
|
matches: impl IntoIterator<Item = impl AsRef<str>>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let futures = matches.into_iter().map(|matche| async move {
|
||||||
|
let matche = matche.as_ref();
|
||||||
|
let p = RIOT_API.match_v5().get_match(route, matche);
|
||||||
|
let m = p
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to get match {}: {:?}", matche, e))?
|
||||||
|
.ok_or(format!("Match {} not found.", matche))?;
|
||||||
|
|
||||||
|
if matche != &*m.metadata.match_id {
|
||||||
|
return Err(format!(
|
||||||
|
"Bad match id? Sent {}, received {}.",
|
||||||
|
matche, m.metadata.match_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if m.metadata.participants.is_empty() {
|
||||||
|
return Err(format!("Match {} should have participants.", matche));
|
||||||
|
}
|
||||||
|
if m.metadata.participants.len() != m.info.participants.len() {
|
||||||
|
// Sometimes only returns match IDs for one team? JP1_391732436
|
||||||
|
// Do not return error.
|
||||||
|
eprintln!(
|
||||||
|
"Match {} participants do not line up with participant UUIDs.",
|
||||||
|
matche
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for participant in &m.info.participants {
|
||||||
|
participant
|
||||||
|
.champion()
|
||||||
|
.map_err(|e| format!("Failed to determine match {} champion: {}", matche, e))?;
|
||||||
|
}
|
||||||
|
if m.info.teams.is_empty() {
|
||||||
|
return Err(format!("Match {} should have teams.", matche));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
join_all_future_errs(futures).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn match_v5_get_timeline(
|
||||||
|
route: RegionalRoute,
|
||||||
|
matches: impl IntoIterator<Item = impl AsRef<str>>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let futures = matches.into_iter().map(|matche| async move {
|
||||||
|
let matche = matche.as_ref();
|
||||||
|
let p = RIOT_API.match_v5().get_timeline(route, matche);
|
||||||
|
let m = p
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to get match {}: {:?}", matche, e))?
|
||||||
|
.ok_or(format!("Match {} not found.", matche))?;
|
||||||
|
if matche != &*m.metadata.match_id {
|
||||||
|
return Err(format!(
|
||||||
|
"Bad match id? Sent {}, received {}.",
|
||||||
|
matche, m.metadata.match_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if m.metadata.participants.is_empty() {
|
||||||
|
return Err(format!("Match {} should have participants.", matche));
|
||||||
|
}
|
||||||
|
if let Some(game_id) = m.info.game_id {
|
||||||
|
if matche[(matche.find('_').unwrap() + 1)..] != game_id.to_string() {
|
||||||
|
return Err(format!("Match {} number ID should match.", matche));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m.info.frames.is_empty() {
|
||||||
|
return Err(format!("Match {} timleine should have frames.", matche));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
join_all_future_errs(futures).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Joins all futures and keeps ALL error messages, separated by newlines.
|
||||||
|
async fn join_all_future_errs<T>(
|
||||||
|
result_tasks: impl Iterator<Item = impl Future<Output = Result<T, String>>>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
futures::future::join_all(result_tasks)
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(Result::err)
|
||||||
|
.reduce(|a, b| a + "\n" + &b)
|
||||||
|
.map(Err)
|
||||||
|
.unwrap_or(Ok(()))
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
|
@ -0,0 +1,10 @@
|
||||||
|
format_code_in_doc_comments = true
|
||||||
|
format_macro_matchers = true
|
||||||
|
group_imports = "StdExternalCrate"
|
||||||
|
hex_literal_case = "Lower"
|
||||||
|
imports_granularity = "Module"
|
||||||
|
newline_style = "Unix"
|
||||||
|
normalize_comments = true
|
||||||
|
normalize_doc_attributes = true
|
||||||
|
use_field_init_shorthand = true
|
||||||
|
use_try_shorthand = true
|
167
src/config.rs
167
src/config.rs
|
@ -1,167 +0,0 @@
|
||||||
//! Configuration of RiotApi.
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use reqwest::ClientBuilder;
|
|
||||||
|
|
||||||
/// Configuration for instantiating RiotApi.
|
|
||||||
///
|
|
||||||
///
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RiotApiConfig {
|
|
||||||
pub(crate) api_key: String,
|
|
||||||
pub(crate) retries: u8,
|
|
||||||
pub(crate) burst_pct: f32,
|
|
||||||
pub(crate) duration_overhead: Duration,
|
|
||||||
pub(crate) client_builder: Option<ClientBuilder>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RiotApiConfig {
|
|
||||||
/// `0.99`
|
|
||||||
///
|
|
||||||
/// `burst_pct` used by `preconfig_burst` (and default `with_key`).
|
|
||||||
pub const PRECONFIG_BURST_BURST_PCT: f32 = 0.99;
|
|
||||||
/// `989` ms
|
|
||||||
///
|
|
||||||
/// `duration_overhead` used by `preconfig_burst` (and default `with_key`).
|
|
||||||
pub const PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS: u64 = 989;
|
|
||||||
|
|
||||||
/// `0.47`
|
|
||||||
///
|
|
||||||
/// `burst_pct` used by `preconfig_throughput`.
|
|
||||||
pub const PRECONFIG_THROUGHPUT_BURST_PCT: f32 = 0.47;
|
|
||||||
/// `10` ms.
|
|
||||||
///
|
|
||||||
/// `duration_overhead` used by `preconfig_throughput`.
|
|
||||||
pub const PRECONFIG_THROUGHPUT_DURATION_OVERHEAD_MILLIS: u64 = 10;
|
|
||||||
|
|
||||||
/// Creates a new `RiotApiConfig` with the given `api_key` with the following
|
|
||||||
/// configuration:
|
|
||||||
///
|
|
||||||
/// * `retries = 3`.
|
|
||||||
/// * `purst_pct = 0.99` (`preconfig_burst`).
|
|
||||||
/// * `duration_overhead = 989 ms` (`preconfig_burst`).
|
|
||||||
///
|
|
||||||
/// `api_key` should be a Riot Games API key from
|
|
||||||
/// [https://developer.riotgames.com/](https://developer.riotgames.com/),
|
|
||||||
/// and should look like `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`.
|
|
||||||
pub fn with_key<T: Into<String>>(api_key: T) -> Self {
|
|
||||||
Self {
|
|
||||||
api_key: api_key.into(),
|
|
||||||
retries: 3,
|
|
||||||
burst_pct: Self::PRECONFIG_BURST_BURST_PCT,
|
|
||||||
duration_overhead: Duration::from_millis(Self::PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS),
|
|
||||||
client_builder: Some(ClientBuilder::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets rate limiting settings to preconfigured values optimized for burst,
|
|
||||||
/// low latency:
|
|
||||||
///
|
|
||||||
/// * `burst_pct = 0.99` (`PRECONFIG_BURST_BURST_PCT`).
|
|
||||||
/// * `duration_overhead = 989 ms` (`PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS`).
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// `self`, for chaining.
|
|
||||||
pub fn preconfig_burst(mut self) -> Self {
|
|
||||||
self.burst_pct = Self::PRECONFIG_BURST_BURST_PCT;
|
|
||||||
self.duration_overhead = Duration::from_millis(Self::PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the rate limiting settings to preconfigured values optimized for
|
|
||||||
/// high throughput:
|
|
||||||
///
|
|
||||||
/// * `burst_pct = 0.47` (`PRECONFIG_THROUGHPUT_BURST_PCT`).
|
|
||||||
/// * `duration_overhead = 10 ms` (`PRECONFIG_THROUGHPUT_DURATION_OVERHEAD_MILLIS`).
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// `self`, for chaining.
|
|
||||||
pub fn preconfig_throughput(mut self) -> Self {
|
|
||||||
self.burst_pct = Self::PRECONFIG_THROUGHPUT_BURST_PCT;
|
|
||||||
self.duration_overhead = Duration::from_millis(Self::PRECONFIG_THROUGHPUT_DURATION_OVERHEAD_MILLIS);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set number of times to retry requests. Naturally, only retryable requests
|
|
||||||
/// will be retried: responses with status codes 5xx or 429 (after waiting
|
|
||||||
/// for retry-after headers). A value of `0` means one request will be sent
|
|
||||||
/// and it will not be retried if it fails.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// `self`, for chaining.
|
|
||||||
pub fn set_retries(mut self, retries: u8) -> Self {
|
|
||||||
self.retries = retries;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Burst percentage controls how many burst requests are allowed and
|
|
||||||
/// therefore how requests are spread out. Higher equals more burst,
|
|
||||||
/// less spread. Lower equals less burst, more spread.
|
|
||||||
///
|
|
||||||
/// The value must be in the range (0, 1];
|
|
||||||
/// Between 0, exclusive, and 1, inclusive. However values should generally
|
|
||||||
/// be larger than 0.25.
|
|
||||||
///
|
|
||||||
/// Burst percentage behaves as follows:<br>
|
|
||||||
/// A burst percentage of x% means, for each token bucket, "x% of the
|
|
||||||
/// tokens can be used in x% of the bucket duration." So, for example, if x
|
|
||||||
/// is 90%, a bucket would allow 90% of the requests to be made without
|
|
||||||
/// any delay. Then, after waiting 90% of the bucket's duration, the
|
|
||||||
/// remaining 10% of requests could be made.
|
|
||||||
///
|
|
||||||
/// A burst percentage of 100% results in no request spreading, which would
|
|
||||||
/// allow for the largest bursts and lowest latency, but could result in
|
|
||||||
/// 429s as bucket boundaries occur.
|
|
||||||
///
|
|
||||||
/// A burst percentage of near 0% results in high spreading causing
|
|
||||||
/// temporally equidistant requests. This prevents 429s but has the highest
|
|
||||||
/// latency. Additionally, if the number of tokens is high, this may lower
|
|
||||||
/// the overall throughput due to the rate at which requests can be
|
|
||||||
/// scheduled.
|
|
||||||
///
|
|
||||||
/// Therefore, for interactive applications like summoner & match history
|
|
||||||
/// lookup, a higher percentage may be better. For data-collection apps
|
|
||||||
/// like champion winrate aggregation, a medium-low percentage may be
|
|
||||||
/// better.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// If `burst_pct` is not in range (0, 1].
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// `self`, for chaining.
|
|
||||||
pub fn set_burst_pct(mut self, burst_pct: f32) -> Self {
|
|
||||||
// Use inverted check to handle NaN.
|
|
||||||
if 0.0 < burst_pct && burst_pct < 1.0 {
|
|
||||||
self.burst_pct = burst_pct;
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
panic!("burst_pct \"{}\" not in range (0, 1].", burst_pct);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the additional bucket duration to consider when rate limiting.
|
|
||||||
/// Increasing this value will decrease the chances of 429s, but will lower
|
|
||||||
/// the overall throughput.
|
|
||||||
///
|
|
||||||
/// In a sense, the `duration_overhead` is how much to "widen" the temporal
|
|
||||||
/// width of buckets.
|
|
||||||
///
|
|
||||||
/// Given a particular Riot Game API rate limit bucket that allows N requests
|
|
||||||
/// per D duration, when counting requests this library will consider requests
|
|
||||||
/// sent in the past `D + duration_overhead` duration.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// `self`, for chaining.
|
|
||||||
pub fn set_duration_overhead(mut self, duration_overhead: Duration) -> Self {
|
|
||||||
self.duration_overhead = duration_overhead;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the reqwest `ClientBuilder`.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// `self`, for chaining.
|
|
||||||
pub fn set_client_builder(mut self, client_builder: ClientBuilder) -> Self {
|
|
||||||
self.client_builder = Some(client_builder);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,526 +0,0 @@
|
||||||
///////////////////////////////////////////////
|
|
||||||
// //
|
|
||||||
// ! //
|
|
||||||
// This file is automatically generated! //
|
|
||||||
// Do not directly edit! //
|
|
||||||
// //
|
|
||||||
///////////////////////////////////////////////
|
|
||||||
|
|
||||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
|
||||||
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
|
||||||
use strum_macros::{ EnumString, EnumIter, Display, AsRefStr, IntoStaticStr };
|
|
||||||
|
|
||||||
/// League of Legend's champions.
|
|
||||||
///
|
|
||||||
/// The documentation of each variant specifies:<br>
|
|
||||||
/// NAME (`IDENTIFIER`, ID).
|
|
||||||
///
|
|
||||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator).
|
|
||||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
|
||||||
#[derive(Serialize_repr, Deserialize_repr)]
|
|
||||||
#[derive(EnumString, EnumIter, Display, AsRefStr, IntoStaticStr)]
|
|
||||||
#[repr(i16)]
|
|
||||||
pub enum Champion {
|
|
||||||
/// A champion that doesn't exist. Used in TeamBans when no champion was banned.
|
|
||||||
///
|
|
||||||
/// None (`NONE`, -1).
|
|
||||||
None = -1,
|
|
||||||
|
|
||||||
/// Aatrox (`Aatrox`, 266).
|
|
||||||
#[strum(to_string="Aatrox")] Aatrox = 266,
|
|
||||||
/// Ahri (`Ahri`, 103).
|
|
||||||
#[strum(to_string="Ahri")] Ahri = 103,
|
|
||||||
/// Akali (`Akali`, 84).
|
|
||||||
#[strum(to_string="Akali")] Akali = 84,
|
|
||||||
/// Alistar (`Alistar`, 12).
|
|
||||||
#[strum(to_string="Alistar")] Alistar = 12,
|
|
||||||
/// Amumu (`Amumu`, 32).
|
|
||||||
#[strum(to_string="Amumu")] Amumu = 32,
|
|
||||||
/// Anivia (`Anivia`, 34).
|
|
||||||
#[strum(to_string="Anivia")] Anivia = 34,
|
|
||||||
/// Annie (`Annie`, 1).
|
|
||||||
#[strum(to_string="Annie")] Annie = 1,
|
|
||||||
/// Aphelios (`Aphelios`, 523).
|
|
||||||
#[strum(to_string="Aphelios")] Aphelios = 523,
|
|
||||||
/// Ashe (`Ashe`, 22).
|
|
||||||
#[strum(to_string="Ashe")] Ashe = 22,
|
|
||||||
/// Aurelion Sol (`AurelionSol`, 136).
|
|
||||||
#[strum(to_string="Aurelion Sol", serialize="AurelionSol")] AurelionSol = 136,
|
|
||||||
/// Azir (`Azir`, 268).
|
|
||||||
#[strum(to_string="Azir")] Azir = 268,
|
|
||||||
/// Bard (`Bard`, 432).
|
|
||||||
#[strum(to_string="Bard")] Bard = 432,
|
|
||||||
/// Blitzcrank (`Blitzcrank`, 53).
|
|
||||||
#[strum(to_string="Blitzcrank")] Blitzcrank = 53,
|
|
||||||
/// Brand (`Brand`, 63).
|
|
||||||
#[strum(to_string="Brand")] Brand = 63,
|
|
||||||
/// Braum (`Braum`, 201).
|
|
||||||
#[strum(to_string="Braum")] Braum = 201,
|
|
||||||
/// Caitlyn (`Caitlyn`, 51).
|
|
||||||
#[strum(to_string="Caitlyn")] Caitlyn = 51,
|
|
||||||
/// Camille (`Camille`, 164).
|
|
||||||
#[strum(to_string="Camille")] Camille = 164,
|
|
||||||
/// Cassiopeia (`Cassiopeia`, 69).
|
|
||||||
#[strum(to_string="Cassiopeia")] Cassiopeia = 69,
|
|
||||||
/// Cho'Gath (`Chogath`, 31).
|
|
||||||
#[strum(to_string="Cho'Gath", serialize="Chogath")] ChoGath = 31,
|
|
||||||
/// Corki (`Corki`, 42).
|
|
||||||
#[strum(to_string="Corki")] Corki = 42,
|
|
||||||
/// Darius (`Darius`, 122).
|
|
||||||
#[strum(to_string="Darius")] Darius = 122,
|
|
||||||
/// Diana (`Diana`, 131).
|
|
||||||
#[strum(to_string="Diana")] Diana = 131,
|
|
||||||
/// Dr. Mundo (`DrMundo`, 36).
|
|
||||||
#[strum(to_string="Dr. Mundo", serialize="DrMundo")] DrMundo = 36,
|
|
||||||
/// Draven (`Draven`, 119).
|
|
||||||
#[strum(to_string="Draven")] Draven = 119,
|
|
||||||
/// Ekko (`Ekko`, 245).
|
|
||||||
#[strum(to_string="Ekko")] Ekko = 245,
|
|
||||||
/// Elise (`Elise`, 60).
|
|
||||||
#[strum(to_string="Elise")] Elise = 60,
|
|
||||||
/// Evelynn (`Evelynn`, 28).
|
|
||||||
#[strum(to_string="Evelynn")] Evelynn = 28,
|
|
||||||
/// Ezreal (`Ezreal`, 81).
|
|
||||||
#[strum(to_string="Ezreal")] Ezreal = 81,
|
|
||||||
/// Fiddlesticks (`FiddleSticks`, 9).
|
|
||||||
#[strum(to_string="Fiddlesticks", serialize="FiddleSticks")] Fiddlesticks = 9,
|
|
||||||
/// Fiora (`Fiora`, 114).
|
|
||||||
#[strum(to_string="Fiora")] Fiora = 114,
|
|
||||||
/// Fizz (`Fizz`, 105).
|
|
||||||
#[strum(to_string="Fizz")] Fizz = 105,
|
|
||||||
/// Galio (`Galio`, 3).
|
|
||||||
#[strum(to_string="Galio")] Galio = 3,
|
|
||||||
/// Gangplank (`Gangplank`, 41).
|
|
||||||
#[strum(to_string="Gangplank")] Gangplank = 41,
|
|
||||||
/// Garen (`Garen`, 86).
|
|
||||||
#[strum(to_string="Garen")] Garen = 86,
|
|
||||||
/// Gnar (`Gnar`, 150).
|
|
||||||
#[strum(to_string="Gnar")] Gnar = 150,
|
|
||||||
/// Gragas (`Gragas`, 79).
|
|
||||||
#[strum(to_string="Gragas")] Gragas = 79,
|
|
||||||
/// Graves (`Graves`, 104).
|
|
||||||
#[strum(to_string="Graves")] Graves = 104,
|
|
||||||
/// Gwen (`Gwen`, 887).
|
|
||||||
#[strum(to_string="Gwen")] Gwen = 887,
|
|
||||||
/// Hecarim (`Hecarim`, 120).
|
|
||||||
#[strum(to_string="Hecarim")] Hecarim = 120,
|
|
||||||
/// Heimerdinger (`Heimerdinger`, 74).
|
|
||||||
#[strum(to_string="Heimerdinger")] Heimerdinger = 74,
|
|
||||||
/// Illaoi (`Illaoi`, 420).
|
|
||||||
#[strum(to_string="Illaoi")] Illaoi = 420,
|
|
||||||
/// Irelia (`Irelia`, 39).
|
|
||||||
#[strum(to_string="Irelia")] Irelia = 39,
|
|
||||||
/// Ivern (`Ivern`, 427).
|
|
||||||
#[strum(to_string="Ivern")] Ivern = 427,
|
|
||||||
/// Janna (`Janna`, 40).
|
|
||||||
#[strum(to_string="Janna")] Janna = 40,
|
|
||||||
/// Jarvan IV (`JarvanIV`, 59).
|
|
||||||
#[strum(to_string="Jarvan IV", serialize="JarvanIV")] JarvanIV = 59,
|
|
||||||
/// Jax (`Jax`, 24).
|
|
||||||
#[strum(to_string="Jax")] Jax = 24,
|
|
||||||
/// Jayce (`Jayce`, 126).
|
|
||||||
#[strum(to_string="Jayce")] Jayce = 126,
|
|
||||||
/// Jhin (`Jhin`, 202).
|
|
||||||
#[strum(to_string="Jhin")] Jhin = 202,
|
|
||||||
/// Jinx (`Jinx`, 222).
|
|
||||||
#[strum(to_string="Jinx")] Jinx = 222,
|
|
||||||
/// Kai'Sa (`Kaisa`, 145).
|
|
||||||
#[strum(to_string="Kai'Sa", serialize="Kaisa")] KaiSa = 145,
|
|
||||||
/// Kalista (`Kalista`, 429).
|
|
||||||
#[strum(to_string="Kalista")] Kalista = 429,
|
|
||||||
/// Karma (`Karma`, 43).
|
|
||||||
#[strum(to_string="Karma")] Karma = 43,
|
|
||||||
/// Karthus (`Karthus`, 30).
|
|
||||||
#[strum(to_string="Karthus")] Karthus = 30,
|
|
||||||
/// Kassadin (`Kassadin`, 38).
|
|
||||||
#[strum(to_string="Kassadin")] Kassadin = 38,
|
|
||||||
/// Katarina (`Katarina`, 55).
|
|
||||||
#[strum(to_string="Katarina")] Katarina = 55,
|
|
||||||
/// Kayle (`Kayle`, 10).
|
|
||||||
#[strum(to_string="Kayle")] Kayle = 10,
|
|
||||||
/// Kayn (`Kayn`, 141).
|
|
||||||
#[strum(to_string="Kayn")] Kayn = 141,
|
|
||||||
/// Kennen (`Kennen`, 85).
|
|
||||||
#[strum(to_string="Kennen")] Kennen = 85,
|
|
||||||
/// Kha'Zix (`Khazix`, 121).
|
|
||||||
#[strum(to_string="Kha'Zix", serialize="Khazix")] KhaZix = 121,
|
|
||||||
/// Kindred (`Kindred`, 203).
|
|
||||||
#[strum(to_string="Kindred")] Kindred = 203,
|
|
||||||
/// Kled (`Kled`, 240).
|
|
||||||
#[strum(to_string="Kled")] Kled = 240,
|
|
||||||
/// Kog'Maw (`KogMaw`, 96).
|
|
||||||
#[strum(to_string="Kog'Maw", serialize="KogMaw")] KogMaw = 96,
|
|
||||||
/// LeBlanc (`Leblanc`, 7).
|
|
||||||
#[strum(to_string="LeBlanc", serialize="Leblanc")] LeBlanc = 7,
|
|
||||||
/// Lee Sin (`LeeSin`, 64).
|
|
||||||
#[strum(to_string="Lee Sin", serialize="LeeSin")] LeeSin = 64,
|
|
||||||
/// Leona (`Leona`, 89).
|
|
||||||
#[strum(to_string="Leona")] Leona = 89,
|
|
||||||
/// Lillia (`Lillia`, 876).
|
|
||||||
#[strum(to_string="Lillia")] Lillia = 876,
|
|
||||||
/// Lissandra (`Lissandra`, 127).
|
|
||||||
#[strum(to_string="Lissandra")] Lissandra = 127,
|
|
||||||
/// Lucian (`Lucian`, 236).
|
|
||||||
#[strum(to_string="Lucian")] Lucian = 236,
|
|
||||||
/// Lulu (`Lulu`, 117).
|
|
||||||
#[strum(to_string="Lulu")] Lulu = 117,
|
|
||||||
/// Lux (`Lux`, 99).
|
|
||||||
#[strum(to_string="Lux")] Lux = 99,
|
|
||||||
/// Malphite (`Malphite`, 54).
|
|
||||||
#[strum(to_string="Malphite")] Malphite = 54,
|
|
||||||
/// Malzahar (`Malzahar`, 90).
|
|
||||||
#[strum(to_string="Malzahar")] Malzahar = 90,
|
|
||||||
/// Maokai (`Maokai`, 57).
|
|
||||||
#[strum(to_string="Maokai")] Maokai = 57,
|
|
||||||
/// Master Yi (`MasterYi`, 11).
|
|
||||||
#[strum(to_string="Master Yi", serialize="MasterYi")] MasterYi = 11,
|
|
||||||
/// Miss Fortune (`MissFortune`, 21).
|
|
||||||
#[strum(to_string="Miss Fortune", serialize="MissFortune")] MissFortune = 21,
|
|
||||||
/// Mordekaiser (`Mordekaiser`, 82).
|
|
||||||
#[strum(to_string="Mordekaiser")] Mordekaiser = 82,
|
|
||||||
/// Morgana (`Morgana`, 25).
|
|
||||||
#[strum(to_string="Morgana")] Morgana = 25,
|
|
||||||
/// Nami (`Nami`, 267).
|
|
||||||
#[strum(to_string="Nami")] Nami = 267,
|
|
||||||
/// Nasus (`Nasus`, 75).
|
|
||||||
#[strum(to_string="Nasus")] Nasus = 75,
|
|
||||||
/// Nautilus (`Nautilus`, 111).
|
|
||||||
#[strum(to_string="Nautilus")] Nautilus = 111,
|
|
||||||
/// Neeko (`Neeko`, 518).
|
|
||||||
#[strum(to_string="Neeko")] Neeko = 518,
|
|
||||||
/// Nidalee (`Nidalee`, 76).
|
|
||||||
#[strum(to_string="Nidalee")] Nidalee = 76,
|
|
||||||
/// Nocturne (`Nocturne`, 56).
|
|
||||||
#[strum(to_string="Nocturne")] Nocturne = 56,
|
|
||||||
/// Nunu & Willump (`Nunu`, 20).
|
|
||||||
#[strum(to_string="Nunu & Willump", serialize="Nunu")] NunuWillump = 20,
|
|
||||||
/// Olaf (`Olaf`, 2).
|
|
||||||
#[strum(to_string="Olaf")] Olaf = 2,
|
|
||||||
/// Orianna (`Orianna`, 61).
|
|
||||||
#[strum(to_string="Orianna")] Orianna = 61,
|
|
||||||
/// Ornn (`Ornn`, 516).
|
|
||||||
#[strum(to_string="Ornn")] Ornn = 516,
|
|
||||||
/// Pantheon (`Pantheon`, 80).
|
|
||||||
#[strum(to_string="Pantheon")] Pantheon = 80,
|
|
||||||
/// Poppy (`Poppy`, 78).
|
|
||||||
#[strum(to_string="Poppy")] Poppy = 78,
|
|
||||||
/// Pyke (`Pyke`, 555).
|
|
||||||
#[strum(to_string="Pyke")] Pyke = 555,
|
|
||||||
/// Qiyana (`Qiyana`, 246).
|
|
||||||
#[strum(to_string="Qiyana")] Qiyana = 246,
|
|
||||||
/// Quinn (`Quinn`, 133).
|
|
||||||
#[strum(to_string="Quinn")] Quinn = 133,
|
|
||||||
/// Rakan (`Rakan`, 497).
|
|
||||||
#[strum(to_string="Rakan")] Rakan = 497,
|
|
||||||
/// Rammus (`Rammus`, 33).
|
|
||||||
#[strum(to_string="Rammus")] Rammus = 33,
|
|
||||||
/// Rek'Sai (`RekSai`, 421).
|
|
||||||
#[strum(to_string="Rek'Sai", serialize="RekSai")] RekSai = 421,
|
|
||||||
/// Rell (`Rell`, 526).
|
|
||||||
#[strum(to_string="Rell")] Rell = 526,
|
|
||||||
/// Renekton (`Renekton`, 58).
|
|
||||||
#[strum(to_string="Renekton")] Renekton = 58,
|
|
||||||
/// Rengar (`Rengar`, 107).
|
|
||||||
#[strum(to_string="Rengar")] Rengar = 107,
|
|
||||||
/// Riven (`Riven`, 92).
|
|
||||||
#[strum(to_string="Riven")] Riven = 92,
|
|
||||||
/// Rumble (`Rumble`, 68).
|
|
||||||
#[strum(to_string="Rumble")] Rumble = 68,
|
|
||||||
/// Ryze (`Ryze`, 13).
|
|
||||||
#[strum(to_string="Ryze")] Ryze = 13,
|
|
||||||
/// Samira (`Samira`, 360).
|
|
||||||
#[strum(to_string="Samira")] Samira = 360,
|
|
||||||
/// Sejuani (`Sejuani`, 113).
|
|
||||||
#[strum(to_string="Sejuani")] Sejuani = 113,
|
|
||||||
/// Senna (`Senna`, 235).
|
|
||||||
#[strum(to_string="Senna")] Senna = 235,
|
|
||||||
/// Seraphine (`Seraphine`, 147).
|
|
||||||
#[strum(to_string="Seraphine")] Seraphine = 147,
|
|
||||||
/// Sett (`Sett`, 875).
|
|
||||||
#[strum(to_string="Sett")] Sett = 875,
|
|
||||||
/// Shaco (`Shaco`, 35).
|
|
||||||
#[strum(to_string="Shaco")] Shaco = 35,
|
|
||||||
/// Shen (`Shen`, 98).
|
|
||||||
#[strum(to_string="Shen")] Shen = 98,
|
|
||||||
/// Shyvana (`Shyvana`, 102).
|
|
||||||
#[strum(to_string="Shyvana")] Shyvana = 102,
|
|
||||||
/// Singed (`Singed`, 27).
|
|
||||||
#[strum(to_string="Singed")] Singed = 27,
|
|
||||||
/// Sion (`Sion`, 14).
|
|
||||||
#[strum(to_string="Sion")] Sion = 14,
|
|
||||||
/// Sivir (`Sivir`, 15).
|
|
||||||
#[strum(to_string="Sivir")] Sivir = 15,
|
|
||||||
/// Skarner (`Skarner`, 72).
|
|
||||||
#[strum(to_string="Skarner")] Skarner = 72,
|
|
||||||
/// Sona (`Sona`, 37).
|
|
||||||
#[strum(to_string="Sona")] Sona = 37,
|
|
||||||
/// Soraka (`Soraka`, 16).
|
|
||||||
#[strum(to_string="Soraka")] Soraka = 16,
|
|
||||||
/// Swain (`Swain`, 50).
|
|
||||||
#[strum(to_string="Swain")] Swain = 50,
|
|
||||||
/// Sylas (`Sylas`, 517).
|
|
||||||
#[strum(to_string="Sylas")] Sylas = 517,
|
|
||||||
/// Syndra (`Syndra`, 134).
|
|
||||||
#[strum(to_string="Syndra")] Syndra = 134,
|
|
||||||
/// Tahm Kench (`TahmKench`, 223).
|
|
||||||
#[strum(to_string="Tahm Kench", serialize="TahmKench")] TahmKench = 223,
|
|
||||||
/// Taliyah (`Taliyah`, 163).
|
|
||||||
#[strum(to_string="Taliyah")] Taliyah = 163,
|
|
||||||
/// Talon (`Talon`, 91).
|
|
||||||
#[strum(to_string="Talon")] Talon = 91,
|
|
||||||
/// Taric (`Taric`, 44).
|
|
||||||
#[strum(to_string="Taric")] Taric = 44,
|
|
||||||
/// Teemo (`Teemo`, 17).
|
|
||||||
#[strum(to_string="Teemo")] Teemo = 17,
|
|
||||||
/// Thresh (`Thresh`, 412).
|
|
||||||
#[strum(to_string="Thresh")] Thresh = 412,
|
|
||||||
/// Tristana (`Tristana`, 18).
|
|
||||||
#[strum(to_string="Tristana")] Tristana = 18,
|
|
||||||
/// Trundle (`Trundle`, 48).
|
|
||||||
#[strum(to_string="Trundle")] Trundle = 48,
|
|
||||||
/// Tryndamere (`Tryndamere`, 23).
|
|
||||||
#[strum(to_string="Tryndamere")] Tryndamere = 23,
|
|
||||||
/// Twisted Fate (`TwistedFate`, 4).
|
|
||||||
#[strum(to_string="Twisted Fate", serialize="TwistedFate")] TwistedFate = 4,
|
|
||||||
/// Twitch (`Twitch`, 29).
|
|
||||||
#[strum(to_string="Twitch")] Twitch = 29,
|
|
||||||
/// Udyr (`Udyr`, 77).
|
|
||||||
#[strum(to_string="Udyr")] Udyr = 77,
|
|
||||||
/// Urgot (`Urgot`, 6).
|
|
||||||
#[strum(to_string="Urgot")] Urgot = 6,
|
|
||||||
/// Varus (`Varus`, 110).
|
|
||||||
#[strum(to_string="Varus")] Varus = 110,
|
|
||||||
/// Vayne (`Vayne`, 67).
|
|
||||||
#[strum(to_string="Vayne")] Vayne = 67,
|
|
||||||
/// Veigar (`Veigar`, 45).
|
|
||||||
#[strum(to_string="Veigar")] Veigar = 45,
|
|
||||||
/// Vel'Koz (`Velkoz`, 161).
|
|
||||||
#[strum(to_string="Vel'Koz", serialize="Velkoz")] VelKoz = 161,
|
|
||||||
/// Vi (`Vi`, 254).
|
|
||||||
#[strum(to_string="Vi")] Vi = 254,
|
|
||||||
/// Viego (`Viego`, 234).
|
|
||||||
#[strum(to_string="Viego")] Viego = 234,
|
|
||||||
/// Viktor (`Viktor`, 112).
|
|
||||||
#[strum(to_string="Viktor")] Viktor = 112,
|
|
||||||
/// Vladimir (`Vladimir`, 8).
|
|
||||||
#[strum(to_string="Vladimir")] Vladimir = 8,
|
|
||||||
/// Volibear (`Volibear`, 106).
|
|
||||||
#[strum(to_string="Volibear")] Volibear = 106,
|
|
||||||
/// Warwick (`Warwick`, 19).
|
|
||||||
#[strum(to_string="Warwick")] Warwick = 19,
|
|
||||||
/// Wukong (`MonkeyKing`, 62).
|
|
||||||
#[strum(to_string="Wukong", serialize="MonkeyKing")] Wukong = 62,
|
|
||||||
/// Xayah (`Xayah`, 498).
|
|
||||||
#[strum(to_string="Xayah")] Xayah = 498,
|
|
||||||
/// Xerath (`Xerath`, 101).
|
|
||||||
#[strum(to_string="Xerath")] Xerath = 101,
|
|
||||||
/// Xin Zhao (`XinZhao`, 5).
|
|
||||||
#[strum(to_string="Xin Zhao", serialize="XinZhao")] XinZhao = 5,
|
|
||||||
/// Yasuo (`Yasuo`, 157).
|
|
||||||
#[strum(to_string="Yasuo")] Yasuo = 157,
|
|
||||||
/// Yone (`Yone`, 777).
|
|
||||||
#[strum(to_string="Yone")] Yone = 777,
|
|
||||||
/// Yorick (`Yorick`, 83).
|
|
||||||
#[strum(to_string="Yorick")] Yorick = 83,
|
|
||||||
/// Yuumi (`Yuumi`, 350).
|
|
||||||
#[strum(to_string="Yuumi")] Yuumi = 350,
|
|
||||||
/// Zac (`Zac`, 154).
|
|
||||||
#[strum(to_string="Zac")] Zac = 154,
|
|
||||||
/// Zed (`Zed`, 238).
|
|
||||||
#[strum(to_string="Zed")] Zed = 238,
|
|
||||||
/// Ziggs (`Ziggs`, 115).
|
|
||||||
#[strum(to_string="Ziggs")] Ziggs = 115,
|
|
||||||
/// Zilean (`Zilean`, 26).
|
|
||||||
#[strum(to_string="Zilean")] Zilean = 26,
|
|
||||||
/// Zoe (`Zoe`, 142).
|
|
||||||
#[strum(to_string="Zoe")] Zoe = 142,
|
|
||||||
/// Zyra (`Zyra`, 143).
|
|
||||||
#[strum(to_string="Zyra")] Zyra = 143,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Champion {
|
|
||||||
/// The champion's name (localized `en_US`), or `"NONE"` for the None variant.
|
|
||||||
pub fn name(self) -> &'static str {
|
|
||||||
self.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The champion's identifier key. Somtimes called "key", "identifier", or "alias".
|
|
||||||
/// This is mainly used in DDragon paths.
|
|
||||||
///
|
|
||||||
/// This is generally the `en_US` name with spaces and punctuation removed,
|
|
||||||
/// but there are the following exceptions:
|
|
||||||
///
|
|
||||||
/// Variant | Name | Identifier
|
|
||||||
/// --------|------|-----------
|
|
||||||
/// `None` | "NONE" | "NONE"
|
|
||||||
/// `ChoGath` | "Cho'Gath" | "Chogath"
|
|
||||||
/// `Fiddlesticks` | "Fiddlesticks" | "FiddleSticks"
|
|
||||||
/// `KaiSa` | "Kai'Sa" | "Kaisa"
|
|
||||||
/// `KhaZix` | "Kha'Zix" | "Khazix"
|
|
||||||
/// `LeBlanc` | "LeBlanc" | "Leblanc"
|
|
||||||
/// `NunuWillump` | "Nunu & Willump" | "Nunu"
|
|
||||||
/// `VelKoz` | "Vel'Koz" | "Velkoz"
|
|
||||||
/// `Wukong` | "Wukong" | "MonkeyKing"
|
|
||||||
pub fn identifier(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::None => "NONE",
|
|
||||||
Self::Aatrox => "Aatrox",
|
|
||||||
Self::Ahri => "Ahri",
|
|
||||||
Self::Akali => "Akali",
|
|
||||||
Self::Alistar => "Alistar",
|
|
||||||
Self::Amumu => "Amumu",
|
|
||||||
Self::Anivia => "Anivia",
|
|
||||||
Self::Annie => "Annie",
|
|
||||||
Self::Aphelios => "Aphelios",
|
|
||||||
Self::Ashe => "Ashe",
|
|
||||||
Self::AurelionSol => "AurelionSol",
|
|
||||||
Self::Azir => "Azir",
|
|
||||||
Self::Bard => "Bard",
|
|
||||||
Self::Blitzcrank => "Blitzcrank",
|
|
||||||
Self::Brand => "Brand",
|
|
||||||
Self::Braum => "Braum",
|
|
||||||
Self::Caitlyn => "Caitlyn",
|
|
||||||
Self::Camille => "Camille",
|
|
||||||
Self::Cassiopeia => "Cassiopeia",
|
|
||||||
Self::ChoGath => "Chogath",
|
|
||||||
Self::Corki => "Corki",
|
|
||||||
Self::Darius => "Darius",
|
|
||||||
Self::Diana => "Diana",
|
|
||||||
Self::DrMundo => "DrMundo",
|
|
||||||
Self::Draven => "Draven",
|
|
||||||
Self::Ekko => "Ekko",
|
|
||||||
Self::Elise => "Elise",
|
|
||||||
Self::Evelynn => "Evelynn",
|
|
||||||
Self::Ezreal => "Ezreal",
|
|
||||||
Self::Fiddlesticks => "FiddleSticks",
|
|
||||||
Self::Fiora => "Fiora",
|
|
||||||
Self::Fizz => "Fizz",
|
|
||||||
Self::Galio => "Galio",
|
|
||||||
Self::Gangplank => "Gangplank",
|
|
||||||
Self::Garen => "Garen",
|
|
||||||
Self::Gnar => "Gnar",
|
|
||||||
Self::Gragas => "Gragas",
|
|
||||||
Self::Graves => "Graves",
|
|
||||||
Self::Gwen => "Gwen",
|
|
||||||
Self::Hecarim => "Hecarim",
|
|
||||||
Self::Heimerdinger => "Heimerdinger",
|
|
||||||
Self::Illaoi => "Illaoi",
|
|
||||||
Self::Irelia => "Irelia",
|
|
||||||
Self::Ivern => "Ivern",
|
|
||||||
Self::Janna => "Janna",
|
|
||||||
Self::JarvanIV => "JarvanIV",
|
|
||||||
Self::Jax => "Jax",
|
|
||||||
Self::Jayce => "Jayce",
|
|
||||||
Self::Jhin => "Jhin",
|
|
||||||
Self::Jinx => "Jinx",
|
|
||||||
Self::KaiSa => "Kaisa",
|
|
||||||
Self::Kalista => "Kalista",
|
|
||||||
Self::Karma => "Karma",
|
|
||||||
Self::Karthus => "Karthus",
|
|
||||||
Self::Kassadin => "Kassadin",
|
|
||||||
Self::Katarina => "Katarina",
|
|
||||||
Self::Kayle => "Kayle",
|
|
||||||
Self::Kayn => "Kayn",
|
|
||||||
Self::Kennen => "Kennen",
|
|
||||||
Self::KhaZix => "Khazix",
|
|
||||||
Self::Kindred => "Kindred",
|
|
||||||
Self::Kled => "Kled",
|
|
||||||
Self::KogMaw => "KogMaw",
|
|
||||||
Self::LeBlanc => "Leblanc",
|
|
||||||
Self::LeeSin => "LeeSin",
|
|
||||||
Self::Leona => "Leona",
|
|
||||||
Self::Lillia => "Lillia",
|
|
||||||
Self::Lissandra => "Lissandra",
|
|
||||||
Self::Lucian => "Lucian",
|
|
||||||
Self::Lulu => "Lulu",
|
|
||||||
Self::Lux => "Lux",
|
|
||||||
Self::Malphite => "Malphite",
|
|
||||||
Self::Malzahar => "Malzahar",
|
|
||||||
Self::Maokai => "Maokai",
|
|
||||||
Self::MasterYi => "MasterYi",
|
|
||||||
Self::MissFortune => "MissFortune",
|
|
||||||
Self::Mordekaiser => "Mordekaiser",
|
|
||||||
Self::Morgana => "Morgana",
|
|
||||||
Self::Nami => "Nami",
|
|
||||||
Self::Nasus => "Nasus",
|
|
||||||
Self::Nautilus => "Nautilus",
|
|
||||||
Self::Neeko => "Neeko",
|
|
||||||
Self::Nidalee => "Nidalee",
|
|
||||||
Self::Nocturne => "Nocturne",
|
|
||||||
Self::NunuWillump => "Nunu",
|
|
||||||
Self::Olaf => "Olaf",
|
|
||||||
Self::Orianna => "Orianna",
|
|
||||||
Self::Ornn => "Ornn",
|
|
||||||
Self::Pantheon => "Pantheon",
|
|
||||||
Self::Poppy => "Poppy",
|
|
||||||
Self::Pyke => "Pyke",
|
|
||||||
Self::Qiyana => "Qiyana",
|
|
||||||
Self::Quinn => "Quinn",
|
|
||||||
Self::Rakan => "Rakan",
|
|
||||||
Self::Rammus => "Rammus",
|
|
||||||
Self::RekSai => "RekSai",
|
|
||||||
Self::Rell => "Rell",
|
|
||||||
Self::Renekton => "Renekton",
|
|
||||||
Self::Rengar => "Rengar",
|
|
||||||
Self::Riven => "Riven",
|
|
||||||
Self::Rumble => "Rumble",
|
|
||||||
Self::Ryze => "Ryze",
|
|
||||||
Self::Samira => "Samira",
|
|
||||||
Self::Sejuani => "Sejuani",
|
|
||||||
Self::Senna => "Senna",
|
|
||||||
Self::Seraphine => "Seraphine",
|
|
||||||
Self::Sett => "Sett",
|
|
||||||
Self::Shaco => "Shaco",
|
|
||||||
Self::Shen => "Shen",
|
|
||||||
Self::Shyvana => "Shyvana",
|
|
||||||
Self::Singed => "Singed",
|
|
||||||
Self::Sion => "Sion",
|
|
||||||
Self::Sivir => "Sivir",
|
|
||||||
Self::Skarner => "Skarner",
|
|
||||||
Self::Sona => "Sona",
|
|
||||||
Self::Soraka => "Soraka",
|
|
||||||
Self::Swain => "Swain",
|
|
||||||
Self::Sylas => "Sylas",
|
|
||||||
Self::Syndra => "Syndra",
|
|
||||||
Self::TahmKench => "TahmKench",
|
|
||||||
Self::Taliyah => "Taliyah",
|
|
||||||
Self::Talon => "Talon",
|
|
||||||
Self::Taric => "Taric",
|
|
||||||
Self::Teemo => "Teemo",
|
|
||||||
Self::Thresh => "Thresh",
|
|
||||||
Self::Tristana => "Tristana",
|
|
||||||
Self::Trundle => "Trundle",
|
|
||||||
Self::Tryndamere => "Tryndamere",
|
|
||||||
Self::TwistedFate => "TwistedFate",
|
|
||||||
Self::Twitch => "Twitch",
|
|
||||||
Self::Udyr => "Udyr",
|
|
||||||
Self::Urgot => "Urgot",
|
|
||||||
Self::Varus => "Varus",
|
|
||||||
Self::Vayne => "Vayne",
|
|
||||||
Self::Veigar => "Veigar",
|
|
||||||
Self::VelKoz => "Velkoz",
|
|
||||||
Self::Vi => "Vi",
|
|
||||||
Self::Viego => "Viego",
|
|
||||||
Self::Viktor => "Viktor",
|
|
||||||
Self::Vladimir => "Vladimir",
|
|
||||||
Self::Volibear => "Volibear",
|
|
||||||
Self::Warwick => "Warwick",
|
|
||||||
Self::Wukong => "MonkeyKing",
|
|
||||||
Self::Xayah => "Xayah",
|
|
||||||
Self::Xerath => "Xerath",
|
|
||||||
Self::XinZhao => "XinZhao",
|
|
||||||
Self::Yasuo => "Yasuo",
|
|
||||||
Self::Yone => "Yone",
|
|
||||||
Self::Yorick => "Yorick",
|
|
||||||
Self::Yuumi => "Yuumi",
|
|
||||||
Self::Zac => "Zac",
|
|
||||||
Self::Zed => "Zed",
|
|
||||||
Self::Ziggs => "Ziggs",
|
|
||||||
Self::Zilean => "Zilean",
|
|
||||||
Self::Zoe => "Zoe",
|
|
||||||
Self::Zyra => "Zyra",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
#![macro_use]
|
|
||||||
macro_rules! serde_string {
|
|
||||||
( $x:ty ) => {
|
|
||||||
impl<'de> serde::de::Deserialize<'de> for $x
|
|
||||||
{
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::de::Deserializer<'de>
|
|
||||||
{
|
|
||||||
let s = String::deserialize(deserializer)?;
|
|
||||||
s.parse().map_err(serde::de::Error::custom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl serde::ser::Serialize for $x {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::ser::Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_str(self.as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
///////////////////////////////////////////////
|
|
||||||
// //
|
|
||||||
// ! //
|
|
||||||
// This file is automatically generated! //
|
|
||||||
// Do not directly edit! //
|
|
||||||
// //
|
|
||||||
///////////////////////////////////////////////
|
|
||||||
|
|
||||||
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
|
||||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
|
||||||
|
|
||||||
/// League of Legends maps.
|
|
||||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
|
||||||
#[derive(Serialize_repr, Deserialize_repr)]
|
|
||||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum Map {
|
|
||||||
/// Summoner's Rift
|
|
||||||
/// Original Summer variant
|
|
||||||
SUMMONERS_RIFT_ORIGINAL_SUMMER_VARIANT = 1,
|
|
||||||
/// Summoner's Rift
|
|
||||||
/// Original Autumn variant
|
|
||||||
SUMMONERS_RIFT_ORIGINAL_AUTUMN_VARIANT = 2,
|
|
||||||
/// The Proving Grounds
|
|
||||||
/// Tutorial Map
|
|
||||||
THE_PROVING_GROUNDS = 3,
|
|
||||||
/// Twisted Treeline
|
|
||||||
/// Original Version
|
|
||||||
TWISTED_TREELINE_ORIGINAL_VERSION = 4,
|
|
||||||
/// The Crystal Scar
|
|
||||||
/// Dominion map
|
|
||||||
THE_CRYSTAL_SCAR = 8,
|
|
||||||
/// Twisted Treeline
|
|
||||||
/// Last TT map
|
|
||||||
TWISTED_TREELINE = 10,
|
|
||||||
/// Summoner's Rift
|
|
||||||
/// Current Version
|
|
||||||
SUMMONERS_RIFT = 11,
|
|
||||||
/// Howling Abyss
|
|
||||||
/// ARAM map
|
|
||||||
HOWLING_ABYSS = 12,
|
|
||||||
/// Butcher's Bridge
|
|
||||||
/// Alternate ARAM map
|
|
||||||
BUTCHERS_BRIDGE = 14,
|
|
||||||
/// Cosmic Ruins
|
|
||||||
/// Dark Star: Singularity map
|
|
||||||
COSMIC_RUINS = 16,
|
|
||||||
/// Valoran City Park
|
|
||||||
/// Star Guardian Invasion map
|
|
||||||
VALORAN_CITY_PARK = 18,
|
|
||||||
/// Substructure 43
|
|
||||||
/// PROJECT: Hunters map
|
|
||||||
SUBSTRUCTURE_43 = 19,
|
|
||||||
/// Crash Site
|
|
||||||
/// Odyssey: Extraction map
|
|
||||||
CRASH_SITE = 20,
|
|
||||||
/// Nexus Blitz
|
|
||||||
/// Nexus Blitz map
|
|
||||||
NEXUS_BLITZ = 21,
|
|
||||||
/// Convergence
|
|
||||||
/// Teamfight Tactics map
|
|
||||||
CONVERGENCE = 22,
|
|
||||||
}
|
|
|
@ -1,250 +0,0 @@
|
||||||
///////////////////////////////////////////////
|
|
||||||
// //
|
|
||||||
// ! //
|
|
||||||
// This file is automatically generated! //
|
|
||||||
// Do not directly edit! //
|
|
||||||
// //
|
|
||||||
///////////////////////////////////////////////
|
|
||||||
|
|
||||||
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
|
||||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
|
||||||
|
|
||||||
/// League of Legends matchmaking queue.
|
|
||||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
#[derive(Eq, PartialEq)]
|
|
||||||
#[derive(Serialize_repr, Deserialize_repr)]
|
|
||||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
|
||||||
#[repr(u16)]
|
|
||||||
pub enum Queue {
|
|
||||||
/// Games on Custom games
|
|
||||||
CUSTOM = 0,
|
|
||||||
/// 5v5 Blind Pick games on Summoner's Rift
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 430
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 430")]
|
|
||||||
SUMMONERS_RIFT_5V5_BLIND_PICK_DEPRECATED_2 = 2,
|
|
||||||
/// 5v5 Ranked Solo games on Summoner's Rift
|
|
||||||
/// Deprecated in favor of queueId 420
|
|
||||||
#[deprecated(note="Deprecated in favor of queueId 420")]
|
|
||||||
SUMMONERS_RIFT_5V5_RANKED_SOLO_DEPRECATED_4 = 4,
|
|
||||||
/// 5v5 Ranked Premade games on Summoner's Rift
|
|
||||||
/// Game mode deprecated
|
|
||||||
#[deprecated(note="Game mode deprecated")]
|
|
||||||
SUMMONERS_RIFT_5V5_RANKED_PREMADE = 6,
|
|
||||||
/// Co-op vs AI games on Summoner's Rift
|
|
||||||
/// Deprecated in favor of queueId 32 and 33
|
|
||||||
#[deprecated(note="Deprecated in favor of queueId 32 and 33")]
|
|
||||||
SUMMONERS_RIFT_CO_OP_VS_AI = 7,
|
|
||||||
/// 3v3 Normal games on Twisted Treeline
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 460
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 460")]
|
|
||||||
TWISTED_TREELINE_3V3_NORMAL = 8,
|
|
||||||
/// 3v3 Ranked Flex games on Twisted Treeline
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 470
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 470")]
|
|
||||||
TWISTED_TREELINE_3V3_RANKED_FLEX_DEPRECATED_9 = 9,
|
|
||||||
/// 5v5 Draft Pick games on Summoner's Rift
|
|
||||||
/// Deprecated in favor of queueId 400
|
|
||||||
#[deprecated(note="Deprecated in favor of queueId 400")]
|
|
||||||
SUMMONERS_RIFT_5V5_DRAFT_PICK_DEPRECATED_14 = 14,
|
|
||||||
/// 5v5 Dominion Blind Pick games on Crystal Scar
|
|
||||||
/// Game mode deprecated
|
|
||||||
#[deprecated(note="Game mode deprecated")]
|
|
||||||
CRYSTAL_SCAR_5V5_DOMINION_BLIND_PICK = 16,
|
|
||||||
/// 5v5 Dominion Draft Pick games on Crystal Scar
|
|
||||||
/// Game mode deprecated
|
|
||||||
#[deprecated(note="Game mode deprecated")]
|
|
||||||
CRYSTAL_SCAR_5V5_DOMINION_DRAFT_PICK = 17,
|
|
||||||
/// Dominion Co-op vs AI games on Crystal Scar
|
|
||||||
/// Game mode deprecated
|
|
||||||
#[deprecated(note="Game mode deprecated")]
|
|
||||||
CRYSTAL_SCAR_DOMINION_CO_OP_VS_AI = 25,
|
|
||||||
/// Co-op vs AI Intro Bot games on Summoner's Rift
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 830
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 830")]
|
|
||||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTRO_BOT_DEPRECATED_31 = 31,
|
|
||||||
/// Co-op vs AI Beginner Bot games on Summoner's Rift
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 840
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 840")]
|
|
||||||
SUMMONERS_RIFT_CO_OP_VS_AI_BEGINNER_BOT_DEPRECATED_32 = 32,
|
|
||||||
/// Co-op vs AI Intermediate Bot games on Summoner's Rift
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 850
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 850")]
|
|
||||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTERMEDIATE_BOT_DEPRECATED_33 = 33,
|
|
||||||
/// 3v3 Ranked Team games on Twisted Treeline
|
|
||||||
/// Game mode deprecated
|
|
||||||
#[deprecated(note="Game mode deprecated")]
|
|
||||||
TWISTED_TREELINE_3V3_RANKED_TEAM = 41,
|
|
||||||
/// 5v5 Ranked Team games on Summoner's Rift
|
|
||||||
/// Game mode deprecated
|
|
||||||
#[deprecated(note="Game mode deprecated")]
|
|
||||||
SUMMONERS_RIFT_5V5_RANKED_TEAM = 42,
|
|
||||||
/// Co-op vs AI games on Twisted Treeline
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 800
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 800")]
|
|
||||||
TWISTED_TREELINE_CO_OP_VS_AI = 52,
|
|
||||||
/// 5v5 Team Builder games on Summoner's Rift
|
|
||||||
/// Game mode deprecated
|
|
||||||
#[deprecated(note="Game mode deprecated")]
|
|
||||||
SUMMONERS_RIFT_5V5_TEAM_BUILDER = 61,
|
|
||||||
/// 5v5 ARAM games on Howling Abyss
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 450
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 450")]
|
|
||||||
HOWLING_ABYSS_5V5_ARAM_DEPRECATED_65 = 65,
|
|
||||||
/// ARAM Co-op vs AI games on Howling Abyss
|
|
||||||
/// Game mode deprecated
|
|
||||||
#[deprecated(note="Game mode deprecated")]
|
|
||||||
HOWLING_ABYSS_ARAM_CO_OP_VS_AI = 67,
|
|
||||||
/// One for All games on Summoner's Rift
|
|
||||||
/// Deprecated in patch 8.6 in favor of queueId 1020
|
|
||||||
#[deprecated(note="Deprecated in patch 8.6 in favor of queueId 1020")]
|
|
||||||
SUMMONERS_RIFT_ONE_FOR_ALL_DEPRECATED_70 = 70,
|
|
||||||
/// 1v1 Snowdown Showdown games on Howling Abyss
|
|
||||||
HOWLING_ABYSS_1V1_SNOWDOWN_SHOWDOWN = 72,
|
|
||||||
/// 2v2 Snowdown Showdown games on Howling Abyss
|
|
||||||
HOWLING_ABYSS_2V2_SNOWDOWN_SHOWDOWN = 73,
|
|
||||||
/// 6v6 Hexakill games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_6V6_HEXAKILL = 75,
|
|
||||||
/// Ultra Rapid Fire games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_ULTRA_RAPID_FIRE = 76,
|
|
||||||
/// One For All: Mirror Mode games on Howling Abyss
|
|
||||||
HOWLING_ABYSS_ONE_FOR_ALL_MIRROR_MODE = 78,
|
|
||||||
/// Co-op vs AI Ultra Rapid Fire games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_CO_OP_VS_AI_ULTRA_RAPID_FIRE = 83,
|
|
||||||
/// Doom Bots Rank 1 games on Summoner's Rift
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 950
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
|
||||||
SUMMONERS_RIFT_DOOM_BOTS_RANK_1 = 91,
|
|
||||||
/// Doom Bots Rank 2 games on Summoner's Rift
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 950
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
|
||||||
SUMMONERS_RIFT_DOOM_BOTS_RANK_2 = 92,
|
|
||||||
/// Doom Bots Rank 5 games on Summoner's Rift
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 950
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
|
||||||
SUMMONERS_RIFT_DOOM_BOTS_RANK_5 = 93,
|
|
||||||
/// Ascension games on Crystal Scar
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 910
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 910")]
|
|
||||||
CRYSTAL_SCAR_ASCENSION_DEPRECATED_96 = 96,
|
|
||||||
/// 6v6 Hexakill games on Twisted Treeline
|
|
||||||
TWISTED_TREELINE_6V6_HEXAKILL = 98,
|
|
||||||
/// 5v5 ARAM games on Butcher's Bridge
|
|
||||||
BUTCHERS_BRIDGE_5V5_ARAM = 100,
|
|
||||||
/// Legend of the Poro King games on Howling Abyss
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 920
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 920")]
|
|
||||||
HOWLING_ABYSS_LEGEND_OF_THE_PORO_KING_DEPRECATED_300 = 300,
|
|
||||||
/// Nemesis games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_NEMESIS = 310,
|
|
||||||
/// Black Market Brawlers games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_BLACK_MARKET_BRAWLERS = 313,
|
|
||||||
/// Nexus Siege games on Summoner's Rift
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 940
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 940")]
|
|
||||||
SUMMONERS_RIFT_NEXUS_SIEGE_DEPRECATED_315 = 315,
|
|
||||||
/// Definitely Not Dominion games on Crystal Scar
|
|
||||||
CRYSTAL_SCAR_DEFINITELY_NOT_DOMINION = 317,
|
|
||||||
/// ARURF games on Summoner's Rift
|
|
||||||
/// Deprecated in patch 7.19 in favor of queueId 900
|
|
||||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 900")]
|
|
||||||
SUMMONERS_RIFT_ARURF = 318,
|
|
||||||
/// All Random games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_ALL_RANDOM = 325,
|
|
||||||
/// 5v5 Draft Pick games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_5V5_DRAFT_PICK = 400,
|
|
||||||
/// 5v5 Ranked Dynamic games on Summoner's Rift
|
|
||||||
/// Game mode deprecated in patch 6.22
|
|
||||||
#[deprecated(note="Game mode deprecated in patch 6.22")]
|
|
||||||
SUMMONERS_RIFT_5V5_RANKED_DYNAMIC = 410,
|
|
||||||
/// 5v5 Ranked Solo games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_5V5_RANKED_SOLO = 420,
|
|
||||||
/// 5v5 Blind Pick games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_5V5_BLIND_PICK = 430,
|
|
||||||
/// 5v5 Ranked Flex games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_5V5_RANKED_FLEX = 440,
|
|
||||||
/// 5v5 ARAM games on Howling Abyss
|
|
||||||
HOWLING_ABYSS_5V5_ARAM = 450,
|
|
||||||
/// 3v3 Blind Pick games on Twisted Treeline
|
|
||||||
/// Deprecated in patch 9.23
|
|
||||||
#[deprecated(note="Deprecated in patch 9.23")]
|
|
||||||
TWISTED_TREELINE_3V3_BLIND_PICK = 460,
|
|
||||||
/// 3v3 Ranked Flex games on Twisted Treeline
|
|
||||||
/// Deprecated in patch 9.23
|
|
||||||
#[deprecated(note="Deprecated in patch 9.23")]
|
|
||||||
TWISTED_TREELINE_3V3_RANKED_FLEX_DEPRECATED_470 = 470,
|
|
||||||
/// Blood Hunt Assassin games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_BLOOD_HUNT_ASSASSIN = 600,
|
|
||||||
/// Dark Star: Singularity games on Cosmic Ruins
|
|
||||||
COSMIC_RUINS_DARK_STAR_SINGULARITY = 610,
|
|
||||||
/// Clash games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_CLASH = 700,
|
|
||||||
/// Co-op vs. AI Intermediate Bot games on Twisted Treeline
|
|
||||||
/// Deprecated in patch 9.23
|
|
||||||
#[deprecated(note="Deprecated in patch 9.23")]
|
|
||||||
TWISTED_TREELINE_CO_OP_VS_AI_INTERMEDIATE_BOT = 800,
|
|
||||||
/// Co-op vs. AI Intro Bot games on Twisted Treeline
|
|
||||||
/// Deprecated in patch 9.23
|
|
||||||
#[deprecated(note="Deprecated in patch 9.23")]
|
|
||||||
TWISTED_TREELINE_CO_OP_VS_AI_INTRO_BOT = 810,
|
|
||||||
/// Co-op vs. AI Beginner Bot games on Twisted Treeline
|
|
||||||
TWISTED_TREELINE_CO_OP_VS_AI_BEGINNER_BOT = 820,
|
|
||||||
/// Co-op vs. AI Intro Bot games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTRO_BOT = 830,
|
|
||||||
/// Co-op vs. AI Beginner Bot games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_CO_OP_VS_AI_BEGINNER_BOT = 840,
|
|
||||||
/// Co-op vs. AI Intermediate Bot games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTERMEDIATE_BOT = 850,
|
|
||||||
/// URF games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_URF = 900,
|
|
||||||
/// Ascension games on Crystal Scar
|
|
||||||
CRYSTAL_SCAR_ASCENSION = 910,
|
|
||||||
/// Legend of the Poro King games on Howling Abyss
|
|
||||||
HOWLING_ABYSS_LEGEND_OF_THE_PORO_KING = 920,
|
|
||||||
/// Nexus Siege games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_NEXUS_SIEGE = 940,
|
|
||||||
/// Doom Bots Voting games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_DOOM_BOTS_VOTING = 950,
|
|
||||||
/// Doom Bots Standard games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_DOOM_BOTS_STANDARD = 960,
|
|
||||||
/// Star Guardian Invasion: Normal games on Valoran City Park
|
|
||||||
VALORAN_CITY_PARK_STAR_GUARDIAN_INVASION_NORMAL = 980,
|
|
||||||
/// Star Guardian Invasion: Onslaught games on Valoran City Park
|
|
||||||
VALORAN_CITY_PARK_STAR_GUARDIAN_INVASION_ONSLAUGHT = 990,
|
|
||||||
/// PROJECT: Hunters games on Overcharge
|
|
||||||
OVERCHARGE_PROJECT_HUNTERS = 1000,
|
|
||||||
/// Snow ARURF games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_SNOW_ARURF = 1010,
|
|
||||||
/// One for All games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_ONE_FOR_ALL = 1020,
|
|
||||||
/// Odyssey Extraction: Intro games on Crash Site
|
|
||||||
CRASH_SITE_ODYSSEY_EXTRACTION_INTRO = 1030,
|
|
||||||
/// Odyssey Extraction: Cadet games on Crash Site
|
|
||||||
CRASH_SITE_ODYSSEY_EXTRACTION_CADET = 1040,
|
|
||||||
/// Odyssey Extraction: Crewmember games on Crash Site
|
|
||||||
CRASH_SITE_ODYSSEY_EXTRACTION_CREWMEMBER = 1050,
|
|
||||||
/// Odyssey Extraction: Captain games on Crash Site
|
|
||||||
CRASH_SITE_ODYSSEY_EXTRACTION_CAPTAIN = 1060,
|
|
||||||
/// Odyssey Extraction: Onslaught games on Crash Site
|
|
||||||
CRASH_SITE_ODYSSEY_EXTRACTION_ONSLAUGHT = 1070,
|
|
||||||
/// Teamfight Tactics games on Convergence
|
|
||||||
CONVERGENCE_TEAMFIGHT_TACTICS = 1090,
|
|
||||||
/// Ranked Teamfight Tactics games on Convergence
|
|
||||||
CONVERGENCE_RANKED_TEAMFIGHT_TACTICS = 1100,
|
|
||||||
/// Teamfight Tactics Tutorial games on Convergence
|
|
||||||
CONVERGENCE_TEAMFIGHT_TACTICS_TUTORIAL = 1110,
|
|
||||||
/// Teamfight Tactics 1v0 testing games on Convergence
|
|
||||||
CONVERGENCE_TEAMFIGHT_TACTICS_1V0_TESTING = 1111,
|
|
||||||
/// Nexus Blitz games on Nexus Blitz
|
|
||||||
/// Deprecated in patch 9.2 in favor of queueId 1300
|
|
||||||
#[deprecated(note="Deprecated in patch 9.2 in favor of queueId 1300")]
|
|
||||||
NEXUS_BLITZ_NEXUS_BLITZ_DEPRECATED_1200 = 1200,
|
|
||||||
/// Nexus Blitz games on Nexus Blitz
|
|
||||||
NEXUS_BLITZ_NEXUS_BLITZ = 1300,
|
|
||||||
/// Tutorial 1 games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_TUTORIAL_1 = 2000,
|
|
||||||
/// Tutorial 2 games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_TUTORIAL_2 = 2010,
|
|
||||||
/// Tutorial 3 games on Summoner's Rift
|
|
||||||
SUMMONERS_RIFT_TUTORIAL_3 = 2020,
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
|
||||||
|
|
||||||
/// LoL or TFT ranked queue types.
|
|
||||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
|
||||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
|
||||||
pub enum QueueType {
|
|
||||||
// League of Legends, Summoner's Rift (5v5), Ranked Solo Queue.
|
|
||||||
RANKED_SOLO_5x5,
|
|
||||||
// League of Legends, Summoner's Rift (5v5), Flex Queue.
|
|
||||||
RANKED_FLEX_SR,
|
|
||||||
// League of Legends, Twisted Treeline (3v3), Flex Queue.
|
|
||||||
RANKED_FLEX_TT,
|
|
||||||
// Ranked Teamfight Tactics.
|
|
||||||
RANKED_TFT,
|
|
||||||
// Ranked Teamfight Tactics, Hyper Roll gamemode.
|
|
||||||
RANKED_TFT_TURBO,
|
|
||||||
}
|
|
||||||
|
|
||||||
serde_string!(QueueType);
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_as_ref() {
|
|
||||||
assert_eq!("RANKED_SOLO_5x5", QueueType::RANKED_SOLO_5x5.as_ref());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_to_string() {
|
|
||||||
assert_eq!("RANKED_SOLO_5x5", QueueType::RANKED_SOLO_5x5.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_from_string() {
|
|
||||||
assert_eq!(Some(QueueType::RANKED_SOLO_5x5), "RANKED_SOLO_5x5".parse().ok());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
|
||||||
|
|
||||||
/// A region served by a single game server.
|
|
||||||
/// Each Riot Games API request is directed at a particular region,
|
|
||||||
/// with tournament API requests directed at the AMERICAS "global" region.
|
|
||||||
///
|
|
||||||
/// Valorant regions are prefixed with `VAL_` due to the name collision with
|
|
||||||
/// `BR` ("BR1") for LoL and `BR` ("BR") for Valorant.
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
||||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum Region {
|
|
||||||
#[strum(to_string="BR1", serialize="BR")]
|
|
||||||
BR,
|
|
||||||
#[strum(to_string="EUN1", serialize="EUNE")]
|
|
||||||
EUNE,
|
|
||||||
#[strum(to_string="EUW1", serialize="EUW")]
|
|
||||||
EUW,
|
|
||||||
#[strum(to_string="NA1", serialize="NA")]
|
|
||||||
NA,
|
|
||||||
#[strum(to_string="KR")]
|
|
||||||
KR,
|
|
||||||
#[strum(to_string="LA1", serialize="LAN")]
|
|
||||||
LAN,
|
|
||||||
#[strum(to_string="LA2", serialize="LAS")]
|
|
||||||
LAS,
|
|
||||||
#[strum(to_string="OC1", serialize="OCE")]
|
|
||||||
OCE,
|
|
||||||
#[strum(to_string="RU")]
|
|
||||||
RU,
|
|
||||||
#[strum(to_string="TR1", serialize="TR")]
|
|
||||||
TR,
|
|
||||||
#[strum(to_string="JP1", serialize="JP")]
|
|
||||||
JP,
|
|
||||||
#[strum(to_string="PBE1", serialize="PBE")]
|
|
||||||
PBE,
|
|
||||||
#[strum(to_string="AMERICAS")]
|
|
||||||
AMERICAS,
|
|
||||||
#[strum(to_string="EUROPE")]
|
|
||||||
EUROPE,
|
|
||||||
#[strum(to_string="ASIA")]
|
|
||||||
ASIA,
|
|
||||||
|
|
||||||
// Problem: serializing and deserializing these will result in a different
|
|
||||||
// enum picked, due to naming collision.
|
|
||||||
#[strum(to_string="AP")]
|
|
||||||
VAL_AP,
|
|
||||||
#[strum(to_string="BR")]
|
|
||||||
VAL_BR,
|
|
||||||
#[strum(to_string="EU")]
|
|
||||||
VAL_EU,
|
|
||||||
#[strum(to_string="KR")]
|
|
||||||
VAL_KR,
|
|
||||||
#[strum(to_string="LATAM")]
|
|
||||||
VAL_LATAM,
|
|
||||||
#[strum(to_string="NA")]
|
|
||||||
VAL_NA,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_basic() {
|
|
||||||
assert_eq!("BR1", Region::BR.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get() {
|
|
||||||
assert_eq!(Ok(Region::JP), "JP".parse());
|
|
||||||
assert_eq!(Ok(Region::NA), "NA1".parse());
|
|
||||||
assert!("LA".parse::<Region>().is_err());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
///////////////////////////////////////////////
|
|
||||||
// //
|
|
||||||
// ! //
|
|
||||||
// This file is automatically generated! //
|
|
||||||
// Do not directly edit! //
|
|
||||||
// //
|
|
||||||
///////////////////////////////////////////////
|
|
||||||
|
|
||||||
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
|
||||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
|
||||||
|
|
||||||
/// League of Legends matchmaking seasons.
|
|
||||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
|
||||||
#[derive(Serialize_repr, Deserialize_repr)]
|
|
||||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum Season {
|
|
||||||
PRESEASON_3 = 0,
|
|
||||||
SEASON_3 = 1,
|
|
||||||
PRESEASON_2014 = 2,
|
|
||||||
SEASON_2014 = 3,
|
|
||||||
PRESEASON_2015 = 4,
|
|
||||||
SEASON_2015 = 5,
|
|
||||||
PRESEASON_2016 = 6,
|
|
||||||
SEASON_2016 = 7,
|
|
||||||
PRESEASON_2017 = 8,
|
|
||||||
SEASON_2017 = 9,
|
|
||||||
PRESEASON_2018 = 10,
|
|
||||||
SEASON_2018 = 11,
|
|
||||||
PRESEASON_2019 = 12,
|
|
||||||
SEASON_2019 = 13,
|
|
||||||
PRESEASON_2020 = 14,
|
|
||||||
SEASON_2020 = 15,
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
|
||||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
|
||||||
|
|
||||||
/// League of Legends team.
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd)]
|
|
||||||
#[derive(Serialize_repr, Deserialize_repr)]
|
|
||||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum Team {
|
|
||||||
/// Blue team (bottom left on Summoner's Rift).
|
|
||||||
BLUE = 100,
|
|
||||||
/// Red team (top right on Summoner's Rift).
|
|
||||||
RED = 200,
|
|
||||||
}
|
|
1555
src/endpoints.rs
1555
src/endpoints.rs
File diff suppressed because it is too large
Load Diff
87
src/meta.rs
87
src/meta.rs
|
@ -1,87 +0,0 @@
|
||||||
///////////////////////////////////////////////
|
|
||||||
// //
|
|
||||||
// ! //
|
|
||||||
// This file is automatically generated! //
|
|
||||||
// Do not directly edit! //
|
|
||||||
// //
|
|
||||||
///////////////////////////////////////////////
|
|
||||||
|
|
||||||
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
|
||||||
// Version bbfb64a2ef9111c6610a823da800b0335587831d
|
|
||||||
|
|
||||||
//! Metadata about the Riot API and Riven.
|
|
||||||
//!
|
|
||||||
//! Note: this modules is automatically generated.
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref ENDPOINT_PATH_METHODID: HashMap<&'static str, &'static str> = {
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
map.insert("/riot/account/v1/accounts/by-puuid/{puuid}", "account-v1.getByPuuid");
|
|
||||||
map.insert("/riot/account/v1/accounts/by-riot-id/{gameName}/{tagLine}", "account-v1.getByRiotId");
|
|
||||||
map.insert("/riot/account/v1/active-shards/by-game/{game}/by-puuid/{puuid}", "account-v1.getActiveShard");
|
|
||||||
map.insert("/lol/champion-mastery/v4/champion-masteries/by-summoner/{encryptedSummonerId}", "champion-mastery-v4.getAllChampionMasteries");
|
|
||||||
map.insert("/lol/champion-mastery/v4/champion-masteries/by-summoner/{encryptedSummonerId}/by-champion/{championId}", "champion-mastery-v4.getChampionMastery");
|
|
||||||
map.insert("/lol/champion-mastery/v4/scores/by-summoner/{encryptedSummonerId}", "champion-mastery-v4.getChampionMasteryScore");
|
|
||||||
map.insert("/lol/platform/v3/champion-rotations", "champion-v3.getChampionInfo");
|
|
||||||
map.insert("/lol/clash/v1/players/by-summoner/{summonerId}", "clash-v1.getPlayersBySummoner");
|
|
||||||
map.insert("/lol/clash/v1/teams/{teamId}", "clash-v1.getTeamById");
|
|
||||||
map.insert("/lol/clash/v1/tournaments", "clash-v1.getTournaments");
|
|
||||||
map.insert("/lol/clash/v1/tournaments/by-team/{teamId}", "clash-v1.getTournamentByTeam");
|
|
||||||
map.insert("/lol/clash/v1/tournaments/{tournamentId}", "clash-v1.getTournamentById");
|
|
||||||
map.insert("/lol/league-exp/v4/entries/{queue}/{tier}/{division}", "league-exp-v4.getLeagueEntries");
|
|
||||||
map.insert("/lol/league/v4/challengerleagues/by-queue/{queue}", "league-v4.getChallengerLeague");
|
|
||||||
map.insert("/lol/league/v4/entries/by-summoner/{encryptedSummonerId}", "league-v4.getLeagueEntriesForSummoner");
|
|
||||||
map.insert("/lol/league/v4/entries/{queue}/{tier}/{division}", "league-v4.getLeagueEntries");
|
|
||||||
map.insert("/lol/league/v4/grandmasterleagues/by-queue/{queue}", "league-v4.getGrandmasterLeague");
|
|
||||||
map.insert("/lol/league/v4/leagues/{leagueId}", "league-v4.getLeagueById");
|
|
||||||
map.insert("/lol/league/v4/masterleagues/by-queue/{queue}", "league-v4.getMasterLeague");
|
|
||||||
map.insert("/lol/status/v3/shard-data", "lol-status-v3.getShardData");
|
|
||||||
map.insert("/lol/status/v4/platform-data", "lol-status-v4.getPlatformData");
|
|
||||||
map.insert("/lor/match/v1/matches/by-puuid/{puuid}/ids", "lor-match-v1.getMatchIdsByPUUID");
|
|
||||||
map.insert("/lor/match/v1/matches/{matchId}", "lor-match-v1.getMatch");
|
|
||||||
map.insert("/lor/ranked/v1/leaderboards", "lor-ranked-v1.getLeaderboards");
|
|
||||||
map.insert("/lor/status/v1/platform-data", "lor-status-v1.getPlatformData");
|
|
||||||
map.insert("/lol/match/v4/matches/by-tournament-code/{tournamentCode}/ids", "match-v4.getMatchIdsByTournamentCode");
|
|
||||||
map.insert("/lol/match/v4/matches/{matchId}", "match-v4.getMatch");
|
|
||||||
map.insert("/lol/match/v4/matches/{matchId}/by-tournament-code/{tournamentCode}", "match-v4.getMatchByTournamentCode");
|
|
||||||
map.insert("/lol/match/v4/matchlists/by-account/{encryptedAccountId}", "match-v4.getMatchlist");
|
|
||||||
map.insert("/lol/match/v4/timelines/by-match/{matchId}", "match-v4.getMatchTimeline");
|
|
||||||
map.insert("/lol/match/v5/matches/by-puuid/{puuid}/ids", "match-v5.getMatchIdsByPUUID");
|
|
||||||
map.insert("/lol/match/v5/matches/{matchId}", "match-v5.getMatch");
|
|
||||||
map.insert("/lol/match/v5/matches/{matchId}/timeline", "match-v5.getTimeline");
|
|
||||||
map.insert("/lol/spectator/v4/active-games/by-summoner/{encryptedSummonerId}", "spectator-v4.getCurrentGameInfoBySummoner");
|
|
||||||
map.insert("/lol/spectator/v4/featured-games", "spectator-v4.getFeaturedGames");
|
|
||||||
map.insert("/lol/summoner/v4/summoners/by-account/{encryptedAccountId}", "summoner-v4.getByAccountId");
|
|
||||||
map.insert("/lol/summoner/v4/summoners/by-name/{summonerName}", "summoner-v4.getBySummonerName");
|
|
||||||
map.insert("/lol/summoner/v4/summoners/by-puuid/{encryptedPUUID}", "summoner-v4.getByPUUID");
|
|
||||||
map.insert("/lol/summoner/v4/summoners/{encryptedSummonerId}", "summoner-v4.getBySummonerId");
|
|
||||||
map.insert("/tft/league/v1/challenger", "tft-league-v1.getChallengerLeague");
|
|
||||||
map.insert("/tft/league/v1/entries/by-summoner/{summonerId}", "tft-league-v1.getLeagueEntriesForSummoner");
|
|
||||||
map.insert("/tft/league/v1/entries/{tier}/{division}", "tft-league-v1.getLeagueEntries");
|
|
||||||
map.insert("/tft/league/v1/grandmaster", "tft-league-v1.getGrandmasterLeague");
|
|
||||||
map.insert("/tft/league/v1/leagues/{leagueId}", "tft-league-v1.getLeagueById");
|
|
||||||
map.insert("/tft/league/v1/master", "tft-league-v1.getMasterLeague");
|
|
||||||
map.insert("/tft/league/v1/rated-ladders/{queue}/top", "tft-league-v1.getTopRatedLadder");
|
|
||||||
map.insert("/tft/match/v1/matches/by-puuid/{puuid}/ids", "tft-match-v1.getMatchIdsByPUUID");
|
|
||||||
map.insert("/tft/match/v1/matches/{matchId}", "tft-match-v1.getMatch");
|
|
||||||
map.insert("/tft/summoner/v1/summoners/by-account/{encryptedAccountId}", "tft-summoner-v1.getByAccountId");
|
|
||||||
map.insert("/tft/summoner/v1/summoners/by-name/{summonerName}", "tft-summoner-v1.getBySummonerName");
|
|
||||||
map.insert("/tft/summoner/v1/summoners/by-puuid/{encryptedPUUID}", "tft-summoner-v1.getByPUUID");
|
|
||||||
map.insert("/tft/summoner/v1/summoners/{encryptedSummonerId}", "tft-summoner-v1.getBySummonerId");
|
|
||||||
map.insert("/lol/platform/v4/third-party-code/by-summoner/{encryptedSummonerId}", "third-party-code-v4.getThirdPartyCodeBySummonerId");
|
|
||||||
map.insert("/lol/tournament-stub/v4/lobby-events/by-code/{tournamentCode}", "tournament-stub-v4.getLobbyEventsByCode");
|
|
||||||
map.insert("/lol/tournament/v4/codes/{tournamentCode}", "tournament-v4.getTournamentCode");
|
|
||||||
map.insert("/lol/tournament/v4/lobby-events/by-code/{tournamentCode}", "tournament-v4.getLobbyEventsByCode");
|
|
||||||
map.insert("/val/content/v1/contents", "val-content-v1.getContent");
|
|
||||||
map.insert("/val/match/v1/matches/{matchId}", "val-match-v1.getMatch");
|
|
||||||
map.insert("/val/match/v1/matchlists/by-puuid/{puuid}", "val-match-v1.getMatchlist");
|
|
||||||
map.insert("/val/match/v1/recent-matches/by-queue/{queue}", "val-match-v1.getRecent");
|
|
||||||
map.insert("/val/ranked/v1/leaderboards/by-act/{actId}", "val-ranked-v1.getLeaderboard");
|
|
||||||
map.insert("/val/status/v1/platform-data", "val-status-v1.getPlatformData");
|
|
||||||
map
|
|
||||||
};
|
|
||||||
}
|
|
2712
src/models.rs
2712
src/models.rs
File diff suppressed because it is too large
Load Diff
|
@ -1,187 +0,0 @@
|
||||||
use std::cmp;
|
|
||||||
use std::time::{ Duration, Instant };
|
|
||||||
|
|
||||||
use log;
|
|
||||||
use parking_lot::{ RwLock, RwLockUpgradableReadGuard };
|
|
||||||
use reqwest::{ StatusCode, Response };
|
|
||||||
use scan_fmt::scan_fmt;
|
|
||||||
|
|
||||||
use crate::RiotApiConfig;
|
|
||||||
use super::{ TokenBucket, VectorTokenBucket };
|
|
||||||
use super::RateLimitType;
|
|
||||||
|
|
||||||
pub struct RateLimit {
|
|
||||||
rate_limit_type: RateLimitType,
|
|
||||||
// Buckets for this rate limit (synchronized).
|
|
||||||
// Almost always read, written only when rate limit rates are updated
|
|
||||||
// from API response.
|
|
||||||
buckets: RwLock<Vec<VectorTokenBucket>>,
|
|
||||||
// Set to when we can retry if a retry-after header is received.
|
|
||||||
retry_after: RwLock<Option<Instant>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RateLimit {
|
|
||||||
/// Header specifying which RateLimitType caused a 429.
|
|
||||||
const HEADER_XRATELIMITTYPE: &'static str = "X-Rate-Limit-Type";
|
|
||||||
/// Header specifying retry after time in seconds after a 429.
|
|
||||||
const HEADER_RETRYAFTER: &'static str = "Retry-After";
|
|
||||||
|
|
||||||
pub fn new(rate_limit_type: RateLimitType) -> Self {
|
|
||||||
let initial_bucket = VectorTokenBucket::new(
|
|
||||||
Duration::from_secs(1), 1, Duration::new(0, 0), 1.0);
|
|
||||||
RateLimit {
|
|
||||||
rate_limit_type: rate_limit_type,
|
|
||||||
// Rate limit before getting from response: 1/s.
|
|
||||||
buckets: RwLock::new(vec![initial_bucket]),
|
|
||||||
retry_after: RwLock::new(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_both_or_delay(app_rate_limit: &Self, method_rate_limit: &Self) -> Option<Duration> {
|
|
||||||
// Check retry after.
|
|
||||||
{
|
|
||||||
let retry_after_delay = app_rate_limit.get_retry_after_delay()
|
|
||||||
.and_then(|a| method_rate_limit.get_retry_after_delay().map(|m| cmp::max(a, m)));
|
|
||||||
if retry_after_delay.is_some() {
|
|
||||||
return retry_after_delay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check buckets.
|
|
||||||
let app_buckets = app_rate_limit.buckets.read();
|
|
||||||
let method_buckets = method_rate_limit.buckets.read();
|
|
||||||
for bucket in app_buckets.iter().chain(method_buckets.iter()) {
|
|
||||||
let delay = bucket.get_delay();
|
|
||||||
if delay.is_some() {
|
|
||||||
return delay;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Success.
|
|
||||||
for bucket in app_buckets.iter().chain(method_buckets.iter()) {
|
|
||||||
bucket.get_tokens(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
log::debug!("Tokens obtained, buckets: APP {:?} METHOD {:?}", app_buckets, method_buckets);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_retry_after_delay(&self) -> Option<Duration> {
|
|
||||||
self.retry_after.read().and_then(|i| Instant::now().checked_duration_since(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_response(&self, config: &RiotApiConfig, response: &Response) {
|
|
||||||
self.on_response_retry_after(response);
|
|
||||||
self.on_response_rate_limits(config, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `on_response` helper for retry after check.
|
|
||||||
#[inline]
|
|
||||||
fn on_response_retry_after(&self, response: &Response) {
|
|
||||||
if let Some(retry_after) = || -> Option<Instant> {
|
|
||||||
// Only care about 429s.
|
|
||||||
if StatusCode::TOO_MANY_REQUESTS != response.status() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// Only care if the header that indicates the relevant RateLimit is present.
|
|
||||||
let type_name_header = response.headers()
|
|
||||||
.get(RateLimit::HEADER_XRATELIMITTYPE)?.to_str()
|
|
||||||
.expect("Failed to read x-rate-limit-type header as string.");
|
|
||||||
// Only care if that header's value matches us.
|
|
||||||
if self.rate_limit_type.type_name() != type_name_header.to_lowercase() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get retry after header. Only care if it exists.
|
|
||||||
let retry_after_header = response.headers()
|
|
||||||
.get(RateLimit::HEADER_RETRYAFTER)?.to_str()
|
|
||||||
.expect("Failed to read retry-after header as string.");
|
|
||||||
|
|
||||||
log::debug!("Hit 429, retry-after {} secs.", retry_after_header);
|
|
||||||
|
|
||||||
// Header currently only returns ints, but float is more general. Can be zero.
|
|
||||||
let retry_after_secs: f32 = retry_after_header.parse()
|
|
||||||
.expect("Failed to parse retry-after header as f32.");
|
|
||||||
// Add 0.5 seconds to account for rounding, cases when response is zero.
|
|
||||||
let delay = Duration::from_secs_f32(0.5 + retry_after_secs);
|
|
||||||
return Some(Instant::now() + delay);
|
|
||||||
}() {
|
|
||||||
*self.retry_after.write() = Some(retry_after);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn on_response_rate_limits(&self, config: &RiotApiConfig, response: &Response) {
|
|
||||||
// Check if rate limits changed.
|
|
||||||
let headers = response.headers();
|
|
||||||
let limit_header_opt = headers.get(self.rate_limit_type.limit_header())
|
|
||||||
.map(|h| h.to_str().expect("Failed to read limit header as string."));
|
|
||||||
let count_header_opt = headers.get(self.rate_limit_type.count_header())
|
|
||||||
.map(|h| h.to_str().expect("Failed to read count header as string."));
|
|
||||||
|
|
||||||
// https://github.com/rust-lang/rust/issues/53667
|
|
||||||
if let Some(limit_header) = limit_header_opt {
|
|
||||||
if let Some(count_header) = count_header_opt {
|
|
||||||
|
|
||||||
let buckets = self.buckets.upgradable_read();
|
|
||||||
if !buckets_require_updating(limit_header, &*buckets) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buckets require updating. Upgrade to write lock.
|
|
||||||
let mut buckets = RwLockUpgradableReadGuard::upgrade(buckets);
|
|
||||||
*buckets = buckets_from_header(config, limit_header, count_header)
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn buckets_require_updating(limit_header: &str, buckets: &Vec<VectorTokenBucket>) -> bool {
|
|
||||||
if buckets.len() != limit_header.split(",").count() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (limit_header_entry, bucket) in limit_header.split(",").zip(&*buckets) {
|
|
||||||
// limit_header_entry "100:60" means 100 req per 60 sec.
|
|
||||||
let bucket_entry = format!("{}:{}", bucket.get_total_limit(), bucket.get_bucket_duration().as_secs());
|
|
||||||
if limit_header_entry != bucket_entry {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn buckets_from_header(config: &RiotApiConfig, limit_header: &str, count_header: &str) -> Vec<VectorTokenBucket> {
|
|
||||||
// Limits: "20000:10,1200000:600"
|
|
||||||
// Counts: "7:10,58:600"
|
|
||||||
let size = limit_header.split(",").count();
|
|
||||||
debug_assert!(size == count_header.split(",").count());
|
|
||||||
let mut out = Vec::with_capacity(size);
|
|
||||||
|
|
||||||
for (limit_entry, count_entry) in limit_header.split(",").zip(count_header.split(",")) {
|
|
||||||
let (limit, limit_secs) = scan_fmt!(limit_entry, "{d}:{d}", usize, u64)
|
|
||||||
.unwrap_or_else(|_| panic!("Failed to parse limit entry \"{}\".", limit_entry));
|
|
||||||
let (count, count_secs) = scan_fmt!(count_entry, "{d}:{d}", usize, u64)
|
|
||||||
.unwrap_or_else(|_| panic!("Failed to parse count entry \"{}\".", count_entry));
|
|
||||||
debug_assert!(limit_secs == count_secs);
|
|
||||||
|
|
||||||
let limit_f32 = limit as f32;
|
|
||||||
let scaled_burst_pct = config.burst_pct * limit_f32 / (limit_f32 + 1.0);
|
|
||||||
|
|
||||||
let bucket = VectorTokenBucket::new(Duration::from_secs(limit_secs), limit,
|
|
||||||
config.duration_overhead, scaled_burst_pct);
|
|
||||||
bucket.get_tokens(count);
|
|
||||||
out.push(bucket);
|
|
||||||
}
|
|
||||||
log::debug!("Set buckets to {} limit, {} count.", limit_header, count_header);
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
// use super::*;
|
|
||||||
|
|
||||||
// fn send_sync() {
|
|
||||||
// fn is_send_sync<T: Send + Sync>() {}
|
|
||||||
// is_send_sync::<RateLimit>();
|
|
||||||
// }
|
|
||||||
}
|
|
|
@ -1,111 +0,0 @@
|
||||||
use std::future::Future;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use log;
|
|
||||||
use reqwest::{ Client, StatusCode, Url };
|
|
||||||
|
|
||||||
use crate::Result;
|
|
||||||
use crate::ResponseInfo;
|
|
||||||
use crate::RiotApiError;
|
|
||||||
use crate::RiotApiConfig;
|
|
||||||
use crate::util::InsertOnlyCHashMap;
|
|
||||||
|
|
||||||
use super::RateLimit;
|
|
||||||
use super::RateLimitType;
|
|
||||||
|
|
||||||
pub struct RegionalRequester {
|
|
||||||
/// The app rate limit.
|
|
||||||
app_rate_limit: RateLimit,
|
|
||||||
/// Method rate limits.
|
|
||||||
method_rate_limits: InsertOnlyCHashMap<&'static str, RateLimit>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RegionalRequester {
|
|
||||||
/// Request header name for the Riot API key.
|
|
||||||
const RIOT_KEY_HEADER: &'static str = "X-Riot-Token";
|
|
||||||
|
|
||||||
/// HTTP status codes which are considered a success but will results in `None`.
|
|
||||||
const NONE_STATUS_CODES: [StatusCode; 2] = [
|
|
||||||
StatusCode::NO_CONTENT, // 204
|
|
||||||
StatusCode::NOT_FOUND, // 404
|
|
||||||
];
|
|
||||||
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
app_rate_limit: RateLimit::new(RateLimitType::Application),
|
|
||||||
method_rate_limits: InsertOnlyCHashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get<'a>(self: Arc<Self>,
|
|
||||||
config: &'a RiotApiConfig, client: &'a Client,
|
|
||||||
method_id: &'static str, region_platform: &'a str, path: String, query: Option<String>)
|
|
||||||
-> impl Future<Output = Result<ResponseInfo>> + 'a
|
|
||||||
{
|
|
||||||
async move {
|
|
||||||
#[cfg(feature = "nightly")] let query = query.as_deref();
|
|
||||||
#[cfg(not(feature = "nightly"))] let query = query.as_ref().map(|s| s.as_ref());
|
|
||||||
|
|
||||||
let mut retries: u8 = 0;
|
|
||||||
loop {
|
|
||||||
let method_rate_limit: Arc<RateLimit> = self.method_rate_limits
|
|
||||||
.get_or_insert_with(method_id, || RateLimit::new(RateLimitType::Method));
|
|
||||||
|
|
||||||
// Rate limiting.
|
|
||||||
while let Some(delay) = RateLimit::get_both_or_delay(&self.app_rate_limit, &*method_rate_limit) {
|
|
||||||
tokio::time::sleep(delay).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send request.
|
|
||||||
let url_base = format!("https://{}.api.riotgames.com", region_platform);
|
|
||||||
let mut url = Url::parse(&*url_base)
|
|
||||||
.unwrap_or_else(|_| panic!("Failed to parse url_base: \"{}\".", url_base));
|
|
||||||
url.set_path(&*path);
|
|
||||||
url.set_query(query);
|
|
||||||
|
|
||||||
let response = client.get(url)
|
|
||||||
.header(Self::RIOT_KEY_HEADER, &*config.api_key)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|e| RiotApiError::new(e, retries, None, None))?;
|
|
||||||
|
|
||||||
// Maybe update rate limits (based on response headers).
|
|
||||||
self.app_rate_limit.on_response(&config, &response);
|
|
||||||
method_rate_limit.on_response(&config, &response);
|
|
||||||
|
|
||||||
let status = response.status();
|
|
||||||
// Handle normal success / failure cases.
|
|
||||||
let status_none = Self::NONE_STATUS_CODES.contains(&status);
|
|
||||||
// Success case.
|
|
||||||
if status.is_success() || status_none {
|
|
||||||
log::trace!("Response {} (retried {} times), success, returning result.", status, retries);
|
|
||||||
break Ok(ResponseInfo {
|
|
||||||
response: response,
|
|
||||||
retries: retries,
|
|
||||||
status_none: status_none,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let err = response.error_for_status_ref().err().unwrap_or_else(
|
|
||||||
|| panic!("Unhandlable response status code, neither success nor failure: {}.", status));
|
|
||||||
// Failure, may or may not be retryable.
|
|
||||||
// Not-retryable: no more retries or 4xx or ? (3xx, redirects exceeded).
|
|
||||||
// Retryable: retries remaining, and 429 or 5xx.
|
|
||||||
if retries >= config.retries ||
|
|
||||||
(StatusCode::TOO_MANY_REQUESTS != status
|
|
||||||
&& !status.is_server_error())
|
|
||||||
{
|
|
||||||
log::debug!("Response {} (retried {} times), failure, returning error.", status, retries);
|
|
||||||
break Err(RiotApiError::new(err, retries, Some(response), Some(status)));
|
|
||||||
}
|
|
||||||
log::debug!("Response {} (retried {} times), retrying.", status, retries);
|
|
||||||
|
|
||||||
retries += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
// use super::*;
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
#![cfg(test)]
|
|
||||||
|
|
||||||
use fake_instant::FakeClock as Instant;
|
|
||||||
|
|
||||||
/// This is a hack to test token bucket, substituting FakeClock for Instant.
|
|
||||||
mod token_bucket {
|
|
||||||
include!("token_bucket.rs");
|
|
||||||
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref D00: Duration = Duration::new(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_basic() {
|
|
||||||
Instant::set_time(50_000);
|
|
||||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 0.95);
|
|
||||||
assert!(bucket.get_tokens(50), "Should have not violated limit.");
|
|
||||||
assert_eq!(None, bucket.get_delay(), "Can get stuff.");
|
|
||||||
assert!(!bucket.get_tokens(51), "Should have violated limit.");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_internal_constructor() {
|
|
||||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 1.0);
|
|
||||||
assert_eq!(100, bucket.burst_limit);
|
|
||||||
|
|
||||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 1e-6);
|
|
||||||
assert_eq!(1, bucket.burst_limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_saturated_100_burst() {
|
|
||||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 1.00);
|
|
||||||
|
|
||||||
Instant::set_time(50_000);
|
|
||||||
assert!(bucket.get_tokens(100), "All tokens should be immediately available.");
|
|
||||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
|
||||||
|
|
||||||
Instant::advance_time(1001); // Extra buffer for Duration(0).
|
|
||||||
assert!(bucket.get_tokens(100), "All tokens should be available after a bucket duration.");
|
|
||||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_saturated_95_burst() {
|
|
||||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 0.50);
|
|
||||||
|
|
||||||
Instant::set_time(50_000);
|
|
||||||
assert!(bucket.get_tokens(95), "95 tokens should be immediately available.");
|
|
||||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
|
||||||
|
|
||||||
Instant::advance_time(475); // Total 951.
|
|
||||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
|
||||||
|
|
||||||
Instant::advance_time(476); // Extra buffer for Duration(0).
|
|
||||||
assert!(bucket.get_tokens(5), "Last 5 tokens should be available.");
|
|
||||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
|
||||||
|
|
||||||
Instant::advance_time(51);
|
|
||||||
assert!(bucket.get_tokens(95), "95 tokens should be available.");
|
|
||||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
|
||||||
|
|
||||||
Instant::advance_time(951);
|
|
||||||
assert!(bucket.get_tokens(5), "Last 5 tokens should be available.");
|
|
||||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_saturated_50_burst() {
|
|
||||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 0.5);
|
|
||||||
|
|
||||||
Instant::set_time(50_000);
|
|
||||||
assert!(bucket.get_tokens(50), "Half the tokens should be immediately available.");
|
|
||||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
|
||||||
|
|
||||||
Instant::advance_time(501); // Extra buffer for Duration(0).
|
|
||||||
assert!(bucket.get_tokens(50), "Half the tokens should be available after a half bucket duration.");
|
|
||||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
|
||||||
|
|
||||||
Instant::advance_time(501);
|
|
||||||
assert!(bucket.get_tokens(50), "Half the tokens should be available after a full bucket duration.");
|
|
||||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
|
||||||
|
|
||||||
Instant::advance_time(501);
|
|
||||||
assert!(bucket.get_tokens(50));
|
|
||||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_many() {
|
|
||||||
Instant::set_time(50_000);
|
|
||||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 0.95);
|
|
||||||
assert!(bucket.get_tokens(50), "Should have not violated limit.");
|
|
||||||
assert_eq!(None, bucket.get_delay(), "Should not be blocked.");
|
|
||||||
for _ in 0..20_000 {
|
|
||||||
Instant::advance_time(501);
|
|
||||||
assert!(bucket.get_tokens(50), "Should have not violated limit.");
|
|
||||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
|
||||||
Instant::advance_time(501);
|
|
||||||
assert!(bucket.get_tokens(50), "Should have not violated limit.");
|
|
||||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
|
||||||
}
|
|
||||||
assert!(bucket.timestamps.lock().len() < 110, "Check memory leak.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue