forked from mirror/Riven
Compare commits
21 Commits
Author | SHA1 | Date |
---|---|---|
Mingwei Samuel | e50c509f17 | |
Mingwei Samuel | a55d7769d2 | |
Mingwei Samuel | efbd101e83 | |
Mingwei Samuel | 35d32eeb24 | |
Mingwei Samuel | b30342ac47 | |
Mingwei Samuel | 80eeb184a0 | |
Mingwei Samuel | f090d107b2 | |
Mingwei Samuel | d72ebba4e3 | |
Mingwei Samuel | d621daf3e1 | |
Mingwei Samuel | 40ff477614 | |
Mingwei Samuel | 3bd6812b94 | |
Mingwei Samuel | 054fa0f405 | |
Mingwei Samuel | ac880ab2e5 | |
Mingwei Samuel | 3a160a51bb | |
Mingwei Samuel | a94453a018 | |
Mingwei Samuel | 612ffde26b | |
Mingwei Samuel | 5987a6f07f | |
Mingwei Samuel | 7af67d0cd6 | |
Mingwei Samuel | 50f1a4afde | |
Mingwei Samuel | 51c0832174 | |
Mingwei Samuel | e7c9595a08 |
|
@ -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
|
|
|
@ -4,3 +4,6 @@
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
apikey.txt
|
apikey.txt
|
||||||
|
|
||||||
|
/srcgen/node_modules/
|
||||||
|
/srcgen/.*.json
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
language: rust
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
include:
|
||||||
|
- rust: stable
|
||||||
|
- rust: nightly
|
||||||
|
env: FEATURES="--features nightly"
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- RUST_BACKTRACE=1
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"rust-analyzer.runnableEnv": [
|
|
||||||
{
|
|
||||||
// Set output levels for `tracing` logging.
|
|
||||||
"env": {
|
|
||||||
"RUST_BACKTRACE": "1",
|
|
||||||
"RUST_LOG": "riven=debug"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
49
Cargo.toml
49
Cargo.toml
|
@ -1,4 +1,45 @@
|
||||||
[workspace]
|
[package]
|
||||||
members = [
|
name = "riven"
|
||||||
"riven",
|
version = "1.15.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" ]
|
||||||
|
native-tls = [ "reqwest/native-tls" ]
|
||||||
|
rustls-tls = [ "reqwest/rustls-tls" ]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
lazy_static = "1.4"
|
||||||
|
log = "0.4"
|
||||||
|
num_enum = "0.5"
|
||||||
|
parking_lot = "0.11"
|
||||||
|
reqwest = { version = "0.11", features = [ "gzip", "json" ] }
|
||||||
|
scan_fmt = { version = "0.2", default-features = false }
|
||||||
|
serde = { version = "1.0", features = [ "derive" ] }
|
||||||
|
serde_repr = "0.1"
|
||||||
|
strum = "0.20"
|
||||||
|
strum_macros = "0.20"
|
||||||
|
tokio = { version = "1", default-features = false, features = [ "time" ] }
|
||||||
|
url = "2"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
colored = "2"
|
||||||
|
env_logger = "0.8"
|
||||||
|
fake_instant = "0.4"
|
||||||
|
tokio = { version = "1", default-features = false, features = [ "rt-multi-thread" ] }
|
||||||
|
|
79
README.md
79
README.md
|
@ -5,7 +5,7 @@
|
||||||
<a href="https://github.com/MingweiSamuel/Riven/"><img src="https://cdn.communitydragon.org/latest/champion/Riven/square" width="20" height="20" alt="Riven Github"></a>
|
<a href="https://github.com/MingweiSamuel/Riven/"><img src="https://cdn.communitydragon.org/latest/champion/Riven/square" width="20" height="20" alt="Riven Github"></a>
|
||||||
<a href="https://crates.io/crates/riven"><img src="https://img.shields.io/crates/v/riven?style=flat-square&logo=rust" alt="Crates.io"></a>
|
<a href="https://crates.io/crates/riven"><img src="https://img.shields.io/crates/v/riven?style=flat-square&logo=rust" alt="Crates.io"></a>
|
||||||
<a href="https://docs.rs/riven/"><img src="https://img.shields.io/badge/docs.rs-Riven-blue?style=flat-square&logo=read-the-docs&logoColor=white" alt="Docs.rs"></a>
|
<a href="https://docs.rs/riven/"><img src="https://img.shields.io/badge/docs.rs-Riven-blue?style=flat-square&logo=read-the-docs&logoColor=white" alt="Docs.rs"></a>
|
||||||
<!--<a href="https://travis-ci.com/MingweiSamuel/Riven"><img src="https://img.shields.io/travis/com/mingweisamuel/riven?style=flat-square" alt="Travis CI"></a>-->
|
<a href="https://travis-ci.com/MingweiSamuel/Riven"><img src="https://img.shields.io/travis/com/mingweisamuel/riven?style=flat-square" alt="Travis CI"></a>
|
||||||
<a href="https://github.com/rust-secure-code/safety-dance/"><img src="https://img.shields.io/badge/unsafe-forbidden-green.svg?style=flat-square" alt="unsafe forbidden"></a>
|
<a href="https://github.com/rust-secure-code/safety-dance/"><img src="https://img.shields.io/badge/unsafe-forbidden-green.svg?style=flat-square" alt="unsafe forbidden"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -15,28 +15,29 @@ Riven's goals are _speed_, _reliability_, and _maintainability_. Riven handles r
|
||||||
Data structs and endpoints are automatically generated from the
|
Data structs and endpoints are automatically generated from the
|
||||||
[Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)).
|
[Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)).
|
||||||
|
|
||||||
# Design
|
## Design
|
||||||
|
|
||||||
* Fast, asynchronous, thread-safe.
|
* Fast, asynchronous, thread-safe.
|
||||||
* Automatically retries failed requests.
|
* Automatically retries failed requests.
|
||||||
* Supports all endpoints, kept up-to-date using [riotapi-schema](https://github.com/MingweiSamuel/riotapi-schema).
|
* TFT API Support.
|
||||||
|
|
||||||
# Usage
|
## Usage
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use riven::RiotApi;
|
use riven::RiotApi;
|
||||||
use riven::consts::PlatformRoute;
|
use riven::consts::Region;
|
||||||
|
|
||||||
// Enter tokio async runtime.
|
// Enter tokio async runtime.
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
let mut rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
// Create RiotApi instance from key string.
|
// Create RiotApi instance from key string.
|
||||||
let api_key = std::env!("RGAPI_KEY"); // "RGAPI-01234567-89ab-cdef-0123-456789abcdef";
|
let api_key = "RGAPI-01234567-89ab-cdef-0123-456789abcdef";
|
||||||
let riot_api = RiotApi::new(api_key);
|
# /* (doc testing) */ let api_key = std::env!("RGAPI_KEY");
|
||||||
|
let riot_api = RiotApi::with_key(api_key);
|
||||||
|
|
||||||
// Get summoner data.
|
// Get summoner data.
|
||||||
let summoner = riot_api.summoner_v4()
|
let summoner = riot_api.summoner_v4()
|
||||||
.get_by_summoner_name(PlatformRoute::NA1, "잘 못").await
|
.get_by_summoner_name(Region::NA, "잘 못").await
|
||||||
.expect("Get summoner failed.")
|
.expect("Get summoner failed.")
|
||||||
.expect("There is no summoner with that name.");
|
.expect("There is no summoner with that name.");
|
||||||
|
|
||||||
|
@ -45,13 +46,13 @@ rt.block_on(async {
|
||||||
|
|
||||||
// Get champion mastery data.
|
// Get champion mastery data.
|
||||||
let masteries = riot_api.champion_mastery_v4()
|
let masteries = riot_api.champion_mastery_v4()
|
||||||
.get_all_champion_masteries_by_puuid(PlatformRoute::NA1, &summoner.puuid).await
|
.get_all_champion_masteries(Region::NA, &summoner.id).await
|
||||||
.expect("Get champion masteries failed.");
|
.expect("Get champion masteries failed.");
|
||||||
|
|
||||||
// Print champion masteries.
|
// Print champioon masteries.
|
||||||
for (i, mastery) in masteries.iter().take(10).enumerate() {
|
for (i, mastery) in masteries.iter().take(10).enumerate() {
|
||||||
println!("{: >2}) {: <9} {: >7} ({})", i + 1,
|
println!("{: >2}) {: <9} {: >7} ({})", i + 1,
|
||||||
mastery.champion_id.name().unwrap_or("UNKNOWN"),
|
mastery.champion_id.to_string(),
|
||||||
mastery.champion_points, mastery.champion_level);
|
mastery.champion_points, mastery.champion_level);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -59,50 +60,29 @@ rt.block_on(async {
|
||||||
Output:
|
Output:
|
||||||
```text
|
```text
|
||||||
잘 못 Champion Masteries:
|
잘 못 Champion Masteries:
|
||||||
1) Riven 1236866 (7)
|
1) Riven 1219895 (7)
|
||||||
2) Fiora 230679 (5)
|
2) Fiora 229714 (5)
|
||||||
3) Katarina 175985 (5)
|
3) Katarina 175985 (5)
|
||||||
4) Lee Sin 156070 (7)
|
4) Lee Sin 150546 (7)
|
||||||
5) Jax 102662 (5)
|
5) Jax 100509 (5)
|
||||||
6) Gnar 76373 (6)
|
6) Gnar 76373 (6)
|
||||||
7) Kai'Sa 64271 (5)
|
7) Kai'Sa 64271 (5)
|
||||||
8) Caitlyn 46614 (5)
|
8) Caitlyn 46479 (5)
|
||||||
9) Irelia 46465 (5)
|
9) Irelia 46465 (5)
|
||||||
10) Vladimir 37176 (5)
|
10) Vladimir 37176 (5)
|
||||||
```
|
```
|
||||||
The [`RiotApi` struct documentation](https://docs.rs/riven/latest/riven/struct.RiotApi.html)
|
|
||||||
contains additional usage information. The [tests](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/tests)
|
|
||||||
and [example proxy](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/examples/proxy)
|
|
||||||
provide more example usage.
|
|
||||||
|
|
||||||
## Feature Flags
|
|
||||||
|
|
||||||
### Nightly vs Stable
|
### Nightly vs Stable
|
||||||
|
|
||||||
Enable the `nightly` feature to use nightly-only functionality. 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).
|
[nightly optimizations in the `parking_lot` crate](https://github.com/Amanieu/parking_lot#nightly-vs-stable).
|
||||||
|
Also required for running async integration tests.
|
||||||
|
|
||||||
```toml
|
### Docs
|
||||||
riven = { version = "...", features = [ "nightly" ] }
|
|
||||||
```
|
|
||||||
|
|
||||||
### rustls
|
|
||||||
|
|
||||||
Riven uses [reqwest](https://github.com/seanmonstar/reqwest) for making requests. By default, reqwest uses the native TLS library.
|
|
||||||
If you prefer using [rustls](https://github.com/ctz/rustls) you can do so by turning off the Riven default features
|
|
||||||
and specifying the `rustls-tls` feature:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
riven = { version = "...", default-features = false, features = [ "rustls-tls" ] }
|
|
||||||
```
|
|
||||||
|
|
||||||
Riven is additionally able to produce [tracing](https://docs.rs/tracing) spans for requests if the `tracing` feature is enabled. This feature is disabled by default.
|
|
||||||
|
|
||||||
## Docs
|
|
||||||
|
|
||||||
[On docs.rs](https://docs.rs/riven/).
|
[On docs.rs](https://docs.rs/riven/).
|
||||||
|
|
||||||
## Error Handling
|
### Error Handling
|
||||||
|
|
||||||
Riven returns either `Result<T>` or `Result<Option<T>>` within futures.
|
Riven returns either `Result<T>` or `Result<Option<T>>` within futures.
|
||||||
|
|
||||||
|
@ -121,12 +101,12 @@ diagnostic information, such as the source Reqwest error, the number of retries
|
||||||
attempted, and the Reqwest `Response` object.
|
attempted, and the Reqwest `Response` object.
|
||||||
|
|
||||||
You can configure the number of time Riven retries using
|
You can configure the number of time Riven retries using
|
||||||
`RiotApiConfig::set_retries(...)` and the `RiotApi::from_config(config)`
|
`RiotApiConfig::set_retries(...)` and the `RiotApi::with_config(config)`
|
||||||
constructor. By default, Riven retries up to 3 times (4 requests total).
|
constructor. By default, Riven retries up to 3 times (4 requests total).
|
||||||
Some errors, such as 400 client errors, are not retried as they would
|
Some errors, such as 400 client errors, are not retried as they would
|
||||||
inevitably fail again.
|
inevitably fail again.
|
||||||
|
|
||||||
## Semantic Versioning
|
### Semantic Versioning
|
||||||
|
|
||||||
This package follows semantic versioning to an extent. However, the Riot API
|
This package follows semantic versioning to an extent. However, the Riot API
|
||||||
itself changes often and does not follow semantic versioning, which makes
|
itself changes often and does not follow semantic versioning, which makes
|
||||||
|
@ -142,22 +122,21 @@ not the major version.
|
||||||
Parts of Riven that do not depend on Riot API changes do follow semantic
|
Parts of Riven that do not depend on Riot API changes do follow semantic
|
||||||
versioning.
|
versioning.
|
||||||
|
|
||||||
## Additional Help
|
### Additional Help
|
||||||
|
|
||||||
Feel free to [make an issue](https://github.com/MingweiSamuel/Riven/issues/new)
|
Feel free to [make an issue](https://github.com/MingweiSamuel/Riven/issues/new)
|
||||||
if you are have any questions or trouble with Riven.
|
if you are have any questions or trouble with Riven.
|
||||||
|
|
||||||
# Development
|
## Development
|
||||||
|
|
||||||
NodeJS is used to generate code for Riven. The
|
NodeJS is used to generate code for Riven. The
|
||||||
[`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)
|
folder contains the code and [doT.js](https://olado.github.io/doT/index.html)
|
||||||
templates. `index.js` lists the JSON files downloaded and used to generate the
|
templates. `index.js` lists the JSON files downloaded and used to generate the
|
||||||
code.
|
code.
|
||||||
|
|
||||||
To set up the srcgen, you will first need to install NodeJS. Then enter the
|
To set up the srcgen, you will first need to install NodeJS. Then enter the
|
||||||
`riven/srcgen` folder and run `npm ci` (or `npm install`) to install
|
srcgen folder and run `npm ci` (or `npm install`) to install dependencies.
|
||||||
dependencies.
|
|
||||||
|
|
||||||
To run the srcgen use `node riven/srcgen` from the repository root.
|
To run the srcgen use `node srcgen` from the main folder.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
target
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "riven_example_proxy"
|
||||||
|
version = "0.0.0"
|
||||||
|
authors = [ "Mingwei Samuel <mingwei.samuel@gmail.com>" ]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
riven = { path = '..' }
|
||||||
|
|
||||||
|
hyper = "0.13"
|
||||||
|
lazy_static = "1.4"
|
||||||
|
tokio = { version = "0.2", features = [ "full" ] }
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Riven Example Proxy
|
||||||
|
|
||||||
|
This is a simple example implementation of a Riot API proxy server using `hyper`. This adds the API key and forwards
|
||||||
|
requests to the Riot API, then returns and forwards responses back to the requester. It handles error cases but only
|
||||||
|
provides minimal failure information. HTTP requests will wait to complete when Riven is waiting on rate limits.
|
||||||
|
|
||||||
|
Set `RGAPI_KEY` env var then run:
|
||||||
|
```bash
|
||||||
|
export RGAPI_KEY=RGAPI-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
|
||||||
|
Test in your browser or using `curl`. The first path segment specifies the region:
|
||||||
|
```json
|
||||||
|
$ curl http://localhost:3000/na1/lol/summoner/v4/summoners/by-name/LugnutsK
|
||||||
|
{"id":"...","accountId":"...","puuid":"...","name":"LugnutsK","profileIconId":4540,"revisionDate":1589704662000,"summonerLevel":111}
|
||||||
|
|
||||||
|
$ curl http://localhost:3000/na1/valorant/v4/players/by-name/LugnutsK # not yet :)
|
||||||
|
{"error":"Riot API endpoint method not found."}
|
||||||
|
```
|
|
@ -0,0 +1,149 @@
|
||||||
|
// #![deny(warnings)]
|
||||||
|
|
||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
|
use hyper::service::{ make_service_fn, service_fn };
|
||||||
|
use hyper::{ Body, Method, Request, Response, Server, StatusCode };
|
||||||
|
use hyper::header::HeaderValue;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use riven::{ RiotApi, RiotApiConfig };
|
||||||
|
use riven::consts::Region;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
/// Create lazy static RiotApi instance.
|
||||||
|
/// Easier than passing it around.
|
||||||
|
pub static ref RIOT_API: RiotApi = {
|
||||||
|
let api_key = std::env::var("RGAPI_KEY").ok()
|
||||||
|
.or_else(|| std::fs::read_to_string("../apikey.txt").ok())
|
||||||
|
.expect("Failed to find RGAPI_KEY env var or apikey.txt.");
|
||||||
|
RiotApi::with_config(RiotApiConfig::with_key(api_key.trim())
|
||||||
|
.preconfig_burst())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to create JSON error responses.
|
||||||
|
fn create_json_response(body: &'static str, status: StatusCode) -> Response<Body> {
|
||||||
|
let mut resp = Response::new(Body::from(body));
|
||||||
|
*resp.status_mut() = status;
|
||||||
|
resp.headers_mut().insert(hyper::header::CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main request handler service.
|
||||||
|
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||||
|
|
||||||
|
if Method::GET != req.method() {
|
||||||
|
return Ok(create_json_response(r#"{"error":"HTTP method must be GET."}"#, StatusCode::METHOD_NOT_ALLOWED));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle path.
|
||||||
|
let path_data_opt = parse_path(req.uri().path());
|
||||||
|
let ( region, method_id, req_path ) = match path_data_opt {
|
||||||
|
None => return Ok(create_json_response(
|
||||||
|
r#"{"error":"Riot API endpoint method not found."}"#, StatusCode::NOT_FOUND)),
|
||||||
|
Some(path_data) => path_data,
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Request to region {} path {}:\n\t{} {}", region, method_id, region, req_path);
|
||||||
|
|
||||||
|
// Send request to Riot API.
|
||||||
|
let query = req.uri().query().map(|s| s.to_owned());
|
||||||
|
let resp_result = RIOT_API.get_raw_response(method_id, region.into(), req_path.to_owned(), query).await;
|
||||||
|
let resp_info = match resp_result {
|
||||||
|
Err(_err) => return Ok(create_json_response(
|
||||||
|
r#"{"error":"Riot API request failed."}"#, StatusCode::INTERNAL_SERVER_ERROR)),
|
||||||
|
Ok(resp_info) => resp_info,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write output.
|
||||||
|
let api_response = resp_info.response;
|
||||||
|
let mut out_response = Response::default();
|
||||||
|
*out_response.headers_mut() = api_response.headers().clone();
|
||||||
|
|
||||||
|
// If None, write "null" to body to be extra nice.
|
||||||
|
if resp_info.status_none {
|
||||||
|
*out_response.body_mut() = Body::from("null");
|
||||||
|
}
|
||||||
|
// Otherwise copy body.
|
||||||
|
else {
|
||||||
|
*out_response.status_mut() = api_response.status();
|
||||||
|
|
||||||
|
// Using streams would be faster.
|
||||||
|
let bytes_result = api_response.bytes().await;
|
||||||
|
let bytes = match bytes_result {
|
||||||
|
Err(_err) => return Ok(create_json_response(
|
||||||
|
r#"{"error":"Failed to get body from Riot API response."}"#, StatusCode::INTERNAL_SERVER_ERROR)),
|
||||||
|
Ok(bytes) => bytes,
|
||||||
|
};
|
||||||
|
*out_response.body_mut() = Body::from((&bytes[..]).to_vec());
|
||||||
|
}
|
||||||
|
return Ok(out_response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the region, method_id, and Riot API path based on the given path.
|
||||||
|
fn parse_path<'a>(req_path: &'a str) -> Option<( Region, &'static str, &'a str )> {
|
||||||
|
|
||||||
|
// Split URI into region and rest of path.
|
||||||
|
let req_path = req_path.trim_start_matches('/');
|
||||||
|
let ( region, req_path ) = req_path.split_at(req_path.find('/')?);
|
||||||
|
let region: Region = region.to_uppercase().parse().ok()?;
|
||||||
|
|
||||||
|
// Find method_id for given path.
|
||||||
|
let method_id = find_matching_method_id(req_path)?;
|
||||||
|
|
||||||
|
return Some(( region, method_id, req_path ))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the method_id given the request path.
|
||||||
|
fn find_matching_method_id(req_path: &str) -> Option<&'static str> {
|
||||||
|
for ( ref_path, method_id ) in &*riven::meta::ENDPOINT_PATH_METHODID {
|
||||||
|
if paths_match(ref_path, req_path) {
|
||||||
|
return Some(method_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the request path (req_path) matches the reference path (ref_path).
|
||||||
|
fn paths_match(ref_path: &str, req_path: &str) -> bool {
|
||||||
|
let mut ref_iter = ref_path.split('/');
|
||||||
|
let mut req_iter = req_path.split('/');
|
||||||
|
loop {
|
||||||
|
let ref_seg_opt = ref_iter.next();
|
||||||
|
let req_seg_opt = req_iter.next();
|
||||||
|
if ref_seg_opt.is_none() != req_seg_opt.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Some(ref_seg) = ref_seg_opt {
|
||||||
|
if let Some(req_seg) = req_seg_opt {
|
||||||
|
if ref_seg.starts_with('{') || ref_seg == req_seg {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
// For every connection, we must make a `Service` to handle all
|
||||||
|
// incoming HTTP requests on said connection.
|
||||||
|
let make_svc = make_service_fn(|_conn| {
|
||||||
|
// This is the `Service` that will handle the connection.
|
||||||
|
// `service_fn` is a helper to convert a function that
|
||||||
|
// returns a Response into a `Service`.
|
||||||
|
async { Ok::<_, Infallible>(service_fn(handle_request)) }
|
||||||
|
});
|
||||||
|
|
||||||
|
let addr = ([ 127, 0, 0, 1 ], 3000).into();
|
||||||
|
|
||||||
|
let server = Server::bind(&addr).serve(make_svc);
|
||||||
|
|
||||||
|
println!("Listening on http://{}", addr);
|
||||||
|
|
||||||
|
server.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -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,
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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"),
|
|
||||||
];
|
|
|
@ -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,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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,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,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 }}"),
|
|
||||||
{{
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
];
|
|
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,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,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
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,532 @@
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// ! //
|
||||||
|
// This file is automatically generated! //
|
||||||
|
// Do not directly edit! //
|
||||||
|
// //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||||
|
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
||||||
|
use strum_macros::{ EnumString, EnumIter, Display, AsRefStr, IntoStaticStr };
|
||||||
|
|
||||||
|
/// League of Legend's champions.
|
||||||
|
///
|
||||||
|
/// The documentation of each variant specifies:<br>
|
||||||
|
/// NAME (`IDENTIFIER`, ID).
|
||||||
|
///
|
||||||
|
/// Implements [IntoEnumIterator](super::IntoEnumIterator).
|
||||||
|
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
|
#[derive(Serialize_repr, Deserialize_repr)]
|
||||||
|
#[derive(EnumString, EnumIter, Display, AsRefStr, IntoStaticStr)]
|
||||||
|
#[repr(i16)]
|
||||||
|
pub enum Champion {
|
||||||
|
/// A champion that doesn't exist. Used in TeamBans when no champion was banned.
|
||||||
|
///
|
||||||
|
/// None (`NONE`, -1).
|
||||||
|
None = -1,
|
||||||
|
|
||||||
|
/// Aatrox (`Aatrox`, 266).
|
||||||
|
#[strum(to_string="Aatrox")] Aatrox = 266,
|
||||||
|
/// Ahri (`Ahri`, 103).
|
||||||
|
#[strum(to_string="Ahri")] Ahri = 103,
|
||||||
|
/// Akali (`Akali`, 84).
|
||||||
|
#[strum(to_string="Akali")] Akali = 84,
|
||||||
|
/// Akshan (`Akshan`, 166).
|
||||||
|
#[strum(to_string="Akshan")] Akshan = 166,
|
||||||
|
/// Alistar (`Alistar`, 12).
|
||||||
|
#[strum(to_string="Alistar")] Alistar = 12,
|
||||||
|
/// Amumu (`Amumu`, 32).
|
||||||
|
#[strum(to_string="Amumu")] Amumu = 32,
|
||||||
|
/// Anivia (`Anivia`, 34).
|
||||||
|
#[strum(to_string="Anivia")] Anivia = 34,
|
||||||
|
/// Annie (`Annie`, 1).
|
||||||
|
#[strum(to_string="Annie")] Annie = 1,
|
||||||
|
/// Aphelios (`Aphelios`, 523).
|
||||||
|
#[strum(to_string="Aphelios")] Aphelios = 523,
|
||||||
|
/// Ashe (`Ashe`, 22).
|
||||||
|
#[strum(to_string="Ashe")] Ashe = 22,
|
||||||
|
/// Aurelion Sol (`AurelionSol`, 136).
|
||||||
|
#[strum(to_string="Aurelion Sol", serialize="AurelionSol")] AurelionSol = 136,
|
||||||
|
/// Azir (`Azir`, 268).
|
||||||
|
#[strum(to_string="Azir")] Azir = 268,
|
||||||
|
/// Bard (`Bard`, 432).
|
||||||
|
#[strum(to_string="Bard")] Bard = 432,
|
||||||
|
/// Blitzcrank (`Blitzcrank`, 53).
|
||||||
|
#[strum(to_string="Blitzcrank")] Blitzcrank = 53,
|
||||||
|
/// Brand (`Brand`, 63).
|
||||||
|
#[strum(to_string="Brand")] Brand = 63,
|
||||||
|
/// Braum (`Braum`, 201).
|
||||||
|
#[strum(to_string="Braum")] Braum = 201,
|
||||||
|
/// Caitlyn (`Caitlyn`, 51).
|
||||||
|
#[strum(to_string="Caitlyn")] Caitlyn = 51,
|
||||||
|
/// Camille (`Camille`, 164).
|
||||||
|
#[strum(to_string="Camille")] Camille = 164,
|
||||||
|
/// Cassiopeia (`Cassiopeia`, 69).
|
||||||
|
#[strum(to_string="Cassiopeia")] Cassiopeia = 69,
|
||||||
|
/// Cho'Gath (`Chogath`, 31).
|
||||||
|
#[strum(to_string="Cho'Gath", serialize="Chogath")] ChoGath = 31,
|
||||||
|
/// Corki (`Corki`, 42).
|
||||||
|
#[strum(to_string="Corki")] Corki = 42,
|
||||||
|
/// Darius (`Darius`, 122).
|
||||||
|
#[strum(to_string="Darius")] Darius = 122,
|
||||||
|
/// Diana (`Diana`, 131).
|
||||||
|
#[strum(to_string="Diana")] Diana = 131,
|
||||||
|
/// Dr. Mundo (`DrMundo`, 36).
|
||||||
|
#[strum(to_string="Dr. Mundo", serialize="DrMundo")] DrMundo = 36,
|
||||||
|
/// Draven (`Draven`, 119).
|
||||||
|
#[strum(to_string="Draven")] Draven = 119,
|
||||||
|
/// Ekko (`Ekko`, 245).
|
||||||
|
#[strum(to_string="Ekko")] Ekko = 245,
|
||||||
|
/// Elise (`Elise`, 60).
|
||||||
|
#[strum(to_string="Elise")] Elise = 60,
|
||||||
|
/// Evelynn (`Evelynn`, 28).
|
||||||
|
#[strum(to_string="Evelynn")] Evelynn = 28,
|
||||||
|
/// Ezreal (`Ezreal`, 81).
|
||||||
|
#[strum(to_string="Ezreal")] Ezreal = 81,
|
||||||
|
/// Fiddlesticks (`FiddleSticks`, 9).
|
||||||
|
#[strum(to_string="Fiddlesticks", serialize="FiddleSticks")] Fiddlesticks = 9,
|
||||||
|
/// Fiora (`Fiora`, 114).
|
||||||
|
#[strum(to_string="Fiora")] Fiora = 114,
|
||||||
|
/// Fizz (`Fizz`, 105).
|
||||||
|
#[strum(to_string="Fizz")] Fizz = 105,
|
||||||
|
/// Galio (`Galio`, 3).
|
||||||
|
#[strum(to_string="Galio")] Galio = 3,
|
||||||
|
/// Gangplank (`Gangplank`, 41).
|
||||||
|
#[strum(to_string="Gangplank")] Gangplank = 41,
|
||||||
|
/// Garen (`Garen`, 86).
|
||||||
|
#[strum(to_string="Garen")] Garen = 86,
|
||||||
|
/// Gnar (`Gnar`, 150).
|
||||||
|
#[strum(to_string="Gnar")] Gnar = 150,
|
||||||
|
/// Gragas (`Gragas`, 79).
|
||||||
|
#[strum(to_string="Gragas")] Gragas = 79,
|
||||||
|
/// Graves (`Graves`, 104).
|
||||||
|
#[strum(to_string="Graves")] Graves = 104,
|
||||||
|
/// Gwen (`Gwen`, 887).
|
||||||
|
#[strum(to_string="Gwen")] Gwen = 887,
|
||||||
|
/// Hecarim (`Hecarim`, 120).
|
||||||
|
#[strum(to_string="Hecarim")] Hecarim = 120,
|
||||||
|
/// Heimerdinger (`Heimerdinger`, 74).
|
||||||
|
#[strum(to_string="Heimerdinger")] Heimerdinger = 74,
|
||||||
|
/// Illaoi (`Illaoi`, 420).
|
||||||
|
#[strum(to_string="Illaoi")] Illaoi = 420,
|
||||||
|
/// Irelia (`Irelia`, 39).
|
||||||
|
#[strum(to_string="Irelia")] Irelia = 39,
|
||||||
|
/// Ivern (`Ivern`, 427).
|
||||||
|
#[strum(to_string="Ivern")] Ivern = 427,
|
||||||
|
/// Janna (`Janna`, 40).
|
||||||
|
#[strum(to_string="Janna")] Janna = 40,
|
||||||
|
/// Jarvan IV (`JarvanIV`, 59).
|
||||||
|
#[strum(to_string="Jarvan IV", serialize="JarvanIV")] JarvanIV = 59,
|
||||||
|
/// Jax (`Jax`, 24).
|
||||||
|
#[strum(to_string="Jax")] Jax = 24,
|
||||||
|
/// Jayce (`Jayce`, 126).
|
||||||
|
#[strum(to_string="Jayce")] Jayce = 126,
|
||||||
|
/// Jhin (`Jhin`, 202).
|
||||||
|
#[strum(to_string="Jhin")] Jhin = 202,
|
||||||
|
/// Jinx (`Jinx`, 222).
|
||||||
|
#[strum(to_string="Jinx")] Jinx = 222,
|
||||||
|
/// Kai'Sa (`Kaisa`, 145).
|
||||||
|
#[strum(to_string="Kai'Sa", serialize="Kaisa")] KaiSa = 145,
|
||||||
|
/// Kalista (`Kalista`, 429).
|
||||||
|
#[strum(to_string="Kalista")] Kalista = 429,
|
||||||
|
/// Karma (`Karma`, 43).
|
||||||
|
#[strum(to_string="Karma")] Karma = 43,
|
||||||
|
/// Karthus (`Karthus`, 30).
|
||||||
|
#[strum(to_string="Karthus")] Karthus = 30,
|
||||||
|
/// Kassadin (`Kassadin`, 38).
|
||||||
|
#[strum(to_string="Kassadin")] Kassadin = 38,
|
||||||
|
/// Katarina (`Katarina`, 55).
|
||||||
|
#[strum(to_string="Katarina")] Katarina = 55,
|
||||||
|
/// Kayle (`Kayle`, 10).
|
||||||
|
#[strum(to_string="Kayle")] Kayle = 10,
|
||||||
|
/// Kayn (`Kayn`, 141).
|
||||||
|
#[strum(to_string="Kayn")] Kayn = 141,
|
||||||
|
/// Kennen (`Kennen`, 85).
|
||||||
|
#[strum(to_string="Kennen")] Kennen = 85,
|
||||||
|
/// Kha'Zix (`Khazix`, 121).
|
||||||
|
#[strum(to_string="Kha'Zix", serialize="Khazix")] KhaZix = 121,
|
||||||
|
/// Kindred (`Kindred`, 203).
|
||||||
|
#[strum(to_string="Kindred")] Kindred = 203,
|
||||||
|
/// Kled (`Kled`, 240).
|
||||||
|
#[strum(to_string="Kled")] Kled = 240,
|
||||||
|
/// Kog'Maw (`KogMaw`, 96).
|
||||||
|
#[strum(to_string="Kog'Maw", serialize="KogMaw")] KogMaw = 96,
|
||||||
|
/// LeBlanc (`Leblanc`, 7).
|
||||||
|
#[strum(to_string="LeBlanc", serialize="Leblanc")] LeBlanc = 7,
|
||||||
|
/// Lee Sin (`LeeSin`, 64).
|
||||||
|
#[strum(to_string="Lee Sin", serialize="LeeSin")] LeeSin = 64,
|
||||||
|
/// Leona (`Leona`, 89).
|
||||||
|
#[strum(to_string="Leona")] Leona = 89,
|
||||||
|
/// Lillia (`Lillia`, 876).
|
||||||
|
#[strum(to_string="Lillia")] Lillia = 876,
|
||||||
|
/// Lissandra (`Lissandra`, 127).
|
||||||
|
#[strum(to_string="Lissandra")] Lissandra = 127,
|
||||||
|
/// Lucian (`Lucian`, 236).
|
||||||
|
#[strum(to_string="Lucian")] Lucian = 236,
|
||||||
|
/// Lulu (`Lulu`, 117).
|
||||||
|
#[strum(to_string="Lulu")] Lulu = 117,
|
||||||
|
/// Lux (`Lux`, 99).
|
||||||
|
#[strum(to_string="Lux")] Lux = 99,
|
||||||
|
/// Malphite (`Malphite`, 54).
|
||||||
|
#[strum(to_string="Malphite")] Malphite = 54,
|
||||||
|
/// Malzahar (`Malzahar`, 90).
|
||||||
|
#[strum(to_string="Malzahar")] Malzahar = 90,
|
||||||
|
/// Maokai (`Maokai`, 57).
|
||||||
|
#[strum(to_string="Maokai")] Maokai = 57,
|
||||||
|
/// Master Yi (`MasterYi`, 11).
|
||||||
|
#[strum(to_string="Master Yi", serialize="MasterYi")] MasterYi = 11,
|
||||||
|
/// Miss Fortune (`MissFortune`, 21).
|
||||||
|
#[strum(to_string="Miss Fortune", serialize="MissFortune")] MissFortune = 21,
|
||||||
|
/// Mordekaiser (`Mordekaiser`, 82).
|
||||||
|
#[strum(to_string="Mordekaiser")] Mordekaiser = 82,
|
||||||
|
/// Morgana (`Morgana`, 25).
|
||||||
|
#[strum(to_string="Morgana")] Morgana = 25,
|
||||||
|
/// Nami (`Nami`, 267).
|
||||||
|
#[strum(to_string="Nami")] Nami = 267,
|
||||||
|
/// Nasus (`Nasus`, 75).
|
||||||
|
#[strum(to_string="Nasus")] Nasus = 75,
|
||||||
|
/// Nautilus (`Nautilus`, 111).
|
||||||
|
#[strum(to_string="Nautilus")] Nautilus = 111,
|
||||||
|
/// Neeko (`Neeko`, 518).
|
||||||
|
#[strum(to_string="Neeko")] Neeko = 518,
|
||||||
|
/// Nidalee (`Nidalee`, 76).
|
||||||
|
#[strum(to_string="Nidalee")] Nidalee = 76,
|
||||||
|
/// Nocturne (`Nocturne`, 56).
|
||||||
|
#[strum(to_string="Nocturne")] Nocturne = 56,
|
||||||
|
/// Nunu & Willump (`Nunu`, 20).
|
||||||
|
#[strum(to_string="Nunu & Willump", serialize="Nunu")] NunuWillump = 20,
|
||||||
|
/// Olaf (`Olaf`, 2).
|
||||||
|
#[strum(to_string="Olaf")] Olaf = 2,
|
||||||
|
/// Orianna (`Orianna`, 61).
|
||||||
|
#[strum(to_string="Orianna")] Orianna = 61,
|
||||||
|
/// Ornn (`Ornn`, 516).
|
||||||
|
#[strum(to_string="Ornn")] Ornn = 516,
|
||||||
|
/// Pantheon (`Pantheon`, 80).
|
||||||
|
#[strum(to_string="Pantheon")] Pantheon = 80,
|
||||||
|
/// Poppy (`Poppy`, 78).
|
||||||
|
#[strum(to_string="Poppy")] Poppy = 78,
|
||||||
|
/// Pyke (`Pyke`, 555).
|
||||||
|
#[strum(to_string="Pyke")] Pyke = 555,
|
||||||
|
/// Qiyana (`Qiyana`, 246).
|
||||||
|
#[strum(to_string="Qiyana")] Qiyana = 246,
|
||||||
|
/// Quinn (`Quinn`, 133).
|
||||||
|
#[strum(to_string="Quinn")] Quinn = 133,
|
||||||
|
/// Rakan (`Rakan`, 497).
|
||||||
|
#[strum(to_string="Rakan")] Rakan = 497,
|
||||||
|
/// Rammus (`Rammus`, 33).
|
||||||
|
#[strum(to_string="Rammus")] Rammus = 33,
|
||||||
|
/// Rek'Sai (`RekSai`, 421).
|
||||||
|
#[strum(to_string="Rek'Sai", serialize="RekSai")] RekSai = 421,
|
||||||
|
/// Rell (`Rell`, 526).
|
||||||
|
#[strum(to_string="Rell")] Rell = 526,
|
||||||
|
/// Renekton (`Renekton`, 58).
|
||||||
|
#[strum(to_string="Renekton")] Renekton = 58,
|
||||||
|
/// Rengar (`Rengar`, 107).
|
||||||
|
#[strum(to_string="Rengar")] Rengar = 107,
|
||||||
|
/// Riven (`Riven`, 92).
|
||||||
|
#[strum(to_string="Riven")] Riven = 92,
|
||||||
|
/// Rumble (`Rumble`, 68).
|
||||||
|
#[strum(to_string="Rumble")] Rumble = 68,
|
||||||
|
/// Ryze (`Ryze`, 13).
|
||||||
|
#[strum(to_string="Ryze")] Ryze = 13,
|
||||||
|
/// Samira (`Samira`, 360).
|
||||||
|
#[strum(to_string="Samira")] Samira = 360,
|
||||||
|
/// Sejuani (`Sejuani`, 113).
|
||||||
|
#[strum(to_string="Sejuani")] Sejuani = 113,
|
||||||
|
/// Senna (`Senna`, 235).
|
||||||
|
#[strum(to_string="Senna")] Senna = 235,
|
||||||
|
/// Seraphine (`Seraphine`, 147).
|
||||||
|
#[strum(to_string="Seraphine")] Seraphine = 147,
|
||||||
|
/// Sett (`Sett`, 875).
|
||||||
|
#[strum(to_string="Sett")] Sett = 875,
|
||||||
|
/// Shaco (`Shaco`, 35).
|
||||||
|
#[strum(to_string="Shaco")] Shaco = 35,
|
||||||
|
/// Shen (`Shen`, 98).
|
||||||
|
#[strum(to_string="Shen")] Shen = 98,
|
||||||
|
/// Shyvana (`Shyvana`, 102).
|
||||||
|
#[strum(to_string="Shyvana")] Shyvana = 102,
|
||||||
|
/// Singed (`Singed`, 27).
|
||||||
|
#[strum(to_string="Singed")] Singed = 27,
|
||||||
|
/// Sion (`Sion`, 14).
|
||||||
|
#[strum(to_string="Sion")] Sion = 14,
|
||||||
|
/// Sivir (`Sivir`, 15).
|
||||||
|
#[strum(to_string="Sivir")] Sivir = 15,
|
||||||
|
/// Skarner (`Skarner`, 72).
|
||||||
|
#[strum(to_string="Skarner")] Skarner = 72,
|
||||||
|
/// Sona (`Sona`, 37).
|
||||||
|
#[strum(to_string="Sona")] Sona = 37,
|
||||||
|
/// Soraka (`Soraka`, 16).
|
||||||
|
#[strum(to_string="Soraka")] Soraka = 16,
|
||||||
|
/// Swain (`Swain`, 50).
|
||||||
|
#[strum(to_string="Swain")] Swain = 50,
|
||||||
|
/// Sylas (`Sylas`, 517).
|
||||||
|
#[strum(to_string="Sylas")] Sylas = 517,
|
||||||
|
/// Syndra (`Syndra`, 134).
|
||||||
|
#[strum(to_string="Syndra")] Syndra = 134,
|
||||||
|
/// Tahm Kench (`TahmKench`, 223).
|
||||||
|
#[strum(to_string="Tahm Kench", serialize="TahmKench")] TahmKench = 223,
|
||||||
|
/// Taliyah (`Taliyah`, 163).
|
||||||
|
#[strum(to_string="Taliyah")] Taliyah = 163,
|
||||||
|
/// Talon (`Talon`, 91).
|
||||||
|
#[strum(to_string="Talon")] Talon = 91,
|
||||||
|
/// Taric (`Taric`, 44).
|
||||||
|
#[strum(to_string="Taric")] Taric = 44,
|
||||||
|
/// Teemo (`Teemo`, 17).
|
||||||
|
#[strum(to_string="Teemo")] Teemo = 17,
|
||||||
|
/// Thresh (`Thresh`, 412).
|
||||||
|
#[strum(to_string="Thresh")] Thresh = 412,
|
||||||
|
/// Tristana (`Tristana`, 18).
|
||||||
|
#[strum(to_string="Tristana")] Tristana = 18,
|
||||||
|
/// Trundle (`Trundle`, 48).
|
||||||
|
#[strum(to_string="Trundle")] Trundle = 48,
|
||||||
|
/// Tryndamere (`Tryndamere`, 23).
|
||||||
|
#[strum(to_string="Tryndamere")] Tryndamere = 23,
|
||||||
|
/// Twisted Fate (`TwistedFate`, 4).
|
||||||
|
#[strum(to_string="Twisted Fate", serialize="TwistedFate")] TwistedFate = 4,
|
||||||
|
/// Twitch (`Twitch`, 29).
|
||||||
|
#[strum(to_string="Twitch")] Twitch = 29,
|
||||||
|
/// Udyr (`Udyr`, 77).
|
||||||
|
#[strum(to_string="Udyr")] Udyr = 77,
|
||||||
|
/// Urgot (`Urgot`, 6).
|
||||||
|
#[strum(to_string="Urgot")] Urgot = 6,
|
||||||
|
/// Varus (`Varus`, 110).
|
||||||
|
#[strum(to_string="Varus")] Varus = 110,
|
||||||
|
/// Vayne (`Vayne`, 67).
|
||||||
|
#[strum(to_string="Vayne")] Vayne = 67,
|
||||||
|
/// Veigar (`Veigar`, 45).
|
||||||
|
#[strum(to_string="Veigar")] Veigar = 45,
|
||||||
|
/// Vel'Koz (`Velkoz`, 161).
|
||||||
|
#[strum(to_string="Vel'Koz", serialize="Velkoz")] VelKoz = 161,
|
||||||
|
/// Vex (`Vex`, 711).
|
||||||
|
#[strum(to_string="Vex")] Vex = 711,
|
||||||
|
/// Vi (`Vi`, 254).
|
||||||
|
#[strum(to_string="Vi")] Vi = 254,
|
||||||
|
/// Viego (`Viego`, 234).
|
||||||
|
#[strum(to_string="Viego")] Viego = 234,
|
||||||
|
/// Viktor (`Viktor`, 112).
|
||||||
|
#[strum(to_string="Viktor")] Viktor = 112,
|
||||||
|
/// Vladimir (`Vladimir`, 8).
|
||||||
|
#[strum(to_string="Vladimir")] Vladimir = 8,
|
||||||
|
/// Volibear (`Volibear`, 106).
|
||||||
|
#[strum(to_string="Volibear")] Volibear = 106,
|
||||||
|
/// Warwick (`Warwick`, 19).
|
||||||
|
#[strum(to_string="Warwick")] Warwick = 19,
|
||||||
|
/// Wukong (`MonkeyKing`, 62).
|
||||||
|
#[strum(to_string="Wukong", serialize="MonkeyKing")] Wukong = 62,
|
||||||
|
/// Xayah (`Xayah`, 498).
|
||||||
|
#[strum(to_string="Xayah")] Xayah = 498,
|
||||||
|
/// Xerath (`Xerath`, 101).
|
||||||
|
#[strum(to_string="Xerath")] Xerath = 101,
|
||||||
|
/// Xin Zhao (`XinZhao`, 5).
|
||||||
|
#[strum(to_string="Xin Zhao", serialize="XinZhao")] XinZhao = 5,
|
||||||
|
/// Yasuo (`Yasuo`, 157).
|
||||||
|
#[strum(to_string="Yasuo")] Yasuo = 157,
|
||||||
|
/// Yone (`Yone`, 777).
|
||||||
|
#[strum(to_string="Yone")] Yone = 777,
|
||||||
|
/// Yorick (`Yorick`, 83).
|
||||||
|
#[strum(to_string="Yorick")] Yorick = 83,
|
||||||
|
/// Yuumi (`Yuumi`, 350).
|
||||||
|
#[strum(to_string="Yuumi")] Yuumi = 350,
|
||||||
|
/// Zac (`Zac`, 154).
|
||||||
|
#[strum(to_string="Zac")] Zac = 154,
|
||||||
|
/// Zed (`Zed`, 238).
|
||||||
|
#[strum(to_string="Zed")] Zed = 238,
|
||||||
|
/// Ziggs (`Ziggs`, 115).
|
||||||
|
#[strum(to_string="Ziggs")] Ziggs = 115,
|
||||||
|
/// Zilean (`Zilean`, 26).
|
||||||
|
#[strum(to_string="Zilean")] Zilean = 26,
|
||||||
|
/// Zoe (`Zoe`, 142).
|
||||||
|
#[strum(to_string="Zoe")] Zoe = 142,
|
||||||
|
/// Zyra (`Zyra`, 143).
|
||||||
|
#[strum(to_string="Zyra")] Zyra = 143,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Champion {
|
||||||
|
/// The champion's name (localized `en_US`), or `"NONE"` for the None variant.
|
||||||
|
pub fn name(self) -> &'static str {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The champion's identifier key. Somtimes called "key", "identifier", or "alias".
|
||||||
|
/// This is mainly used in DDragon paths.
|
||||||
|
///
|
||||||
|
/// This is generally the `en_US` name with spaces and punctuation removed,
|
||||||
|
/// but there are the following exceptions:
|
||||||
|
///
|
||||||
|
/// Variant | Name | Identifier
|
||||||
|
/// --------|------|-----------
|
||||||
|
/// `None` | "NONE" | "NONE"
|
||||||
|
/// `ChoGath` | "Cho'Gath" | "Chogath"
|
||||||
|
/// `Fiddlesticks` | "Fiddlesticks" | "FiddleSticks"
|
||||||
|
/// `KaiSa` | "Kai'Sa" | "Kaisa"
|
||||||
|
/// `KhaZix` | "Kha'Zix" | "Khazix"
|
||||||
|
/// `LeBlanc` | "LeBlanc" | "Leblanc"
|
||||||
|
/// `NunuWillump` | "Nunu & Willump" | "Nunu"
|
||||||
|
/// `VelKoz` | "Vel'Koz" | "Velkoz"
|
||||||
|
/// `Wukong` | "Wukong" | "MonkeyKing"
|
||||||
|
pub fn identifier(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::None => "NONE",
|
||||||
|
Self::Aatrox => "Aatrox",
|
||||||
|
Self::Ahri => "Ahri",
|
||||||
|
Self::Akali => "Akali",
|
||||||
|
Self::Akshan => "Akshan",
|
||||||
|
Self::Alistar => "Alistar",
|
||||||
|
Self::Amumu => "Amumu",
|
||||||
|
Self::Anivia => "Anivia",
|
||||||
|
Self::Annie => "Annie",
|
||||||
|
Self::Aphelios => "Aphelios",
|
||||||
|
Self::Ashe => "Ashe",
|
||||||
|
Self::AurelionSol => "AurelionSol",
|
||||||
|
Self::Azir => "Azir",
|
||||||
|
Self::Bard => "Bard",
|
||||||
|
Self::Blitzcrank => "Blitzcrank",
|
||||||
|
Self::Brand => "Brand",
|
||||||
|
Self::Braum => "Braum",
|
||||||
|
Self::Caitlyn => "Caitlyn",
|
||||||
|
Self::Camille => "Camille",
|
||||||
|
Self::Cassiopeia => "Cassiopeia",
|
||||||
|
Self::ChoGath => "Chogath",
|
||||||
|
Self::Corki => "Corki",
|
||||||
|
Self::Darius => "Darius",
|
||||||
|
Self::Diana => "Diana",
|
||||||
|
Self::DrMundo => "DrMundo",
|
||||||
|
Self::Draven => "Draven",
|
||||||
|
Self::Ekko => "Ekko",
|
||||||
|
Self::Elise => "Elise",
|
||||||
|
Self::Evelynn => "Evelynn",
|
||||||
|
Self::Ezreal => "Ezreal",
|
||||||
|
Self::Fiddlesticks => "FiddleSticks",
|
||||||
|
Self::Fiora => "Fiora",
|
||||||
|
Self::Fizz => "Fizz",
|
||||||
|
Self::Galio => "Galio",
|
||||||
|
Self::Gangplank => "Gangplank",
|
||||||
|
Self::Garen => "Garen",
|
||||||
|
Self::Gnar => "Gnar",
|
||||||
|
Self::Gragas => "Gragas",
|
||||||
|
Self::Graves => "Graves",
|
||||||
|
Self::Gwen => "Gwen",
|
||||||
|
Self::Hecarim => "Hecarim",
|
||||||
|
Self::Heimerdinger => "Heimerdinger",
|
||||||
|
Self::Illaoi => "Illaoi",
|
||||||
|
Self::Irelia => "Irelia",
|
||||||
|
Self::Ivern => "Ivern",
|
||||||
|
Self::Janna => "Janna",
|
||||||
|
Self::JarvanIV => "JarvanIV",
|
||||||
|
Self::Jax => "Jax",
|
||||||
|
Self::Jayce => "Jayce",
|
||||||
|
Self::Jhin => "Jhin",
|
||||||
|
Self::Jinx => "Jinx",
|
||||||
|
Self::KaiSa => "Kaisa",
|
||||||
|
Self::Kalista => "Kalista",
|
||||||
|
Self::Karma => "Karma",
|
||||||
|
Self::Karthus => "Karthus",
|
||||||
|
Self::Kassadin => "Kassadin",
|
||||||
|
Self::Katarina => "Katarina",
|
||||||
|
Self::Kayle => "Kayle",
|
||||||
|
Self::Kayn => "Kayn",
|
||||||
|
Self::Kennen => "Kennen",
|
||||||
|
Self::KhaZix => "Khazix",
|
||||||
|
Self::Kindred => "Kindred",
|
||||||
|
Self::Kled => "Kled",
|
||||||
|
Self::KogMaw => "KogMaw",
|
||||||
|
Self::LeBlanc => "Leblanc",
|
||||||
|
Self::LeeSin => "LeeSin",
|
||||||
|
Self::Leona => "Leona",
|
||||||
|
Self::Lillia => "Lillia",
|
||||||
|
Self::Lissandra => "Lissandra",
|
||||||
|
Self::Lucian => "Lucian",
|
||||||
|
Self::Lulu => "Lulu",
|
||||||
|
Self::Lux => "Lux",
|
||||||
|
Self::Malphite => "Malphite",
|
||||||
|
Self::Malzahar => "Malzahar",
|
||||||
|
Self::Maokai => "Maokai",
|
||||||
|
Self::MasterYi => "MasterYi",
|
||||||
|
Self::MissFortune => "MissFortune",
|
||||||
|
Self::Mordekaiser => "Mordekaiser",
|
||||||
|
Self::Morgana => "Morgana",
|
||||||
|
Self::Nami => "Nami",
|
||||||
|
Self::Nasus => "Nasus",
|
||||||
|
Self::Nautilus => "Nautilus",
|
||||||
|
Self::Neeko => "Neeko",
|
||||||
|
Self::Nidalee => "Nidalee",
|
||||||
|
Self::Nocturne => "Nocturne",
|
||||||
|
Self::NunuWillump => "Nunu",
|
||||||
|
Self::Olaf => "Olaf",
|
||||||
|
Self::Orianna => "Orianna",
|
||||||
|
Self::Ornn => "Ornn",
|
||||||
|
Self::Pantheon => "Pantheon",
|
||||||
|
Self::Poppy => "Poppy",
|
||||||
|
Self::Pyke => "Pyke",
|
||||||
|
Self::Qiyana => "Qiyana",
|
||||||
|
Self::Quinn => "Quinn",
|
||||||
|
Self::Rakan => "Rakan",
|
||||||
|
Self::Rammus => "Rammus",
|
||||||
|
Self::RekSai => "RekSai",
|
||||||
|
Self::Rell => "Rell",
|
||||||
|
Self::Renekton => "Renekton",
|
||||||
|
Self::Rengar => "Rengar",
|
||||||
|
Self::Riven => "Riven",
|
||||||
|
Self::Rumble => "Rumble",
|
||||||
|
Self::Ryze => "Ryze",
|
||||||
|
Self::Samira => "Samira",
|
||||||
|
Self::Sejuani => "Sejuani",
|
||||||
|
Self::Senna => "Senna",
|
||||||
|
Self::Seraphine => "Seraphine",
|
||||||
|
Self::Sett => "Sett",
|
||||||
|
Self::Shaco => "Shaco",
|
||||||
|
Self::Shen => "Shen",
|
||||||
|
Self::Shyvana => "Shyvana",
|
||||||
|
Self::Singed => "Singed",
|
||||||
|
Self::Sion => "Sion",
|
||||||
|
Self::Sivir => "Sivir",
|
||||||
|
Self::Skarner => "Skarner",
|
||||||
|
Self::Sona => "Sona",
|
||||||
|
Self::Soraka => "Soraka",
|
||||||
|
Self::Swain => "Swain",
|
||||||
|
Self::Sylas => "Sylas",
|
||||||
|
Self::Syndra => "Syndra",
|
||||||
|
Self::TahmKench => "TahmKench",
|
||||||
|
Self::Taliyah => "Taliyah",
|
||||||
|
Self::Talon => "Talon",
|
||||||
|
Self::Taric => "Taric",
|
||||||
|
Self::Teemo => "Teemo",
|
||||||
|
Self::Thresh => "Thresh",
|
||||||
|
Self::Tristana => "Tristana",
|
||||||
|
Self::Trundle => "Trundle",
|
||||||
|
Self::Tryndamere => "Tryndamere",
|
||||||
|
Self::TwistedFate => "TwistedFate",
|
||||||
|
Self::Twitch => "Twitch",
|
||||||
|
Self::Udyr => "Udyr",
|
||||||
|
Self::Urgot => "Urgot",
|
||||||
|
Self::Varus => "Varus",
|
||||||
|
Self::Vayne => "Vayne",
|
||||||
|
Self::Veigar => "Veigar",
|
||||||
|
Self::VelKoz => "Velkoz",
|
||||||
|
Self::Vex => "Vex",
|
||||||
|
Self::Vi => "Vi",
|
||||||
|
Self::Viego => "Viego",
|
||||||
|
Self::Viktor => "Viktor",
|
||||||
|
Self::Vladimir => "Vladimir",
|
||||||
|
Self::Volibear => "Volibear",
|
||||||
|
Self::Warwick => "Warwick",
|
||||||
|
Self::Wukong => "MonkeyKing",
|
||||||
|
Self::Xayah => "Xayah",
|
||||||
|
Self::Xerath => "Xerath",
|
||||||
|
Self::XinZhao => "XinZhao",
|
||||||
|
Self::Yasuo => "Yasuo",
|
||||||
|
Self::Yone => "Yone",
|
||||||
|
Self::Yorick => "Yorick",
|
||||||
|
Self::Yuumi => "Yuumi",
|
||||||
|
Self::Zac => "Zac",
|
||||||
|
Self::Zed => "Zed",
|
||||||
|
Self::Ziggs => "Ziggs",
|
||||||
|
Self::Zilean => "Zilean",
|
||||||
|
Self::Zoe => "Zoe",
|
||||||
|
Self::Zyra => "Zyra",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
|
|
||||||
/// LoL and TFT rank divisions, I, II, III, IV, and (deprecated) V.
|
/// LoL and TFT rank divisions, I, II, III, IV, and (deprecated) V.
|
||||||
///
|
///
|
||||||
|
@ -12,38 +11,22 @@ use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||||
/// Repr'd as equivalent numeric values, (1, 2, 3, 4, 5).
|
/// Repr'd as equivalent numeric values, (1, 2, 3, 4, 5).
|
||||||
///
|
///
|
||||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator). Iterator excludes deprecated `Division::V`.
|
/// Implements [IntoEnumIterator](super::IntoEnumIterator). Iterator excludes deprecated `Division::V`.
|
||||||
#[derive(
|
#[derive(Debug, Copy, Clone)]
|
||||||
Debug,
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
Copy,
|
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||||
Clone,
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
Eq,
|
|
||||||
PartialEq,
|
|
||||||
Hash,
|
|
||||||
EnumString,
|
|
||||||
Display,
|
|
||||||
AsRefStr,
|
|
||||||
IntoStaticStr,
|
|
||||||
IntoPrimitive,
|
|
||||||
TryFromPrimitive,
|
|
||||||
Serialize,
|
|
||||||
Deserialize,
|
|
||||||
)]
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Division {
|
pub enum Division {
|
||||||
/// Division 1, the best/highest division in a [`Tier`](crate::consts::Tier), or the only division in
|
|
||||||
/// [apex tiers](crate::consts::Tier::is_apex).
|
|
||||||
I = 1,
|
I = 1,
|
||||||
/// Division 2, the second highest division.
|
|
||||||
II = 2,
|
II = 2,
|
||||||
/// Division 3, the third highest division.
|
|
||||||
III = 3,
|
III = 3,
|
||||||
/// Division 4, the fourth and lowest division since 2019.
|
|
||||||
IV = 4,
|
IV = 4,
|
||||||
/// Division 5, the lowest division, only used before 2019.
|
|
||||||
#[deprecated(note="Removed for 2019.")]
|
#[deprecated(note="Removed for 2019.")]
|
||||||
V = 5,
|
V = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serde_string!(Division);
|
||||||
|
|
||||||
/// Returns a DoubleEndedIterator of I, II, III, IV.
|
/// Returns a DoubleEndedIterator of I, II, III, IV.
|
||||||
/// Ordered from high rank (I) to low (IV).
|
/// Ordered from high rank (I) to low (IV).
|
||||||
/// Excludes V, which is deprecated.
|
/// Excludes V, which is deprecated.
|
|
@ -1,5 +1,4 @@
|
||||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
///////////////////////////////////////////////
|
||||||
///////////////////////////////////////////////
|
|
||||||
// //
|
// //
|
||||||
// ! //
|
// ! //
|
||||||
// This file is automatically generated! //
|
// This file is automatically generated! //
|
||||||
|
@ -7,20 +6,16 @@
|
||||||
// //
|
// //
|
||||||
///////////////////////////////////////////////
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
use strum_macros::{ EnumString, EnumVariantNames, IntoStaticStr };
|
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
|
|
||||||
/// League of Legends game mode, such as Classic,
|
/// League of Legends game mode, such as Classic,
|
||||||
/// ARAM, URF, One For All, Ascension, etc.
|
/// ARAM, URF, One For All, Ascension, etc.
|
||||||
#[non_exhaustive]
|
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
#[derive(EnumString, EnumVariantNames, IntoStaticStr)]
|
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum GameMode {
|
pub enum GameMode {
|
||||||
/// Catch-all variant for new, unknown game modes.
|
|
||||||
#[strum(default)]
|
|
||||||
UNKNOWN(String),
|
|
||||||
|
|
||||||
/// ARAM games
|
/// ARAM games
|
||||||
ARAM,
|
ARAM,
|
||||||
/// All Random Summoner's Rift games
|
/// All Random Summoner's Rift games
|
||||||
|
@ -29,8 +24,6 @@ pub enum GameMode {
|
||||||
ASCENSION,
|
ASCENSION,
|
||||||
/// Blood Hunt Assassin games
|
/// Blood Hunt Assassin games
|
||||||
ASSASSINATE,
|
ASSASSINATE,
|
||||||
/// 2v2v2v2
|
|
||||||
CHERRY,
|
|
||||||
/// Classic Summoner's Rift and Twisted Treeline games
|
/// Classic Summoner's Rift and Twisted Treeline games
|
||||||
CLASSIC,
|
CLASSIC,
|
||||||
/// Dark Star: Singularity games
|
/// Dark Star: Singularity games
|
||||||
|
@ -39,11 +32,11 @@ pub enum GameMode {
|
||||||
DOOMBOTSTEEMO,
|
DOOMBOTSTEEMO,
|
||||||
/// Snowdown Showdown games
|
/// Snowdown Showdown games
|
||||||
FIRSTBLOOD,
|
FIRSTBLOOD,
|
||||||
/// Nexus Blitz games
|
/// Nexus Blitz games, deprecated in patch 9.2 in favor of gameMode NEXUSBLITZ.
|
||||||
GAMEMODEX,
|
GAMEMODEX,
|
||||||
/// Legend of the Poro King games
|
/// Legend of the Poro King games
|
||||||
KINGPORO,
|
KINGPORO,
|
||||||
/// Nexus Blitz games
|
/// Nexus Blitz games.
|
||||||
NEXUSBLITZ,
|
NEXUSBLITZ,
|
||||||
/// Dominion/Crystal Scar games
|
/// Dominion/Crystal Scar games
|
||||||
ODIN,
|
ODIN,
|
||||||
|
@ -51,16 +44,12 @@ pub enum GameMode {
|
||||||
ODYSSEY,
|
ODYSSEY,
|
||||||
/// One for All games
|
/// One for All games
|
||||||
ONEFORALL,
|
ONEFORALL,
|
||||||
/// Practice tool training games.
|
|
||||||
PRACTICETOOL,
|
|
||||||
/// PROJECT: Hunters games
|
/// PROJECT: Hunters games
|
||||||
PROJECT,
|
PROJECT,
|
||||||
/// Nexus Siege games
|
/// Nexus Siege games
|
||||||
SIEGE,
|
SIEGE,
|
||||||
/// Star Guardian Invasion games
|
/// Star Guardian Invasion games
|
||||||
STARGUARDIAN,
|
STARGUARDIAN,
|
||||||
/// Teamfight Tactics, used in `spectator-v4` endpoints.
|
|
||||||
TFT,
|
|
||||||
/// Tutorial games
|
/// Tutorial games
|
||||||
TUTORIAL,
|
TUTORIAL,
|
||||||
/// Tutorial: Welcome to League.
|
/// Tutorial: Welcome to League.
|
||||||
|
@ -69,10 +58,10 @@ pub enum GameMode {
|
||||||
TUTORIAL_MODULE_2,
|
TUTORIAL_MODULE_2,
|
||||||
/// Tutorial: Shop for Gear.
|
/// Tutorial: Shop for Gear.
|
||||||
TUTORIAL_MODULE_3,
|
TUTORIAL_MODULE_3,
|
||||||
/// Ultimate Spellbook games
|
/// Ultimate Spellbook games.
|
||||||
ULTBOOK,
|
ULTBOOK,
|
||||||
/// URF games
|
/// URF games
|
||||||
URF,
|
URF,
|
||||||
}
|
}
|
||||||
|
|
||||||
serde_strum_unknown!(GameMode);
|
serde_string!(GameMode);
|
|
@ -1,5 +1,4 @@
|
||||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
///////////////////////////////////////////////
|
||||||
///////////////////////////////////////////////
|
|
||||||
// //
|
// //
|
||||||
// ! //
|
// ! //
|
||||||
// This file is automatically generated! //
|
// This file is automatically generated! //
|
||||||
|
@ -7,29 +6,20 @@
|
||||||
// //
|
// //
|
||||||
///////////////////////////////////////////////
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
use serde::{ Serialize, Deserialize };
|
|
||||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
|
|
||||||
/// League of Legends game type: matched game, custom game, or tutorial game.
|
/// League of Legends game type: matched game, custom game, or tutorial game.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum GameType {
|
pub enum GameType {
|
||||||
/// Custom games
|
/// Custom games
|
||||||
#[strum(to_string = "CUSTOM_GAME", serialize = "CUSTOM")]
|
|
||||||
#[serde(alias = "CUSTOM")]
|
|
||||||
CUSTOM_GAME,
|
CUSTOM_GAME,
|
||||||
/// all other games
|
/// all other games
|
||||||
#[strum(to_string = "MATCHED_GAME", serialize = "MATCHED")]
|
|
||||||
#[serde(alias = "MATCHED")]
|
|
||||||
MATCHED_GAME,
|
MATCHED_GAME,
|
||||||
/// Tutorial games
|
/// Tutorial games
|
||||||
#[strum(to_string = "TUTORIAL_GAME", serialize = "TUTORIAL")]
|
|
||||||
#[serde(alias = "TUTORIAL")]
|
|
||||||
TUTORIAL_GAME,
|
TUTORIAL_GAME,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
serde_string!(GameType);
|
||||||
mod test;
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// ! //
|
||||||
|
// This file is automatically generated! //
|
||||||
|
// Do not directly edit! //
|
||||||
|
// //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
||||||
|
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||||
|
|
||||||
|
/// League of Legends maps.
|
||||||
|
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||||
|
#[derive(Serialize_repr, Deserialize_repr)]
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Map {
|
||||||
|
/// Summoner's Rift
|
||||||
|
/// Original Summer variant
|
||||||
|
SUMMONERS_RIFT_ORIGINAL_SUMMER_VARIANT = 1,
|
||||||
|
/// Summoner's Rift
|
||||||
|
/// Original Autumn variant
|
||||||
|
SUMMONERS_RIFT_ORIGINAL_AUTUMN_VARIANT = 2,
|
||||||
|
/// The Proving Grounds
|
||||||
|
/// Tutorial Map
|
||||||
|
THE_PROVING_GROUNDS = 3,
|
||||||
|
/// Twisted Treeline
|
||||||
|
/// Original Version
|
||||||
|
TWISTED_TREELINE_ORIGINAL_VERSION = 4,
|
||||||
|
/// The Crystal Scar
|
||||||
|
/// Dominion map
|
||||||
|
THE_CRYSTAL_SCAR = 8,
|
||||||
|
/// Twisted Treeline
|
||||||
|
/// Last TT map
|
||||||
|
TWISTED_TREELINE = 10,
|
||||||
|
/// Summoner's Rift
|
||||||
|
/// Current Version
|
||||||
|
SUMMONERS_RIFT = 11,
|
||||||
|
/// Howling Abyss
|
||||||
|
/// ARAM map
|
||||||
|
HOWLING_ABYSS = 12,
|
||||||
|
/// Butcher's Bridge
|
||||||
|
/// Alternate ARAM map
|
||||||
|
BUTCHERS_BRIDGE = 14,
|
||||||
|
/// Cosmic Ruins
|
||||||
|
/// Dark Star: Singularity map
|
||||||
|
COSMIC_RUINS = 16,
|
||||||
|
/// Valoran City Park
|
||||||
|
/// Star Guardian Invasion map
|
||||||
|
VALORAN_CITY_PARK = 18,
|
||||||
|
/// Substructure 43
|
||||||
|
/// PROJECT: Hunters map
|
||||||
|
SUBSTRUCTURE_43 = 19,
|
||||||
|
/// Crash Site
|
||||||
|
/// Odyssey: Extraction map
|
||||||
|
CRASH_SITE = 20,
|
||||||
|
/// Nexus Blitz
|
||||||
|
/// Nexus Blitz map
|
||||||
|
NEXUS_BLITZ = 21,
|
||||||
|
/// Convergence
|
||||||
|
/// Teamfight Tactics map
|
||||||
|
CONVERGENCE = 22,
|
||||||
|
}
|
|
@ -6,49 +6,41 @@
|
||||||
#![allow(deprecated)]
|
#![allow(deprecated)]
|
||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
mod macros;
|
mod macro_serde_string;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
mod champion;
|
mod champion;
|
||||||
pub use champion::*;
|
pub use champion::*;
|
||||||
|
|
||||||
mod division;
|
mod division;
|
||||||
pub use division::*;
|
pub use division::*;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
mod game_mode;
|
mod game_mode;
|
||||||
pub use game_mode::*;
|
pub use game_mode::*;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
mod game_type;
|
mod game_type;
|
||||||
pub use game_type::*;
|
pub use game_type::*;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
mod map;
|
mod map;
|
||||||
pub use map::*;
|
pub use map::*;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
mod queue_type;
|
mod queue_type;
|
||||||
pub use queue_type::*;
|
pub use queue_type::*;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
mod queue;
|
mod queue;
|
||||||
pub use queue::*;
|
pub use queue::*;
|
||||||
|
|
||||||
pub mod ranks;
|
pub mod ranks;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
mod region;
|
||||||
mod route;
|
pub use region::*;
|
||||||
pub use route::*;
|
|
||||||
|
|
||||||
mod route_ext;
|
|
||||||
pub use route_ext::*;
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
mod season;
|
mod season;
|
||||||
pub use season::*;
|
pub use season::*;
|
||||||
|
|
||||||
/// Trait allowing iteration of enum types, implemented by several enums in this module.
|
/// Trait allowing iteration of enum types, implemented by several enums in this module.
|
||||||
/// Re-exported from strum.
|
/// Re-exported from strum.
|
||||||
|
///
|
||||||
|
///
|
||||||
pub use strum::IntoEnumIterator;
|
pub use strum::IntoEnumIterator;
|
||||||
|
|
||||||
mod team;
|
mod team;
|
|
@ -0,0 +1,252 @@
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// ! //
|
||||||
|
// This file is automatically generated! //
|
||||||
|
// Do not directly edit! //
|
||||||
|
// //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
||||||
|
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||||
|
|
||||||
|
/// League of Legends matchmaking queue.
|
||||||
|
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
#[derive(Serialize_repr, Deserialize_repr)]
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum Queue {
|
||||||
|
/// Games on Custom games
|
||||||
|
CUSTOM = 0,
|
||||||
|
/// 5v5 Blind Pick games on Summoner's Rift
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 430
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 430")]
|
||||||
|
SUMMONERS_RIFT_5V5_BLIND_PICK_DEPRECATED_2 = 2,
|
||||||
|
/// 5v5 Ranked Solo games on Summoner's Rift
|
||||||
|
/// Deprecated in favor of queueId 420
|
||||||
|
#[deprecated(note="Deprecated in favor of queueId 420")]
|
||||||
|
SUMMONERS_RIFT_5V5_RANKED_SOLO_DEPRECATED_4 = 4,
|
||||||
|
/// 5v5 Ranked Premade games on Summoner's Rift
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
SUMMONERS_RIFT_5V5_RANKED_PREMADE = 6,
|
||||||
|
/// Co-op vs AI games on Summoner's Rift
|
||||||
|
/// Deprecated in favor of queueId 32 and 33
|
||||||
|
#[deprecated(note="Deprecated in favor of queueId 32 and 33")]
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI = 7,
|
||||||
|
/// 3v3 Normal games on Twisted Treeline
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 460
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 460")]
|
||||||
|
TWISTED_TREELINE_3V3_NORMAL = 8,
|
||||||
|
/// 3v3 Ranked Flex games on Twisted Treeline
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 470
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 470")]
|
||||||
|
TWISTED_TREELINE_3V3_RANKED_FLEX_DEPRECATED_9 = 9,
|
||||||
|
/// 5v5 Draft Pick games on Summoner's Rift
|
||||||
|
/// Deprecated in favor of queueId 400
|
||||||
|
#[deprecated(note="Deprecated in favor of queueId 400")]
|
||||||
|
SUMMONERS_RIFT_5V5_DRAFT_PICK_DEPRECATED_14 = 14,
|
||||||
|
/// 5v5 Dominion Blind Pick games on Crystal Scar
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
CRYSTAL_SCAR_5V5_DOMINION_BLIND_PICK = 16,
|
||||||
|
/// 5v5 Dominion Draft Pick games on Crystal Scar
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
CRYSTAL_SCAR_5V5_DOMINION_DRAFT_PICK = 17,
|
||||||
|
/// Dominion Co-op vs AI games on Crystal Scar
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
CRYSTAL_SCAR_DOMINION_CO_OP_VS_AI = 25,
|
||||||
|
/// Co-op vs AI Intro Bot games on Summoner's Rift
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 830
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 830")]
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_INTRO_BOT_DEPRECATED_31 = 31,
|
||||||
|
/// Co-op vs AI Beginner Bot games on Summoner's Rift
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 840
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 840")]
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_BEGINNER_BOT_DEPRECATED_32 = 32,
|
||||||
|
/// Co-op vs AI Intermediate Bot games on Summoner's Rift
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 850
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 850")]
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_INTERMEDIATE_BOT_DEPRECATED_33 = 33,
|
||||||
|
/// 3v3 Ranked Team games on Twisted Treeline
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
TWISTED_TREELINE_3V3_RANKED_TEAM = 41,
|
||||||
|
/// 5v5 Ranked Team games on Summoner's Rift
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
SUMMONERS_RIFT_5V5_RANKED_TEAM = 42,
|
||||||
|
/// Co-op vs AI games on Twisted Treeline
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 800
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 800")]
|
||||||
|
TWISTED_TREELINE_CO_OP_VS_AI = 52,
|
||||||
|
/// 5v5 Team Builder games on Summoner's Rift
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
SUMMONERS_RIFT_5V5_TEAM_BUILDER = 61,
|
||||||
|
/// 5v5 ARAM games on Howling Abyss
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 450
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 450")]
|
||||||
|
HOWLING_ABYSS_5V5_ARAM_DEPRECATED_65 = 65,
|
||||||
|
/// ARAM Co-op vs AI games on Howling Abyss
|
||||||
|
/// Game mode deprecated
|
||||||
|
#[deprecated(note="Game mode deprecated")]
|
||||||
|
HOWLING_ABYSS_ARAM_CO_OP_VS_AI = 67,
|
||||||
|
/// One for All games on Summoner's Rift
|
||||||
|
/// Deprecated in patch 8.6 in favor of queueId 1020
|
||||||
|
#[deprecated(note="Deprecated in patch 8.6 in favor of queueId 1020")]
|
||||||
|
SUMMONERS_RIFT_ONE_FOR_ALL_DEPRECATED_70 = 70,
|
||||||
|
/// 1v1 Snowdown Showdown games on Howling Abyss
|
||||||
|
HOWLING_ABYSS_1V1_SNOWDOWN_SHOWDOWN = 72,
|
||||||
|
/// 2v2 Snowdown Showdown games on Howling Abyss
|
||||||
|
HOWLING_ABYSS_2V2_SNOWDOWN_SHOWDOWN = 73,
|
||||||
|
/// 6v6 Hexakill games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_6V6_HEXAKILL = 75,
|
||||||
|
/// Ultra Rapid Fire games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_ULTRA_RAPID_FIRE = 76,
|
||||||
|
/// One For All: Mirror Mode games on Howling Abyss
|
||||||
|
HOWLING_ABYSS_ONE_FOR_ALL_MIRROR_MODE = 78,
|
||||||
|
/// Co-op vs AI Ultra Rapid Fire games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_ULTRA_RAPID_FIRE = 83,
|
||||||
|
/// Doom Bots Rank 1 games on Summoner's Rift
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 950
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
||||||
|
SUMMONERS_RIFT_DOOM_BOTS_RANK_1 = 91,
|
||||||
|
/// Doom Bots Rank 2 games on Summoner's Rift
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 950
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
||||||
|
SUMMONERS_RIFT_DOOM_BOTS_RANK_2 = 92,
|
||||||
|
/// Doom Bots Rank 5 games on Summoner's Rift
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 950
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
||||||
|
SUMMONERS_RIFT_DOOM_BOTS_RANK_5 = 93,
|
||||||
|
/// Ascension games on Crystal Scar
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 910
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 910")]
|
||||||
|
CRYSTAL_SCAR_ASCENSION_DEPRECATED_96 = 96,
|
||||||
|
/// 6v6 Hexakill games on Twisted Treeline
|
||||||
|
TWISTED_TREELINE_6V6_HEXAKILL = 98,
|
||||||
|
/// 5v5 ARAM games on Butcher's Bridge
|
||||||
|
BUTCHERS_BRIDGE_5V5_ARAM = 100,
|
||||||
|
/// Legend of the Poro King games on Howling Abyss
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 920
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 920")]
|
||||||
|
HOWLING_ABYSS_LEGEND_OF_THE_PORO_KING_DEPRECATED_300 = 300,
|
||||||
|
/// Nemesis games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_NEMESIS = 310,
|
||||||
|
/// Black Market Brawlers games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_BLACK_MARKET_BRAWLERS = 313,
|
||||||
|
/// Nexus Siege games on Summoner's Rift
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 940
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 940")]
|
||||||
|
SUMMONERS_RIFT_NEXUS_SIEGE_DEPRECATED_315 = 315,
|
||||||
|
/// Definitely Not Dominion games on Crystal Scar
|
||||||
|
CRYSTAL_SCAR_DEFINITELY_NOT_DOMINION = 317,
|
||||||
|
/// ARURF games on Summoner's Rift
|
||||||
|
/// Deprecated in patch 7.19 in favor of queueId 900
|
||||||
|
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 900")]
|
||||||
|
SUMMONERS_RIFT_ARURF = 318,
|
||||||
|
/// All Random games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_ALL_RANDOM = 325,
|
||||||
|
/// 5v5 Draft Pick games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_5V5_DRAFT_PICK = 400,
|
||||||
|
/// 5v5 Ranked Dynamic games on Summoner's Rift
|
||||||
|
/// Game mode deprecated in patch 6.22
|
||||||
|
#[deprecated(note="Game mode deprecated in patch 6.22")]
|
||||||
|
SUMMONERS_RIFT_5V5_RANKED_DYNAMIC = 410,
|
||||||
|
/// 5v5 Ranked Solo games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_5V5_RANKED_SOLO = 420,
|
||||||
|
/// 5v5 Blind Pick games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_5V5_BLIND_PICK = 430,
|
||||||
|
/// 5v5 Ranked Flex games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_5V5_RANKED_FLEX = 440,
|
||||||
|
/// 5v5 ARAM games on Howling Abyss
|
||||||
|
HOWLING_ABYSS_5V5_ARAM = 450,
|
||||||
|
/// 3v3 Blind Pick games on Twisted Treeline
|
||||||
|
/// Deprecated in patch 9.23
|
||||||
|
#[deprecated(note="Deprecated in patch 9.23")]
|
||||||
|
TWISTED_TREELINE_3V3_BLIND_PICK = 460,
|
||||||
|
/// 3v3 Ranked Flex games on Twisted Treeline
|
||||||
|
/// Deprecated in patch 9.23
|
||||||
|
#[deprecated(note="Deprecated in patch 9.23")]
|
||||||
|
TWISTED_TREELINE_3V3_RANKED_FLEX_DEPRECATED_470 = 470,
|
||||||
|
/// Blood Hunt Assassin games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_BLOOD_HUNT_ASSASSIN = 600,
|
||||||
|
/// Dark Star: Singularity games on Cosmic Ruins
|
||||||
|
COSMIC_RUINS_DARK_STAR_SINGULARITY = 610,
|
||||||
|
/// Clash games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_CLASH = 700,
|
||||||
|
/// Co-op vs. AI Intermediate Bot games on Twisted Treeline
|
||||||
|
/// Deprecated in patch 9.23
|
||||||
|
#[deprecated(note="Deprecated in patch 9.23")]
|
||||||
|
TWISTED_TREELINE_CO_OP_VS_AI_INTERMEDIATE_BOT = 800,
|
||||||
|
/// Co-op vs. AI Intro Bot games on Twisted Treeline
|
||||||
|
/// Deprecated in patch 9.23
|
||||||
|
#[deprecated(note="Deprecated in patch 9.23")]
|
||||||
|
TWISTED_TREELINE_CO_OP_VS_AI_INTRO_BOT = 810,
|
||||||
|
/// Co-op vs. AI Beginner Bot games on Twisted Treeline
|
||||||
|
TWISTED_TREELINE_CO_OP_VS_AI_BEGINNER_BOT = 820,
|
||||||
|
/// Co-op vs. AI Intro Bot games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_INTRO_BOT = 830,
|
||||||
|
/// Co-op vs. AI Beginner Bot games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_BEGINNER_BOT = 840,
|
||||||
|
/// Co-op vs. AI Intermediate Bot games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_CO_OP_VS_AI_INTERMEDIATE_BOT = 850,
|
||||||
|
/// URF games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_URF = 900,
|
||||||
|
/// Ascension games on Crystal Scar
|
||||||
|
CRYSTAL_SCAR_ASCENSION = 910,
|
||||||
|
/// Legend of the Poro King games on Howling Abyss
|
||||||
|
HOWLING_ABYSS_LEGEND_OF_THE_PORO_KING = 920,
|
||||||
|
/// Nexus Siege games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_NEXUS_SIEGE = 940,
|
||||||
|
/// Doom Bots Voting games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_DOOM_BOTS_VOTING = 950,
|
||||||
|
/// Doom Bots Standard games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_DOOM_BOTS_STANDARD = 960,
|
||||||
|
/// Star Guardian Invasion: Normal games on Valoran City Park
|
||||||
|
VALORAN_CITY_PARK_STAR_GUARDIAN_INVASION_NORMAL = 980,
|
||||||
|
/// Star Guardian Invasion: Onslaught games on Valoran City Park
|
||||||
|
VALORAN_CITY_PARK_STAR_GUARDIAN_INVASION_ONSLAUGHT = 990,
|
||||||
|
/// PROJECT: Hunters games on Overcharge
|
||||||
|
OVERCHARGE_PROJECT_HUNTERS = 1000,
|
||||||
|
/// Snow ARURF games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_SNOW_ARURF = 1010,
|
||||||
|
/// One for All games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_ONE_FOR_ALL = 1020,
|
||||||
|
/// Odyssey Extraction: Intro games on Crash Site
|
||||||
|
CRASH_SITE_ODYSSEY_EXTRACTION_INTRO = 1030,
|
||||||
|
/// Odyssey Extraction: Cadet games on Crash Site
|
||||||
|
CRASH_SITE_ODYSSEY_EXTRACTION_CADET = 1040,
|
||||||
|
/// Odyssey Extraction: Crewmember games on Crash Site
|
||||||
|
CRASH_SITE_ODYSSEY_EXTRACTION_CREWMEMBER = 1050,
|
||||||
|
/// Odyssey Extraction: Captain games on Crash Site
|
||||||
|
CRASH_SITE_ODYSSEY_EXTRACTION_CAPTAIN = 1060,
|
||||||
|
/// Odyssey Extraction: Onslaught games on Crash Site
|
||||||
|
CRASH_SITE_ODYSSEY_EXTRACTION_ONSLAUGHT = 1070,
|
||||||
|
/// Teamfight Tactics games on Convergence
|
||||||
|
CONVERGENCE_TEAMFIGHT_TACTICS = 1090,
|
||||||
|
/// Ranked Teamfight Tactics games on Convergence
|
||||||
|
CONVERGENCE_RANKED_TEAMFIGHT_TACTICS = 1100,
|
||||||
|
/// Teamfight Tactics Tutorial games on Convergence
|
||||||
|
CONVERGENCE_TEAMFIGHT_TACTICS_TUTORIAL = 1110,
|
||||||
|
/// Teamfight Tactics 1v0 testing games on Convergence
|
||||||
|
CONVERGENCE_TEAMFIGHT_TACTICS_1V0_TESTING = 1111,
|
||||||
|
/// Nexus Blitz games on Nexus Blitz
|
||||||
|
/// Deprecated in patch 9.2 in favor of queueId 1300
|
||||||
|
#[deprecated(note="Deprecated in patch 9.2 in favor of queueId 1300")]
|
||||||
|
NEXUS_BLITZ_NEXUS_BLITZ_DEPRECATED_1200 = 1200,
|
||||||
|
/// Nexus Blitz games on Nexus Blitz
|
||||||
|
NEXUS_BLITZ_NEXUS_BLITZ = 1300,
|
||||||
|
/// Ultimate Spellbook games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_ULTIMATE_SPELLBOOK = 1400,
|
||||||
|
/// Tutorial 1 games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_TUTORIAL_1 = 2000,
|
||||||
|
/// Tutorial 2 games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_TUTORIAL_2 = 2010,
|
||||||
|
/// Tutorial 3 games on Summoner's Rift
|
||||||
|
SUMMONERS_RIFT_TUTORIAL_3 = 2020,
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
|
|
||||||
|
/// LoL or TFT ranked queue types.
|
||||||
|
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
|
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||||
|
pub enum QueueType {
|
||||||
|
// League of Legends, Summoner's Rift (5v5), Ranked Solo Queue.
|
||||||
|
RANKED_SOLO_5x5,
|
||||||
|
// League of Legends, Summoner's Rift (5v5), Flex Queue.
|
||||||
|
RANKED_FLEX_SR,
|
||||||
|
// League of Legends, Twisted Treeline (3v3), Flex Queue.
|
||||||
|
RANKED_FLEX_TT,
|
||||||
|
// Ranked Teamfight Tactics.
|
||||||
|
RANKED_TFT,
|
||||||
|
// Ranked Teamfight Tactics, Hyper Roll gamemode.
|
||||||
|
RANKED_TFT_TURBO,
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_string!(QueueType);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_as_ref() {
|
||||||
|
assert_eq!("RANKED_SOLO_5x5", QueueType::RANKED_SOLO_5x5.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_to_string() {
|
||||||
|
assert_eq!("RANKED_SOLO_5x5", QueueType::RANKED_SOLO_5x5.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_from_string() {
|
||||||
|
assert_eq!(Some(QueueType::RANKED_SOLO_5x5), "RANKED_SOLO_5x5".parse().ok());
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ use std::iter::Peekable;
|
||||||
|
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use super::{Division, Tier};
|
use super::{ Tier, Division };
|
||||||
|
|
||||||
/// (Tier, Division) tuple representing a rank.
|
/// (Tier, Division) tuple representing a rank.
|
||||||
pub type Rank = ( Tier, Division );
|
pub type Rank = ( Tier, Division );
|
||||||
|
@ -20,7 +20,8 @@ impl Iterator for Iter {
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
// First find the tier (innermost loop).
|
// First find the tier (innermost loop).
|
||||||
// If none found, we go to next tier (in unwrap_or_else case).
|
// If none found, we go to next tier (in unwrap_or_else case).
|
||||||
let div = self.div_iter.next().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.
|
// If no divisions available, go to next tier, reset the divisions, and return I.
|
||||||
self.tier_iter.next();
|
self.tier_iter.next();
|
||||||
self.div_iter = Division::iter();
|
self.div_iter = Division::iter();
|
||||||
|
@ -58,14 +59,14 @@ pub fn non_apex_iter() -> Iter {
|
||||||
tier_iter.next();
|
tier_iter.next();
|
||||||
}
|
}
|
||||||
Iter {
|
Iter {
|
||||||
tier_iter,
|
tier_iter: tier_iter,
|
||||||
div_iter: Division::iter(),
|
div_iter: Division::iter(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Division, Tier};
|
use super::{ Tier, Division };
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn iter() {
|
fn iter() {
|
||||||
|
@ -83,6 +84,7 @@ mod tests {
|
||||||
assert_eq!(None, it.next());
|
assert_eq!(None, it.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn non_apex_iter() {
|
fn non_apex_iter() {
|
||||||
let mut it = super::non_apex_iter();
|
let mut it = super::non_apex_iter();
|
||||||
|
@ -90,12 +92,12 @@ mod tests {
|
||||||
assert_eq!(Some((Tier::DIAMOND, Division::II)), it.next());
|
assert_eq!(Some((Tier::DIAMOND, Division::II)), it.next());
|
||||||
assert_eq!(Some((Tier::DIAMOND, Division::III)), it.next());
|
assert_eq!(Some((Tier::DIAMOND, Division::III)), it.next());
|
||||||
assert_eq!(Some((Tier::DIAMOND, Division::IV)), it.next());
|
assert_eq!(Some((Tier::DIAMOND, Division::IV)), it.next());
|
||||||
assert_eq!(Some((Tier::EMERALD, Division::I)), it.next());
|
|
||||||
assert_eq!(Some((Tier::EMERALD, Division::II)), it.next());
|
|
||||||
assert_eq!(Some((Tier::EMERALD, Division::III)), it.next());
|
|
||||||
assert_eq!(Some((Tier::EMERALD, Division::IV)), it.next());
|
|
||||||
assert_eq!(Some((Tier::PLATINUM, Division::I)), it.next());
|
assert_eq!(Some((Tier::PLATINUM, Division::I)), it.next());
|
||||||
let 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!(Some((Tier::IRON, Division::IV)), last);
|
||||||
|
assert_eq!(None, it.next());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
|
|
||||||
|
/// A region served by a single game server.
|
||||||
|
/// Each Riot Games API request is directed at a particular region,
|
||||||
|
/// with tournament API requests directed at the AMERICAS "global" region.
|
||||||
|
///
|
||||||
|
/// Valorant regions are prefixed with `VAL_` due to the name collision with
|
||||||
|
/// `BR` ("BR1") for LoL and `BR` ("BR") for Valorant.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Region {
|
||||||
|
#[strum(to_string="BR1", serialize="BR")]
|
||||||
|
BR,
|
||||||
|
#[strum(to_string="EUN1", serialize="EUNE")]
|
||||||
|
EUNE,
|
||||||
|
#[strum(to_string="EUW1", serialize="EUW")]
|
||||||
|
EUW,
|
||||||
|
#[strum(to_string="NA1", serialize="NA")]
|
||||||
|
NA,
|
||||||
|
#[strum(to_string="KR")]
|
||||||
|
KR,
|
||||||
|
#[strum(to_string="LA1", serialize="LAN")]
|
||||||
|
LAN,
|
||||||
|
#[strum(to_string="LA2", serialize="LAS")]
|
||||||
|
LAS,
|
||||||
|
#[strum(to_string="OC1", serialize="OCE")]
|
||||||
|
OCE,
|
||||||
|
#[strum(to_string="RU")]
|
||||||
|
RU,
|
||||||
|
#[strum(to_string="TR1", serialize="TR")]
|
||||||
|
TR,
|
||||||
|
#[strum(to_string="JP1", serialize="JP")]
|
||||||
|
JP,
|
||||||
|
#[strum(to_string="PBE1", serialize="PBE")]
|
||||||
|
PBE,
|
||||||
|
#[strum(to_string="AMERICAS")]
|
||||||
|
AMERICAS,
|
||||||
|
#[strum(to_string="EUROPE")]
|
||||||
|
EUROPE,
|
||||||
|
#[strum(to_string="ASIA")]
|
||||||
|
ASIA,
|
||||||
|
|
||||||
|
// Problem: serializing and deserializing these will result in a different
|
||||||
|
// enum picked, due to naming collision.
|
||||||
|
#[strum(to_string="AP")]
|
||||||
|
VAL_AP,
|
||||||
|
#[strum(to_string="BR")]
|
||||||
|
VAL_BR,
|
||||||
|
#[strum(to_string="EU")]
|
||||||
|
VAL_EU,
|
||||||
|
#[strum(to_string="KR")]
|
||||||
|
VAL_KR,
|
||||||
|
#[strum(to_string="LATAM")]
|
||||||
|
VAL_LATAM,
|
||||||
|
#[strum(to_string="NA")]
|
||||||
|
VAL_NA,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic() {
|
||||||
|
assert_eq!("BR1", Region::BR.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get() {
|
||||||
|
assert_eq!(Ok(Region::JP), "JP".parse());
|
||||||
|
assert_eq!(Ok(Region::NA), "NA1".parse());
|
||||||
|
assert!("LA".parse::<Region>().is_err());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// ! //
|
||||||
|
// This file is automatically generated! //
|
||||||
|
// Do not directly edit! //
|
||||||
|
// //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
||||||
|
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||||
|
|
||||||
|
/// League of Legends matchmaking seasons.
|
||||||
|
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||||
|
#[derive(Serialize_repr, Deserialize_repr)]
|
||||||
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Season {
|
||||||
|
PRESEASON_3 = 0,
|
||||||
|
SEASON_3 = 1,
|
||||||
|
PRESEASON_2014 = 2,
|
||||||
|
SEASON_2014 = 3,
|
||||||
|
PRESEASON_2015 = 4,
|
||||||
|
SEASON_2015 = 5,
|
||||||
|
PRESEASON_2016 = 6,
|
||||||
|
SEASON_2016 = 7,
|
||||||
|
PRESEASON_2017 = 8,
|
||||||
|
SEASON_2017 = 9,
|
||||||
|
PRESEASON_2018 = 10,
|
||||||
|
SEASON_2018 = 11,
|
||||||
|
PRESEASON_2019 = 12,
|
||||||
|
SEASON_2019 = 13,
|
||||||
|
PRESEASON_2020 = 14,
|
||||||
|
SEASON_2020 = 15,
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||||
|
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
|
|
||||||
/// LoL and TFT ranked tiers, such as gold, diamond, challenger, etc.
|
/// LoL and TFT ranked tiers, such as gold, diamond, challenger, etc.
|
||||||
///
|
///
|
||||||
|
@ -10,24 +9,10 @@ use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||||
/// Repr'd as arbitrary `u8` values.
|
/// Repr'd as arbitrary `u8` values.
|
||||||
///
|
///
|
||||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator).
|
/// Implements [IntoEnumIterator](super::IntoEnumIterator).
|
||||||
#[derive(
|
#[derive(Debug, Copy, Clone)]
|
||||||
Debug,
|
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||||
Copy,
|
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||||
Clone,
|
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||||
Eq,
|
|
||||||
PartialEq,
|
|
||||||
Hash,
|
|
||||||
PartialOrd,
|
|
||||||
Ord,
|
|
||||||
IntoPrimitive,
|
|
||||||
TryFromPrimitive,
|
|
||||||
EnumString,
|
|
||||||
Display,
|
|
||||||
AsRefStr,
|
|
||||||
IntoStaticStr,
|
|
||||||
Serialize,
|
|
||||||
Deserialize,
|
|
||||||
)]
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Tier {
|
pub enum Tier {
|
||||||
/// Challenger, the highest tier, an apex tier. Repr: `220_u8`.
|
/// Challenger, the highest tier, an apex tier. Repr: `220_u8`.
|
||||||
|
@ -38,8 +23,6 @@ pub enum Tier {
|
||||||
MASTER = 180,
|
MASTER = 180,
|
||||||
/// Diamond, the higest non-apex tier. Repr: `140_u8`.
|
/// Diamond, the higest non-apex tier. Repr: `140_u8`.
|
||||||
DIAMOND = 140,
|
DIAMOND = 140,
|
||||||
/// Emerald. Added in 2023. Repr: `130_u8`.
|
|
||||||
EMERALD = 130,
|
|
||||||
/// Platinum. Repr: `120_u8`.
|
/// Platinum. Repr: `120_u8`.
|
||||||
PLATINUM = 120,
|
PLATINUM = 120,
|
||||||
/// Gold. Repr: `100_u8`.
|
/// Gold. Repr: `100_u8`.
|
||||||
|
@ -52,14 +35,14 @@ pub enum Tier {
|
||||||
IRON = 40,
|
IRON = 40,
|
||||||
|
|
||||||
/// Unranked, no tier. Repr: `0_u8`.
|
/// Unranked, no tier. Repr: `0_u8`.
|
||||||
/// Also deserializes from "NONE" returned by `lol-challenges-v1.getChallengePercentiles`.
|
|
||||||
#[serde(alias = "NONE")]
|
|
||||||
UNRANKED = 0,
|
UNRANKED = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serde_string!(Tier);
|
||||||
|
|
||||||
impl Tier {
|
impl Tier {
|
||||||
/// If this tier is an apex tier: [`Self::MASTER`], [`Self::GRANDMASTER`],
|
/// If this tier is an apex tier: master, grandmaster, or challenger.
|
||||||
/// or [`Self::CHALLENGER`]. Returns false for [`Self::UNRANKED`].
|
/// Returns false for unranked.
|
||||||
///
|
///
|
||||||
/// These tiers are NOT queryable by LeagueV4Endpoints::get_league_entries(...).
|
/// These tiers are NOT queryable by LeagueV4Endpoints::get_league_entries(...).
|
||||||
pub const fn is_apex(self) -> bool {
|
pub const fn is_apex(self) -> bool {
|
||||||
|
@ -70,7 +53,7 @@ impl Tier {
|
||||||
/// If this tier is a "standard" tier: iron through diamond.
|
/// If this tier is a "standard" tier: iron through diamond.
|
||||||
/// Returns false for unranked.
|
/// Returns false for unranked.
|
||||||
///
|
///
|
||||||
/// ONLY these tiers are queryable by [`LeagueV4::get_league_entries(...)`](crate::endpoints::LeagueV4::get_league_entries).
|
/// ONLY these tiers are queryable by LeagueV4Endpoints::get_league_entries(...).
|
||||||
pub fn is_standard(self) -> bool {
|
pub fn is_standard(self) -> bool {
|
||||||
// Casts needed for const.
|
// Casts needed for const.
|
||||||
((Self::UNRANKED as u8) < (self as u8)) && ((self as u8) < (Self::MASTER as u8))
|
((Self::UNRANKED as u8) < (self as u8)) && ((self as u8) < (Self::MASTER as u8))
|
||||||
|
@ -92,11 +75,7 @@ impl Tier {
|
||||||
|
|
||||||
/// Converts UNRANKED to None and all ranked tiers to Some(...).
|
/// Converts UNRANKED to None and all ranked tiers to Some(...).
|
||||||
pub fn to_ranked(self) -> Option<Self> {
|
pub fn to_ranked(self) -> Option<Self> {
|
||||||
if self.is_unranked() {
|
if self.is_unranked() { None } else { Some(self) }
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,19 +86,10 @@ impl IntoEnumIterator for Tier {
|
||||||
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
||||||
fn iter() -> Self::Iterator {
|
fn iter() -> Self::Iterator {
|
||||||
[
|
[
|
||||||
Self::CHALLENGER,
|
Self::CHALLENGER, Self::GRANDMASTER, Self::MASTER,
|
||||||
Self::GRANDMASTER,
|
Self::DIAMOND, Self::PLATINUM, Self::GOLD,
|
||||||
Self::MASTER,
|
Self::SILVER, Self::BRONZE, Self::IRON
|
||||||
Self::DIAMOND,
|
].iter().copied()
|
||||||
Self::EMERALD,
|
|
||||||
Self::PLATINUM,
|
|
||||||
Self::GOLD,
|
|
||||||
Self::SILVER,
|
|
||||||
Self::BRONZE,
|
|
||||||
Self::IRON,
|
|
||||||
]
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +161,6 @@ mod tests {
|
||||||
iter.next();
|
iter.next();
|
||||||
iter.next();
|
iter.next();
|
||||||
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
||||||
assert_eq!(Some(Tier::EMERALD), iter.next());
|
|
||||||
iter.next();
|
iter.next();
|
||||||
iter.next();
|
iter.next();
|
||||||
iter.next();
|
iter.next();
|
||||||
|
@ -206,7 +175,6 @@ mod tests {
|
||||||
iter.next();
|
iter.next();
|
||||||
iter.next();
|
iter.next();
|
||||||
iter.next();
|
iter.next();
|
||||||
assert_eq!(Some(Tier::EMERALD), iter.next());
|
|
||||||
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
||||||
iter.next();
|
iter.next();
|
||||||
iter.next();
|
iter.next();
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,4 @@
|
||||||
|
use std::error::Error as StdError;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use reqwest::{ Error, Response, StatusCode };
|
use reqwest::{ Error, Response, StatusCode };
|
||||||
|
@ -17,17 +18,12 @@ pub struct RiotApiError {
|
||||||
status_code: Option<StatusCode>,
|
status_code: Option<StatusCode>,
|
||||||
}
|
}
|
||||||
impl RiotApiError {
|
impl RiotApiError {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(reqwest_error: Error, retries: u8, response: Option<Response>, status_code: Option<StatusCode>) -> Self {
|
||||||
reqwest_error: Error,
|
|
||||||
retries: u8,
|
|
||||||
response: Option<Response>,
|
|
||||||
status_code: Option<StatusCode>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
reqwest_error,
|
reqwest_error: reqwest_error,
|
||||||
retries,
|
retries: retries,
|
||||||
response,
|
response: response,
|
||||||
status_code,
|
status_code: status_code,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// The reqwest::Error for the final failed request.
|
/// The reqwest::Error for the final failed request.
|
||||||
|
@ -39,17 +35,11 @@ impl RiotApiError {
|
||||||
self.retries
|
self.retries
|
||||||
}
|
}
|
||||||
/// The failed response.
|
/// The failed response.
|
||||||
/// `Some(&reqwest::Response)` if the request was sent and failed.
|
/// `Some(reqwest::Response)` if the request was sent and failed.
|
||||||
/// `None` if the request was not sent, OR if parsing the response JSON failed.
|
/// `None` if the request was not sent, OR if parsing the response JSON failed.
|
||||||
pub fn response(&self) -> Option<&Response> {
|
pub fn response(&self) -> Option<&Response> {
|
||||||
self.response.as_ref()
|
self.response.as_ref()
|
||||||
}
|
}
|
||||||
/// The failed response.
|
|
||||||
/// `Some(reqwest::Response)` if the request was sent and failed.
|
|
||||||
/// `None` if the request was not sent, OR if parsing the response JSON failed.
|
|
||||||
pub fn take_response(&mut self) -> Option<Response> {
|
|
||||||
self.response.take()
|
|
||||||
}
|
|
||||||
/// The failed response's HTTP status code.
|
/// The failed response's HTTP status code.
|
||||||
/// `Some(reqwest::StatusCode)` if the request was sent and failed, OR if parsing the response JSON failed.
|
/// `Some(reqwest::StatusCode)` if the request was sent and failed, OR if parsing the response JSON failed.
|
||||||
/// `None` if the request was not sent.
|
/// `None` if the request was not sent.
|
||||||
|
@ -62,8 +52,8 @@ impl fmt::Display for RiotApiError {
|
||||||
write!(f, "{:#?}", self)
|
write!(f, "{:#?}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::error::Error for RiotApiError {
|
impl StdError for RiotApiError {
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
Some(&self.reqwest_error)
|
Some(&self.reqwest_error)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
|
||||||
///////////////////////////////////////////////
|
///////////////////////////////////////////////
|
||||||
// //
|
// //
|
||||||
// ! //
|
// ! //
|
||||||
|
@ -8,7 +7,6 @@
|
||||||
///////////////////////////////////////////////
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![deny(missing_docs)]
|
|
||||||
|
|
||||||
//! <h1 align="center">
|
//! <h1 align="center">
|
||||||
//! Riven<br>
|
//! Riven<br>
|
||||||
|
@ -17,7 +15,7 @@
|
||||||
//! <a href="https://github.com/MingweiSamuel/Riven/"><img src="https://cdn.communitydragon.org/latest/champion/Riven/square" width="20" height="20" alt="Riven Github"></a>
|
//! <a href="https://github.com/MingweiSamuel/Riven/"><img src="https://cdn.communitydragon.org/latest/champion/Riven/square" width="20" height="20" alt="Riven Github"></a>
|
||||||
//! <a href="https://crates.io/crates/riven"><img src="https://img.shields.io/crates/v/riven?style=flat-square&logo=rust" alt="Crates.io"></a>
|
//! <a href="https://crates.io/crates/riven"><img src="https://img.shields.io/crates/v/riven?style=flat-square&logo=rust" alt="Crates.io"></a>
|
||||||
//! <a href="https://docs.rs/riven/"><img src="https://img.shields.io/badge/docs.rs-Riven-blue?style=flat-square&logo=read-the-docs&logoColor=white" alt="Docs.rs"></a>
|
//! <a href="https://docs.rs/riven/"><img src="https://img.shields.io/badge/docs.rs-Riven-blue?style=flat-square&logo=read-the-docs&logoColor=white" alt="Docs.rs"></a>
|
||||||
//! <!--<a href="https://travis-ci.com/MingweiSamuel/Riven"><img src="https://img.shields.io/travis/com/mingweisamuel/riven?style=flat-square" alt="Travis CI"></a>-->
|
//! <a href="https://travis-ci.com/MingweiSamuel/Riven"><img src="https://img.shields.io/travis/com/mingweisamuel/riven?style=flat-square" alt="Travis CI"></a>
|
||||||
//! <a href="https://github.com/rust-secure-code/safety-dance/"><img src="https://img.shields.io/badge/unsafe-forbidden-green.svg?style=flat-square" alt="unsafe forbidden"></a>
|
//! <a href="https://github.com/rust-secure-code/safety-dance/"><img src="https://img.shields.io/badge/unsafe-forbidden-green.svg?style=flat-square" alt="unsafe forbidden"></a>
|
||||||
//! </p>
|
//! </p>
|
||||||
//!
|
//!
|
||||||
|
@ -27,28 +25,29 @@
|
||||||
//! Data structs and endpoints are automatically generated from the
|
//! Data structs and endpoints are automatically generated from the
|
||||||
//! [Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)).
|
//! [Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)).
|
||||||
//!
|
//!
|
||||||
//! # Design
|
//! ## Design
|
||||||
//!
|
//!
|
||||||
//! * Fast, asynchronous, thread-safe.
|
//! * Fast, asynchronous, thread-safe.
|
||||||
//! * Automatically retries failed requests.
|
//! * Automatically retries failed requests.
|
||||||
//! * Supports all endpoints, kept up-to-date using [riotapi-schema](https://github.com/MingweiSamuel/riotapi-schema).
|
//! * TFT API Support.
|
||||||
//!
|
//!
|
||||||
//! # Usage
|
//! ## Usage
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use riven::RiotApi;
|
//! use riven::RiotApi;
|
||||||
//! use riven::consts::PlatformRoute;
|
//! use riven::consts::Region;
|
||||||
//!
|
//!
|
||||||
//! // Enter tokio async runtime.
|
//! // Enter tokio async runtime.
|
||||||
//! let rt = tokio::runtime::Runtime::new().unwrap();
|
//! let mut rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
//! rt.block_on(async {
|
//! rt.block_on(async {
|
||||||
//! // Create RiotApi instance from key string.
|
//! // Create RiotApi instance from key string.
|
||||||
//! let api_key = std::env!("RGAPI_KEY"); // "RGAPI-01234567-89ab-cdef-0123-456789abcdef";
|
//! let api_key = "RGAPI-01234567-89ab-cdef-0123-456789abcdef";
|
||||||
//! let riot_api = RiotApi::new(api_key);
|
//! # /* (doc testing) */ let api_key = std::env!("RGAPI_KEY");
|
||||||
|
//! let riot_api = RiotApi::with_key(api_key);
|
||||||
//!
|
//!
|
||||||
//! // Get summoner data.
|
//! // Get summoner data.
|
||||||
//! let summoner = riot_api.summoner_v4()
|
//! let summoner = riot_api.summoner_v4()
|
||||||
//! .get_by_summoner_name(PlatformRoute::NA1, "잘 못").await
|
//! .get_by_summoner_name(Region::NA, "잘 못").await
|
||||||
//! .expect("Get summoner failed.")
|
//! .expect("Get summoner failed.")
|
||||||
//! .expect("There is no summoner with that name.");
|
//! .expect("There is no summoner with that name.");
|
||||||
//!
|
//!
|
||||||
|
@ -57,13 +56,13 @@
|
||||||
//!
|
//!
|
||||||
//! // Get champion mastery data.
|
//! // Get champion mastery data.
|
||||||
//! let masteries = riot_api.champion_mastery_v4()
|
//! let masteries = riot_api.champion_mastery_v4()
|
||||||
//! .get_all_champion_masteries_by_puuid(PlatformRoute::NA1, &summoner.puuid).await
|
//! .get_all_champion_masteries(Region::NA, &summoner.id).await
|
||||||
//! .expect("Get champion masteries failed.");
|
//! .expect("Get champion masteries failed.");
|
||||||
//!
|
//!
|
||||||
//! // Print champion masteries.
|
//! // Print champioon masteries.
|
||||||
//! for (i, mastery) in masteries.iter().take(10).enumerate() {
|
//! for (i, mastery) in masteries.iter().take(10).enumerate() {
|
||||||
//! println!("{: >2}) {: <9} {: >7} ({})", i + 1,
|
//! println!("{: >2}) {: <9} {: >7} ({})", i + 1,
|
||||||
//! mastery.champion_id.name().unwrap_or("UNKNOWN"),
|
//! mastery.champion_id.to_string(),
|
||||||
//! mastery.champion_points, mastery.champion_level);
|
//! mastery.champion_points, mastery.champion_level);
|
||||||
//! }
|
//! }
|
||||||
//! });
|
//! });
|
||||||
|
@ -71,50 +70,29 @@
|
||||||
//! Output:
|
//! Output:
|
||||||
//! ```text
|
//! ```text
|
||||||
//! 잘 못 Champion Masteries:
|
//! 잘 못 Champion Masteries:
|
||||||
//! 1) Riven 1236866 (7)
|
//! 1) Riven 1219895 (7)
|
||||||
//! 2) Fiora 230679 (5)
|
//! 2) Fiora 229714 (5)
|
||||||
//! 3) Katarina 175985 (5)
|
//! 3) Katarina 175985 (5)
|
||||||
//! 4) Lee Sin 156070 (7)
|
//! 4) Lee Sin 150546 (7)
|
||||||
//! 5) Jax 102662 (5)
|
//! 5) Jax 100509 (5)
|
||||||
//! 6) Gnar 76373 (6)
|
//! 6) Gnar 76373 (6)
|
||||||
//! 7) Kai'Sa 64271 (5)
|
//! 7) Kai'Sa 64271 (5)
|
||||||
//! 8) Caitlyn 46614 (5)
|
//! 8) Caitlyn 46479 (5)
|
||||||
//! 9) Irelia 46465 (5)
|
//! 9) Irelia 46465 (5)
|
||||||
//! 10) Vladimir 37176 (5)
|
//! 10) Vladimir 37176 (5)
|
||||||
//! ```
|
//! ```
|
||||||
//! The [`RiotApi` struct documentation](https://docs.rs/riven/latest/riven/struct.RiotApi.html)
|
|
||||||
//! contains additional usage information. The [tests](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/tests)
|
|
||||||
//! and [example proxy](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/examples/proxy)
|
|
||||||
//! provide more example usage.
|
|
||||||
//!
|
|
||||||
//! ## Feature Flags
|
|
||||||
//!
|
//!
|
||||||
//! ### Nightly vs Stable
|
//! ### Nightly vs Stable
|
||||||
//!
|
//!
|
||||||
//! Enable the `nightly` feature to use nightly-only functionality. 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).
|
//! [nightly optimizations in the `parking_lot` crate](https://github.com/Amanieu/parking_lot#nightly-vs-stable).
|
||||||
|
//! Also required for running async integration tests.
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ### Docs
|
||||||
//! riven = { version = "...", features = [ "nightly" ] }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ### rustls
|
|
||||||
//!
|
|
||||||
//! Riven uses [reqwest](https://github.com/seanmonstar/reqwest) for making requests. By default, reqwest uses the native TLS library.
|
|
||||||
//! If you prefer using [rustls](https://github.com/ctz/rustls) you can do so by turning off the Riven default features
|
|
||||||
//! and specifying the `rustls-tls` feature:
|
|
||||||
//!
|
|
||||||
//! ```toml
|
|
||||||
//! riven = { version = "...", default-features = false, features = [ "rustls-tls" ] }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Riven is additionally able to produce [tracing](https://docs.rs/tracing) spans for requests if the `tracing` feature is enabled. This feature is disabled by default.
|
|
||||||
//!
|
|
||||||
//! ## Docs
|
|
||||||
//!
|
//!
|
||||||
//! [On docs.rs](https://docs.rs/riven/).
|
//! [On docs.rs](https://docs.rs/riven/).
|
||||||
//!
|
//!
|
||||||
//! ## Error Handling
|
//! ### Error Handling
|
||||||
//!
|
//!
|
||||||
//! Riven returns either `Result<T>` or `Result<Option<T>>` within futures.
|
//! Riven returns either `Result<T>` or `Result<Option<T>>` within futures.
|
||||||
//!
|
//!
|
||||||
|
@ -133,12 +111,12 @@
|
||||||
//! attempted, and the Reqwest `Response` object.
|
//! attempted, and the Reqwest `Response` object.
|
||||||
//!
|
//!
|
||||||
//! You can configure the number of time Riven retries using
|
//! You can configure the number of time Riven retries using
|
||||||
//! `RiotApiConfig::set_retries(...)` and the `RiotApi::from_config(config)`
|
//! `RiotApiConfig::set_retries(...)` and the `RiotApi::with_config(config)`
|
||||||
//! constructor. By default, Riven retries up to 3 times (4 requests total).
|
//! constructor. By default, Riven retries up to 3 times (4 requests total).
|
||||||
//! Some errors, such as 400 client errors, are not retried as they would
|
//! Some errors, such as 400 client errors, are not retried as they would
|
||||||
//! inevitably fail again.
|
//! inevitably fail again.
|
||||||
//!
|
//!
|
||||||
//! ## Semantic Versioning
|
//! ### Semantic Versioning
|
||||||
//!
|
//!
|
||||||
//! This package follows semantic versioning to an extent. However, the Riot API
|
//! This package follows semantic versioning to an extent. However, the Riot API
|
||||||
//! itself changes often and does not follow semantic versioning, which makes
|
//! itself changes often and does not follow semantic versioning, which makes
|
||||||
|
@ -154,36 +132,35 @@
|
||||||
//! Parts of Riven that do not depend on Riot API changes do follow semantic
|
//! Parts of Riven that do not depend on Riot API changes do follow semantic
|
||||||
//! versioning.
|
//! versioning.
|
||||||
//!
|
//!
|
||||||
//! ## Additional Help
|
//! ### Additional Help
|
||||||
//!
|
//!
|
||||||
//! Feel free to [make an issue](https://github.com/MingweiSamuel/Riven/issues/new)
|
//! Feel free to [make an issue](https://github.com/MingweiSamuel/Riven/issues/new)
|
||||||
//! if you are have any questions or trouble with Riven.
|
//! if you are have any questions or trouble with Riven.
|
||||||
//!
|
//!
|
||||||
//! # Development
|
//! ## Development
|
||||||
//!
|
//!
|
||||||
//! NodeJS is used to generate code for Riven. The
|
//! NodeJS is used to generate code for Riven. The
|
||||||
//! [`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)
|
//! folder contains the code and [doT.js](https://olado.github.io/doT/index.html)
|
||||||
//! templates. `index.js` lists the JSON files downloaded and used to generate the
|
//! templates. `index.js` lists the JSON files downloaded and used to generate the
|
||||||
//! code.
|
//! code.
|
||||||
//!
|
//!
|
||||||
//! To set up the srcgen, you will first need to install NodeJS. Then enter the
|
//! To set up the srcgen, you will first need to install NodeJS. Then enter the
|
||||||
//! `riven/srcgen` folder and run `npm ci` (or `npm install`) to install
|
//! srcgen folder and run `npm ci` (or `npm install`) to install dependencies.
|
||||||
//! dependencies.
|
|
||||||
//!
|
//!
|
||||||
//! To run the srcgen use `node riven/srcgen` from the repository root.
|
//! To run the srcgen use `node srcgen` from the main folder.
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
|
|
||||||
// Re-exported reqwest types.
|
// Re-exported reqwest types.
|
||||||
pub use reqwest;
|
pub use reqwest;
|
||||||
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
pub use config::RiotApiConfig;
|
pub use config::RiotApiConfig;
|
||||||
|
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
pub mod endpoints;
|
pub mod endpoints;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
|
@ -191,9 +168,7 @@ pub use error::*;
|
||||||
|
|
||||||
pub mod meta;
|
pub mod meta;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
pub mod models;
|
pub mod models;
|
||||||
mod models_impls;
|
|
||||||
|
|
||||||
mod req;
|
mod req;
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// ! //
|
||||||
|
// This file is automatically generated! //
|
||||||
|
// Do not directly edit! //
|
||||||
|
// //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
||||||
|
// Version 1f61128ea79f3d07bd47c2f585e9f22905bed753
|
||||||
|
|
||||||
|
//! Metadata about the Riot API and Riven.
|
||||||
|
//!
|
||||||
|
//! Note: this modules is automatically generated.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref ENDPOINT_PATH_METHODID: HashMap<&'static str, &'static str> = {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert("/riot/account/v1/accounts/by-puuid/{puuid}", "account-v1.getByPuuid");
|
||||||
|
map.insert("/riot/account/v1/accounts/by-riot-id/{gameName}/{tagLine}", "account-v1.getByRiotId");
|
||||||
|
map.insert("/riot/account/v1/active-shards/by-game/{game}/by-puuid/{puuid}", "account-v1.getActiveShard");
|
||||||
|
map.insert("/lol/champion-mastery/v4/champion-masteries/by-summoner/{encryptedSummonerId}", "champion-mastery-v4.getAllChampionMasteries");
|
||||||
|
map.insert("/lol/champion-mastery/v4/champion-masteries/by-summoner/{encryptedSummonerId}/by-champion/{championId}", "champion-mastery-v4.getChampionMastery");
|
||||||
|
map.insert("/lol/champion-mastery/v4/scores/by-summoner/{encryptedSummonerId}", "champion-mastery-v4.getChampionMasteryScore");
|
||||||
|
map.insert("/lol/platform/v3/champion-rotations", "champion-v3.getChampionInfo");
|
||||||
|
map.insert("/lol/clash/v1/players/by-summoner/{summonerId}", "clash-v1.getPlayersBySummoner");
|
||||||
|
map.insert("/lol/clash/v1/teams/{teamId}", "clash-v1.getTeamById");
|
||||||
|
map.insert("/lol/clash/v1/tournaments", "clash-v1.getTournaments");
|
||||||
|
map.insert("/lol/clash/v1/tournaments/by-team/{teamId}", "clash-v1.getTournamentByTeam");
|
||||||
|
map.insert("/lol/clash/v1/tournaments/{tournamentId}", "clash-v1.getTournamentById");
|
||||||
|
map.insert("/lol/league-exp/v4/entries/{queue}/{tier}/{division}", "league-exp-v4.getLeagueEntries");
|
||||||
|
map.insert("/lol/league/v4/challengerleagues/by-queue/{queue}", "league-v4.getChallengerLeague");
|
||||||
|
map.insert("/lol/league/v4/entries/by-summoner/{encryptedSummonerId}", "league-v4.getLeagueEntriesForSummoner");
|
||||||
|
map.insert("/lol/league/v4/entries/{queue}/{tier}/{division}", "league-v4.getLeagueEntries");
|
||||||
|
map.insert("/lol/league/v4/grandmasterleagues/by-queue/{queue}", "league-v4.getGrandmasterLeague");
|
||||||
|
map.insert("/lol/league/v4/leagues/{leagueId}", "league-v4.getLeagueById");
|
||||||
|
map.insert("/lol/league/v4/masterleagues/by-queue/{queue}", "league-v4.getMasterLeague");
|
||||||
|
map.insert("/lol/status/v3/shard-data", "lol-status-v3.getShardData");
|
||||||
|
map.insert("/lol/status/v4/platform-data", "lol-status-v4.getPlatformData");
|
||||||
|
map.insert("/lor/match/v1/matches/by-puuid/{puuid}/ids", "lor-match-v1.getMatchIdsByPUUID");
|
||||||
|
map.insert("/lor/match/v1/matches/{matchId}", "lor-match-v1.getMatch");
|
||||||
|
map.insert("/lor/ranked/v1/leaderboards", "lor-ranked-v1.getLeaderboards");
|
||||||
|
map.insert("/lor/status/v1/platform-data", "lor-status-v1.getPlatformData");
|
||||||
|
map.insert("/lol/match/v5/matches/by-puuid/{puuid}/ids", "match-v5.getMatchIdsByPUUID");
|
||||||
|
map.insert("/lol/match/v5/matches/{matchId}", "match-v5.getMatch");
|
||||||
|
map.insert("/lol/match/v5/matches/{matchId}/timeline", "match-v5.getTimeline");
|
||||||
|
map.insert("/lol/spectator/v4/active-games/by-summoner/{encryptedSummonerId}", "spectator-v4.getCurrentGameInfoBySummoner");
|
||||||
|
map.insert("/lol/spectator/v4/featured-games", "spectator-v4.getFeaturedGames");
|
||||||
|
map.insert("/lol/summoner/v4/summoners/by-account/{encryptedAccountId}", "summoner-v4.getByAccountId");
|
||||||
|
map.insert("/lol/summoner/v4/summoners/by-name/{summonerName}", "summoner-v4.getBySummonerName");
|
||||||
|
map.insert("/lol/summoner/v4/summoners/by-puuid/{encryptedPUUID}", "summoner-v4.getByPUUID");
|
||||||
|
map.insert("/lol/summoner/v4/summoners/{encryptedSummonerId}", "summoner-v4.getBySummonerId");
|
||||||
|
map.insert("/tft/league/v1/challenger", "tft-league-v1.getChallengerLeague");
|
||||||
|
map.insert("/tft/league/v1/entries/by-summoner/{summonerId}", "tft-league-v1.getLeagueEntriesForSummoner");
|
||||||
|
map.insert("/tft/league/v1/entries/{tier}/{division}", "tft-league-v1.getLeagueEntries");
|
||||||
|
map.insert("/tft/league/v1/grandmaster", "tft-league-v1.getGrandmasterLeague");
|
||||||
|
map.insert("/tft/league/v1/leagues/{leagueId}", "tft-league-v1.getLeagueById");
|
||||||
|
map.insert("/tft/league/v1/master", "tft-league-v1.getMasterLeague");
|
||||||
|
map.insert("/tft/league/v1/rated-ladders/{queue}/top", "tft-league-v1.getTopRatedLadder");
|
||||||
|
map.insert("/tft/match/v1/matches/by-puuid/{puuid}/ids", "tft-match-v1.getMatchIdsByPUUID");
|
||||||
|
map.insert("/tft/match/v1/matches/{matchId}", "tft-match-v1.getMatch");
|
||||||
|
map.insert("/tft/summoner/v1/summoners/by-account/{encryptedAccountId}", "tft-summoner-v1.getByAccountId");
|
||||||
|
map.insert("/tft/summoner/v1/summoners/by-name/{summonerName}", "tft-summoner-v1.getBySummonerName");
|
||||||
|
map.insert("/tft/summoner/v1/summoners/by-puuid/{encryptedPUUID}", "tft-summoner-v1.getByPUUID");
|
||||||
|
map.insert("/tft/summoner/v1/summoners/{encryptedSummonerId}", "tft-summoner-v1.getBySummonerId");
|
||||||
|
map.insert("/lol/platform/v4/third-party-code/by-summoner/{encryptedSummonerId}", "third-party-code-v4.getThirdPartyCodeBySummonerId");
|
||||||
|
map.insert("/lol/tournament-stub/v4/lobby-events/by-code/{tournamentCode}", "tournament-stub-v4.getLobbyEventsByCode");
|
||||||
|
map.insert("/lol/tournament/v4/codes/{tournamentCode}", "tournament-v4.getTournamentCode");
|
||||||
|
map.insert("/lol/tournament/v4/lobby-events/by-code/{tournamentCode}", "tournament-v4.getLobbyEventsByCode");
|
||||||
|
map.insert("/val/content/v1/contents", "val-content-v1.getContent");
|
||||||
|
map.insert("/val/match/v1/matches/{matchId}", "val-match-v1.getMatch");
|
||||||
|
map.insert("/val/match/v1/matchlists/by-puuid/{puuid}", "val-match-v1.getMatchlist");
|
||||||
|
map.insert("/val/match/v1/recent-matches/by-queue/{queue}", "val-match-v1.getRecent");
|
||||||
|
map.insert("/val/ranked/v1/leaderboards/by-act/{actId}", "val-ranked-v1.getLeaderboard");
|
||||||
|
map.insert("/val/status/v1/platform-data", "val-status-v1.getPlatformData");
|
||||||
|
map
|
||||||
|
};
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -4,9 +4,9 @@ mod rate_limit;
|
||||||
pub use rate_limit::*;
|
pub use rate_limit::*;
|
||||||
|
|
||||||
mod rate_limit_type;
|
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;
|
mod token_bucket;
|
||||||
pub use token_bucket::*;
|
pub use token_bucket::*;
|
||||||
|
|
|
@ -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
|
#[derive(Copy, Clone)]
|
||||||
/// entire app (`Application`) or for a specific method (`Method`).
|
|
||||||
/// Method rate limit will handle service violations.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub enum RateLimitType {
|
pub enum RateLimitType {
|
||||||
Application,
|
Application,
|
||||||
Method,
|
Method,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RateLimitType {
|
impl RateLimitType {
|
||||||
pub 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 {
|
match self {
|
||||||
Self::Application => "X-App-Rate-Limit",
|
Self::Application => "X-App-Rate-Limit",
|
||||||
Self::Method => "X-Method-Rate-Limit",
|
Self::Method => "X-Method-Rate-Limit",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn count_header(self) -> &'static str {
|
pub fn count_header(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Application => "X-App-Rate-Limit-Count",
|
Self::Application => "X-App-Rate-Limit-Count",
|
||||||
Self::Method => "X-Method-Rate-Limit-Count",
|
Self::Method => "X-Method-Rate-Limit-Count",
|
|
@ -0,0 +1,111 @@
|
||||||
|
use std::future::Future;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use log;
|
||||||
|
use reqwest::{ Client, StatusCode, Url };
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
use crate::ResponseInfo;
|
||||||
|
use crate::RiotApiError;
|
||||||
|
use crate::RiotApiConfig;
|
||||||
|
use crate::util::InsertOnlyCHashMap;
|
||||||
|
|
||||||
|
use super::RateLimit;
|
||||||
|
use super::RateLimitType;
|
||||||
|
|
||||||
|
pub struct RegionalRequester {
|
||||||
|
/// The app rate limit.
|
||||||
|
app_rate_limit: RateLimit,
|
||||||
|
/// Method rate limits.
|
||||||
|
method_rate_limits: InsertOnlyCHashMap<&'static str, RateLimit>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegionalRequester {
|
||||||
|
/// Request header name for the Riot API key.
|
||||||
|
const RIOT_KEY_HEADER: &'static str = "X-Riot-Token";
|
||||||
|
|
||||||
|
/// HTTP status codes which are considered a success but will results in `None`.
|
||||||
|
const NONE_STATUS_CODES: [StatusCode; 2] = [
|
||||||
|
StatusCode::NO_CONTENT, // 204
|
||||||
|
StatusCode::NOT_FOUND, // 404
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
app_rate_limit: RateLimit::new(RateLimitType::Application),
|
||||||
|
method_rate_limits: InsertOnlyCHashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get<'a>(self: Arc<Self>,
|
||||||
|
config: &'a RiotApiConfig, client: &'a Client,
|
||||||
|
method_id: &'static str, region_platform: &'a str, path: String, query: Option<String>)
|
||||||
|
-> impl Future<Output = Result<ResponseInfo>> + 'a
|
||||||
|
{
|
||||||
|
async move {
|
||||||
|
#[cfg(feature = "nightly")] let query = query.as_deref();
|
||||||
|
#[cfg(not(feature = "nightly"))] let query = query.as_ref().map(|s| s.as_ref());
|
||||||
|
|
||||||
|
let mut retries: u8 = 0;
|
||||||
|
loop {
|
||||||
|
let method_rate_limit: Arc<RateLimit> = self.method_rate_limits
|
||||||
|
.get_or_insert_with(method_id, || RateLimit::new(RateLimitType::Method));
|
||||||
|
|
||||||
|
// Rate limiting.
|
||||||
|
while let Some(delay) = RateLimit::get_both_or_delay(&self.app_rate_limit, &*method_rate_limit) {
|
||||||
|
tokio::time::sleep(delay).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request.
|
||||||
|
let url_base = format!("https://{}.api.riotgames.com", region_platform);
|
||||||
|
let mut url = Url::parse(&*url_base)
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to parse url_base: \"{}\".", url_base));
|
||||||
|
url.set_path(&*path);
|
||||||
|
url.set_query(query);
|
||||||
|
|
||||||
|
let response = client.get(url)
|
||||||
|
.header(Self::RIOT_KEY_HEADER, &*config.api_key)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| RiotApiError::new(e, retries, None, None))?;
|
||||||
|
|
||||||
|
// Maybe update rate limits (based on response headers).
|
||||||
|
self.app_rate_limit.on_response(&config, &response);
|
||||||
|
method_rate_limit.on_response(&config, &response);
|
||||||
|
|
||||||
|
let status = response.status();
|
||||||
|
// Handle normal success / failure cases.
|
||||||
|
let status_none = Self::NONE_STATUS_CODES.contains(&status);
|
||||||
|
// Success case.
|
||||||
|
if status.is_success() || status_none {
|
||||||
|
log::trace!("Response {} (retried {} times), success, returning result.", status, retries);
|
||||||
|
break Ok(ResponseInfo {
|
||||||
|
response: response,
|
||||||
|
retries: retries,
|
||||||
|
status_none: status_none,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let err = response.error_for_status_ref().err().unwrap_or_else(
|
||||||
|
|| panic!("Unhandlable response status code, neither success nor failure: {}.", status));
|
||||||
|
// Failure, may or may not be retryable.
|
||||||
|
// Not-retryable: no more retries or 4xx or ? (3xx, redirects exceeded).
|
||||||
|
// Retryable: retries remaining, and 429 or 5xx.
|
||||||
|
if retries >= config.retries ||
|
||||||
|
(StatusCode::TOO_MANY_REQUESTS != status
|
||||||
|
&& !status.is_server_error())
|
||||||
|
{
|
||||||
|
log::debug!("Response {} (retried {} times), failure, returning error.", status, retries);
|
||||||
|
break Err(RiotApiError::new(err, retries, Some(response), Some(status)));
|
||||||
|
}
|
||||||
|
log::debug!("Response {} (retried {} times), retrying.", status, retries);
|
||||||
|
|
||||||
|
retries += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
// use super::*;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use parking_lot::{Mutex, MutexGuard};
|
use parking_lot::{Mutex, MutexGuard};
|
||||||
|
@ -39,16 +39,10 @@ pub trait TokenBucket {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VectorTokenBucket {
|
pub struct VectorTokenBucket {
|
||||||
/// The total limit supplied to the constructor, unadjusted by the [rate_usage_factor].
|
|
||||||
_given_total_limit: usize,
|
|
||||||
/// Additional factor to reduce rate limit usage, in range (0, 1\].
|
|
||||||
_rate_usage_factor: f32,
|
|
||||||
|
|
||||||
/// Duration of this TokenBucket.
|
/// Duration of this TokenBucket.
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
// Total tokens available from this TokenBucket.
|
// Total tokens available from this TokenBucket.
|
||||||
total_limit: usize,
|
total_limit: usize,
|
||||||
|
|
||||||
/// Extra duration to be considered on top of `duration`, to account for
|
/// Extra duration to be considered on top of `duration`, to account for
|
||||||
/// varying network latency.
|
/// varying network latency.
|
||||||
duration_overhead: Duration,
|
duration_overhead: Duration,
|
||||||
|
@ -58,53 +52,34 @@ pub struct VectorTokenBucket {
|
||||||
/// Limit allowed per burst_duration, for burst factor.
|
/// Limit allowed per burst_duration, for burst factor.
|
||||||
burst_limit: usize,
|
burst_limit: usize,
|
||||||
|
|
||||||
|
|
||||||
/// Record of timestamps (synchronized).
|
/// Record of timestamps (synchronized).
|
||||||
timestamps: Mutex<VecDeque<Instant>>,
|
timestamps: Mutex<VecDeque<Instant>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VectorTokenBucket {
|
impl VectorTokenBucket {
|
||||||
pub fn new(
|
pub fn new(duration: Duration, total_limit: usize,
|
||||||
duration: Duration,
|
duration_overhead: Duration, burst_pct: f32) -> Self
|
||||||
given_total_limit: usize,
|
{
|
||||||
duration_overhead: Duration,
|
debug_assert!(0.0 < burst_pct && burst_pct <= 1.0,
|
||||||
burst_factor: f32,
|
"BAD burst_pct {}.", burst_pct);
|
||||||
rate_usage_factor: f32,
|
|
||||||
) -> Self {
|
|
||||||
debug_assert!(
|
|
||||||
0.0 < rate_usage_factor && rate_usage_factor <= 1.0,
|
|
||||||
"BAD rate_usage_factor {}.",
|
|
||||||
rate_usage_factor
|
|
||||||
);
|
|
||||||
debug_assert!(
|
|
||||||
0.0 < burst_factor && burst_factor <= 1.0,
|
|
||||||
"BAD burst_factor {}.",
|
|
||||||
burst_factor
|
|
||||||
);
|
|
||||||
// Float ops may lose precision, but nothing should be that precise.
|
// Float ops may lose precision, but nothing should be that precise.
|
||||||
// API always uses round numbers, burst_factor is frac of 256.
|
// API always uses round numbers, burst_pct is frac of 256.
|
||||||
|
|
||||||
// Adjust everything by rate_usage_factor.
|
|
||||||
let total_limit = std::cmp::max(
|
|
||||||
1,
|
|
||||||
(given_total_limit as f32 * rate_usage_factor).floor() as usize,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Effective duration.
|
// Effective duration.
|
||||||
let d_eff = duration + duration_overhead;
|
let d_eff = duration + duration_overhead;
|
||||||
let burst_duration = d_eff.mul_f32(burst_factor);
|
let burst_duration = d_eff.mul_f32(burst_pct);
|
||||||
let burst_limit = std::cmp::max(1, (total_limit as f32 * burst_factor).floor() as usize);
|
let burst_limit = std::cmp::max(1,
|
||||||
|
(total_limit as f32 * burst_pct).floor() as usize);
|
||||||
debug_assert!(burst_limit <= total_limit);
|
debug_assert!(burst_limit <= total_limit);
|
||||||
|
|
||||||
VectorTokenBucket {
|
VectorTokenBucket {
|
||||||
_given_total_limit: given_total_limit,
|
duration: duration,
|
||||||
_rate_usage_factor: rate_usage_factor,
|
total_limit: total_limit,
|
||||||
|
duration_overhead: duration_overhead,
|
||||||
|
|
||||||
duration,
|
burst_duration: burst_duration,
|
||||||
total_limit,
|
burst_limit: burst_limit,
|
||||||
|
|
||||||
duration_overhead,
|
|
||||||
burst_duration,
|
|
||||||
burst_limit,
|
|
||||||
|
|
||||||
timestamps: Mutex::new(VecDeque::with_capacity(total_limit)),
|
timestamps: Mutex::new(VecDeque::with_capacity(total_limit)),
|
||||||
}
|
}
|
||||||
|
@ -113,32 +88,36 @@ impl VectorTokenBucket {
|
||||||
fn update_get_timestamps(&self) -> MutexGuard<VecDeque<Instant>> {
|
fn update_get_timestamps(&self) -> MutexGuard<VecDeque<Instant>> {
|
||||||
let mut timestamps = self.timestamps.lock();
|
let mut timestamps = self.timestamps.lock();
|
||||||
let cutoff = Instant::now() - self.duration - self.duration_overhead;
|
let cutoff = Instant::now() - self.duration - self.duration_overhead;
|
||||||
// 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) {
|
while timestamps.back().map_or(false, |ts| *ts < cutoff) {
|
||||||
timestamps.pop_back();
|
timestamps.pop_back();
|
||||||
}
|
}
|
||||||
timestamps
|
return timestamps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TokenBucket for VectorTokenBucket {
|
impl TokenBucket for VectorTokenBucket {
|
||||||
|
|
||||||
fn get_delay(&self) -> Option<Duration> {
|
fn get_delay(&self) -> Option<Duration> {
|
||||||
let timestamps = self.update_get_timestamps();
|
let timestamps = self.update_get_timestamps();
|
||||||
|
|
||||||
|
// The "?" means:
|
||||||
|
// `if timestamps.len() < self.total_limit { return None }`
|
||||||
|
// Timestamp that needs to be popped before
|
||||||
|
// we can enter another timestamp.
|
||||||
|
|
||||||
// Full rate limit.
|
// Full rate limit.
|
||||||
if let Some(ts) = timestamps.get(self.total_limit - 1) {
|
if let Some(ts) = timestamps.get(self.total_limit - 1) {
|
||||||
// Return amount of time needed for timestamp `ts` to go away.
|
// Return amount of time needed for timestamp `ts` to go away.
|
||||||
Instant::now()
|
Instant::now().checked_duration_since(*ts)
|
||||||
.checked_duration_since(*ts)
|
.and_then(|passed_dur| (self.duration + self.duration_overhead)
|
||||||
.and_then(|passed_dur| {
|
.checked_sub(passed_dur))
|
||||||
(self.duration + self.duration_overhead).checked_sub(passed_dur)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
// Otherwise burst rate limit.
|
// Otherwise burst rate limit.
|
||||||
else if let Some(ts) = timestamps.get(self.burst_limit - 1) {
|
else if let Some(ts) = timestamps.get(self.burst_limit - 1) {
|
||||||
// Return amount of time needed for timestamp `ts` to go away.
|
// Return amount of time needed for timestamp `ts` to go away.
|
||||||
Instant::now()
|
Instant::now().checked_duration_since(*ts)
|
||||||
.checked_duration_since(*ts)
|
|
||||||
.and_then(|passed_dur| self.burst_duration.checked_sub(passed_dur))
|
.and_then(|passed_dur| self.burst_duration.checked_sub(passed_dur))
|
||||||
}
|
}
|
||||||
// No delay needed.
|
// No delay needed.
|
||||||
|
@ -170,7 +149,7 @@ impl TokenBucket for VectorTokenBucket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bucket_duration(&self) -> Duration {
|
fn get_bucket_duration(&self) -> Duration {
|
||||||
|
@ -184,12 +163,6 @@ impl TokenBucket for VectorTokenBucket {
|
||||||
|
|
||||||
impl fmt::Debug for VectorTokenBucket {
|
impl fmt::Debug for VectorTokenBucket {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
write!(f, "({}/{}:{})", self.timestamps.lock().len(), self.total_limit, self.duration.as_secs())
|
||||||
f,
|
|
||||||
"({}/{}:{})",
|
|
||||||
self.timestamps.lock().len(),
|
|
||||||
self.total_limit,
|
|
||||||
self.duration.as_secs()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
#![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_95_burst() {
|
||||||
|
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 0.95);
|
||||||
|
|
||||||
|
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, *D00, 0.50);
|
||||||
|
|
||||||
|
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, *D00, 0.50);
|
||||||
|
|
||||||
|
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.5);
|
||||||
|
assert!(bucket.get_tokens(50), "Should have not violated limit. i=-1.");
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay. i=-1.");
|
||||||
|
for i in 0..20_000 {
|
||||||
|
Instant::advance_time(501);
|
||||||
|
assert!(bucket.get_tokens(50), "Should have not violated limit. i={}.", i);
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay. i={}.", i);
|
||||||
|
Instant::advance_time(501);
|
||||||
|
assert!(bucket.get_tokens(50), "Should have not violated limit. i={}.", i);
|
||||||
|
assert_ne!(None, bucket.get_delay(), "Bucket should have delay. i={}.", i);
|
||||||
|
}
|
||||||
|
assert!(bucket.timestamps.lock().len() < 110, "Should not memory leak.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
use std::future::Future;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use log;
|
||||||
|
use reqwest::Client;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
use crate::ResponseInfo;
|
||||||
|
use crate::RiotApiConfig;
|
||||||
|
use crate::RiotApiError;
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A future resolving to a `Result` containg either a `Option<T>` (success) or a `RiotApiError` (failure).
|
||||||
|
pub async fn get_optional<'a, T: serde::de::DeserializeOwned + 'a>(&'a self,
|
||||||
|
method_id: &'static str, region_platform: &'static str, path: String, query: Option<String>)
|
||||||
|
-> Result<Option<T>>
|
||||||
|
{
|
||||||
|
let rinfo = self.get_raw_response(method_id, region_platform, path, query).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 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.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A future resolving to a `Result` containg either a `T` (success) or a `RiotApiError` (failure).
|
||||||
|
pub async fn get<'a, T: serde::de::DeserializeOwned + 'a>(&'a self,
|
||||||
|
method_id: &'static str, region_platform: &'static str, path: String, query: Option<String>)
|
||||||
|
-> Result<T>
|
||||||
|
{
|
||||||
|
let rinfo = self.get_raw_response(method_id, region_platform, path, query).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 is not meant to be used directly.
|
||||||
|
///
|
||||||
|
/// This sends a GET 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, prepended to `.api.riotgames.com` to create the hostname.
|
||||||
|
/// * `path` - The path relative to the hostname.
|
||||||
|
/// * `query` - An optional query string.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A future resolving to a `Result` containg either a `ResponseInfo` (success) or a `RiotApiError` (failure).
|
||||||
|
pub fn get_raw_response<'a>(&'a self,
|
||||||
|
method_id: &'static str, region_platform: &'static str, path: String, query: Option<String>)
|
||||||
|
-> impl Future<Output = Result<ResponseInfo>> + '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]
|
#[inline]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
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]
|
#[inline]
|
||||||
pub fn get_or_insert_with<F: FnOnce() -> V>(&self, key: K, default: F) -> Arc<V> {
|
pub fn get_or_insert_with<F: FnOnce() -> V>(&self, key: K, default: F) -> Arc<V>
|
||||||
Arc::clone(
|
{
|
||||||
self.base
|
Arc::clone(self.base.lock()
|
||||||
.lock()
|
|
||||||
.entry(key)
|
.entry(key)
|
||||||
.or_insert_with(|| Arc::new(default())),
|
.or_insert_with(|| Arc::new(default())))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 }}"{{? name !== alias }}, 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 }}",
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,18 @@
|
||||||
{{
|
{{
|
||||||
const dotUtils = require('./dotUtils.js');
|
const dotUtils = require('./dotUtils.js');
|
||||||
const gameModes = require('./.gameModes.json');
|
const gameModes = require('./.gameModes.json');
|
||||||
}}{{= dotUtils.preamble() }}
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
use strum_macros::{ EnumString, EnumVariantNames, IntoStaticStr };
|
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
|
|
||||||
/// League of Legends game mode, such as Classic,
|
/// League of Legends game mode, such as Classic,
|
||||||
/// ARAM, URF, One For All, Ascension, etc.
|
/// ARAM, URF, One For All, Ascension, etc.
|
||||||
#[non_exhaustive]
|
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
#[derive(EnumString, EnumVariantNames, IntoStaticStr)]
|
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum GameMode {
|
pub enum GameMode {
|
||||||
/// Catch-all variant for new, unknown game modes.
|
|
||||||
#[strum(default)]
|
|
||||||
UNKNOWN(String),
|
|
||||||
|
|
||||||
{{
|
{{
|
||||||
for (const e of gameModes) {
|
for (const e of gameModes) {
|
||||||
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||||
|
@ -30,4 +26,4 @@ pub enum GameMode {
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
serde_strum_unknown!(GameMode);
|
serde_string!(GameMode);
|
|
@ -1,33 +1,27 @@
|
||||||
{{
|
{{
|
||||||
const dotUtils = require('./dotUtils.js');
|
const dotUtils = require('./dotUtils.js');
|
||||||
const gameTypes = require('./.gameTypes.json');
|
const gameTypes = require('./.gameTypes.json');
|
||||||
}}{{= dotUtils.preamble() }}
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
use serde::{ Serialize, Deserialize };
|
|
||||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||||
|
|
||||||
/// League of Legends game type: matched game, custom game, or tutorial game.
|
/// League of Legends game type: matched game, custom game, or tutorial game.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum GameType {
|
pub enum GameType {
|
||||||
{{
|
{{
|
||||||
for (const e of gameTypes) {
|
for (const e of gameTypes) {
|
||||||
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||||
const nameNoGame = e['x-name'].replace(/_GAME$/, "");
|
|
||||||
}}
|
}}
|
||||||
{{~ desc :line }}
|
{{~ desc :line }}
|
||||||
/// {{= line }}
|
/// {{= line }}
|
||||||
{{~}}
|
{{~}}
|
||||||
#[strum(to_string = "{{= e['x-name'] }}", serialize = "{{= nameNoGame }}")]
|
|
||||||
#[serde(alias = "{{= nameNoGame }}")]
|
|
||||||
{{= e['x-name'] }},
|
{{= e['x-name'] }},
|
||||||
{{
|
{{
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
serde_string!(GameType);
|
||||||
mod test;
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
{{
|
||||||
|
const dotUtils = require('./dotUtils.js');
|
||||||
|
const maps = require('./.maps.json');
|
||||||
|
}}{{= 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 e of maps) {
|
||||||
|
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||||
|
}}
|
||||||
|
{{~ desc :line }}
|
||||||
|
/// {{= line }}
|
||||||
|
{{~}}
|
||||||
|
{{= e['x-name'] }} = {{= e['x-value'] }},
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{{
|
||||||
|
const dotUtils = require('./dotUtils.js');
|
||||||
|
const queues = require('./.queues.json');
|
||||||
|
}}{{= 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 (const e of queues) {
|
||||||
|
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'] }} = {{= e['x-value'] }},
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
{{
|
||||||
|
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 e of seasons) {
|
||||||
|
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||||
|
}}
|
||||||
|
{{~ desc :line }}
|
||||||
|
/// {{= line }}
|
||||||
|
{{~}}
|
||||||
|
{{= e['x-name'] }} = {{= e['x-value'] }},
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
|
@ -1,5 +1,14 @@
|
||||||
const changeCase = require('change-case');
|
const changeCase = require('change-case');
|
||||||
|
|
||||||
|
const enumTypeLookup = {
|
||||||
|
champion: 'i16',
|
||||||
|
gameMode: 'u8',
|
||||||
|
gameType: 'u8',
|
||||||
|
map: 'u8',
|
||||||
|
queue: 'u16',
|
||||||
|
season: 'u8',
|
||||||
|
};
|
||||||
|
|
||||||
// flatMap: https://gist.github.com/samgiles/762ee337dff48623e729
|
// flatMap: https://gist.github.com/samgiles/762ee337dff48623e729
|
||||||
// [B](f: (A) ⇒ [B]): [B] ; Although the types in the arrays aren't strict (:
|
// [B](f: (A) ⇒ [B]): [B] ; Although the types in the arrays aren't strict (:
|
||||||
Array.prototype.flatMap = function(lambda) {
|
Array.prototype.flatMap = function(lambda) {
|
||||||
|
@ -28,7 +37,6 @@ Array.prototype.sortBy = function(lambda) {
|
||||||
|
|
||||||
function preamble() {
|
function preamble() {
|
||||||
return `\
|
return `\
|
||||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
|
||||||
///////////////////////////////////////////////
|
///////////////////////////////////////////////
|
||||||
// //
|
// //
|
||||||
// ! //
|
// ! //
|
||||||
|
@ -101,48 +109,47 @@ function formatJsonProperty(name) {
|
||||||
return `#[serde(rename = "${name}")]`;
|
return `#[serde(rename = "${name}")]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatAddQueryParam(param) {
|
function formatQueryParamStringify(name, prop, useOwned = false) {
|
||||||
const k = `"${param.name}"`;
|
const own = useOwned ? '' : '&*';
|
||||||
const name = normalizePropName(param.name);
|
if (prop['x-enum']) {
|
||||||
const condStart = param.required ? '' : `if let Some(${name}) = ${name} { `;
|
|
||||||
const condEnd = param.required ? '' : ' } else { request }'
|
|
||||||
const prop = param.schema;
|
|
||||||
switch (prop.type) {
|
switch (prop.type) {
|
||||||
case 'array': return `let request = ${condStart}request.query(&*${name}.iter()`
|
case 'integer':
|
||||||
+ `.map(|w| ( ${k}, w )).collect::<Vec<_>>())${condEnd};`;
|
return `${own}Into::<${enumTypeLookup[prop['x-enum']]}>::into(${name}).to_string()`;
|
||||||
case 'object':
|
default: // Fall-through.
|
||||||
throw 'unsupported';
|
}
|
||||||
default:
|
}
|
||||||
return `let request = ${condStart}request.query(&[ (${k}, ${name}) ])${condEnd};`;
|
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) {
|
function formatAddQueryParam(param) {
|
||||||
const k = `"${param.name}"`;
|
const k = `"${param.name}"`;
|
||||||
const name = changeCase.snakeCase(param.name);
|
const name = normalizePropName(param.name);
|
||||||
const condStart = param.required ? '' : `mut request = request; if let Some(${name}) = ${name} { `;
|
const nc = param.required ? '' : `if let Some(${name}) = ${name} `;
|
||||||
const condEnd = param.required ? '' : ' }'
|
|
||||||
const prop = param.schema;
|
const prop = param.schema;
|
||||||
switch (prop.type) {
|
switch (prop.type) {
|
||||||
case 'string':
|
case 'array': return `${nc}{ query_params.extend_pairs(${name}.iter()`
|
||||||
return `let ${condStart}request = request.header(${k}, ${name});${condEnd}`;
|
+ `.map(|w| (${k}, ${formatQueryParamStringify("w", prop.items, true)}))); }`;
|
||||||
case 'object':
|
case 'object': throw 'unsupported';
|
||||||
throw 'unsupported';
|
|
||||||
default:
|
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 = []) {
|
function formatRouteArgument(route, pathParams = []) {
|
||||||
if (!pathParams.length)
|
if (!pathParams.length)
|
||||||
return `"${route}"`;
|
return `"${route}".to_owned()`;
|
||||||
|
|
||||||
route = route.replace(/\{\S+?\}/g, '{}');
|
route = route.replace(/\{\S+?\}/g, '{}');
|
||||||
const args = pathParams
|
const args = pathParams
|
||||||
.map(({name}) => name)
|
.map(({name}) => name)
|
||||||
.map(changeCase.snakeCase)
|
.map(changeCase.snakeCase)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
return `&format!("${route}", ${args})`;
|
return `format!("${route}", ${args})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -156,6 +163,5 @@ module.exports = {
|
||||||
stringifyType,
|
stringifyType,
|
||||||
formatJsonProperty,
|
formatJsonProperty,
|
||||||
formatAddQueryParam,
|
formatAddQueryParam,
|
||||||
formatAddHeaderParam,
|
|
||||||
formatRouteArgument,
|
formatRouteArgument,
|
||||||
};
|
};
|
|
@ -0,0 +1,167 @@
|
||||||
|
{{
|
||||||
|
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) /* Only support GET parameters. */
|
||||||
|
continue;
|
||||||
|
if ((get.parameters || []).some(p => 'header' === p.in)) /* Do not support header parameter methods. */
|
||||||
|
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 descArr = get.description.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.normalizePropName(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' }})
|
||||||
|
}
|
||||||
|
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
|
@ -23,10 +23,6 @@ const files = [
|
||||||
'http://www.mingweisamuel.com/riotapi-schema/enums/queues.json',
|
'http://www.mingweisamuel.com/riotapi-schema/enums/queues.json',
|
||||||
'.queues.json'
|
'.queues.json'
|
||||||
],
|
],
|
||||||
[
|
|
||||||
'http://www.mingweisamuel.com/riotapi-schema/enums/queueTypes.json',
|
|
||||||
'.queueTypes.json'
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
'http://www.mingweisamuel.com/riotapi-schema/enums/gameTypes.json',
|
'http://www.mingweisamuel.com/riotapi-schema/enums/gameTypes.json',
|
||||||
'.gameTypes.json'
|
'.gameTypes.json'
|
||||||
|
@ -38,12 +34,8 @@ const files = [
|
||||||
[
|
[
|
||||||
'http://www.mingweisamuel.com/riotapi-schema/enums/maps.json',
|
'http://www.mingweisamuel.com/riotapi-schema/enums/maps.json',
|
||||||
'.maps.json'
|
'.maps.json'
|
||||||
],
|
]
|
||||||
[
|
]
|
||||||
'http://www.mingweisamuel.com/riotapi-schema/routesTable.json',
|
|
||||||
'.routesTable.json'
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
const downloadFilesPromise = Promise.all(files.map(([url, file]) => req(url)
|
const downloadFilesPromise = Promise.all(files.map(([url, file]) => req(url)
|
||||||
.then(body => fs.writeFileAsync(file, body, "utf8"))));
|
.then(body => fs.writeFileAsync(file, body, "utf8"))));
|
||||||
|
@ -80,7 +72,6 @@ downloadFilesPromise.then(() => glob.promise("**/*" + suffix, { ignore: ["**/nod
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error(`Error thrown while running "${file}":`, e);
|
console.error(`Error thrown while running "${file}":`, e);
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(output => fs.writeFileAsync("../src/" + file.slice(0, -suffix.length), output, "utf8"))
|
.then(output => fs.writeFileAsync("../src/" + file.slice(0, -suffix.length), output, "utf8"))
|
|
@ -1,24 +1,23 @@
|
||||||
{{
|
{{
|
||||||
const dotUtils = require('./dotUtils.js');
|
const dotUtils = require('./dotUtils.js');
|
||||||
const readme = require('fs').readFileSync('../../README.md', 'utf-8').split(/\r?\n/);
|
const readme = require('fs').readFileSync('../README.md', 'utf-8').split(/\r?\n/);
|
||||||
}}{{= dotUtils.preamble() }}
|
}}{{= dotUtils.preamble() }}
|
||||||
|
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![deny(missing_docs)]
|
|
||||||
|
|
||||||
{{~ readme :line }}
|
{{~ readme :line }}
|
||||||
//!{{= line ? (' ' + line) : '' }}
|
//! {{= line }}
|
||||||
{{~}}
|
{{~}}
|
||||||
|
|
||||||
// Re-exported reqwest types.
|
// Re-exported reqwest types.
|
||||||
pub use reqwest;
|
pub use reqwest;
|
||||||
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
pub use config::RiotApiConfig;
|
pub use config::RiotApiConfig;
|
||||||
|
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
pub mod endpoints;
|
pub mod endpoints;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
|
@ -26,9 +25,7 @@ pub use error::*;
|
||||||
|
|
||||||
pub mod meta;
|
pub mod meta;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
pub mod models;
|
pub mod models;
|
||||||
mod models_impls;
|
|
||||||
|
|
||||||
mod req;
|
mod req;
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
{{
|
||||||
|
const spec = require('./.spec.json');
|
||||||
|
const dotUtils = require('./dotUtils.js');
|
||||||
|
}}{{= 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.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref ENDPOINT_PATH_METHODID: HashMap<&'static str, &'static str> = {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
{{
|
||||||
|
for (const [ route, path ] of Object.entries(spec.paths)) {
|
||||||
|
const get = path.get;
|
||||||
|
if (!get) /* Only support GET parameters. */
|
||||||
|
continue;
|
||||||
|
if ((get.parameters || []).some(p => 'header' === p.in)) /* Do not support header parameter methods. */
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const operationId = get.operationId;
|
||||||
|
}}
|
||||||
|
map.insert("{{= route }}", "{{= operationId }}");
|
||||||
|
{{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
map
|
||||||
|
};
|
||||||
|
}
|
|
@ -6,8 +6,6 @@
|
||||||
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
||||||
// Version {{= spec.info.version }}
|
// Version {{= spec.info.version }}
|
||||||
|
|
||||||
#![allow(missing_docs)]
|
|
||||||
|
|
||||||
//! Data transfer structs.
|
//! Data transfer structs.
|
||||||
//!
|
//!
|
||||||
//! Separated into separate modules for each endpoint.
|
//! Separated into separate modules for each endpoint.
|
||||||
|
@ -40,37 +38,24 @@ pub mod {{= dotUtils.changeCase.snakeCase(endpoint) }} {
|
||||||
/// {{= schemaName }} data object.
|
/// {{= schemaName }} data object.
|
||||||
{{? schema.description }}
|
{{? schema.description }}
|
||||||
/// # Description
|
/// # Description
|
||||||
/// {{= schema.description.split('\n').map(x => x.trim()).join('<br>\r\n /// ') }}
|
/// {{= schema.description }}
|
||||||
///
|
///
|
||||||
/// Note: This struct is automatically generated
|
/// Note: This struct is automatically generated
|
||||||
{{?}}
|
{{?}}
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug)]
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
#[cfg_attr(feature = "deny-unknown-fields", serde(deny_unknown_fields))]
|
|
||||||
pub struct {{= schemaName }} {
|
pub struct {{= schemaName }} {
|
||||||
{{
|
{{
|
||||||
for (let [ propKey, prop ] of Object.entries(props))
|
for (let [ propKey, prop ] of Object.entries(props))
|
||||||
{
|
{
|
||||||
const name = dotUtils.normalizePropName(propKey);
|
const name = dotUtils.normalizePropName(propKey);
|
||||||
const optional = !requiredSet.has(propKey);
|
const required = requiredSet.has(propKey);
|
||||||
}}
|
}}
|
||||||
{{? prop.description }}
|
{{? prop.description }}
|
||||||
/// {{= prop.description.split('\n').map(x => x.trim()).join('<br>\r\n /// ') }}
|
/// {{= prop.description.split('\n').map(x => x.trim()).join('<br>\r\n /// ') }}
|
||||||
{{?}}
|
{{?}}
|
||||||
{{= dotUtils.formatJsonProperty(propKey) }}
|
{{= dotUtils.formatJsonProperty(propKey) }}
|
||||||
{{? optional }}
|
pub {{= name }}: {{= dotUtils.stringifyType(prop, { optional: !required }) }},
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
{{?}}
|
|
||||||
{{? 'championId' === propKey && (prop.description || '').includes('this field returned invalid championIds') }}
|
|
||||||
///
|
|
||||||
/// Instead use [`Self::champion()`] which checks this field then parses [`Self::champion_name`].
|
|
||||||
#[deprecated(since = "2.5.0", note = "Use `Participant.champion()` instead. Riot sometimes returns corrupted data for this field: https://github.com/RiotGames/developer-relations/issues/553")]
|
|
||||||
#[serde(serialize_with = "crate::consts::Champion::serialize_result")]
|
|
||||||
#[serde(deserialize_with = "crate::consts::Champion::deserialize_result")]
|
|
||||||
pub {{= name }}: Result<crate::consts::Champion, std::num::TryFromIntError>,
|
|
||||||
{{??}}
|
|
||||||
pub {{= name }}: {{= dotUtils.stringifyType(prop, { optional }) }},
|
|
||||||
{{?}}
|
|
||||||
{{
|
{{
|
||||||
}
|
}
|
||||||
}}
|
}}
|
|
@ -0,0 +1,659 @@
|
||||||
|
{
|
||||||
|
"name": "dot-gen",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/events": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g=="
|
||||||
|
},
|
||||||
|
"@types/glob": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
|
||||||
|
"requires": {
|
||||||
|
"@types/events": "*",
|
||||||
|
"@types/minimatch": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/minimatch": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA=="
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"version": "12.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz",
|
||||||
|
"integrity": "sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A=="
|
||||||
|
},
|
||||||
|
"ajv": {
|
||||||
|
"version": "6.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
|
||||||
|
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
|
||||||
|
"requires": {
|
||||||
|
"fast-deep-equal": "^2.0.1",
|
||||||
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
|
"json-schema-traverse": "^0.4.1",
|
||||||
|
"uri-js": "^4.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"asn1": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
|
||||||
|
"requires": {
|
||||||
|
"safer-buffer": "~2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"assert-plus": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
|
||||||
|
},
|
||||||
|
"asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||||
|
},
|
||||||
|
"aws-sign2": {
|
||||||
|
"version": "0.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||||
|
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
|
||||||
|
},
|
||||||
|
"aws4": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
|
||||||
|
},
|
||||||
|
"balanced-match": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||||
|
},
|
||||||
|
"bcrypt-pbkdf": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
|
||||||
|
"requires": {
|
||||||
|
"tweetnacl": "^0.14.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"camel-case": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
|
||||||
|
"requires": {
|
||||||
|
"no-case": "^2.2.0",
|
||||||
|
"upper-case": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"caseless": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||||
|
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
|
||||||
|
},
|
||||||
|
"change-case": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/change-case/-/change-case-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==",
|
||||||
|
"requires": {
|
||||||
|
"camel-case": "^3.0.0",
|
||||||
|
"constant-case": "^2.0.0",
|
||||||
|
"dot-case": "^2.1.0",
|
||||||
|
"header-case": "^1.0.0",
|
||||||
|
"is-lower-case": "^1.1.0",
|
||||||
|
"is-upper-case": "^1.1.0",
|
||||||
|
"lower-case": "^1.1.1",
|
||||||
|
"lower-case-first": "^1.0.0",
|
||||||
|
"no-case": "^2.3.2",
|
||||||
|
"param-case": "^2.1.0",
|
||||||
|
"pascal-case": "^2.0.0",
|
||||||
|
"path-case": "^2.1.0",
|
||||||
|
"sentence-case": "^2.1.0",
|
||||||
|
"snake-case": "^2.1.0",
|
||||||
|
"swap-case": "^1.1.0",
|
||||||
|
"title-case": "^2.1.0",
|
||||||
|
"upper-case": "^1.1.1",
|
||||||
|
"upper-case-first": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"requires": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"concat-map": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
|
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||||
|
},
|
||||||
|
"constant-case": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/constant-case/-/constant-case-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-QXV2TTidP6nI7NKRhu1gBSQ7akY=",
|
||||||
|
"requires": {
|
||||||
|
"snake-case": "^2.1.0",
|
||||||
|
"upper-case": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"core-util-is": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||||
|
},
|
||||||
|
"dashdash": {
|
||||||
|
"version": "1.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||||
|
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
||||||
|
},
|
||||||
|
"dot": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dot/-/dot-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-/nt74Rm+PcfnirXGEdhZleTwGC2LMnuKTeeTIlI82xb5loBBoXNYzr2ezCroPSMtilK8EZIfcNZwOcHN+ib1Lg=="
|
||||||
|
},
|
||||||
|
"dot-case": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz",
|
||||||
|
"integrity": "sha1-NNzzf1Co6TwrO8qLt/uRVcfaO+4=",
|
||||||
|
"requires": {
|
||||||
|
"no-case": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ecc-jsbn": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
|
||||||
|
"requires": {
|
||||||
|
"jsbn": "~0.1.0",
|
||||||
|
"safer-buffer": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extend": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||||
|
},
|
||||||
|
"extsprintf": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||||
|
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
|
||||||
|
},
|
||||||
|
"fast-deep-equal": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
|
||||||
|
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
|
||||||
|
},
|
||||||
|
"fast-json-stable-stringify": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
|
||||||
|
},
|
||||||
|
"forever-agent": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||||
|
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
|
||||||
|
},
|
||||||
|
"form-data": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
||||||
|
"requires": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.6",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fs.realpath": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||||
|
},
|
||||||
|
"getpass": {
|
||||||
|
"version": "0.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||||
|
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"glob": {
|
||||||
|
"version": "7.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
|
||||||
|
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
|
||||||
|
"requires": {
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"inflight": "^1.0.4",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "^3.0.4",
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"path-is-absolute": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"glob-promise": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw==",
|
||||||
|
"requires": {
|
||||||
|
"@types/glob": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"har-schema": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
|
||||||
|
},
|
||||||
|
"har-validator": {
|
||||||
|
"version": "5.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
|
||||||
|
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
|
||||||
|
"requires": {
|
||||||
|
"ajv": "^6.5.5",
|
||||||
|
"har-schema": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"header-case": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/header-case/-/header-case-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-lTWXMZfBRLCWE81l0xfvGZY70C0=",
|
||||||
|
"requires": {
|
||||||
|
"no-case": "^2.2.0",
|
||||||
|
"upper-case": "^1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"http-signature": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"jsprim": "^1.2.2",
|
||||||
|
"sshpk": "^1.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inflight": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
|
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||||
|
"requires": {
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
|
},
|
||||||
|
"is-lower-case": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz",
|
||||||
|
"integrity": "sha1-fhR75HaNxGbbO/shzGCzHmrWk5M=",
|
||||||
|
"requires": {
|
||||||
|
"lower-case": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"is-typedarray": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
|
||||||
|
},
|
||||||
|
"is-upper-case": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz",
|
||||||
|
"integrity": "sha1-jQsfp+eTOh5YSDYA7H2WYcuvdW8=",
|
||||||
|
"requires": {
|
||||||
|
"upper-case": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isstream": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
||||||
|
},
|
||||||
|
"jsbn": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||||
|
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
|
||||||
|
},
|
||||||
|
"json-schema": {
|
||||||
|
"version": "0.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||||
|
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
|
||||||
|
},
|
||||||
|
"json-schema-traverse": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||||
|
},
|
||||||
|
"json-stringify-safe": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||||
|
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
|
||||||
|
},
|
||||||
|
"jsprim": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||||
|
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "1.0.0",
|
||||||
|
"extsprintf": "1.3.0",
|
||||||
|
"json-schema": "0.2.3",
|
||||||
|
"verror": "1.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
|
},
|
||||||
|
"lower-case": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
|
||||||
|
"integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw="
|
||||||
|
},
|
||||||
|
"lower-case-first": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-5dp8JvKacHO+AtUrrJmA5ZIq36E=",
|
||||||
|
"requires": {
|
||||||
|
"lower-case": "^1.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mime-db": {
|
||||||
|
"version": "1.40.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
|
||||||
|
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
|
||||||
|
},
|
||||||
|
"mime-types": {
|
||||||
|
"version": "2.1.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
|
||||||
|
"integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
|
||||||
|
"requires": {
|
||||||
|
"mime-db": "1.40.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"no-case": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
|
||||||
|
"requires": {
|
||||||
|
"lower-case": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth-sign": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
|
||||||
|
},
|
||||||
|
"once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||||
|
"requires": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"param-case": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
|
||||||
|
"integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
|
||||||
|
"requires": {
|
||||||
|
"no-case": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pascal-case": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz",
|
||||||
|
"integrity": "sha1-LVeNNFX2YNpl7KGO+VtODekSdh4=",
|
||||||
|
"requires": {
|
||||||
|
"camel-case": "^3.0.0",
|
||||||
|
"upper-case-first": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path-case": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-case/-/path-case-2.1.1.tgz",
|
||||||
|
"integrity": "sha1-lLgDfDctP+KQbkZbtF4l0ibo7qU=",
|
||||||
|
"requires": {
|
||||||
|
"no-case": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path-is-absolute": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||||
|
},
|
||||||
|
"performance-now": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
||||||
|
},
|
||||||
|
"psl": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw=="
|
||||||
|
},
|
||||||
|
"punycode": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
||||||
|
},
|
||||||
|
"qs": {
|
||||||
|
"version": "6.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||||
|
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"version": "2.88.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
|
||||||
|
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
|
||||||
|
"requires": {
|
||||||
|
"aws-sign2": "~0.7.0",
|
||||||
|
"aws4": "^1.8.0",
|
||||||
|
"caseless": "~0.12.0",
|
||||||
|
"combined-stream": "~1.0.6",
|
||||||
|
"extend": "~3.0.2",
|
||||||
|
"forever-agent": "~0.6.1",
|
||||||
|
"form-data": "~2.3.2",
|
||||||
|
"har-validator": "~5.1.0",
|
||||||
|
"http-signature": "~1.2.0",
|
||||||
|
"is-typedarray": "~1.0.0",
|
||||||
|
"isstream": "~0.1.2",
|
||||||
|
"json-stringify-safe": "~5.0.1",
|
||||||
|
"mime-types": "~2.1.19",
|
||||||
|
"oauth-sign": "~0.9.0",
|
||||||
|
"performance-now": "^2.1.0",
|
||||||
|
"qs": "~6.5.2",
|
||||||
|
"safe-buffer": "^5.1.2",
|
||||||
|
"tough-cookie": "~2.4.3",
|
||||||
|
"tunnel-agent": "^0.6.0",
|
||||||
|
"uuid": "^3.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"request-promise-core": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==",
|
||||||
|
"requires": {
|
||||||
|
"lodash": "^4.17.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"request-promise-native": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==",
|
||||||
|
"requires": {
|
||||||
|
"request-promise-core": "1.1.2",
|
||||||
|
"stealthy-require": "^1.1.1",
|
||||||
|
"tough-cookie": "^2.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe-buffer": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
|
||||||
|
},
|
||||||
|
"safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
|
},
|
||||||
|
"sentence-case": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz",
|
||||||
|
"integrity": "sha1-H24t2jnBaL+S0T+G1KkYkz9mftQ=",
|
||||||
|
"requires": {
|
||||||
|
"no-case": "^2.2.0",
|
||||||
|
"upper-case-first": "^1.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"snake-case": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-Qb2xtz8w7GagTU4srRt2OH1NbZ8=",
|
||||||
|
"requires": {
|
||||||
|
"no-case": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sshpk": {
|
||||||
|
"version": "1.16.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
|
||||||
|
"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
|
||||||
|
"requires": {
|
||||||
|
"asn1": "~0.2.3",
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"bcrypt-pbkdf": "^1.0.0",
|
||||||
|
"dashdash": "^1.12.0",
|
||||||
|
"ecc-jsbn": "~0.1.1",
|
||||||
|
"getpass": "^0.1.1",
|
||||||
|
"jsbn": "~0.1.0",
|
||||||
|
"safer-buffer": "^2.0.2",
|
||||||
|
"tweetnacl": "~0.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stealthy-require": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
|
||||||
|
},
|
||||||
|
"swap-case": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz",
|
||||||
|
"integrity": "sha1-w5IDpFhzhfrTyFCgvRvK+ggZdOM=",
|
||||||
|
"requires": {
|
||||||
|
"lower-case": "^1.1.1",
|
||||||
|
"upper-case": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title-case": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz",
|
||||||
|
"integrity": "sha1-PhJyFtpY0rxb7PE3q5Ha46fNj6o=",
|
||||||
|
"requires": {
|
||||||
|
"no-case": "^2.2.0",
|
||||||
|
"upper-case": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tough-cookie": {
|
||||||
|
"version": "2.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||||
|
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
||||||
|
"requires": {
|
||||||
|
"psl": "^1.1.24",
|
||||||
|
"punycode": "^1.4.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||||
|
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tunnel-agent": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
|
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tweetnacl": {
|
||||||
|
"version": "0.14.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||||
|
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
|
||||||
|
},
|
||||||
|
"upper-case": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
|
||||||
|
"integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
|
||||||
|
},
|
||||||
|
"upper-case-first": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz",
|
||||||
|
"integrity": "sha1-XXm+3P8UQZUY/S7bCgUHybaFkRU=",
|
||||||
|
"requires": {
|
||||||
|
"upper-case": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uri-js": {
|
||||||
|
"version": "4.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
|
||||||
|
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
|
||||||
|
"requires": {
|
||||||
|
"punycode": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
|
||||||
|
},
|
||||||
|
"verror": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||||
|
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
|
||||||
|
"requires": {
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"core-util-is": "1.0.2",
|
||||||
|
"extsprintf": "^1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue