forked from mirror/Riven
Compare commits
1 commit
v/2.x.x
...
users/ming
Author | SHA1 | Date | |
---|---|---|---|
|
f716a30e31 |
114 changed files with 7244 additions and 15451 deletions
120
.github/workflows/ci.yml
vendored
120
.github/workflows/ci.yml
vendored
|
@ -1,120 +0,0 @@
|
|||
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
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -4,3 +4,6 @@
|
|||
Cargo.lock
|
||||
apikey.txt
|
||||
|
||||
/srcgen/node_modules/
|
||||
/srcgen/.*.json
|
||||
|
||||
|
|
10
.travis.yml
Normal file
10
.travis.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
language: rust
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- rust: stable
|
||||
- rust: nightly
|
||||
env: FEATURES="--features nightly"
|
||||
env:
|
||||
global:
|
||||
- RUST_BACKTRACE=1
|
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"rust-analyzer.runnableEnv": [
|
||||
{
|
||||
// Set output levels for `tracing` logging.
|
||||
"env": {
|
||||
"RUST_BACKTRACE": "1",
|
||||
"RUST_LOG": "riven=debug"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
47
Cargo.toml
47
Cargo.toml
|
@ -1,4 +1,43 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"riven",
|
||||
]
|
||||
[package]
|
||||
name = "riven"
|
||||
version = "1.1.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]
|
||||
nightly = [ "parking_lot/nightly" ]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
num_enum = "0.4"
|
||||
parking_lot = "0.10"
|
||||
reqwest = { version = "0.10", features = [ "gzip", "json" ] }
|
||||
scan_fmt = { version = "0.2", default-features = false }
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
serde_repr = "0.1"
|
||||
strum = "0.17"
|
||||
strum_macros = "0.17"
|
||||
tokio = { version = "0.2", default-features = false, features = [ "time" ] }
|
||||
url = "2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
colored = "1.8"
|
||||
env_logger = "0.7"
|
||||
fake_instant = "0.4"
|
||||
lazy_static = "1.4"
|
||||
tokio = "0.2"
|
||||
|
|
81
README.md
81
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://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://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>
|
||||
|
||||
|
@ -15,28 +15,29 @@ Riven's goals are _speed_, _reliability_, and _maintainability_. Riven handles r
|
|||
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
|
||||
## 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).
|
||||
* TFT API Support.
|
||||
|
||||
# Usage
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use riven::RiotApi;
|
||||
use riven::consts::PlatformRoute;
|
||||
use riven::consts::Region;
|
||||
|
||||
// Enter tokio async runtime.
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut 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);
|
||||
let api_key = "RGAPI-01234567-89ab-cdef-0123-456789abcdef";
|
||||
# /* (doc testing) */ let api_key = std::env!("RGAPI_KEY");
|
||||
let riot_api = RiotApi::with_key(api_key);
|
||||
|
||||
// Get summoner data.
|
||||
let summoner = riot_api.summoner_v4()
|
||||
.get_by_summoner_name(PlatformRoute::NA1, "잘 못").await
|
||||
.get_by_summoner_name(Region::NA, "잘못").await
|
||||
.expect("Get summoner failed.")
|
||||
.expect("There is no summoner with that name.");
|
||||
|
||||
|
@ -45,13 +46,13 @@ rt.block_on(async {
|
|||
|
||||
// Get champion mastery data.
|
||||
let masteries = riot_api.champion_mastery_v4()
|
||||
.get_all_champion_masteries_by_puuid(PlatformRoute::NA1, &summoner.puuid).await
|
||||
.get_all_champion_masteries(Region::NA, &summoner.id).await
|
||||
.expect("Get champion masteries failed.");
|
||||
|
||||
// Print champion masteries.
|
||||
for (i, mastery) in masteries.iter().take(10).enumerate() {
|
||||
// Print champioon masteries.
|
||||
for (i, mastery) in masteries[..10].iter().enumerate() {
|
||||
println!("{: >2}) {: <9} {: >7} ({})", i + 1,
|
||||
mastery.champion_id.name().unwrap_or("UNKNOWN"),
|
||||
mastery.champion_id.to_string(),
|
||||
mastery.champion_points, mastery.champion_level);
|
||||
}
|
||||
});
|
||||
|
@ -59,50 +60,29 @@ rt.block_on(async {
|
|||
Output:
|
||||
```text
|
||||
잘 못 Champion Masteries:
|
||||
1) Riven 1236866 (7)
|
||||
2) Fiora 230679 (5)
|
||||
1) Riven 1219895 (7)
|
||||
2) Fiora 229714 (5)
|
||||
3) Katarina 175985 (5)
|
||||
4) Lee Sin 156070 (7)
|
||||
5) Jax 102662 (5)
|
||||
4) Lee Sin 150546 (7)
|
||||
5) Jax 100509 (5)
|
||||
6) Gnar 76373 (6)
|
||||
7) Kai'Sa 64271 (5)
|
||||
8) Caitlyn 46614 (5)
|
||||
8) Caitlyn 46479 (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
|
||||
Enable the `nightly` feature to use nightly-only functionality. Mainly enables
|
||||
[nightly optimizations in the `parking_lot` crate](https://github.com/Amanieu/parking_lot#nightly-vs-stable).
|
||||
Also required for running async integration tests.
|
||||
|
||||
```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
|
||||
### Docs
|
||||
|
||||
[On docs.rs](https://docs.rs/riven/).
|
||||
|
||||
## Error Handling
|
||||
### Error Handling
|
||||
|
||||
Riven returns either `Result<T>` or `Result<Option<T>>` within futures.
|
||||
|
||||
|
@ -121,12 +101,12 @@ 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)`
|
||||
`RiotApiConfig::set_retries(...)` and the `RiotApi::with_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
|
||||
### 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
|
||||
|
@ -142,22 +122,21 @@ not the major version.
|
|||
Parts of Riven that do not depend on Riot API changes do follow semantic
|
||||
versioning.
|
||||
|
||||
## Additional Help
|
||||
### 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
|
||||
## Development
|
||||
|
||||
NodeJS is used to generate code for Riven. The
|
||||
[`riven/srcgen`](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/srcgen)
|
||||
[`srcgen/`](https://github.com/MingweiSamuel/Riven/tree/master/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.
|
||||
srcgen folder and run `npm ci` (or `npm install`) to install dependencies.
|
||||
|
||||
To run the srcgen use `node riven/srcgen` from the repository root.
|
||||
To run the srcgen use `node srcgen` from the main folder.
|
||||
|
||||
|
|
2
doc.bash
2
doc.bash
|
@ -1,2 +0,0 @@
|
|||
#!/bin/bash
|
||||
cargo doc --features nightly
|
|
@ -1,64 +0,0 @@
|
|||
[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"
|
|
@ -1,33 +0,0 @@
|
|||
# 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."}
|
||||
```
|
|
@ -1,193 +0,0 @@
|
|||
// #![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(())
|
||||
}
|
|
@ -1,304 +0,0 @@
|
|||
//! 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,24 +0,0 @@
|
|||
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());
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
#![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 { "?" })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
#![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,
|
||||
}
|
||||
}
|
|
@ -1,397 +0,0 @@
|
|||
#![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,
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
#![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;
|
|
@ -1,52 +0,0 @@
|
|||
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));
|
||||
|
||||
}
|
|
@ -1,345 +0,0 @@
|
|||
#![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,
|
||||
}
|
|
@ -1,198 +0,0 @@
|
|||
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());
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
#![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,
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
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,221 +0,0 @@
|
|||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||
|
||||
/// LoL and TFT ranked tiers, such as gold, diamond, challenger, etc.
|
||||
///
|
||||
/// Sorts from lowest rank to highest rank.
|
||||
///
|
||||
/// Repr'd as arbitrary `u8` values.
|
||||
///
|
||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator).
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
IntoPrimitive,
|
||||
TryFromPrimitive,
|
||||
EnumString,
|
||||
Display,
|
||||
AsRefStr,
|
||||
IntoStaticStr,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum Tier {
|
||||
/// Challenger, the highest tier, an apex tier. Repr: `220_u8`.
|
||||
CHALLENGER = 220,
|
||||
/// Grand Master, an apex tier. Repr: `200_u8`.
|
||||
GRANDMASTER = 200,
|
||||
/// Master, an apex tier. Repr: `180_u8`.
|
||||
MASTER = 180,
|
||||
/// Diamond, the higest non-apex tier. Repr: `140_u8`.
|
||||
DIAMOND = 140,
|
||||
/// Emerald. Added in 2023. Repr: `130_u8`.
|
||||
EMERALD = 130,
|
||||
/// Platinum. Repr: `120_u8`.
|
||||
PLATINUM = 120,
|
||||
/// Gold. Repr: `100_u8`.
|
||||
GOLD = 100,
|
||||
/// Silver. Repr: `80_u8`.
|
||||
SILVER = 80,
|
||||
/// Bronze. Repr: `60_u8`.
|
||||
BRONZE = 60,
|
||||
/// Iron, the lowest tier. Repr: `40_u8`.
|
||||
IRON = 40,
|
||||
|
||||
/// Unranked, no tier. Repr: `0_u8`.
|
||||
/// Also deserializes from "NONE" returned by `lol-challenges-v1.getChallengePercentiles`.
|
||||
#[serde(alias = "NONE")]
|
||||
UNRANKED = 0,
|
||||
}
|
||||
|
||||
impl Tier {
|
||||
/// If this tier is an apex tier: [`Self::MASTER`], [`Self::GRANDMASTER`],
|
||||
/// or [`Self::CHALLENGER`]. Returns false for [`Self::UNRANKED`].
|
||||
///
|
||||
/// These tiers are NOT queryable by LeagueV4Endpoints::get_league_entries(...).
|
||||
pub const fn is_apex(self) -> bool {
|
||||
// Casts needed for const.
|
||||
(Self::MASTER as u8) <= (self as u8)
|
||||
}
|
||||
|
||||
/// If this tier is a "standard" tier: iron through diamond.
|
||||
/// Returns false for unranked.
|
||||
///
|
||||
/// ONLY these tiers are queryable by [`LeagueV4::get_league_entries(...)`](crate::endpoints::LeagueV4::get_league_entries).
|
||||
pub fn is_standard(self) -> bool {
|
||||
// Casts needed for const.
|
||||
((Self::UNRANKED as u8) < (self as u8)) && ((self as u8) < (Self::MASTER as u8))
|
||||
}
|
||||
|
||||
/// If this tier is ranked. Returns true for iron through challenger, false for unranked.
|
||||
pub const fn is_ranked(self) -> bool {
|
||||
// Casts needed for const.
|
||||
(Self::UNRANKED as u8) < (self as u8)
|
||||
}
|
||||
|
||||
/// If this tier is unranked (`Tier::UNRANKED`).
|
||||
///
|
||||
/// UNRANKED is returned by `Participant.highest_achieved_season_tier`.
|
||||
pub const fn is_unranked(self) -> bool {
|
||||
// Casts needed for const.
|
||||
(self as u8) <= (Self::UNRANKED as u8)
|
||||
}
|
||||
|
||||
/// Converts UNRANKED to None and all ranked tiers to Some(...).
|
||||
pub fn to_ranked(self) -> Option<Self> {
|
||||
if self.is_unranked() {
|
||||
None
|
||||
} else {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a DoubleEndedIterator of I, II, III, IV.
|
||||
/// Ordered from high rank (I) to low (IV).
|
||||
/// Excludes V, which is deprecated.
|
||||
impl IntoEnumIterator for Tier {
|
||||
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
||||
fn iter() -> Self::Iterator {
|
||||
[
|
||||
Self::CHALLENGER,
|
||||
Self::GRANDMASTER,
|
||||
Self::MASTER,
|
||||
Self::DIAMOND,
|
||||
Self::EMERALD,
|
||||
Self::PLATINUM,
|
||||
Self::GOLD,
|
||||
Self::SILVER,
|
||||
Self::BRONZE,
|
||||
Self::IRON,
|
||||
]
|
||||
.iter()
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ord() {
|
||||
assert!(Tier::GOLD < Tier::DIAMOND);
|
||||
assert!(Tier::UNRANKED < Tier::IRON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_apex() {
|
||||
assert!(Tier::GRANDMASTER.is_apex());
|
||||
assert!(!Tier::DIAMOND.is_apex());
|
||||
assert!(!Tier::UNRANKED.is_apex());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_ranked() {
|
||||
assert!(Tier::GRANDMASTER.is_ranked());
|
||||
assert!(Tier::DIAMOND.is_ranked());
|
||||
assert!(!Tier::UNRANKED.is_ranked());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_unranked() {
|
||||
assert!(!Tier::GRANDMASTER.is_unranked());
|
||||
assert!(!Tier::DIAMOND.is_unranked());
|
||||
assert!(Tier::UNRANKED.is_unranked());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_ranked() {
|
||||
assert_eq!(Some(Tier::GRANDMASTER), Tier::GRANDMASTER.to_ranked());
|
||||
assert_eq!(Some(Tier::DIAMOND), Tier::DIAMOND.to_ranked());
|
||||
assert_eq!(None, Tier::UNRANKED.to_ranked());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_standard() {
|
||||
assert!(!Tier::GRANDMASTER.is_standard());
|
||||
assert!(Tier::DIAMOND.is_standard());
|
||||
assert!(!Tier::UNRANKED.is_standard());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
assert_eq!("GRANDMASTER", Tier::GRANDMASTER.as_ref());
|
||||
assert_eq!("GRANDMASTER", Tier::GRANDMASTER.to_string());
|
||||
assert_eq!("UNRANKED", Tier::UNRANKED.as_ref());
|
||||
assert_eq!("UNRANKED", Tier::UNRANKED.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_string() {
|
||||
assert_eq!(Ok(Tier::GRANDMASTER), "GRANDMASTER".parse());
|
||||
assert_eq!(Ok(Tier::UNRANKED), "UNRANKED".parse());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter() {
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
let mut iter = Tier::iter();
|
||||
assert_eq!(Some(Tier::CHALLENGER), iter.next());
|
||||
iter.next();
|
||||
iter.next();
|
||||
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
||||
assert_eq!(Some(Tier::EMERALD), iter.next());
|
||||
iter.next();
|
||||
iter.next();
|
||||
iter.next();
|
||||
iter.next();
|
||||
assert_eq!(Some(Tier::IRON), iter.next());
|
||||
assert_eq!(None, iter.next());
|
||||
assert_eq!(None, iter.next_back());
|
||||
|
||||
let mut iter = Tier::iter().rev();
|
||||
assert_eq!(Some(Tier::IRON), iter.next());
|
||||
iter.next();
|
||||
iter.next();
|
||||
iter.next();
|
||||
iter.next();
|
||||
assert_eq!(Some(Tier::EMERALD), iter.next());
|
||||
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
||||
iter.next();
|
||||
iter.next();
|
||||
assert_eq!(Some(Tier::CHALLENGER), iter.next());
|
||||
assert_eq!(None, iter.next());
|
||||
assert_eq!(None, iter.next_back());
|
||||
|
||||
let mut iter = Tier::iter();
|
||||
assert_eq!(Some(Tier::CHALLENGER), iter.next());
|
||||
assert_eq!(Some(Tier::IRON), iter.next_back());
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
206
riven/src/lib.rs
206
riven/src/lib.rs
|
@ -1,206 +0,0 @@
|
|||
#![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;
|
|
@ -1,100 +0,0 @@
|
|||
#![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"),
|
||||
];
|
3940
riven/src/models.rs
3940
riven/src/models.rs
File diff suppressed because it is too large
Load diff
|
@ -1,21 +0,0 @@
|
|||
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())
|
||||
}
|
||||
}
|
|
@ -1,307 +0,0 @@
|
|||
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,136 +0,0 @@
|
|||
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,200 +0,0 @@
|
|||
#![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."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
use reqwest::Response;
|
||||
|
||||
/// A "raw" unparsed successful response from the Riot API, for internal or advanced use cases.
|
||||
pub struct ResponseInfo {
|
||||
/// The reqwest response.
|
||||
pub response: Response,
|
||||
/// The number of retries used, zero for first-try success.
|
||||
pub retries: u8,
|
||||
/// If the response has an HTTP status code indicating a `None` response (i.e. 204, 404).
|
||||
pub status_none: bool,
|
||||
}
|
|
@ -1,201 +0,0 @@
|
|||
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()
|
||||
})
|
||||
}
|
||||
}
|
2
riven/srcgen/.gitignore
vendored
2
riven/srcgen/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
node_modules/
|
||||
.*.json
|
|
@ -1,166 +0,0 @@
|
|||
{{
|
||||
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,33 +0,0 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const gameModes = require('./.gameModes.json');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
use strum_macros::{ EnumString, EnumVariantNames, IntoStaticStr };
|
||||
|
||||
/// League of Legends game mode, such as Classic,
|
||||
/// ARAM, URF, One For All, Ascension, etc.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, EnumVariantNames, IntoStaticStr)]
|
||||
#[repr(u8)]
|
||||
pub enum GameMode {
|
||||
/// Catch-all variant for new, unknown game modes.
|
||||
#[strum(default)]
|
||||
UNKNOWN(String),
|
||||
|
||||
{{
|
||||
for (const e of gameModes) {
|
||||
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||
}}
|
||||
{{~ desc :line }}
|
||||
/// {{= line }}
|
||||
{{~}}
|
||||
{{= e['x-name'] }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
serde_strum_unknown!(GameMode);
|
|
@ -1,33 +0,0 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const gameTypes = require('./.gameTypes.json');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
use serde::{ Serialize, Deserialize };
|
||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// League of Legends game type: matched game, custom game, or tutorial game.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum GameType {
|
||||
{{
|
||||
for (const e of gameTypes) {
|
||||
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||
const nameNoGame = e['x-name'].replace(/_GAME$/, "");
|
||||
}}
|
||||
{{~ desc :line }}
|
||||
/// {{= line }}
|
||||
{{~}}
|
||||
#[strum(to_string = "{{= e['x-name'] }}", serialize = "{{= nameNoGame }}")]
|
||||
#[serde(alias = "{{= nameNoGame }}")]
|
||||
{{= e['x-name'] }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
|
@ -1,22 +0,0 @@
|
|||
{{
|
||||
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'] }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
{{
|
||||
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'] }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
{{
|
||||
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;
|
|
@ -1,196 +0,0 @@
|
|||
{{
|
||||
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 }},
|
||||
{{
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
{{
|
||||
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,216 +0,0 @@
|
|||
{{
|
||||
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
|
||||
}
|
||||
|
||||
{{
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
{{
|
||||
}
|
||||
}}
|
|
@ -1,41 +0,0 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const readme = require('fs').readFileSync('../../README.md', 'utf-8').split(/\r?\n/);
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
{{~ readme :line }}
|
||||
//!{{= line ? (' ' + line) : '' }}
|
||||
{{~}}
|
||||
|
||||
// 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;
|
|
@ -1,31 +0,0 @@
|
|||
{{
|
||||
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 }}"),
|
||||
{{
|
||||
}
|
||||
}}
|
||||
];
|
1445
riven/srcgen/package-lock.json
generated
1445
riven/srcgen/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,26 +0,0 @@
|
|||
#![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(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
#![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
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
#![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
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
#![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
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
#![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(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
#![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(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
#![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(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
#![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(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
#![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(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
#![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(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
#![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(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
#![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(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
#![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: ValPlatformRoute = ValPlatformRoute::LATAM;
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
val_content_ranked_test: async {
|
||||
let p = RIOT_API.val_content_v1().get_content(ROUTE, Some("zh-CN"));
|
||||
let contents = p.await.map_err(|e| format!("Failed to get content: {}", e))?;
|
||||
|
||||
// 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()))?;
|
||||
|
||||
let p = RIOT_API.val_ranked_v1().get_leaderboard(ROUTE, &act.id, None, None);
|
||||
let leaderboard = p.await.map_err(|e| e.to_string())?
|
||||
.ok_or(format!("Failed to get act leaderboard {} {}.", act.id, act.name))?;
|
||||
|
||||
rassert_eq!(act.id, leaderboard.act_id);
|
||||
|
||||
for (i, p) in leaderboard.players.iter().take(10).enumerate() {
|
||||
rassert_eq!(i + 1, p.leaderboard_rank as usize);
|
||||
println!("{:>2}: {:>4} {:<22} ({} wins)",
|
||||
p.leaderboard_rank,
|
||||
p.ranked_rating,
|
||||
format!("{}#{}",
|
||||
p.game_name.as_deref().unwrap_or("<NONE>"),
|
||||
p.tag_line.as_deref().unwrap_or("<NONE>")),
|
||||
p.number_of_wins);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
#![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(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
#![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(()))
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
10
rustfmt.toml
10
rustfmt.toml
|
@ -1,10 +0,0 @@
|
|||
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
Normal file
167
src/config.rs
Normal file
|
@ -0,0 +1,167 @@
|
|||
//! 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
|
||||
}
|
||||
}
|
505
src/consts/champion.rs
Normal file
505
src/consts/champion.rs
Normal file
|
@ -0,0 +1,505 @@
|
|||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// 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", serialize="Aatrox")] Aatrox = 266,
|
||||
/// Ahri (`Ahri`, 103).
|
||||
#[strum(to_string="Ahri", serialize="Ahri")] Ahri = 103,
|
||||
/// Akali (`Akali`, 84).
|
||||
#[strum(to_string="Akali", serialize="Akali")] Akali = 84,
|
||||
/// Alistar (`Alistar`, 12).
|
||||
#[strum(to_string="Alistar", serialize="Alistar")] Alistar = 12,
|
||||
/// Amumu (`Amumu`, 32).
|
||||
#[strum(to_string="Amumu", serialize="Amumu")] Amumu = 32,
|
||||
/// Anivia (`Anivia`, 34).
|
||||
#[strum(to_string="Anivia", serialize="Anivia")] Anivia = 34,
|
||||
/// Annie (`Annie`, 1).
|
||||
#[strum(to_string="Annie", serialize="Annie")] Annie = 1,
|
||||
/// Aphelios (`Aphelios`, 523).
|
||||
#[strum(to_string="Aphelios", serialize="Aphelios")] Aphelios = 523,
|
||||
/// Ashe (`Ashe`, 22).
|
||||
#[strum(to_string="Ashe", serialize="Ashe")] Ashe = 22,
|
||||
/// Aurelion Sol (`AurelionSol`, 136).
|
||||
#[strum(to_string="Aurelion Sol", serialize="AurelionSol")] AurelionSol = 136,
|
||||
/// Azir (`Azir`, 268).
|
||||
#[strum(to_string="Azir", serialize="Azir")] Azir = 268,
|
||||
/// Bard (`Bard`, 432).
|
||||
#[strum(to_string="Bard", serialize="Bard")] Bard = 432,
|
||||
/// Blitzcrank (`Blitzcrank`, 53).
|
||||
#[strum(to_string="Blitzcrank", serialize="Blitzcrank")] Blitzcrank = 53,
|
||||
/// Brand (`Brand`, 63).
|
||||
#[strum(to_string="Brand", serialize="Brand")] Brand = 63,
|
||||
/// Braum (`Braum`, 201).
|
||||
#[strum(to_string="Braum", serialize="Braum")] Braum = 201,
|
||||
/// Caitlyn (`Caitlyn`, 51).
|
||||
#[strum(to_string="Caitlyn", serialize="Caitlyn")] Caitlyn = 51,
|
||||
/// Camille (`Camille`, 164).
|
||||
#[strum(to_string="Camille", serialize="Camille")] Camille = 164,
|
||||
/// Cassiopeia (`Cassiopeia`, 69).
|
||||
#[strum(to_string="Cassiopeia", serialize="Cassiopeia")] Cassiopeia = 69,
|
||||
/// Cho'Gath (`Chogath`, 31).
|
||||
#[strum(to_string="Cho'Gath", serialize="Chogath")] ChoGath = 31,
|
||||
/// Corki (`Corki`, 42).
|
||||
#[strum(to_string="Corki", serialize="Corki")] Corki = 42,
|
||||
/// Darius (`Darius`, 122).
|
||||
#[strum(to_string="Darius", serialize="Darius")] Darius = 122,
|
||||
/// Diana (`Diana`, 131).
|
||||
#[strum(to_string="Diana", serialize="Diana")] Diana = 131,
|
||||
/// Dr. Mundo (`DrMundo`, 36).
|
||||
#[strum(to_string="Dr. Mundo", serialize="DrMundo")] DrMundo = 36,
|
||||
/// Draven (`Draven`, 119).
|
||||
#[strum(to_string="Draven", serialize="Draven")] Draven = 119,
|
||||
/// Ekko (`Ekko`, 245).
|
||||
#[strum(to_string="Ekko", serialize="Ekko")] Ekko = 245,
|
||||
/// Elise (`Elise`, 60).
|
||||
#[strum(to_string="Elise", serialize="Elise")] Elise = 60,
|
||||
/// Evelynn (`Evelynn`, 28).
|
||||
#[strum(to_string="Evelynn", serialize="Evelynn")] Evelynn = 28,
|
||||
/// Ezreal (`Ezreal`, 81).
|
||||
#[strum(to_string="Ezreal", serialize="Ezreal")] Ezreal = 81,
|
||||
/// Fiddlesticks (`FiddleSticks`, 9).
|
||||
#[strum(to_string="Fiddlesticks", serialize="FiddleSticks")] Fiddlesticks = 9,
|
||||
/// Fiora (`Fiora`, 114).
|
||||
#[strum(to_string="Fiora", serialize="Fiora")] Fiora = 114,
|
||||
/// Fizz (`Fizz`, 105).
|
||||
#[strum(to_string="Fizz", serialize="Fizz")] Fizz = 105,
|
||||
/// Galio (`Galio`, 3).
|
||||
#[strum(to_string="Galio", serialize="Galio")] Galio = 3,
|
||||
/// Gangplank (`Gangplank`, 41).
|
||||
#[strum(to_string="Gangplank", serialize="Gangplank")] Gangplank = 41,
|
||||
/// Garen (`Garen`, 86).
|
||||
#[strum(to_string="Garen", serialize="Garen")] Garen = 86,
|
||||
/// Gnar (`Gnar`, 150).
|
||||
#[strum(to_string="Gnar", serialize="Gnar")] Gnar = 150,
|
||||
/// Gragas (`Gragas`, 79).
|
||||
#[strum(to_string="Gragas", serialize="Gragas")] Gragas = 79,
|
||||
/// Graves (`Graves`, 104).
|
||||
#[strum(to_string="Graves", serialize="Graves")] Graves = 104,
|
||||
/// Hecarim (`Hecarim`, 120).
|
||||
#[strum(to_string="Hecarim", serialize="Hecarim")] Hecarim = 120,
|
||||
/// Heimerdinger (`Heimerdinger`, 74).
|
||||
#[strum(to_string="Heimerdinger", serialize="Heimerdinger")] Heimerdinger = 74,
|
||||
/// Illaoi (`Illaoi`, 420).
|
||||
#[strum(to_string="Illaoi", serialize="Illaoi")] Illaoi = 420,
|
||||
/// Irelia (`Irelia`, 39).
|
||||
#[strum(to_string="Irelia", serialize="Irelia")] Irelia = 39,
|
||||
/// Ivern (`Ivern`, 427).
|
||||
#[strum(to_string="Ivern", serialize="Ivern")] Ivern = 427,
|
||||
/// Janna (`Janna`, 40).
|
||||
#[strum(to_string="Janna", serialize="Janna")] Janna = 40,
|
||||
/// Jarvan IV (`JarvanIV`, 59).
|
||||
#[strum(to_string="Jarvan IV", serialize="JarvanIV")] JarvanIV = 59,
|
||||
/// Jax (`Jax`, 24).
|
||||
#[strum(to_string="Jax", serialize="Jax")] Jax = 24,
|
||||
/// Jayce (`Jayce`, 126).
|
||||
#[strum(to_string="Jayce", serialize="Jayce")] Jayce = 126,
|
||||
/// Jhin (`Jhin`, 202).
|
||||
#[strum(to_string="Jhin", serialize="Jhin")] Jhin = 202,
|
||||
/// Jinx (`Jinx`, 222).
|
||||
#[strum(to_string="Jinx", serialize="Jinx")] Jinx = 222,
|
||||
/// Kai'Sa (`Kaisa`, 145).
|
||||
#[strum(to_string="Kai'Sa", serialize="Kaisa")] KaiSa = 145,
|
||||
/// Kalista (`Kalista`, 429).
|
||||
#[strum(to_string="Kalista", serialize="Kalista")] Kalista = 429,
|
||||
/// Karma (`Karma`, 43).
|
||||
#[strum(to_string="Karma", serialize="Karma")] Karma = 43,
|
||||
/// Karthus (`Karthus`, 30).
|
||||
#[strum(to_string="Karthus", serialize="Karthus")] Karthus = 30,
|
||||
/// Kassadin (`Kassadin`, 38).
|
||||
#[strum(to_string="Kassadin", serialize="Kassadin")] Kassadin = 38,
|
||||
/// Katarina (`Katarina`, 55).
|
||||
#[strum(to_string="Katarina", serialize="Katarina")] Katarina = 55,
|
||||
/// Kayle (`Kayle`, 10).
|
||||
#[strum(to_string="Kayle", serialize="Kayle")] Kayle = 10,
|
||||
/// Kayn (`Kayn`, 141).
|
||||
#[strum(to_string="Kayn", serialize="Kayn")] Kayn = 141,
|
||||
/// Kennen (`Kennen`, 85).
|
||||
#[strum(to_string="Kennen", serialize="Kennen")] Kennen = 85,
|
||||
/// Kha'Zix (`Khazix`, 121).
|
||||
#[strum(to_string="Kha'Zix", serialize="Khazix")] KhaZix = 121,
|
||||
/// Kindred (`Kindred`, 203).
|
||||
#[strum(to_string="Kindred", serialize="Kindred")] Kindred = 203,
|
||||
/// Kled (`Kled`, 240).
|
||||
#[strum(to_string="Kled", serialize="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", serialize="Leona")] Leona = 89,
|
||||
/// Lissandra (`Lissandra`, 127).
|
||||
#[strum(to_string="Lissandra", serialize="Lissandra")] Lissandra = 127,
|
||||
/// Lucian (`Lucian`, 236).
|
||||
#[strum(to_string="Lucian", serialize="Lucian")] Lucian = 236,
|
||||
/// Lulu (`Lulu`, 117).
|
||||
#[strum(to_string="Lulu", serialize="Lulu")] Lulu = 117,
|
||||
/// Lux (`Lux`, 99).
|
||||
#[strum(to_string="Lux", serialize="Lux")] Lux = 99,
|
||||
/// Malphite (`Malphite`, 54).
|
||||
#[strum(to_string="Malphite", serialize="Malphite")] Malphite = 54,
|
||||
/// Malzahar (`Malzahar`, 90).
|
||||
#[strum(to_string="Malzahar", serialize="Malzahar")] Malzahar = 90,
|
||||
/// Maokai (`Maokai`, 57).
|
||||
#[strum(to_string="Maokai", serialize="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", serialize="Mordekaiser")] Mordekaiser = 82,
|
||||
/// Morgana (`Morgana`, 25).
|
||||
#[strum(to_string="Morgana", serialize="Morgana")] Morgana = 25,
|
||||
/// Nami (`Nami`, 267).
|
||||
#[strum(to_string="Nami", serialize="Nami")] Nami = 267,
|
||||
/// Nasus (`Nasus`, 75).
|
||||
#[strum(to_string="Nasus", serialize="Nasus")] Nasus = 75,
|
||||
/// Nautilus (`Nautilus`, 111).
|
||||
#[strum(to_string="Nautilus", serialize="Nautilus")] Nautilus = 111,
|
||||
/// Neeko (`Neeko`, 518).
|
||||
#[strum(to_string="Neeko", serialize="Neeko")] Neeko = 518,
|
||||
/// Nidalee (`Nidalee`, 76).
|
||||
#[strum(to_string="Nidalee", serialize="Nidalee")] Nidalee = 76,
|
||||
/// Nocturne (`Nocturne`, 56).
|
||||
#[strum(to_string="Nocturne", serialize="Nocturne")] Nocturne = 56,
|
||||
/// Nunu & Willump (`Nunu`, 20).
|
||||
#[strum(to_string="Nunu & Willump", serialize="Nunu")] NunuWillump = 20,
|
||||
/// Olaf (`Olaf`, 2).
|
||||
#[strum(to_string="Olaf", serialize="Olaf")] Olaf = 2,
|
||||
/// Orianna (`Orianna`, 61).
|
||||
#[strum(to_string="Orianna", serialize="Orianna")] Orianna = 61,
|
||||
/// Ornn (`Ornn`, 516).
|
||||
#[strum(to_string="Ornn", serialize="Ornn")] Ornn = 516,
|
||||
/// Pantheon (`Pantheon`, 80).
|
||||
#[strum(to_string="Pantheon", serialize="Pantheon")] Pantheon = 80,
|
||||
/// Poppy (`Poppy`, 78).
|
||||
#[strum(to_string="Poppy", serialize="Poppy")] Poppy = 78,
|
||||
/// Pyke (`Pyke`, 555).
|
||||
#[strum(to_string="Pyke", serialize="Pyke")] Pyke = 555,
|
||||
/// Qiyana (`Qiyana`, 246).
|
||||
#[strum(to_string="Qiyana", serialize="Qiyana")] Qiyana = 246,
|
||||
/// Quinn (`Quinn`, 133).
|
||||
#[strum(to_string="Quinn", serialize="Quinn")] Quinn = 133,
|
||||
/// Rakan (`Rakan`, 497).
|
||||
#[strum(to_string="Rakan", serialize="Rakan")] Rakan = 497,
|
||||
/// Rammus (`Rammus`, 33).
|
||||
#[strum(to_string="Rammus", serialize="Rammus")] Rammus = 33,
|
||||
/// Rek'Sai (`RekSai`, 421).
|
||||
#[strum(to_string="Rek'Sai", serialize="RekSai")] RekSai = 421,
|
||||
/// Renekton (`Renekton`, 58).
|
||||
#[strum(to_string="Renekton", serialize="Renekton")] Renekton = 58,
|
||||
/// Rengar (`Rengar`, 107).
|
||||
#[strum(to_string="Rengar", serialize="Rengar")] Rengar = 107,
|
||||
/// Riven (`Riven`, 92).
|
||||
#[strum(to_string="Riven", serialize="Riven")] Riven = 92,
|
||||
/// Rumble (`Rumble`, 68).
|
||||
#[strum(to_string="Rumble", serialize="Rumble")] Rumble = 68,
|
||||
/// Ryze (`Ryze`, 13).
|
||||
#[strum(to_string="Ryze", serialize="Ryze")] Ryze = 13,
|
||||
/// Sejuani (`Sejuani`, 113).
|
||||
#[strum(to_string="Sejuani", serialize="Sejuani")] Sejuani = 113,
|
||||
/// Senna (`Senna`, 235).
|
||||
#[strum(to_string="Senna", serialize="Senna")] Senna = 235,
|
||||
/// Sett (`Sett`, 875).
|
||||
#[strum(to_string="Sett", serialize="Sett")] Sett = 875,
|
||||
/// Shaco (`Shaco`, 35).
|
||||
#[strum(to_string="Shaco", serialize="Shaco")] Shaco = 35,
|
||||
/// Shen (`Shen`, 98).
|
||||
#[strum(to_string="Shen", serialize="Shen")] Shen = 98,
|
||||
/// Shyvana (`Shyvana`, 102).
|
||||
#[strum(to_string="Shyvana", serialize="Shyvana")] Shyvana = 102,
|
||||
/// Singed (`Singed`, 27).
|
||||
#[strum(to_string="Singed", serialize="Singed")] Singed = 27,
|
||||
/// Sion (`Sion`, 14).
|
||||
#[strum(to_string="Sion", serialize="Sion")] Sion = 14,
|
||||
/// Sivir (`Sivir`, 15).
|
||||
#[strum(to_string="Sivir", serialize="Sivir")] Sivir = 15,
|
||||
/// Skarner (`Skarner`, 72).
|
||||
#[strum(to_string="Skarner", serialize="Skarner")] Skarner = 72,
|
||||
/// Sona (`Sona`, 37).
|
||||
#[strum(to_string="Sona", serialize="Sona")] Sona = 37,
|
||||
/// Soraka (`Soraka`, 16).
|
||||
#[strum(to_string="Soraka", serialize="Soraka")] Soraka = 16,
|
||||
/// Swain (`Swain`, 50).
|
||||
#[strum(to_string="Swain", serialize="Swain")] Swain = 50,
|
||||
/// Sylas (`Sylas`, 517).
|
||||
#[strum(to_string="Sylas", serialize="Sylas")] Sylas = 517,
|
||||
/// Syndra (`Syndra`, 134).
|
||||
#[strum(to_string="Syndra", serialize="Syndra")] Syndra = 134,
|
||||
/// Tahm Kench (`TahmKench`, 223).
|
||||
#[strum(to_string="Tahm Kench", serialize="TahmKench")] TahmKench = 223,
|
||||
/// Taliyah (`Taliyah`, 163).
|
||||
#[strum(to_string="Taliyah", serialize="Taliyah")] Taliyah = 163,
|
||||
/// Talon (`Talon`, 91).
|
||||
#[strum(to_string="Talon", serialize="Talon")] Talon = 91,
|
||||
/// Taric (`Taric`, 44).
|
||||
#[strum(to_string="Taric", serialize="Taric")] Taric = 44,
|
||||
/// Teemo (`Teemo`, 17).
|
||||
#[strum(to_string="Teemo", serialize="Teemo")] Teemo = 17,
|
||||
/// Thresh (`Thresh`, 412).
|
||||
#[strum(to_string="Thresh", serialize="Thresh")] Thresh = 412,
|
||||
/// Tristana (`Tristana`, 18).
|
||||
#[strum(to_string="Tristana", serialize="Tristana")] Tristana = 18,
|
||||
/// Trundle (`Trundle`, 48).
|
||||
#[strum(to_string="Trundle", serialize="Trundle")] Trundle = 48,
|
||||
/// Tryndamere (`Tryndamere`, 23).
|
||||
#[strum(to_string="Tryndamere", serialize="Tryndamere")] Tryndamere = 23,
|
||||
/// Twisted Fate (`TwistedFate`, 4).
|
||||
#[strum(to_string="Twisted Fate", serialize="TwistedFate")] TwistedFate = 4,
|
||||
/// Twitch (`Twitch`, 29).
|
||||
#[strum(to_string="Twitch", serialize="Twitch")] Twitch = 29,
|
||||
/// Udyr (`Udyr`, 77).
|
||||
#[strum(to_string="Udyr", serialize="Udyr")] Udyr = 77,
|
||||
/// Urgot (`Urgot`, 6).
|
||||
#[strum(to_string="Urgot", serialize="Urgot")] Urgot = 6,
|
||||
/// Varus (`Varus`, 110).
|
||||
#[strum(to_string="Varus", serialize="Varus")] Varus = 110,
|
||||
/// Vayne (`Vayne`, 67).
|
||||
#[strum(to_string="Vayne", serialize="Vayne")] Vayne = 67,
|
||||
/// Veigar (`Veigar`, 45).
|
||||
#[strum(to_string="Veigar", serialize="Veigar")] Veigar = 45,
|
||||
/// Vel'Koz (`Velkoz`, 161).
|
||||
#[strum(to_string="Vel'Koz", serialize="Velkoz")] VelKoz = 161,
|
||||
/// Vi (`Vi`, 254).
|
||||
#[strum(to_string="Vi", serialize="Vi")] Vi = 254,
|
||||
/// Viktor (`Viktor`, 112).
|
||||
#[strum(to_string="Viktor", serialize="Viktor")] Viktor = 112,
|
||||
/// Vladimir (`Vladimir`, 8).
|
||||
#[strum(to_string="Vladimir", serialize="Vladimir")] Vladimir = 8,
|
||||
/// Volibear (`Volibear`, 106).
|
||||
#[strum(to_string="Volibear", serialize="Volibear")] Volibear = 106,
|
||||
/// Warwick (`Warwick`, 19).
|
||||
#[strum(to_string="Warwick", serialize="Warwick")] Warwick = 19,
|
||||
/// Wukong (`MonkeyKing`, 62).
|
||||
#[strum(to_string="Wukong", serialize="MonkeyKing")] Wukong = 62,
|
||||
/// Xayah (`Xayah`, 498).
|
||||
#[strum(to_string="Xayah", serialize="Xayah")] Xayah = 498,
|
||||
/// Xerath (`Xerath`, 101).
|
||||
#[strum(to_string="Xerath", serialize="Xerath")] Xerath = 101,
|
||||
/// Xin Zhao (`XinZhao`, 5).
|
||||
#[strum(to_string="Xin Zhao", serialize="XinZhao")] XinZhao = 5,
|
||||
/// Yasuo (`Yasuo`, 157).
|
||||
#[strum(to_string="Yasuo", serialize="Yasuo")] Yasuo = 157,
|
||||
/// Yorick (`Yorick`, 83).
|
||||
#[strum(to_string="Yorick", serialize="Yorick")] Yorick = 83,
|
||||
/// Yuumi (`Yuumi`, 350).
|
||||
#[strum(to_string="Yuumi", serialize="Yuumi")] Yuumi = 350,
|
||||
/// Zac (`Zac`, 154).
|
||||
#[strum(to_string="Zac", serialize="Zac")] Zac = 154,
|
||||
/// Zed (`Zed`, 238).
|
||||
#[strum(to_string="Zed", serialize="Zed")] Zed = 238,
|
||||
/// Ziggs (`Ziggs`, 115).
|
||||
#[strum(to_string="Ziggs", serialize="Ziggs")] Ziggs = 115,
|
||||
/// Zilean (`Zilean`, 26).
|
||||
#[strum(to_string="Zilean", serialize="Zilean")] Zilean = 26,
|
||||
/// Zoe (`Zoe`, 142).
|
||||
#[strum(to_string="Zoe", serialize="Zoe")] Zoe = 142,
|
||||
/// Zyra (`Zyra`, 143).
|
||||
#[strum(to_string="Zyra", serialize="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::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::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::Renekton => "Renekton",
|
||||
Self::Rengar => "Rengar",
|
||||
Self::Riven => "Riven",
|
||||
Self::Rumble => "Rumble",
|
||||
Self::Ryze => "Ryze",
|
||||
Self::Sejuani => "Sejuani",
|
||||
Self::Senna => "Senna",
|
||||
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::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::Yorick => "Yorick",
|
||||
Self::Yuumi => "Yuumi",
|
||||
Self::Zac => "Zac",
|
||||
Self::Zed => "Zed",
|
||||
Self::Ziggs => "Ziggs",
|
||||
Self::Zilean => "Zilean",
|
||||
Self::Zoe => "Zoe",
|
||||
Self::Zyra => "Zyra",
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||
|
||||
/// LoL and TFT rank divisions, I, II, III, IV, and (deprecated) V.
|
||||
///
|
||||
|
@ -12,45 +11,29 @@ use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
|||
/// Repr'd as equivalent numeric values, (1, 2, 3, 4, 5).
|
||||
///
|
||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator). Iterator excludes deprecated `Division::V`.
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Hash,
|
||||
EnumString,
|
||||
Display,
|
||||
AsRefStr,
|
||||
IntoStaticStr,
|
||||
IntoPrimitive,
|
||||
TryFromPrimitive,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
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,
|
||||
/// Division 2, the second highest division.
|
||||
II = 2,
|
||||
/// Division 3, the third highest division.
|
||||
III = 3,
|
||||
/// Division 4, the fourth and lowest division since 2019.
|
||||
IV = 4,
|
||||
/// Division 5, the lowest division, only used before 2019.
|
||||
#[deprecated(note="Removed for 2019.")]
|
||||
V = 5,
|
||||
}
|
||||
|
||||
serde_string!(Division);
|
||||
|
||||
/// Returns a DoubleEndedIterator of I, II, III, IV.
|
||||
/// Ordered from high rank (I) to low (IV).
|
||||
/// Excludes V, which is deprecated.
|
||||
impl IntoEnumIterator for Division {
|
||||
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
||||
type Iterator = std::slice::Iter<'static, Self>;
|
||||
fn iter() -> Self::Iterator {
|
||||
[Self::I, Self::II, Self::III, Self::IV].iter().copied()
|
||||
[ Self::I, Self::II, Self::III, Self::IV ].iter()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
///////////////////////////////////////////////
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
|
@ -7,72 +6,52 @@
|
|||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
use strum_macros::{ EnumString, EnumVariantNames, IntoStaticStr };
|
||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// League of Legends game mode, such as Classic,
|
||||
/// ARAM, URF, One For All, Ascension, etc.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, EnumVariantNames, IntoStaticStr)]
|
||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||
#[repr(u8)]
|
||||
pub enum GameMode {
|
||||
/// Catch-all variant for new, unknown game modes.
|
||||
#[strum(default)]
|
||||
UNKNOWN(String),
|
||||
|
||||
/// ARAM games
|
||||
ARAM,
|
||||
/// All Random Summoner's Rift games
|
||||
ARSR,
|
||||
/// Ascension games
|
||||
ASCENSION,
|
||||
/// Blood Hunt Assassin games
|
||||
ASSASSINATE,
|
||||
/// 2v2v2v2
|
||||
CHERRY,
|
||||
/// Classic Summoner's Rift and Twisted Treeline games
|
||||
CLASSIC,
|
||||
/// Dark Star: Singularity games
|
||||
DARKSTAR,
|
||||
/// Doom Bot games
|
||||
DOOMBOTSTEEMO,
|
||||
/// Snowdown Showdown games
|
||||
FIRSTBLOOD,
|
||||
/// Nexus Blitz games
|
||||
GAMEMODEX,
|
||||
/// Legend of the Poro King games
|
||||
KINGPORO,
|
||||
/// Nexus Blitz games
|
||||
NEXUSBLITZ,
|
||||
/// Dominion/Crystal Scar games
|
||||
ODIN,
|
||||
/// Odyssey: Extraction games
|
||||
ODYSSEY,
|
||||
/// One for All games
|
||||
ONEFORALL,
|
||||
/// Practice tool training games.
|
||||
PRACTICETOOL,
|
||||
/// PROJECT: Hunters games
|
||||
PROJECT,
|
||||
/// Nexus Siege games
|
||||
SIEGE,
|
||||
/// Star Guardian Invasion games
|
||||
STARGUARDIAN,
|
||||
/// Teamfight Tactics, used in `spectator-v4` endpoints.
|
||||
TFT,
|
||||
/// ARAM games
|
||||
ARAM,
|
||||
/// Tutorial games
|
||||
TUTORIAL,
|
||||
/// Tutorial: Welcome to League.
|
||||
TUTORIAL_MODULE_1,
|
||||
/// Tutorial: Power Up.
|
||||
TUTORIAL_MODULE_2,
|
||||
/// Tutorial: Shop for Gear.
|
||||
TUTORIAL_MODULE_3,
|
||||
/// Ultimate Spellbook games
|
||||
ULTBOOK,
|
||||
/// URF games
|
||||
URF,
|
||||
/// Doom Bot games
|
||||
DOOMBOTSTEEMO,
|
||||
/// One for All games
|
||||
ONEFORALL,
|
||||
/// Ascension games
|
||||
ASCENSION,
|
||||
/// Snowdown Showdown games
|
||||
FIRSTBLOOD,
|
||||
/// Legend of the Poro King games
|
||||
KINGPORO,
|
||||
/// Nexus Siege games
|
||||
SIEGE,
|
||||
/// Blood Hunt Assassin games
|
||||
ASSASSINATE,
|
||||
/// All Random Summoner's Rift games
|
||||
ARSR,
|
||||
/// Dark Star: Singularity games
|
||||
DARKSTAR,
|
||||
/// Star Guardian Invasion games
|
||||
STARGUARDIAN,
|
||||
/// PROJECT: Hunters games
|
||||
PROJECT,
|
||||
/// Nexus Blitz games
|
||||
GAMEMODEX,
|
||||
/// Odyssey: Extraction games
|
||||
ODYSSEY,
|
||||
}
|
||||
|
||||
serde_strum_unknown!(GameMode);
|
||||
serde_string!(GameMode);
|
|
@ -1,5 +1,4 @@
|
|||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
///////////////////////////////////////////////
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
|
@ -7,29 +6,20 @@
|
|||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
use serde::{ Serialize, Deserialize };
|
||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// League of Legends game type: matched game, custom game, or tutorial game.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum GameType {
|
||||
/// Custom games
|
||||
#[strum(to_string = "CUSTOM_GAME", serialize = "CUSTOM")]
|
||||
#[serde(alias = "CUSTOM")]
|
||||
CUSTOM_GAME,
|
||||
/// all other games
|
||||
#[strum(to_string = "MATCHED_GAME", serialize = "MATCHED")]
|
||||
#[serde(alias = "MATCHED")]
|
||||
MATCHED_GAME,
|
||||
/// Tutorial games
|
||||
#[strum(to_string = "TUTORIAL_GAME", serialize = "TUTORIAL")]
|
||||
#[serde(alias = "TUTORIAL")]
|
||||
TUTORIAL_GAME,
|
||||
/// all other games
|
||||
MATCHED_GAME,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
serde_string!(GameType);
|
23
src/consts/macro_serde_string.rs
Normal file
23
src/consts/macro_serde_string.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
#![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())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
73
src/consts/map.rs
Normal file
73
src/consts/map.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// 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
|
||||
/// <br>Original Summer variant
|
||||
SUMMONERS_RIFT_ORIGINAL_SUMMER_VARIANT = 1,
|
||||
/// Summoner's Rift
|
||||
/// <br>Original Autumn variant
|
||||
SUMMONERS_RIFT_ORIGINAL_AUTUMN_VARIANT = 2,
|
||||
/// Summoner's Rift
|
||||
/// <br>Current Version
|
||||
SUMMONERS_RIFT = 11,
|
||||
|
||||
/// The Proving Grounds
|
||||
/// <br>Tutorial Map
|
||||
THE_PROVING_GROUNDS = 3,
|
||||
|
||||
/// Twisted Treeline
|
||||
/// <br>Original Version
|
||||
TWISTED_TREELINE_ORIGINAL_VERSION = 4,
|
||||
/// Twisted Treeline
|
||||
/// <br>Last TT map
|
||||
TWISTED_TREELINE = 10,
|
||||
|
||||
/// The Crystal Scar
|
||||
/// <br>Dominion map
|
||||
THE_CRYSTAL_SCAR = 8,
|
||||
|
||||
/// Howling Abyss
|
||||
/// <br>ARAM map
|
||||
HOWLING_ABYSS = 12,
|
||||
|
||||
/// Butcher's Bridge
|
||||
/// <br>Alternate ARAM map
|
||||
BUTCHERS_BRIDGE = 14,
|
||||
|
||||
/// Cosmic Ruins
|
||||
/// <br>Dark Star: Singularity map
|
||||
COSMIC_RUINS = 16,
|
||||
|
||||
/// Valoran City Park
|
||||
/// <br>Star Guardian Invasion map
|
||||
VALORAN_CITY_PARK = 18,
|
||||
|
||||
/// Substructure 43
|
||||
/// <br>PROJECT: Hunters map
|
||||
SUBSTRUCTURE43 = 19,
|
||||
|
||||
/// Crash Site
|
||||
/// <br>Odyssey: Extraction map
|
||||
CRASH_SITE = 20,
|
||||
|
||||
/// Nexus Blitz
|
||||
/// <br>Nexus Blitz map
|
||||
NEXUS_BLITZ = 21,
|
||||
}
|
|
@ -6,49 +6,41 @@
|
|||
#![allow(deprecated)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
mod macros;
|
||||
mod macro_serde_string;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod champion;
|
||||
pub use champion::*;
|
||||
|
||||
mod division;
|
||||
pub use division::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod game_mode;
|
||||
pub use game_mode::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod game_type;
|
||||
pub use game_type::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod map;
|
||||
pub use map::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod queue_type;
|
||||
pub use queue_type::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod queue;
|
||||
pub use queue::*;
|
||||
|
||||
pub mod ranks;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod route;
|
||||
pub use route::*;
|
||||
mod region;
|
||||
pub use region::*;
|
||||
|
||||
mod route_ext;
|
||||
pub use route_ext::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod season;
|
||||
pub use season::*;
|
||||
|
||||
/// Trait allowing iteration of enum types, implemented by several enums in this module.
|
||||
/// Re-exported from strum.
|
||||
///
|
||||
///
|
||||
pub use strum::IntoEnumIterator;
|
||||
|
||||
mod team;
|
314
src/consts/queue.rs
Normal file
314
src/consts/queue.rs
Normal file
|
@ -0,0 +1,314 @@
|
|||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// 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 {
|
||||
|
||||
/// Custom games.
|
||||
CUSTOM_GAMES_ = 0,
|
||||
|
||||
/// 5v5 Blind Pick games games on Summoner's Rift.
|
||||
/// <br>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_GAMES_DEPRECATED_2 = 2,
|
||||
/// 5v5 Blind Pick games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_5V5_BLIND_PICK_GAMES = 430,
|
||||
|
||||
/// 5v5 Ranked Solo games games on Summoner's Rift.
|
||||
/// <br>Deprecated in favor of queueId 420
|
||||
#[deprecated(note="Deprecated in favor of queueId 420")]
|
||||
SUMMONERS_RIFT_5V5_RANKED_SOLO_GAMES_DEPRECATED_4 = 4,
|
||||
/// 5v5 Ranked Solo games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_5V5_RANKED_SOLO_GAMES = 420,
|
||||
|
||||
/// 5v5 Ranked Premade games games on Summoner's Rift.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
SUMMONERS_RIFT_5V5_RANKED_PREMADE_GAMES = 6,
|
||||
|
||||
/// Co-op vs AI games games on Summoner's Rift.
|
||||
/// <br>Deprecated in favor of queueId 32 and 33
|
||||
#[deprecated(note="Deprecated in favor of queueId 32 and 33")]
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_GAMES = 7,
|
||||
|
||||
/// 3v3 Normal games games on Twisted Treeline.
|
||||
/// <br>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_GAMES = 8,
|
||||
|
||||
/// 3v3 Ranked Flex games games on Twisted Treeline.
|
||||
/// <br>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_GAMES_DEPRECATED_9 = 9,
|
||||
/// 3v3 Ranked Flex games games on Twisted Treeline.
|
||||
/// <br>Deprecated in patch 9.23
|
||||
#[deprecated(note="Deprecated in patch 9.23")]
|
||||
TWISTED_TREELINE_3V3_RANKED_FLEX_GAMES_DEPRECATED_470 = 470,
|
||||
|
||||
/// 5v5 Draft Pick games games on Summoner's Rift.
|
||||
/// <br>Deprecated in favor of queueId 400
|
||||
#[deprecated(note="Deprecated in favor of queueId 400")]
|
||||
SUMMONERS_RIFT_5V5_DRAFT_PICK_GAMES_DEPRECATED_14 = 14,
|
||||
/// 5v5 Draft Pick games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_5V5_DRAFT_PICK_GAMES = 400,
|
||||
|
||||
/// 5v5 Dominion Blind Pick games games on Crystal Scar.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
CRYSTAL_SCAR_5V5_DOMINION_BLIND_PICK_GAMES = 16,
|
||||
|
||||
/// 5v5 Dominion Draft Pick games games on Crystal Scar.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
CRYSTAL_SCAR_5V5_DOMINION_DRAFT_PICK_GAMES = 17,
|
||||
|
||||
/// Dominion Co-op vs AI games games on Crystal Scar.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
CRYSTAL_SCAR_DOMINION_CO_OP_VS_AI_GAMES = 25,
|
||||
|
||||
/// Co-op vs AI Intro Bot games games on Summoner's Rift.
|
||||
/// <br>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_GAMES_DEPRECATED_31 = 31,
|
||||
/// Co-op vs. AI Intro Bot games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTRO_BOT_GAMES = 830,
|
||||
|
||||
/// Co-op vs AI Beginner Bot games games on Summoner's Rift.
|
||||
/// <br>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_GAMES_DEPRECATED_32 = 32,
|
||||
/// Co-op vs. AI Beginner Bot games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_BEGINNER_BOT_GAMES = 840,
|
||||
|
||||
/// Co-op vs AI Intermediate Bot games games on Summoner's Rift.
|
||||
/// <br>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_GAMES_DEPRECATED_33 = 33,
|
||||
/// Co-op vs. AI Intermediate Bot games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTERMEDIATE_BOT_GAMES = 850,
|
||||
|
||||
/// 3v3 Ranked Team games games on Twisted Treeline.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
TWISTED_TREELINE_3V3_RANKED_TEAM_GAMES = 41,
|
||||
|
||||
/// 5v5 Ranked Team games games on Summoner's Rift.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
SUMMONERS_RIFT_5V5_RANKED_TEAM_GAMES = 42,
|
||||
|
||||
/// Co-op vs AI games games on Twisted Treeline.
|
||||
/// <br>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_GAMES = 52,
|
||||
|
||||
/// 5v5 Team Builder games games on Summoner's Rift.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
SUMMONERS_RIFT_5V5_TEAM_BUILDER_GAMES = 61,
|
||||
|
||||
/// 5v5 ARAM games games on Howling Abyss.
|
||||
/// <br>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_GAMES_DEPRECATED_65 = 65,
|
||||
/// 5v5 ARAM games games on Howling Abyss.
|
||||
HOWLING_ABYSS_5V5_ARAM_GAMES = 450,
|
||||
|
||||
/// ARAM Co-op vs AI games games on Howling Abyss.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
HOWLING_ABYSS_ARAM_CO_OP_VS_AI_GAMES = 67,
|
||||
|
||||
/// One for All games games on Summoner's Rift.
|
||||
/// <br>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_GAMES_DEPRECATED_70 = 70,
|
||||
/// One for All games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_ONE_FOR_ALL_GAMES = 1020,
|
||||
|
||||
/// 1v1 Snowdown Showdown games games on Howling Abyss.
|
||||
HOWLING_ABYSS_1V1_SNOWDOWN_SHOWDOWN_GAMES = 72,
|
||||
|
||||
/// 2v2 Snowdown Showdown games games on Howling Abyss.
|
||||
HOWLING_ABYSS_2V2_SNOWDOWN_SHOWDOWN_GAMES = 73,
|
||||
|
||||
/// 6v6 Hexakill games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_6V6_HEXAKILL_GAMES = 75,
|
||||
|
||||
/// Ultra Rapid Fire games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_ULTRA_RAPID_FIRE_GAMES = 76,
|
||||
|
||||
/// One For All: Mirror Mode games games on Howling Abyss.
|
||||
HOWLING_ABYSS_ONE_FOR_ALL_MIRROR_MODE_GAMES = 78,
|
||||
|
||||
/// Co-op vs AI Ultra Rapid Fire games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_ULTRA_RAPID_FIRE_GAMES = 83,
|
||||
|
||||
/// Doom Bots Rank 1 games games on Summoner's Rift.
|
||||
/// <br>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_RANK1_GAMES = 91,
|
||||
|
||||
/// Doom Bots Rank 2 games games on Summoner's Rift.
|
||||
/// <br>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_RANK2_GAMES = 92,
|
||||
|
||||
/// Doom Bots Rank 5 games games on Summoner's Rift.
|
||||
/// <br>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_RANK5_GAMES = 93,
|
||||
|
||||
/// Ascension games games on Crystal Scar.
|
||||
/// <br>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_GAMES_DEPRECATED_96 = 96,
|
||||
/// Ascension games games on Crystal Scar.
|
||||
CRYSTAL_SCAR_ASCENSION_GAMES = 910,
|
||||
|
||||
/// 6v6 Hexakill games games on Twisted Treeline.
|
||||
TWISTED_TREELINE_6V6_HEXAKILL_GAMES = 98,
|
||||
|
||||
/// 5v5 ARAM games games on Butcher's Bridge.
|
||||
BUTCHERS_BRIDGE_5V5_ARAM_GAMES = 100,
|
||||
|
||||
/// Legend of the Poro King games games on Howling Abyss.
|
||||
/// <br>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_GAMES_DEPRECATED_300 = 300,
|
||||
/// Legend of the Poro King games games on Howling Abyss.
|
||||
HOWLING_ABYSS_LEGEND_OF_THE_PORO_KING_GAMES = 920,
|
||||
|
||||
/// Nemesis games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_NEMESIS_GAMES = 310,
|
||||
|
||||
/// Black Market Brawlers games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_BLACK_MARKET_BRAWLERS_GAMES = 313,
|
||||
|
||||
/// Nexus Siege games games on Summoner's Rift.
|
||||
/// <br>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_GAMES_DEPRECATED_315 = 315,
|
||||
/// Nexus Siege games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_NEXUS_SIEGE_GAMES = 940,
|
||||
|
||||
/// Definitely Not Dominion games games on Crystal Scar.
|
||||
CRYSTAL_SCAR_DEFINITELY_NOT_DOMINION_GAMES = 317,
|
||||
|
||||
/// ARURF games games on Summoner's Rift.
|
||||
/// <br>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_GAMES = 318,
|
||||
|
||||
/// All Random games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_ALL_RANDOM_GAMES = 325,
|
||||
|
||||
/// 5v5 Ranked Dynamic games games on Summoner's Rift.
|
||||
/// <br>Game mode deprecated in patch 6.22
|
||||
#[deprecated(note="Game mode deprecated in patch 6.22")]
|
||||
SUMMONERS_RIFT_5V5_RANKED_DYNAMIC_GAMES = 410,
|
||||
|
||||
/// 5v5 Ranked Flex games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_5V5_RANKED_FLEX_GAMES = 440,
|
||||
|
||||
/// 3v3 Blind Pick games games on Twisted Treeline.
|
||||
/// <br>Deprecated in patch 9.23
|
||||
#[deprecated(note="Deprecated in patch 9.23")]
|
||||
TWISTED_TREELINE_3V3_BLIND_PICK_GAMES = 460,
|
||||
|
||||
/// Blood Hunt Assassin games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_BLOOD_HUNT_ASSASSIN_GAMES = 600,
|
||||
|
||||
/// Dark Star: Singularity games games on Cosmic Ruins.
|
||||
COSMIC_RUINS_DARK_STAR_SINGULARITY_GAMES = 610,
|
||||
|
||||
/// Clash games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_CLASH_GAMES = 700,
|
||||
|
||||
/// Co-op vs. AI Intermediate Bot games games on Twisted Treeline.
|
||||
/// <br>Deprecated in patch 9.23
|
||||
#[deprecated(note="Deprecated in patch 9.23")]
|
||||
TWISTED_TREELINE_CO_OP_VS_AI_INTERMEDIATE_BOT_GAMES = 800,
|
||||
|
||||
/// Co-op vs. AI Intro Bot games games on Twisted Treeline.
|
||||
/// <br>Deprecated in patch 9.23
|
||||
#[deprecated(note="Deprecated in patch 9.23")]
|
||||
TWISTED_TREELINE_CO_OP_VS_AI_INTRO_BOT_GAMES = 810,
|
||||
|
||||
/// Co-op vs. AI Beginner Bot games games on Twisted Treeline.
|
||||
TWISTED_TREELINE_CO_OP_VS_AI_BEGINNER_BOT_GAMES = 820,
|
||||
|
||||
/// URF games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_URF_GAMES = 900,
|
||||
|
||||
/// Doom Bots Voting games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_DOOM_BOTS_VOTING_GAMES = 950,
|
||||
|
||||
/// Doom Bots Standard games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_DOOM_BOTS_STANDARD_GAMES = 960,
|
||||
|
||||
/// Star Guardian Invasion: Normal games games on Valoran City Park.
|
||||
VALORAN_CITY_PARK_STAR_GUARDIAN_INVASION_NORMAL_GAMES = 980,
|
||||
|
||||
/// Star Guardian Invasion: Onslaught games games on Valoran City Park.
|
||||
VALORAN_CITY_PARK_STAR_GUARDIAN_INVASION_ONSLAUGHT_GAMES = 990,
|
||||
|
||||
/// PROJECT: Hunters games games on Overcharge.
|
||||
OVERCHARGE_PROJECT_HUNTERS_GAMES = 1000,
|
||||
|
||||
/// Snow ARURF games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_SNOW_ARURF_GAMES = 1010,
|
||||
|
||||
/// Odyssey Extraction: Intro games games on Crash Site.
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_INTRO_GAMES = 1030,
|
||||
|
||||
/// Odyssey Extraction: Cadet games games on Crash Site.
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_CADET_GAMES = 1040,
|
||||
|
||||
/// Odyssey Extraction: Crewmember games games on Crash Site.
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_CREWMEMBER_GAMES = 1050,
|
||||
|
||||
/// Odyssey Extraction: Captain games games on Crash Site.
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_CAPTAIN_GAMES = 1060,
|
||||
|
||||
/// Odyssey Extraction: Onslaught games games on Crash Site.
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_ONSLAUGHT_GAMES = 1070,
|
||||
|
||||
/// Teamfight Tactics games games on Convergence.
|
||||
CONVERGENCE_TEAMFIGHT_TACTICS_GAMES = 1090,
|
||||
|
||||
/// Ranked Teamfight Tactics games games on Convergence.
|
||||
CONVERGENCE_RANKED_TEAMFIGHT_TACTICS_GAMES = 1100,
|
||||
|
||||
/// Teamfight Tactics Tutorial games games on Convergence.
|
||||
CONVERGENCE_TEAMFIGHT_TACTICS_TUTORIAL_GAMES = 1110,
|
||||
|
||||
/// Nexus Blitz games games on Nexus Blitz.
|
||||
/// <br>Deprecated in patch 9.2
|
||||
#[deprecated(note="Deprecated in patch 9.2")]
|
||||
NEXUS_BLITZ_NEXUS_BLITZ_GAMES = 1200,
|
||||
|
||||
/// Tutorial 1 games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_TUTORIAL1 = 2000,
|
||||
|
||||
/// Tutorial 2 games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_TUTORIAL2 = 2010,
|
||||
|
||||
/// Tutorial 3 games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_TUTORIAL3 = 2020,
|
||||
}
|
40
src/consts/queue_type.rs
Normal file
40
src/consts/queue_type.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
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.
|
||||
#[deprecated(note = "Teamfight Tactics ranks should be acquired using `TftLeagueV1::get_league_entries`.")]
|
||||
RANKED_TFT,
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -4,10 +4,7 @@ use std::iter::Peekable;
|
|||
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use super::{Division, Tier};
|
||||
|
||||
/// (Tier, Division) tuple representing a rank.
|
||||
pub type Rank = (Tier, Division);
|
||||
use super::{ Tier, Division };
|
||||
|
||||
/// Iterator for iterating `(Tier, Division)` rank tuples.
|
||||
pub struct Iter {
|
||||
|
@ -16,11 +13,12 @@ pub struct Iter {
|
|||
}
|
||||
|
||||
impl Iterator for Iter {
|
||||
type Item = Rank;
|
||||
type Item = (Tier, Division);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// First find the tier (innermost loop).
|
||||
// If none found, we go to next tier (in unwrap_or_else case).
|
||||
let div = self.div_iter.next().unwrap_or_else(|| {
|
||||
let div = *self.div_iter.next()
|
||||
.unwrap_or_else(|| {
|
||||
// If no divisions available, go to next tier, reset the divisions, and return I.
|
||||
self.tier_iter.next();
|
||||
self.div_iter = Division::iter();
|
||||
|
@ -58,14 +56,14 @@ pub fn non_apex_iter() -> Iter {
|
|||
tier_iter.next();
|
||||
}
|
||||
Iter {
|
||||
tier_iter,
|
||||
tier_iter: tier_iter,
|
||||
div_iter: Division::iter(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Division, Tier};
|
||||
use super::{ Tier, Division };
|
||||
|
||||
#[test]
|
||||
fn iter() {
|
||||
|
@ -83,6 +81,7 @@ mod tests {
|
|||
assert_eq!(None, it.next());
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn non_apex_iter() {
|
||||
let mut it = super::non_apex_iter();
|
||||
|
@ -90,12 +89,12 @@ mod tests {
|
|||
assert_eq!(Some((Tier::DIAMOND, Division::II)), it.next());
|
||||
assert_eq!(Some((Tier::DIAMOND, Division::III)), 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());
|
||||
let last = it.last();
|
||||
let mut last = None;
|
||||
for next in &mut it {
|
||||
last = Some(next);
|
||||
}
|
||||
assert_eq!(Some((Tier::IRON, Division::IV)), last);
|
||||
assert_eq!(None, it.next());
|
||||
}
|
||||
}
|
58
src/consts/region.rs
Normal file
58
src/consts/region.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
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.
|
||||
#[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", serialize="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", serialize="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", serialize="AMERICAS")]
|
||||
AMERICAS,
|
||||
#[strum(to_string="EUROPE", serialize="EUROPE")]
|
||||
EUROPE,
|
||||
#[strum(to_string="ASIA", serialize="ASIA")]
|
||||
ASIA,
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
}
|
34
src/consts/season.rs
Normal file
34
src/consts/season.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// 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,
|
||||
}
|
15
src/consts/team.rs
Normal file
15
src/consts/team.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
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,
|
||||
}
|
91
src/consts/tier.rs
Normal file
91
src/consts/tier.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||
use strum_macros::{ EnumString, EnumIter, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// LoL and TFT ranked tiers, such as gold, diamond, challenger, etc.
|
||||
///
|
||||
/// Sorts from lowest rank to highest rank.
|
||||
///
|
||||
/// Repr'd as arbitrary `u8` values.
|
||||
///
|
||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator).
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[derive(EnumString, EnumIter, Display, AsRefStr, IntoStaticStr)]
|
||||
#[repr(u8)]
|
||||
pub enum Tier {
|
||||
/// Challenger, the highest tier, an apex tier. Repr: `220_u8`.
|
||||
CHALLENGER = 220,
|
||||
/// Grand Master, an apex tier. Repr: `200_u8`.
|
||||
GRANDMASTER = 200,
|
||||
/// Master, an apex tier. Repr: `180_u8`.
|
||||
MASTER = 180,
|
||||
/// Diamond, the higest non-apex tier. Repr: `140_u8`.
|
||||
DIAMOND = 140,
|
||||
/// Platinum. Repr: `120_u8`.
|
||||
PLATINUM = 120,
|
||||
/// Gold. Repr: `100_u8`.
|
||||
GOLD = 100,
|
||||
/// Silver. Repr: `80_u8`.
|
||||
SILVER = 80,
|
||||
/// Bronze. Repr: `60_u8`.
|
||||
BRONZE = 60,
|
||||
/// Iron, the lowest tier. Repr: `40_u8`.
|
||||
IRON = 40,
|
||||
}
|
||||
|
||||
serde_string!(Tier);
|
||||
|
||||
impl Tier {
|
||||
/// If this tier is an apex tier: master and above.
|
||||
///
|
||||
/// Inverse of is_standard().
|
||||
///
|
||||
/// These tiers are NOT queryable by LeagueV4Endpoints::get_league_entries(...).
|
||||
pub const fn is_apex(self) -> bool {
|
||||
(Self::MASTER as u8) <= (self as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sort() {
|
||||
assert!(Tier::GOLD < Tier::DIAMOND);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apex_check() {
|
||||
assert!( Tier::GRANDMASTER.is_apex());
|
||||
assert!(!Tier::DIAMOND.is_apex());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
assert_eq!("GRANDMASTER", Tier::GRANDMASTER.as_ref());
|
||||
assert_eq!("GRANDMASTER", Tier::GRANDMASTER.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_string() {
|
||||
assert_eq!(Ok(Tier::GRANDMASTER), "GRANDMASTER".parse());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter() {
|
||||
use strum::IntoEnumIterator;
|
||||
let mut iter = Tier::iter();
|
||||
assert_eq!(Some(Tier::CHALLENGER), iter.next());
|
||||
iter.next();
|
||||
iter.next();
|
||||
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
||||
iter.next();
|
||||
iter.next();
|
||||
iter.next();
|
||||
iter.next();
|
||||
assert_eq!(Some(Tier::IRON), iter.next());
|
||||
assert_eq!(None, iter.next());
|
||||
}
|
||||
}
|
1062
src/endpoints.rs
Normal file
1062
src/endpoints.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,7 @@
|
|||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
|
||||
use reqwest::{Error, Response, StatusCode};
|
||||
use crate::reqwest::*;
|
||||
|
||||
/// Result containing RiotApiError on failure.
|
||||
pub type Result<T> = std::result::Result<T, RiotApiError>;
|
||||
|
@ -17,17 +18,12 @@ pub struct RiotApiError {
|
|||
status_code: Option<StatusCode>,
|
||||
}
|
||||
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 {
|
||||
reqwest_error,
|
||||
retries,
|
||||
response,
|
||||
status_code,
|
||||
reqwest_error: reqwest_error,
|
||||
retries: retries,
|
||||
response: response,
|
||||
status_code: status_code,
|
||||
}
|
||||
}
|
||||
/// The reqwest::Error for the final failed request.
|
||||
|
@ -39,17 +35,11 @@ impl RiotApiError {
|
|||
self.retries
|
||||
}
|
||||
/// 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.
|
||||
pub fn response(&self) -> Option<&Response> {
|
||||
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.
|
||||
/// `Some(reqwest::StatusCode)` if the request was sent and failed, OR if parsing the response JSON failed.
|
||||
/// `None` if the request was not sent.
|
||||
|
@ -62,8 +52,8 @@ impl fmt::Display for RiotApiError {
|
|||
write!(f, "{:#?}", self)
|
||||
}
|
||||
}
|
||||
impl std::error::Error for RiotApiError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
impl StdError for RiotApiError {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
Some(&self.reqwest_error)
|
||||
}
|
||||
}
|
29
src/lib.rs
Normal file
29
src/lib.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
#![cfg_attr(feature = "nightly", feature(non_exhaustive))]
|
||||
#![cfg_attr(feature = "nightly", feature(external_doc))]
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
#![cfg_attr(feature = "nightly", doc(include = "../README.md"))]
|
||||
#![cfg_attr(not(feature = "nightly"), doc = "See [README.md](https://github.com/MingweiSamuel/Riven#readme).")]
|
||||
|
||||
/// Re-exported `reqwest` types.
|
||||
pub use reqwest;
|
||||
|
||||
mod config;
|
||||
pub use config::RiotApiConfig;
|
||||
|
||||
pub mod consts;
|
||||
|
||||
pub mod endpoints;
|
||||
|
||||
mod error;
|
||||
pub use error::*;
|
||||
|
||||
pub mod models;
|
||||
|
||||
mod req;
|
||||
|
||||
mod riot_api;
|
||||
pub use riot_api::*;
|
||||
|
||||
mod util;
|
1777
src/models.rs
Normal file
1777
src/models.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -4,9 +4,9 @@ mod rate_limit;
|
|||
pub use rate_limit::*;
|
||||
|
||||
mod rate_limit_type;
|
||||
use std::time::Instant;
|
||||
pub use rate_limit_type::*;
|
||||
|
||||
pub use rate_limit_type::*; // Hack for token_bucket_test.rs.
|
||||
use std::time::Instant; // Hack for token_bucket_test.rs.
|
||||
mod token_bucket;
|
||||
pub use token_bucket::*;
|
||||
|
187
src/req/rate_limit.rs
Normal file
187
src/req/rate_limit.rs
Normal file
|
@ -0,0 +1,187 @@
|
|||
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,21 +1,25 @@
|
|||
/// 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)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum RateLimitType {
|
||||
Application,
|
||||
Method,
|
||||
}
|
||||
|
||||
impl RateLimitType {
|
||||
pub const fn limit_header(self) -> &'static str {
|
||||
pub fn type_name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Application => "application",
|
||||
Self::Method => "method",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn limit_header(self) -> &'static str {
|
||||
match self {
|
||||
Self::Application => "X-App-Rate-Limit",
|
||||
Self::Method => "X-Method-Rate-Limit",
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn count_header(self) -> &'static str {
|
||||
pub fn count_header(self) -> &'static str {
|
||||
match self {
|
||||
Self::Application => "X-App-Rate-Limit-Count",
|
||||
Self::Method => "X-Method-Rate-Limit-Count",
|
128
src/req/regional_requester.rs
Normal file
128
src/req/regional_requester.rs
Normal file
|
@ -0,0 +1,128 @@
|
|||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
use log;
|
||||
use reqwest::{ Client, StatusCode, Url };
|
||||
use tokio::time::delay_for;
|
||||
|
||||
use crate::Result;
|
||||
use crate::RiotApiError;
|
||||
use crate::RiotApiConfig;
|
||||
use crate::util::InsertOnlyCHashMap;
|
||||
|
||||
use super::RateLimit;
|
||||
use super::RateLimitType;
|
||||
|
||||
pub struct RegionalRequester {
|
||||
/// Represents the app rate limit.
|
||||
app_rate_limit: RateLimit,
|
||||
/// Represents 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_optional<'a, T: serde::de::DeserializeOwned>(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<Option<T>>> + 'a
|
||||
{
|
||||
async move {
|
||||
let response_result = self.get(config, client,
|
||||
method_id, region_platform, path, query).await;
|
||||
response_result.map(|value| Some(value))
|
||||
.or_else(|e| {
|
||||
if let Some(status) = e.status_code() {
|
||||
if Self::NONE_STATUS_CODES.contains(&status) {
|
||||
return Ok(None);
|
||||
}}
|
||||
Err(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get<'a, T: serde::de::DeserializeOwned>(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<T>> + '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) {
|
||||
delay_for(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.
|
||||
match response.error_for_status_ref() {
|
||||
// Success.
|
||||
Ok(_response) => {
|
||||
log::trace!("Response {} (retried {} times), parsed result.", status, retries);
|
||||
let value = response.json::<T>().await;
|
||||
break value.map_err(|e| RiotApiError::new(e, retries, None, Some(status)));
|
||||
},
|
||||
// Failure, may or may not be retryable.
|
||||
Err(err) => {
|
||||
// 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), returning.", 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,5 +1,5 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::collections::VecDeque;
|
||||
use std::time::Duration;
|
||||
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
|
@ -39,16 +39,10 @@ pub trait TokenBucket {
|
|||
}
|
||||
|
||||
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: Duration,
|
||||
// Total tokens available from this TokenBucket.
|
||||
total_limit: usize,
|
||||
|
||||
/// Extra duration to be considered on top of `duration`, to account for
|
||||
/// varying network latency.
|
||||
duration_overhead: Duration,
|
||||
|
@ -58,53 +52,36 @@ pub struct VectorTokenBucket {
|
|||
/// Limit allowed per burst_duration, for burst factor.
|
||||
burst_limit: usize,
|
||||
|
||||
|
||||
/// Record of timestamps (synchronized).
|
||||
timestamps: Mutex<VecDeque<Instant>>,
|
||||
}
|
||||
|
||||
impl VectorTokenBucket {
|
||||
pub fn new(
|
||||
duration: Duration,
|
||||
given_total_limit: usize,
|
||||
duration_overhead: Duration,
|
||||
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
|
||||
);
|
||||
pub fn new(duration: Duration, total_limit: usize,
|
||||
duration_overhead: Duration, burst_pct: f32) -> Self
|
||||
{
|
||||
debug_assert!(0.0 < burst_pct && burst_pct <= 1.0,
|
||||
"BAD burst_pct {}.", burst_pct);
|
||||
// Float ops may lose precision, but nothing should be that precise.
|
||||
// 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,
|
||||
);
|
||||
// API always uses round numbers, burst_pct is frac of 256.
|
||||
|
||||
// Effective duration.
|
||||
let d_eff = duration + duration_overhead;
|
||||
let burst_duration = d_eff.mul_f32(burst_factor);
|
||||
let burst_limit = std::cmp::max(1, (total_limit as f32 * burst_factor).floor() as usize);
|
||||
let burst_duration = Duration::new(
|
||||
(d_eff.as_secs() as f32 * burst_pct).ceil() as u64,
|
||||
(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);
|
||||
|
||||
VectorTokenBucket {
|
||||
_given_total_limit: given_total_limit,
|
||||
_rate_usage_factor: rate_usage_factor,
|
||||
duration: duration,
|
||||
total_limit: total_limit,
|
||||
duration_overhead: duration_overhead,
|
||||
|
||||
duration,
|
||||
total_limit,
|
||||
|
||||
duration_overhead,
|
||||
burst_duration,
|
||||
burst_limit,
|
||||
burst_duration: burst_duration,
|
||||
burst_limit: burst_limit,
|
||||
|
||||
timestamps: Mutex::new(VecDeque::with_capacity(total_limit)),
|
||||
}
|
||||
|
@ -113,32 +90,36 @@ impl VectorTokenBucket {
|
|||
fn update_get_timestamps(&self) -> MutexGuard<VecDeque<Instant>> {
|
||||
let mut timestamps = self.timestamps.lock();
|
||||
let cutoff = Instant::now() - self.duration - self.duration_overhead;
|
||||
// Pop off timestamps that are beyound the bucket duration.
|
||||
// We only need to trim the end of the queue to not leak memory.
|
||||
// We could do it lazily somehow if we wanted to be really fancy.
|
||||
while timestamps.back().map_or(false, |ts| *ts < cutoff) {
|
||||
timestamps.pop_back();
|
||||
}
|
||||
timestamps
|
||||
return timestamps;
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenBucket for VectorTokenBucket {
|
||||
|
||||
fn get_delay(&self) -> Option<Duration> {
|
||||
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.
|
||||
if let Some(ts) = timestamps.get(self.total_limit - 1) {
|
||||
// Return amount of time needed for timestamp `ts` to go away.
|
||||
Instant::now()
|
||||
.checked_duration_since(*ts)
|
||||
.and_then(|passed_dur| {
|
||||
(self.duration + self.duration_overhead).checked_sub(passed_dur)
|
||||
})
|
||||
Instant::now().checked_duration_since(*ts)
|
||||
.and_then(|passed_dur| (self.duration + self.duration_overhead)
|
||||
.checked_sub(passed_dur))
|
||||
}
|
||||
// Otherwise burst rate limit.
|
||||
else if let Some(ts) = timestamps.get(self.burst_limit - 1) {
|
||||
// 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))
|
||||
}
|
||||
// No delay needed.
|
||||
|
@ -156,21 +137,7 @@ impl TokenBucket for VectorTokenBucket {
|
|||
for _ in 0..n {
|
||||
timestamps.push_front(now);
|
||||
}
|
||||
|
||||
// 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
|
||||
timestamps.len() <= self.total_limit
|
||||
}
|
||||
|
||||
fn get_bucket_duration(&self) -> Duration {
|
||||
|
@ -184,12 +151,6 @@ impl TokenBucket for VectorTokenBucket {
|
|||
|
||||
impl fmt::Debug for VectorTokenBucket {
|
||||
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())
|
||||
}
|
||||
}
|
110
src/req/token_bucket.test.rs
Normal file
110
src/req/token_bucket.test.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
#![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.");
|
||||
}
|
||||
}
|
||||
}
|
109
src/riot_api.rs
Normal file
109
src/riot_api.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
use log;
|
||||
use reqwest::Client;
|
||||
|
||||
use crate::Result;
|
||||
use crate::RiotApiConfig;
|
||||
use crate::req::RegionalRequester;
|
||||
use crate::util::InsertOnlyCHashMap;
|
||||
|
||||
/// For retrieving data from the Riot Games API.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// Construct an instance using [`with_key(api_key)`](RiotApi::with_key) or
|
||||
/// [`with_config(config)`](RiotApi::with_config).
|
||||
///
|
||||
/// An instance provides access to "endpoint handles" which in turn provide access
|
||||
/// to individual API method calls. For example, getting a summoner by name:
|
||||
/// ```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 (theoretically) could change at any time.
|
||||
/// Riven keeps track of changing rate limits seamlessly, preventing you from
|
||||
/// getting blacklisted.
|
||||
///
|
||||
/// Riven's rate limiting is highly efficient, meaning that it can reach the limits
|
||||
/// of your rate limit without going over.
|
||||
///
|
||||
/// To adjust rate limiting, see [RiotApiConfig](crate::RiotApiConfig) and use
|
||||
/// [`with_config(config)`](RiotApi::with_config) 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 the given [RiotApiConfig](crate::RiotApiConfig), consuming it.
|
||||
pub fn with_config(mut config: RiotApiConfig) -> Self {
|
||||
let client_builder = config.client_builder.take()
|
||||
.expect("!NONE CLIENT_BUILDER IN CONFIG.");
|
||||
Self {
|
||||
config: config,
|
||||
client: client_builder.build().expect("Failed to create client from builder."),
|
||||
regional_requesters: InsertOnlyCHashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new instance from the given API key, using default configuration.
|
||||
///
|
||||
/// `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::with_config(RiotApiConfig::with_key(api_key))
|
||||
}
|
||||
|
||||
/// This method is not meant to be used directly.
|
||||
///
|
||||
/// This sends a GET 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, prepended to `.api.riotgames.com` to create the hostname.
|
||||
/// * `path` - The path relative to the hostname.
|
||||
/// * `query` - An optional query string.
|
||||
pub fn get_optional<'a, T: serde::de::DeserializeOwned + 'a>(&'a self,
|
||||
method_id: &'static str, region_platform: &'static str, path: String, query: Option<String>)
|
||||
-> impl Future<Output = Result<Option<T>>> + 'a
|
||||
{
|
||||
self.regional_requester(region_platform)
|
||||
.get_optional(&self.config, &self.client, method_id, region_platform, path, query)
|
||||
}
|
||||
|
||||
/// This method is not meant to be used directly.
|
||||
///
|
||||
/// This sends a GET 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, prepended to `.api.riotgames.com` to create the hostname.
|
||||
/// * `path` - The path relative to the hostname.
|
||||
/// * `query` - An optional query string.
|
||||
pub fn get<'a, T: serde::de::DeserializeOwned + 'a>(&'a self,
|
||||
method_id: &'static str, region_platform: &'static str, path: String, query: Option<String>)
|
||||
-> impl Future<Output = Result<T>> + 'a
|
||||
{
|
||||
self.regional_requester(region_platform)
|
||||
.get(&self.config, &self.client, method_id, region_platform, path, query)
|
||||
}
|
||||
|
||||
/// 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]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base: Mutex::new(HashMap::new()),
|
||||
base: Mutex::new(HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,12 +27,10 @@ impl<K: Hash + Eq, V> InsertOnlyCHashMap<K, V> {
|
|||
// }
|
||||
|
||||
#[inline]
|
||||
pub fn get_or_insert_with<F: FnOnce() -> V>(&self, key: K, default: F) -> Arc<V> {
|
||||
Arc::clone(
|
||||
self.base
|
||||
.lock()
|
||||
pub fn get_or_insert_with<F: FnOnce() -> V>(&self, key: K, default: F) -> Arc<V>
|
||||
{
|
||||
Arc::clone(self.base.lock()
|
||||
.entry(key)
|
||||
.or_insert_with(|| Arc::new(default())),
|
||||
)
|
||||
.or_insert_with(|| Arc::new(default())))
|
||||
}
|
||||
}
|
85
srcgen/consts/champion.rs.dt
Normal file
85
srcgen/consts/champion.rs.dt
Normal file
|
@ -0,0 +1,85 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const champions = require('./.champion.json')
|
||||
.filter(({ id }) => id > 0)
|
||||
.sortBy(({ name }) => name);
|
||||
const hashFactor = 256;
|
||||
const enumName = name => name.replace(/[^a-z]+/i, '');
|
||||
const strHash = function(str) {
|
||||
let h = 0;
|
||||
for (let c of str)
|
||||
h = hashFactor * h + c.charCodeAt(0);
|
||||
return h;
|
||||
};
|
||||
const padId = function(id) { return ('' + id).padEnd(3); };
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
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,
|
||||
|
||||
{{
|
||||
for (let { id, alias, name } of champions) {
|
||||
}}
|
||||
/// {{= name }} (`{{= alias }}`, {{= id }}).
|
||||
#[strum(to_string="{{= name }}", serialize="{{= alias }}")] {{= enumName(name) }} = {{= id }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
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"
|
||||
{{
|
||||
for (let { name, alias } of champions) {
|
||||
if (name.replace(/[^a-zA-Z0-9]+/, '') !== alias) {
|
||||
}}
|
||||
/// `{{= enumName(name) }}` | "{{= name }}" | "{{= alias }}"
|
||||
{{
|
||||
}
|
||||
}
|
||||
}}
|
||||
pub fn identifier(self) -> &'static str {
|
||||
match self {
|
||||
Self::None => "NONE",
|
||||
{{
|
||||
for (let { name, alias } of champions) {
|
||||
}}
|
||||
Self::{{= enumName(name).padEnd(12) }} => "{{= alias }}",
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
26
srcgen/consts/game_mode.rs.dt
Normal file
26
srcgen/consts/game_mode.rs.dt
Normal file
|
@ -0,0 +1,26 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const gameModes = require('./.gameModes.json');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// League of Legends game mode, such as Classic,
|
||||
/// ARAM, URF, One For All, Ascension, etc.
|
||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||
#[repr(u8)]
|
||||
pub enum GameMode {
|
||||
{{
|
||||
for (const { gameMode, description } of gameModes) {
|
||||
}}
|
||||
/// {{= description }}
|
||||
{{= gameMode }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
serde_string!(GameMode);
|
24
srcgen/consts/game_type.rs.dt
Normal file
24
srcgen/consts/game_type.rs.dt
Normal file
|
@ -0,0 +1,24 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const gameTypes = require('./.gameTypes.json');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// League of Legends game type: matched game, custom game, or tutorial game.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||
#[repr(u8)]
|
||||
pub enum GameType {
|
||||
{{
|
||||
for (const { gametype: gameType, description } of gameTypes) {
|
||||
}}
|
||||
/// {{= description }}
|
||||
{{= gameType }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
serde_string!(GameType);
|
36
srcgen/consts/map.rs.dt
Normal file
36
srcgen/consts/map.rs.dt
Normal file
|
@ -0,0 +1,36 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const maps = require('./.maps.json');
|
||||
const groupedMaps = maps.groupBy(({ mapName }) =>
|
||||
dotUtils.changeCase.constantCase(mapName.replace(/[ ']+/, '')));
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
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 {
|
||||
{{
|
||||
for (const [ groupName, colMaps ] of groupedMaps) {
|
||||
}}
|
||||
|
||||
{{
|
||||
for (const [ i, { mapId, mapName, notes } ] of colMaps.entries()) {
|
||||
let name = groupName;
|
||||
if (i != colMaps.length - 1)
|
||||
name += '_' + dotUtils.changeCase.constantCase(notes);
|
||||
}}
|
||||
/// {{= mapName }}
|
||||
/// <br>{{= notes }}
|
||||
{{= name }} = {{= mapId }},
|
||||
{{
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
45
srcgen/consts/queue.rs.dt
Normal file
45
srcgen/consts/queue.rs.dt
Normal file
|
@ -0,0 +1,45 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const queues = require('./.queues.json');
|
||||
|
||||
const groupedQueues = queues.groupBy(({ map, description }) => {
|
||||
const name = dotUtils.changeCase.constantCase((map || '').replace("'", ''))
|
||||
+ '_' + dotUtils.changeCase.constantCase((description || '').replace(/\s+(?=\d)/g, ''));
|
||||
return name;
|
||||
});
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
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 {
|
||||
{{
|
||||
for (let [ groupName, colQueues ] of groupedQueues) {
|
||||
}}
|
||||
|
||||
{{
|
||||
for (let { queueId, map, description, notes } of colQueues) {
|
||||
const doc = (description ? description + ' games on ' : '') + map;
|
||||
const deprecated = (notes || '').toUpperCase().indexOf('DEPRECATED') >= 0;
|
||||
const name = groupName + ((colQueues.length > 1 && deprecated) ? `_DEPRECATED_${queueId}` : '');
|
||||
}}
|
||||
/// {{= doc }}.
|
||||
{{? notes }}
|
||||
/// <br>{{= notes }}
|
||||
{{?}}
|
||||
{{? deprecated}}
|
||||
#[deprecated(note="{{= notes }}")]
|
||||
{{?}}
|
||||
{{= name }} = {{= queueId }},
|
||||
{{
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
25
srcgen/consts/season.rs.dt
Normal file
25
srcgen/consts/season.rs.dt
Normal file
|
@ -0,0 +1,25 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const seasons = require('./.seasons.json');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
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 {
|
||||
{{
|
||||
for (const { id, season } of seasons) {
|
||||
const name = season.replace(' ', '_');
|
||||
}}
|
||||
{{= name }} = {{= id }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
|
@ -1,5 +1,14 @@
|
|||
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
|
||||
// [B](f: (A) ⇒ [B]): [B] ; Although the types in the arrays aren't strict (:
|
||||
Array.prototype.flatMap = function(lambda) {
|
||||
|
@ -28,7 +37,6 @@ Array.prototype.sortBy = function(lambda) {
|
|||
|
||||
function preamble() {
|
||||
return `\
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
|
@ -57,9 +65,7 @@ function normalizeArgName(name) {
|
|||
}
|
||||
|
||||
function normalizePropName(propName) {
|
||||
let out = changeCase.snakeCase(propName);
|
||||
if (/^\d/.test(out)) // No leading digits.
|
||||
out = 'x' + out;
|
||||
const out = changeCase.snakeCase(propName);
|
||||
if ('type' === out)
|
||||
return 'r#' + out;
|
||||
return out;
|
||||
|
@ -70,7 +76,7 @@ function stringifyType(prop, { endpoint = null, optional = false, fullpath = tru
|
|||
prop = prop.anyOf[0];
|
||||
}
|
||||
if (optional) {
|
||||
return `Option<${stringifyType(prop, { endpoint, fullpath, owned })}>`;
|
||||
return `Option<${stringifyType(prop, { endpoint, fullpath })}>`;
|
||||
}
|
||||
|
||||
let enumType = prop['x-enum'];
|
||||
|
@ -101,48 +107,47 @@ function formatJsonProperty(name) {
|
|||
return `#[serde(rename = "${name}")]`;
|
||||
}
|
||||
|
||||
function formatAddQueryParam(param) {
|
||||
const k = `"${param.name}"`;
|
||||
const name = normalizePropName(param.name);
|
||||
const condStart = param.required ? '' : `if let Some(${name}) = ${name} { `;
|
||||
const condEnd = param.required ? '' : ' } else { request }'
|
||||
const prop = param.schema;
|
||||
function formatQueryParamStringify(name, prop, useOwned = false) {
|
||||
const own = useOwned ? '' : '&*';
|
||||
if (prop['x-enum']) {
|
||||
switch (prop.type) {
|
||||
case 'array': return `let request = ${condStart}request.query(&*${name}.iter()`
|
||||
+ `.map(|w| ( ${k}, w )).collect::<Vec<_>>())${condEnd};`;
|
||||
case 'object':
|
||||
throw 'unsupported';
|
||||
default:
|
||||
return `let request = ${condStart}request.query(&[ (${k}, ${name}) ])${condEnd};`;
|
||||
case 'integer':
|
||||
return `${own}Into::<${enumTypeLookup[prop['x-enum']]}>::into(*${name}).to_string()`;
|
||||
default: throw new Error(`Enum not supported: ${JSON.stringify(prop)}.`)
|
||||
}
|
||||
}
|
||||
switch (prop.type) {
|
||||
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 formatAddHeaderParam(param) {
|
||||
const k = `"${param.name}"`;
|
||||
const name = changeCase.snakeCase(param.name);
|
||||
const condStart = param.required ? '' : `mut request = request; if let Some(${name}) = ${name} { `;
|
||||
const condEnd = param.required ? '' : ' }'
|
||||
const prop = param.schema;
|
||||
function formatAddQueryParam(param) {
|
||||
let k = `"${param.name}"`;
|
||||
let name = changeCase.snakeCase(param.name);
|
||||
let nc = param.required ? '' : `if let Some(${name}) = ${name} `;
|
||||
let prop = param.schema;
|
||||
switch (prop.type) {
|
||||
case 'string':
|
||||
return `let ${condStart}request = request.header(${k}, ${name});${condEnd}`;
|
||||
case 'object':
|
||||
throw 'unsupported';
|
||||
case 'array': return `${nc}{ query_params.extend_pairs(${name}.iter()`
|
||||
+ `.map(|w| (${k}, ${formatQueryParamStringify("w", prop.items, true)}))); }`;
|
||||
case 'object': throw 'unsupported';
|
||||
default:
|
||||
return `let ${condStart}request = request.header(${k}, ${name}.to_string());${condEnd}`;
|
||||
return `${nc}{ query_params.append_pair(${k}, ${formatQueryParamStringify(name, prop)}); }`;
|
||||
}
|
||||
}
|
||||
|
||||
function formatRouteArgument(route, pathParams = []) {
|
||||
if (!pathParams.length)
|
||||
return `"${route}"`;
|
||||
return `"${route}".to_owned()`;
|
||||
|
||||
route = route.replace(/\{\S+?\}/g, '{}');
|
||||
const args = pathParams
|
||||
.map(({name}) => name)
|
||||
.map(changeCase.snakeCase)
|
||||
.join(', ');
|
||||
return `&format!("${route}", ${args})`;
|
||||
return `format!("${route}", ${args})`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -156,6 +161,5 @@ module.exports = {
|
|||
stringifyType,
|
||||
formatJsonProperty,
|
||||
formatAddQueryParam,
|
||||
formatAddHeaderParam,
|
||||
formatRouteArgument,
|
||||
};
|
168
srcgen/endpoints.rs.dt
Normal file
168
srcgen/endpoints.rs.dt
Normal file
|
@ -0,0 +1,168 @@
|
|||
{{
|
||||
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.
|
||||
|
||||
use crate::models::*;
|
||||
|
||||
use std::future::Future;
|
||||
use std::vec::Vec;
|
||||
|
||||
use url::form_urlencoded::Serializer;
|
||||
|
||||
use crate::Result;
|
||||
use crate::consts::Region;
|
||||
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.
|
||||
pub struct {{= endpoint }}<'a> {
|
||||
base: &'a RiotApi,
|
||||
}
|
||||
impl<'a> {{= endpoint }}<'a> {
|
||||
{{
|
||||
for (let [ route, path ] of endpointMethods)
|
||||
{
|
||||
let get = path.get;
|
||||
if (!get)
|
||||
continue;
|
||||
let operationId = get.operationId;
|
||||
let method = dotUtils.changeCase.snakeCase(operationId.slice(operationId.indexOf('.') + 1));
|
||||
|
||||
let jsonInfo = get.responses['200'].content['application/json'];
|
||||
let returnOptional = !!get['x-nullable-404'];
|
||||
let parseType = dotUtils.stringifyType(jsonInfo.schema, { endpoint, fullpath: false });
|
||||
let returnType = returnOptional ? `Option<${parseType}>` : parseType;
|
||||
|
||||
|
||||
/* Cases if not rate limited. */
|
||||
let rateLimitExcluded = get['x-app-rate-limit-excluded'] ? true : false;
|
||||
|
||||
/* Description processing. */
|
||||
let desc = get.description;
|
||||
let descArr = desc
|
||||
.replace(/(#+)\s*([^\\]+)\\n(.*?)([\\n$])/g,
|
||||
(m, g1, g2, g3, g4) => `<h${g1.length}>${g2}</h${g1.length}>\\n${g3}${g4}`)
|
||||
.split('\n');
|
||||
|
||||
/* Build argument comment & string. */
|
||||
let argBuilder = [];
|
||||
let makeParamCode = '';
|
||||
let allParams = get.parameters;
|
||||
let queryParams = [];
|
||||
let routeArgument = dotUtils.formatRouteArgument(route);
|
||||
if (allParams && allParams.length)
|
||||
{
|
||||
let pathParams = allParams.filter(p => 'path' === p.in)
|
||||
.sortBy(({ name }) => route.indexOf(name));
|
||||
let reqParams = allParams.filter(p => 'path' !== p.in && p.required);
|
||||
let optParams = allParams.filter(p => 'path' !== 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 = reqParams.concat(optParams);
|
||||
|
||||
for (let paramList of [ pathParams, reqParams, optParams ])
|
||||
{
|
||||
let required = paramList === pathParams;
|
||||
for (let param of paramList)
|
||||
{
|
||||
argBuilder.push(', ', dotUtils.changeCase.snakeCase(param.name), ': ',
|
||||
dotUtils.stringifyType(param.schema, { endpoint, optional: !required, owned: false }));
|
||||
}
|
||||
}
|
||||
|
||||
routeArgument = dotUtils.formatRouteArgument(route, pathParams);
|
||||
}
|
||||
for (var descLine of descArr)
|
||||
{
|
||||
}}
|
||||
///{{= descLine ? ' ' + descLine : '' }}
|
||||
{{
|
||||
}
|
||||
}}
|
||||
/// # Parameters
|
||||
/// * `region` - Region to query.
|
||||
{{
|
||||
if (allParams)
|
||||
{
|
||||
for (let param of allParams)
|
||||
{
|
||||
}}
|
||||
/// * `{{= param.name }}`{{= param.required ? '' : ' (optional)' }}{{= param.description ? ' - ' + param.description : ''}}
|
||||
{{
|
||||
}
|
||||
}
|
||||
}}
|
||||
/// # Riot Developer API Reference
|
||||
/// <a href="{{= get.externalDocs.url }}" target="_blank">`{{= operationId }}`</a>
|
||||
///
|
||||
/// Note: this method is automatically generated.
|
||||
pub fn {{= method }}(&self, region: Region{{= argBuilder.join('') }})
|
||||
-> impl Future<Output = Result<{{= returnType }}>> + 'a
|
||||
{
|
||||
{{? queryParams.length }}
|
||||
let mut query_params = Serializer::new(String::new());
|
||||
{{
|
||||
for (let queryParam of queryParams)
|
||||
{
|
||||
}}
|
||||
{{= dotUtils.formatAddQueryParam(queryParam) }};
|
||||
{{
|
||||
}
|
||||
}}
|
||||
let query_string = query_params.finish();
|
||||
{{?}}
|
||||
let path_string = {{= routeArgument }};
|
||||
self.base.get{{= returnOptional ? '_optional' : '' }}::<{{= parseType }}>("{{= operationId }}", region.into(), path_string, {{= queryParams.length ? 'Some(query_string)' : 'None' }})
|
||||
}
|
||||
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
{{
|
||||
}
|
||||
}}
|
|
@ -16,34 +16,26 @@ const files = [
|
|||
'.spec.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/enums/seasons.json',
|
||||
'http://static.developer.riotgames.com/docs/lol/seasons.json',
|
||||
'.seasons.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/enums/queues.json',
|
||||
'http://static.developer.riotgames.com/docs/lol/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://static.developer.riotgames.com/docs/lol/gameTypes.json',
|
||||
'.gameTypes.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/enums/gameModes.json',
|
||||
'http://static.developer.riotgames.com/docs/lol/gameModes.json',
|
||||
'.gameModes.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/enums/maps.json',
|
||||
'http://static.developer.riotgames.com/docs/lol/maps.json',
|
||||
'.maps.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/routesTable.json',
|
||||
'.routesTable.json'
|
||||
],
|
||||
];
|
||||
]
|
||||
]
|
||||
|
||||
const downloadFilesPromise = Promise.all(files.map(([url, file]) => req(url)
|
||||
.then(body => fs.writeFileAsync(file, body, "utf8"))));
|
||||
|
@ -80,7 +72,6 @@ downloadFilesPromise.then(() => glob.promise("**/*" + suffix, { ignore: ["**/nod
|
|||
}
|
||||
catch (e) {
|
||||
console.error(`Error thrown while running "${file}":`, e);
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
.then(output => fs.writeFileAsync("../src/" + file.slice(0, -suffix.length), output, "utf8"))
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue