forked from mirror/Riven
Compare commits
280 Commits
users/ming
...
v/2.x.x
Author | SHA1 | Date |
---|---|---|
Mingwei Samuel | 886ed4032d | |
Mingwei Samuel | 5e235e3c51 | |
Guillermo Lloret Talavera | 0f5fae1193 | |
Mingwei Samuel | 416140c41d | |
Mingwei Samuel | 4039a355d3 | |
Mingwei Samuel | b52ddcd5df | |
Mingwei Samuel | 4cc241baac | |
Mingwei Samuel | 8d810ccca8 | |
Mingwei Samuel | 33ef298039 | |
Mingwei Samuel | 655d04f8df | |
Mingwei Samuel | 25f41388f0 | |
Mingwei Samuel | 83d7fc3001 | |
Mingwei Samuel | 25b5d9fd22 | |
Mingwei Samuel | 4067b0cec7 | |
Mingwei Samuel | 406df5188f | |
Mingwei Samuel | af759728e3 | |
Mingwei Samuel | 1e136feaf3 | |
Mingwei Samuel | 172bc84620 | |
Mingwei Samuel | 2c284d860c | |
Mingwei Samuel | ceac9d203b | |
Mingwei Samuel | 4265d7e3c9 | |
Mingwei Samuel | 4391d214aa | |
Mingwei Samuel | 13a06b224a | |
Mingwei Samuel | b4398e4e98 | |
Mingwei Samuel | 904a6b083f | |
Mingwei Samuel | ce2cd7c7a2 | |
Mingwei Samuel | c3982c390e | |
Mingwei Samuel | c7f5c59495 | |
Mingwei Samuel | ac12a8bfc2 | |
Mingwei Samuel | 09f0f2562b | |
Mingwei Samuel | dd77ffa6dd | |
Mingwei Samuel | a9a0990d5c | |
Mingwei Samuel | c449a4fc52 | |
Mingwei Samuel | dc73fcbf46 | |
Mingwei Samuel | fb95601526 | |
Mingwei Samuel | bd76b24b2b | |
Mingwei Samuel | 56b75ace18 | |
Mingwei Samuel | 2661acf5e9 | |
Mingwei Samuel | c4a9993ac2 | |
Mingwei Samuel | 512a7e9020 | |
Mingwei Samuel | daf18c476c | |
Mingwei Samuel | e67ffff627 | |
Mingwei Samuel | 9b08e058b7 | |
Mingwei Samuel | f79f2edab3 | |
Mingwei Samuel | 1ec4c18ae4 | |
Mingwei Samuel | c58da65298 | |
Mingwei Samuel | ac76a87367 | |
Mingwei Samuel | fc42abcbe2 | |
Mingwei Samuel | 1fc947a040 | |
Mingwei Samuel | 8e3e081944 | |
Mingwei Samuel | 41a00d68f6 | |
Mingwei Samuel | 800f9c83f4 | |
Mingwei Samuel | 7c0fff55ae | |
Mingwei Samuel | 908d0d64ed | |
Mingwei Samuel | 2aa493abfe | |
Mingwei Samuel | d15d43797e | |
Mingwei Samuel | 08bb2afd68 | |
Mingwei Samuel | 9a2e5277c1 | |
Mingwei Samuel | 3253fa4ec2 | |
Mingwei Samuel | bf51ba0a83 | |
Mingwei Samuel | 3136f84680 | |
Mingwei Samuel | 3070216709 | |
Mingwei Samuel | 78f187570f | |
Mingwei Samuel | ad08632089 | |
Mingwei Samuel | 4b3afbd8ab | |
Mingwei Samuel | cb5bd58784 | |
Mingwei Samuel | e53d78c807 | |
Mingwei Samuel | 3d1188b5c9 | |
Mingwei Samuel | 150566983e | |
Mingwei Samuel | 7e78abb7e5 | |
Mingwei Samuel | 1567bae16e | |
Mingwei Samuel | 112f6bceae | |
Mingwei Samuel | 94e0a0d1d3 | |
Mingwei Samuel | 975b954f3f | |
Mingwei Samuel | 0ea966a105 | |
Mingwei Samuel | 642ed27fa2 | |
Mingwei Samuel | 26adf882fb | |
Mingwei Samuel | ac46e74de4 | |
Mingwei Samuel | 0a1d5537cc | |
Mingwei Samuel | e5803092bf | |
Mingwei Samuel | fa3e7a4bab | |
Mingwei Samuel | 520fad7e7e | |
Mingwei Samuel | 728aa0f99a | |
Mingwei Samuel | 32fe532f07 | |
Mingwei Samuel | d05b068bb8 | |
Mingwei Samuel | d2c661b9ab | |
Mingwei Samuel | 599f88ec73 | |
Mingwei Samuel | 7f0b63bf92 | |
Mingwei Samuel | 563f9e84a7 | |
Mingwei Samuel | 9f5b12cd1b | |
Mingwei Samuel | 129002bf23 | |
Mingwei Samuel | a11b6874f6 | |
Mingwei Samuel | f51a67c1fc | |
sir naji | 6512866c26 | |
Mingwei Samuel | 97b6017b42 | |
Mingwei Samuel | d7330fcd84 | |
Mingwei Samuel | d2a213c287 | |
Mingwei Samuel | 4f5d115831 | |
Mingwei Samuel | 871044974c | |
Mingwei Samuel | 6c5af2bfd6 | |
Mingwei Samuel | 08e276a558 | |
Mingwei Samuel | 0753fe1b34 | |
Mingwei Samuel | c9bca855f2 | |
Mingwei Samuel | 342c87e682 | |
dependabot[bot] | d05c3a11d4 | |
Mingwei Samuel | 6c41bd03e4 | |
Mingwei Samuel | 998eb4ec46 | |
Mingwei Samuel | f3f34b6ff5 | |
tomchandler | 1af0539a84 | |
Mingwei Samuel | 036f818457 | |
Mingwei Samuel | 7d545b55af | |
Mingwei Samuel | a2699a695f | |
djazouli | 2f4aae6635 | |
Mingwei Samuel | 650380cdf9 | |
Mingwei Samuel | 76a48963e5 | |
Mingwei Samuel | 3ae9f36888 | |
Mingwei Samuel | 713995491e | |
Mingwei Samuel | bfa9bdc36e | |
Mingwei Samuel | 2e52b03c63 | |
Mingwei Samuel | b24fdcb765 | |
Mingwei Samuel | 104db04d9d | |
Mingwei Samuel | 20863c5bcc | |
Mingwei Samuel | b82a1725c1 | |
Mingwei Samuel | 056bacf67c | |
Mingwei Samuel | b1f7e8e605 | |
Mingwei Samuel | 8e6073aa63 | |
Mingwei Samuel | f0f4af13af | |
Mingwei Samuel | b821314492 | |
Mingwei Samuel | 03a053d142 | |
Mingwei Samuel | 061ce5bd39 | |
Mingwei Samuel | 7661c534cb | |
Mingwei Samuel | 295ccc4daf | |
Mingwei Samuel | 8c458c000a | |
Mingwei Samuel | 43d0578ab7 | |
dependabot[bot] | 1b62208c78 | |
Mingwei Samuel | 8d48956324 | |
Mingwei Samuel | f37e7a5cbb | |
Mingwei Samuel | 386d41c208 | |
Mingwei Samuel | 038e5eb493 | |
Mingwei Samuel | ede1247bf8 | |
Mingwei Samuel | 9f5935a3f5 | |
Mingwei Samuel | 62f323da88 | |
Mingwei Samuel | 1d70c479cf | |
Mingwei Samuel | 7865a439e1 | |
Mingwei Samuel | cb145bea0f | |
Mingwei Samuel | 9478e3f95e | |
Mingwei Samuel | d05f89c0d4 | |
Mingwei Samuel | 2f4d1dc65c | |
Mingwei Samuel | de1e5cdbca | |
Mingwei Samuel | c07efb1087 | |
Mingwei Samuel | 44b78abca9 | |
Mingwei Samuel | 40edebf78b | |
Mingwei Samuel | fd892b1451 | |
aPinat | 260bf8250a | |
Mingwei Samuel | 2be19f05ca | |
Mingwei Samuel | 7e5bccd039 | |
Mingwei Samuel | b981adbffc | |
Mingwei Samuel | 0b16c2a385 | |
Mingwei Samuel | 0e22623056 | |
Mingwei Samuel | fda0836d27 | |
Mingwei Samuel | 324016f33d | |
Mingwei Samuel | 9f759369f8 | |
Mingwei Samuel | 4c618eb939 | |
Mingwei Samuel | ad1b4fd630 | |
Mingwei Samuel | fed9c07e7f | |
Mingwei Samuel | 8d2bcfe694 | |
Mingwei Samuel | 3cd41bce4c | |
Mingwei Samuel | 9c13d126a6 | |
Mingwei Samuel | f43795c5a3 | |
Mingwei Samuel | 1a0ae872f5 | |
Mingwei Samuel | 8f5d7327d1 | |
Mingwei Samuel | c9465eee32 | |
Mingwei Samuel | 32e70437ab | |
Mingwei Samuel | 394e6c72e2 | |
Mingwei Samuel | f1a51693fa | |
Mingwei Samuel | 74eb5fa045 | |
Mingwei Samuel | 5c26ddd211 | |
Mingwei Samuel | 9dcbc626c7 | |
Mingwei Samuel | dafc181bbe | |
Mingwei Samuel | 44320bdeeb | |
Mingwei Samuel | e1bf531235 | |
Mingwei Samuel | 00e520b7af | |
Mingwei Samuel | 8dbcca434d | |
Thijs Molendijk | 7f3fa7f59e | |
Thijs Molendijk | 3c4f3c967a | |
Thijs Molendijk | 032d3ac97d | |
Mingwei Samuel | f182191680 | |
Mingwei Samuel | 75ab41aca2 | |
Mingwei Samuel | c10275831f | |
Mingwei Samuel | 56973ae26e | |
Mingwei Samuel | 27e694a61c | |
Mingwei Samuel | 23645a9319 | |
Mingwei Samuel | 9c4679fb88 | |
Mingwei Samuel | 2cb8db61f5 | |
Mingwei Samuel | a1bca52576 | |
Mingwei Samuel | 62e2001457 | |
Mingwei Samuel | 4b23f9afed | |
Mingwei Samuel | 35f826b198 | |
Mingwei Samuel | 2d80f97170 | |
Mingwei Samuel | 88124ecb3a | |
Mingwei Samuel | 5daeab990a | |
Mingwei Samuel | 07348a5c53 | |
Mingwei Samuel | 6307a0aa13 | |
Mingwei Samuel | 2989c4483e | |
Mingwei Samuel | 569a5bbc1b | |
Mingwei Samuel | b6cf9217d8 | |
Mingwei Samuel | 4b0cb11197 | |
Mingwei Samuel | c38afa7753 | |
Mingwei Samuel | c813228603 | |
Mingwei Samuel | 2b1f109427 | |
Mingwei Samuel | 1c4cadb6d9 | |
Mingwei Samuel | 20d5196f13 | |
Mingwei Samuel | ad5b1c70ea | |
Mingwei Samuel | e7e40a3cc4 | |
Mingwei Samuel | dded4d5644 | |
Mingwei Samuel | d4cf56f496 | |
Mingwei Samuel | 9c8313c604 | |
Mingwei Samuel | 39af464170 | |
Mingwei Samuel | 4e0d45e59d | |
Mingwei Samuel | 40d49f837d | |
Mingwei Samuel | 53230dd8df | |
Mingwei Samuel | 8934790c1c | |
Mingwei Samuel | 7f046e99f7 | |
Mingwei Samuel | 869216aab5 | |
Mingwei Samuel | ae0ff8745f | |
Mingwei Samuel | 0c4e9ec962 | |
Mingwei Samuel | 32082b0a62 | |
Mingwei Samuel | 4f575530c7 | |
dependabot[bot] | b2ad4ec50d | |
Mingwei Samuel | 103aa74d92 | |
Mingwei Samuel | 225aacca44 | |
Mingwei Samuel | 0c48125e00 | |
Mingwei Samuel | 0f400ab43a | |
Rémi CORNIERE | a97bceb827 | |
Mingwei Samuel | 7bbe5fc3aa | |
Mingwei Samuel | c460516cfe | |
Mingwei Samuel | b8f886d6e9 | |
Mingwei Samuel | cd0ceddc9a | |
Mingwei Samuel | 19eeeae637 | |
Mingwei Samuel | 6c3d51a071 | |
Mingwei Samuel | 4b5776ca8d | |
Mingwei Samuel | 426d8e9ee9 | |
Mingwei Samuel | 20b8d49b89 | |
Mingwei Samuel | 4782ba48b3 | |
Mingwei Samuel | 2147b41d1e | |
Mingwei Samuel | cb953f7b34 | |
Mingwei Samuel | 18b076203b | |
Mingwei Samuel | 8ed63837b1 | |
Mingwei Samuel | 3f00aa9d66 | |
Mingwei Samuel | e91fdc0c78 | |
Mingwei Samuel | 5be2884eb4 | |
Mingwei Samuel | 8f0bd8dbe7 | |
Mingwei Samuel | a47fb6f77c | |
Mingwei Samuel | 5a78205dcd | |
Mingwei Samuel | da1b82fcd7 | |
Mingwei Samuel | 51c038fb7f | |
Mingwei Samuel | 0f9d783b94 | |
Mingwei Samuel | 7105c778f4 | |
Mingwei Samuel | 1a88ae6365 | |
Mingwei Samuel | bdba6bb70c | |
Mingwei Samuel | d2656a40de | |
Mingwei Samuel | e4884b7918 | |
Mingwei Samuel | 02056f1e59 | |
Mingwei Samuel | a0b1234c16 | |
Mingwei Samuel | b3742ef919 | |
Mingwei Samuel | 15eee5883a | |
Mingwei Samuel | 1975b3f036 | |
Mingwei Samuel | 2516fe269b | |
Mingwei Samuel | 3d56d7c722 | |
Mingwei Samuel | b15672baaa | |
Mingwei Samuel | 14e6faa24e | |
Mingwei Samuel | ea1af34854 | |
Mingwei Samuel | 1b80fa2595 | |
Mingwei Samuel | ae6056569e | |
Mingwei Samuel | 2f1698b183 | |
Mingwei Samuel | 33cec4c395 | |
Mingwei Samuel | eacc207331 | |
Mingwei Samuel | b399ef4a4c | |
Mingwei Samuel | ddd4cb8cbe | |
Mingwei Samuel | 21579835c0 |
|
@ -0,0 +1,120 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [v/2.x.x]
|
||||
pull_request:
|
||||
branches: [v/2.x.x]
|
||||
schedule:
|
||||
- cron: "24 05 * * *" # Daily at 10:24 PM PDT, 9:24 PM PST.
|
||||
workflow_dispatch:
|
||||
# TODO: generate nightly releases
|
||||
# inputs:
|
||||
# should_bench:
|
||||
# description: "Should Benchmark? (`true`)"
|
||||
# required: true
|
||||
# default: "false"
|
||||
|
||||
jobs:
|
||||
pre_job:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_check
|
||||
uses: fkirc/skip-duplicate-actions@v3.4.0
|
||||
with:
|
||||
cancel_others: "true"
|
||||
|
||||
test:
|
||||
name: Check & Test
|
||||
needs: pre_job
|
||||
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: npm ci --prefix riven/srcgen
|
||||
|
||||
- name: Run codegen
|
||||
run: node riven/srcgen
|
||||
|
||||
- name: Install Rust nightly toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
components: rustfmt
|
||||
|
||||
- name: Run `cargo +stable check --all-targets`
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
command: check
|
||||
args: --all-targets
|
||||
|
||||
- name: Run `cargo +stable check --all-targets --features tracing`
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
command: check
|
||||
args: --all-targets --features tracing
|
||||
|
||||
- name: Run `cargo check --all-targets --features nightly,tracing`
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all-targets --features nightly,tracing
|
||||
|
||||
- name: Run `cargo build --all-targets --features nightly,deny-unknown`
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all-targets --features nightly,deny-unknown
|
||||
|
||||
- name: Run `cargo test --features nightly,deny-unknown`
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --no-fail-fast --features nightly,deny-unknown
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
RUSTLOG: riven=trace
|
||||
RGAPI_KEY: ${{ secrets.RGAPI_KEY }}
|
||||
|
||||
lints:
|
||||
name: Lints
|
||||
needs: pre_job
|
||||
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install nightly toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Run cargo fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: Run cargo clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
|
@ -4,6 +4,3 @@
|
|||
Cargo.lock
|
||||
apikey.txt
|
||||
|
||||
/srcgen/node_modules/
|
||||
/srcgen/.*.json
|
||||
|
||||
|
|
10
.travis.yml
10
.travis.yml
|
@ -1,10 +0,0 @@
|
|||
language: rust
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- rust: stable
|
||||
- rust: nightly
|
||||
env: FEATURES="--features nightly"
|
||||
env:
|
||||
global:
|
||||
- RUST_BACKTRACE=1
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"rust-analyzer.runnableEnv": [
|
||||
{
|
||||
// Set output levels for `tracing` logging.
|
||||
"env": {
|
||||
"RUST_BACKTRACE": "1",
|
||||
"RUST_LOG": "riven=debug"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
47
Cargo.toml
47
Cargo.toml
|
@ -1,43 +1,4 @@
|
|||
[package]
|
||||
name = "riven"
|
||||
version = "1.1.0"
|
||||
authors = ["Mingwei Samuel <mingwei.samuel@gmail.com>"]
|
||||
repository = "https://github.com/MingweiSamuel/Riven"
|
||||
description = "Riot Games API Library"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
edition = "2018"
|
||||
include = [ "src/**", "/README.md" ]
|
||||
keywords = [ "riot-games", "riot", "league", "league-of-legends" ]
|
||||
categories = [ "api-bindings", "web-programming::http-client" ]
|
||||
|
||||
#[badges]
|
||||
#travis-ci = { repository = "MingweiSamuel/Riven" }
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [ "nightly" ]
|
||||
|
||||
[features]
|
||||
nightly = [ "parking_lot/nightly" ]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
num_enum = "0.4"
|
||||
parking_lot = "0.10"
|
||||
reqwest = { version = "0.10", features = [ "gzip", "json" ] }
|
||||
scan_fmt = { version = "0.2", default-features = false }
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
serde_repr = "0.1"
|
||||
strum = "0.17"
|
||||
strum_macros = "0.17"
|
||||
tokio = { version = "0.2", default-features = false, features = [ "time" ] }
|
||||
url = "2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
colored = "1.8"
|
||||
env_logger = "0.7"
|
||||
fake_instant = "0.4"
|
||||
lazy_static = "1.4"
|
||||
tokio = "0.2"
|
||||
[workspace]
|
||||
members = [
|
||||
"riven",
|
||||
]
|
||||
|
|
12
LICENSE
12
LICENSE
|
@ -1,7 +1,7 @@
|
|||
Copyright 2019 Mingwei Samuel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
Copyright 2019 Mingwei Samuel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
305
README.md
305
README.md
|
@ -1,142 +1,163 @@
|
|||
<h1 align="center">
|
||||
Riven<br>
|
||||
</h1>
|
||||
<p align="center">
|
||||
<a href="https://github.com/MingweiSamuel/Riven/"><img src="https://cdn.communitydragon.org/latest/champion/Riven/square" width="20" height="20" alt="Riven Github"></a>
|
||||
<a href="https://crates.io/crates/riven"><img src="https://img.shields.io/crates/v/riven?style=flat-square&logo=rust" alt="Crates.io"></a>
|
||||
<a href="https://docs.rs/riven/"><img src="https://img.shields.io/badge/docs.rs-Riven-blue?style=flat-square&logo=read-the-docs&logoColor=white" alt="Docs.rs"></a>
|
||||
<a href="https://travis-ci.com/MingweiSamuel/Riven"><img src="https://img.shields.io/travis/com/mingweisamuel/riven?style=flat-square" alt="Travis CI"></a>
|
||||
<a href="https://github.com/rust-secure-code/safety-dance/"><img src="https://img.shields.io/badge/unsafe-forbidden-green.svg?style=flat-square" alt="unsafe forbidden"></a>
|
||||
</p>
|
||||
|
||||
Rust Library for the [Riot Games API](https://developer.riotgames.com/).
|
||||
|
||||
Riven's goals are _speed_, _reliability_, and _maintainability_. Riven handles rate limits and large requests with ease.
|
||||
Data structs and endpoints are automatically generated from the
|
||||
[Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)).
|
||||
|
||||
## Design
|
||||
|
||||
* Fast, asynchronous, thread-safe.
|
||||
* Automatically retries failed requests.
|
||||
* TFT API Support.
|
||||
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use riven::RiotApi;
|
||||
use riven::consts::Region;
|
||||
|
||||
// Enter tokio async runtime.
|
||||
let mut rt = tokio::runtime::Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
// Create RiotApi instance from key string.
|
||||
let api_key = "RGAPI-01234567-89ab-cdef-0123-456789abcdef";
|
||||
# /* (doc testing) */ let api_key = std::env!("RGAPI_KEY");
|
||||
let riot_api = RiotApi::with_key(api_key);
|
||||
|
||||
// Get summoner data.
|
||||
let summoner = riot_api.summoner_v4()
|
||||
.get_by_summoner_name(Region::NA, "잘못").await
|
||||
.expect("Get summoner failed.")
|
||||
.expect("There is no summoner with that name.");
|
||||
|
||||
// Print summoner name.
|
||||
println!("{} Champion Masteries:", summoner.name);
|
||||
|
||||
// Get champion mastery data.
|
||||
let masteries = riot_api.champion_mastery_v4()
|
||||
.get_all_champion_masteries(Region::NA, &summoner.id).await
|
||||
.expect("Get champion masteries failed.");
|
||||
|
||||
// Print champioon masteries.
|
||||
for (i, mastery) in masteries[..10].iter().enumerate() {
|
||||
println!("{: >2}) {: <9} {: >7} ({})", i + 1,
|
||||
mastery.champion_id.to_string(),
|
||||
mastery.champion_points, mastery.champion_level);
|
||||
}
|
||||
});
|
||||
```
|
||||
Output:
|
||||
```text
|
||||
잘 못 Champion Masteries:
|
||||
1) Riven 1219895 (7)
|
||||
2) Fiora 229714 (5)
|
||||
3) Katarina 175985 (5)
|
||||
4) Lee Sin 150546 (7)
|
||||
5) Jax 100509 (5)
|
||||
6) Gnar 76373 (6)
|
||||
7) Kai'Sa 64271 (5)
|
||||
8) Caitlyn 46479 (5)
|
||||
9) Irelia 46465 (5)
|
||||
10) Vladimir 37176 (5)
|
||||
```
|
||||
|
||||
### Nightly vs Stable
|
||||
|
||||
Enable the `nightly` feature to use nightly-only functionality. Mainly enables
|
||||
[nightly optimizations in the `parking_lot` crate](https://github.com/Amanieu/parking_lot#nightly-vs-stable).
|
||||
Also required for running async integration tests.
|
||||
|
||||
### Docs
|
||||
|
||||
[On docs.rs](https://docs.rs/riven/).
|
||||
|
||||
### Error Handling
|
||||
|
||||
Riven returns either `Result<T>` or `Result<Option<T>>` within futures.
|
||||
|
||||
If the `Result` is errored, this indicates that the API request failed to
|
||||
complete successfully, which may be due to bad user input, Riot server errors,
|
||||
incorrect API key, etc.
|
||||
|
||||
If the `Option` is `None`, this indicates that the request completed
|
||||
successfully but no data was returned. This happens in several situations, such
|
||||
as getting a summoner (by name) or match (by id) that doesn't exist, or getting
|
||||
spectator data for a summoner who is not in-game.
|
||||
Specifically, the API returned a 404 HTTP status code in this situation.
|
||||
|
||||
The error type used by Riven is `riven::RiotApiError`. It provides some basic
|
||||
diagnostic information, such as the source Reqwest error, the number of retries
|
||||
attempted, and the Reqwest `Response` object.
|
||||
|
||||
You can configure the number of time Riven retries using
|
||||
`RiotApiConfig::set_retries(...)` and the `RiotApi::with_config(config)`
|
||||
constructor. By default, Riven retries up to 3 times (4 requests total).
|
||||
Some errors, such as 400 client errors, are not retried as they would
|
||||
inevitably fail again.
|
||||
|
||||
### Semantic Versioning
|
||||
|
||||
This package follows semantic versioning to an extent. However, the Riot API
|
||||
itself changes often and does not follow semantic versioning, which makes
|
||||
things difficult. Out-of-date versions will slowly partially cease to work due
|
||||
to this.
|
||||
|
||||
When the API changes, this may result in breaking changes in the `models`
|
||||
module, `endpoints` module, and some of the `consts` module. "Handle accessor"
|
||||
methods may be removed from `RiotApi` if the corresponding endpoint is removed
|
||||
from the Riot API. These breaking changes will increment the **MINOR** version,
|
||||
not the major version.
|
||||
|
||||
Parts of Riven that do not depend on Riot API changes do follow semantic
|
||||
versioning.
|
||||
|
||||
### Additional Help
|
||||
|
||||
Feel free to [make an issue](https://github.com/MingweiSamuel/Riven/issues/new)
|
||||
if you are have any questions or trouble with Riven.
|
||||
|
||||
## Development
|
||||
|
||||
NodeJS is used to generate code for Riven. The
|
||||
[`srcgen/`](https://github.com/MingweiSamuel/Riven/tree/master/srcgen)
|
||||
folder contains the code and [doT.js](https://olado.github.io/doT/index.html)
|
||||
templates. `index.js` lists the JSON files downloaded and used to generate the
|
||||
code.
|
||||
|
||||
To set up the srcgen, you will first need to install NodeJS. Then enter the
|
||||
srcgen folder and run `npm ci` (or `npm install`) to install dependencies.
|
||||
|
||||
To run the srcgen use `node srcgen` from the main folder.
|
||||
|
||||
<h1 align="center">
|
||||
Riven<br>
|
||||
</h1>
|
||||
<p align="center">
|
||||
<a href="https://github.com/MingweiSamuel/Riven/"><img src="https://cdn.communitydragon.org/latest/champion/Riven/square" width="20" height="20" alt="Riven Github"></a>
|
||||
<a href="https://crates.io/crates/riven"><img src="https://img.shields.io/crates/v/riven?style=flat-square&logo=rust" alt="Crates.io"></a>
|
||||
<a href="https://docs.rs/riven/"><img src="https://img.shields.io/badge/docs.rs-Riven-blue?style=flat-square&logo=read-the-docs&logoColor=white" alt="Docs.rs"></a>
|
||||
<!--<a href="https://travis-ci.com/MingweiSamuel/Riven"><img src="https://img.shields.io/travis/com/mingweisamuel/riven?style=flat-square" alt="Travis CI"></a>-->
|
||||
<a href="https://github.com/rust-secure-code/safety-dance/"><img src="https://img.shields.io/badge/unsafe-forbidden-green.svg?style=flat-square" alt="unsafe forbidden"></a>
|
||||
</p>
|
||||
|
||||
Rust Library for the [Riot Games API](https://developer.riotgames.com/).
|
||||
|
||||
Riven's goals are _speed_, _reliability_, and _maintainability_. Riven handles rate limits and large requests with ease.
|
||||
Data structs and endpoints are automatically generated from the
|
||||
[Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)).
|
||||
|
||||
# Design
|
||||
|
||||
* Fast, asynchronous, thread-safe.
|
||||
* Automatically retries failed requests.
|
||||
* Supports all endpoints, kept up-to-date using [riotapi-schema](https://github.com/MingweiSamuel/riotapi-schema).
|
||||
|
||||
# Usage
|
||||
|
||||
```rust
|
||||
use riven::RiotApi;
|
||||
use riven::consts::PlatformRoute;
|
||||
|
||||
// Enter tokio async runtime.
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
// Create RiotApi instance from key string.
|
||||
let api_key = std::env!("RGAPI_KEY"); // "RGAPI-01234567-89ab-cdef-0123-456789abcdef";
|
||||
let riot_api = RiotApi::new(api_key);
|
||||
|
||||
// Get summoner data.
|
||||
let summoner = riot_api.summoner_v4()
|
||||
.get_by_summoner_name(PlatformRoute::NA1, "잘 못").await
|
||||
.expect("Get summoner failed.")
|
||||
.expect("There is no summoner with that name.");
|
||||
|
||||
// Print summoner name.
|
||||
println!("{} Champion Masteries:", summoner.name);
|
||||
|
||||
// Get champion mastery data.
|
||||
let masteries = riot_api.champion_mastery_v4()
|
||||
.get_all_champion_masteries_by_puuid(PlatformRoute::NA1, &summoner.puuid).await
|
||||
.expect("Get champion masteries failed.");
|
||||
|
||||
// Print champion masteries.
|
||||
for (i, mastery) in masteries.iter().take(10).enumerate() {
|
||||
println!("{: >2}) {: <9} {: >7} ({})", i + 1,
|
||||
mastery.champion_id.name().unwrap_or("UNKNOWN"),
|
||||
mastery.champion_points, mastery.champion_level);
|
||||
}
|
||||
});
|
||||
```
|
||||
Output:
|
||||
```text
|
||||
잘 못 Champion Masteries:
|
||||
1) Riven 1236866 (7)
|
||||
2) Fiora 230679 (5)
|
||||
3) Katarina 175985 (5)
|
||||
4) Lee Sin 156070 (7)
|
||||
5) Jax 102662 (5)
|
||||
6) Gnar 76373 (6)
|
||||
7) Kai'Sa 64271 (5)
|
||||
8) Caitlyn 46614 (5)
|
||||
9) Irelia 46465 (5)
|
||||
10) Vladimir 37176 (5)
|
||||
```
|
||||
The [`RiotApi` struct documentation](https://docs.rs/riven/latest/riven/struct.RiotApi.html)
|
||||
contains additional usage information. The [tests](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/tests)
|
||||
and [example proxy](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/examples/proxy)
|
||||
provide more example usage.
|
||||
|
||||
## Feature Flags
|
||||
|
||||
### Nightly vs Stable
|
||||
|
||||
Enable the `nightly` feature to use nightly-only functionality. This enables
|
||||
[nightly optimizations in the `parking_lot` crate](https://github.com/Amanieu/parking_lot#nightly-vs-stable).
|
||||
|
||||
```toml
|
||||
riven = { version = "...", features = [ "nightly" ] }
|
||||
```
|
||||
|
||||
### rustls
|
||||
|
||||
Riven uses [reqwest](https://github.com/seanmonstar/reqwest) for making requests. By default, reqwest uses the native TLS library.
|
||||
If you prefer using [rustls](https://github.com/ctz/rustls) you can do so by turning off the Riven default features
|
||||
and specifying the `rustls-tls` feature:
|
||||
|
||||
```toml
|
||||
riven = { version = "...", default-features = false, features = [ "rustls-tls" ] }
|
||||
```
|
||||
|
||||
Riven is additionally able to produce [tracing](https://docs.rs/tracing) spans for requests if the `tracing` feature is enabled. This feature is disabled by default.
|
||||
|
||||
## Docs
|
||||
|
||||
[On docs.rs](https://docs.rs/riven/).
|
||||
|
||||
## Error Handling
|
||||
|
||||
Riven returns either `Result<T>` or `Result<Option<T>>` within futures.
|
||||
|
||||
If the `Result` is errored, this indicates that the API request failed to
|
||||
complete successfully, which may be due to bad user input, Riot server errors,
|
||||
incorrect API key, etc.
|
||||
|
||||
If the `Option` is `None`, this indicates that the request completed
|
||||
successfully but no data was returned. This happens in several situations, such
|
||||
as getting a summoner (by name) or match (by id) that doesn't exist, or getting
|
||||
spectator data for a summoner who is not in-game.
|
||||
Specifically, the API returned a 404 HTTP status code in this situation.
|
||||
|
||||
The error type used by Riven is `riven::RiotApiError`. It provides some basic
|
||||
diagnostic information, such as the source Reqwest error, the number of retries
|
||||
attempted, and the Reqwest `Response` object.
|
||||
|
||||
You can configure the number of time Riven retries using
|
||||
`RiotApiConfig::set_retries(...)` and the `RiotApi::from_config(config)`
|
||||
constructor. By default, Riven retries up to 3 times (4 requests total).
|
||||
Some errors, such as 400 client errors, are not retried as they would
|
||||
inevitably fail again.
|
||||
|
||||
## Semantic Versioning
|
||||
|
||||
This package follows semantic versioning to an extent. However, the Riot API
|
||||
itself changes often and does not follow semantic versioning, which makes
|
||||
things difficult. Out-of-date versions will slowly partially cease to work due
|
||||
to this.
|
||||
|
||||
When the API changes, this may result in breaking changes in the `models`
|
||||
module, `endpoints` module, and some of the `consts` module. "Handle accessor"
|
||||
methods may be removed from `RiotApi` if the corresponding endpoint is removed
|
||||
from the Riot API. These breaking changes will increment the **MINOR** version,
|
||||
not the major version.
|
||||
|
||||
Parts of Riven that do not depend on Riot API changes do follow semantic
|
||||
versioning.
|
||||
|
||||
## Additional Help
|
||||
|
||||
Feel free to [make an issue](https://github.com/MingweiSamuel/Riven/issues/new)
|
||||
if you are have any questions or trouble with Riven.
|
||||
|
||||
# Development
|
||||
|
||||
NodeJS is used to generate code for Riven. The
|
||||
[`riven/srcgen`](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/srcgen)
|
||||
folder contains the code and [doT.js](https://olado.github.io/doT/index.html)
|
||||
templates. `index.js` lists the JSON files downloaded and used to generate the
|
||||
code.
|
||||
|
||||
To set up the srcgen, you will first need to install NodeJS. Then enter the
|
||||
`riven/srcgen` folder and run `npm ci` (or `npm install`) to install
|
||||
dependencies.
|
||||
|
||||
To run the srcgen use `node riven/srcgen` from the repository root.
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
[package]
|
||||
name = "riven"
|
||||
version = "2.32.0"
|
||||
authors = ["Mingwei Samuel <mingwei.samuel@gmail.com>"]
|
||||
repository = "https://github.com/MingweiSamuel/Riven"
|
||||
description = "Riot Games API Library"
|
||||
readme = "../README.md"
|
||||
license = "MIT"
|
||||
edition = "2018"
|
||||
include = [ "src/**", "../README.md" ]
|
||||
keywords = [ "riot-games", "riot", "league", "league-of-legends" ]
|
||||
categories = [ "api-bindings", "web-programming::http-client" ]
|
||||
|
||||
#[badges]
|
||||
#travis-ci = { repository = "MingweiSamuel/Riven" }
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [ "nightly" ]
|
||||
|
||||
[features]
|
||||
default = [ "default-tls" ]
|
||||
|
||||
nightly = [ "parking_lot/nightly" ]
|
||||
|
||||
default-tls = [ "reqwest/default-tls" ]
|
||||
native-tls = [ "reqwest/native-tls" ]
|
||||
rustls-tls = [ "reqwest/rustls-tls" ]
|
||||
|
||||
deny-unknown = [ "deny-unknown-fields", "deny-unknown-enum-variants" ]
|
||||
# If enabled, extra unknown fields encountered during deserialization will
|
||||
# cause an error instead of being ignored.
|
||||
deny-unknown-fields = []
|
||||
# If enabled, deserialization of unknown enum variants will cause an error
|
||||
# instead of being deserialized to `UNKNOWN` or other integer variants.
|
||||
deny-unknown-enum-variants = [ "deny-unknown-enum-variants-strings", "deny-unknown-enum-variants-integers" ]
|
||||
deny-unknown-enum-variants-strings = []
|
||||
deny-unknown-enum-variants-integers = []
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.4"
|
||||
log = "0.4"
|
||||
num_enum = "0.5"
|
||||
parking_lot = "0.12"
|
||||
reqwest = { version = "0.11", default-features = false, features = [ "gzip", "json" ] }
|
||||
scan_fmt = { version = "0.2", default-features = false }
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
serde_json = "1.0"
|
||||
serde_repr = "0.1"
|
||||
strum = "0.20"
|
||||
strum_macros = "0.20"
|
||||
tokio = { version = "1", default-features = false, features = [ "time", "macros", "parking_lot" ] }
|
||||
tracing = { version = "0.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
colored = "2"
|
||||
env_logger = "0.10.0"
|
||||
fake_instant = "0.5.0"
|
||||
futures = "0.3"
|
||||
hyper = { version = "0.14", features = [ "server" ] }
|
||||
tokio = { version = "1", features = [ "full" ] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.2"
|
|
@ -0,0 +1,33 @@
|
|||
# Riven Example Proxy
|
||||
|
||||
This is a simple example implementation of a Riot API proxy server using
|
||||
[`hyper`](https://github.com/hyperium/hyper). This adds the API key and
|
||||
forwards requests to the Riot API, then returns and forwards responses back to
|
||||
the requester.
|
||||
|
||||
This can handle not just GET endpoints but also POST and PUT (and any others)
|
||||
with bodies, however it does not handle RSO endpoints which require an
|
||||
`Authorization` header parameter. This handles errors but provides only minimal
|
||||
failure information. Rate limits are enforced, so requests will wait to complete
|
||||
when Riven is at the rate limit.
|
||||
|
||||
Set `RGAPI_KEY` env var then run:
|
||||
```bash
|
||||
export RGAPI_KEY=RGAPI-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
|
||||
cargo run --example proxy
|
||||
```
|
||||
|
||||
Test in your browser or using `curl`. The first path segment specifies the region:
|
||||
```json
|
||||
$ curl http://localhost:3000/na1/lol/summoner/v4/summoners/by-name/LugnutsK
|
||||
{"id":"...","accountId":"...","puuid":"...","name":"LugnutsK","profileIconId":4540,"revisionDate":1589704662000,"summonerLevel":111}
|
||||
|
||||
$ curl http://localhost:3000/eu/val/status/v1/platform-data
|
||||
{"id": "EU", "name": "Europe", "locales": ["..."], "maintenances": [], "incidents": []}
|
||||
|
||||
$ curl http://localhost:3000/americas/lol/tournament-stub/v5/providers -H "Content-Type: application/json" -d '{"region":"JP","url":"https://github.com/MingweiSamuel/Riven"}'
|
||||
1
|
||||
|
||||
$ curl http://localhost:3000/na1/unknown/endpoint
|
||||
{"error":"Riot API endpoint method not found."}
|
||||
```
|
|
@ -0,0 +1,193 @@
|
|||
// #![deny(warnings)]
|
||||
|
||||
use std::convert::Infallible;
|
||||
|
||||
use http::{Method, Request, Response, StatusCode};
|
||||
use hyper::header::HeaderValue;
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{http, Body, Server};
|
||||
use lazy_static::lazy_static;
|
||||
use riven::consts::Route;
|
||||
use riven::{RiotApi, RiotApiConfig};
|
||||
use tracing as log;
|
||||
|
||||
lazy_static! {
|
||||
/// Create lazy static RiotApi instance.
|
||||
/// Easier than passing it around.
|
||||
pub static ref RIOT_API: RiotApi = {
|
||||
let api_key = std::env::var("RGAPI_KEY").ok()
|
||||
.or_else(|| {
|
||||
let path: std::path::PathBuf = [ env!("CARGO_MANIFEST_DIR"), "../../apikey.txt" ].iter().collect();
|
||||
std::fs::read_to_string(path).ok()
|
||||
})
|
||||
.expect("Failed to find RGAPI_KEY env var or apikey.txt.");
|
||||
RiotApi::new(RiotApiConfig::with_key(api_key.trim()).preconfig_burst())
|
||||
};
|
||||
}
|
||||
|
||||
/// Helper to create JSON error responses.
|
||||
fn create_json_response(body: &'static str, status: StatusCode) -> Response<Body> {
|
||||
let mut resp = Response::new(Body::from(body));
|
||||
*resp.status_mut() = status;
|
||||
resp.headers_mut().insert(
|
||||
hyper::header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/json"),
|
||||
);
|
||||
resp
|
||||
}
|
||||
|
||||
/// Main request handler service.
|
||||
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||
let (parts, body) = req.into_parts();
|
||||
let http::request::Parts { method, uri, .. } = parts;
|
||||
|
||||
// Handle path.
|
||||
let path_data_opt = parse_path(&method, uri.path());
|
||||
let (route, method_id, req_path) = match path_data_opt {
|
||||
None => {
|
||||
return Ok(create_json_response(
|
||||
r#"{"error":"Riot API endpoint method not found."}"#,
|
||||
StatusCode::NOT_FOUND,
|
||||
))
|
||||
}
|
||||
Some(path_data) => path_data,
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
"Request to route {:?}, method ID {:?}: {} {:?}.",
|
||||
route,
|
||||
method_id,
|
||||
method,
|
||||
req_path
|
||||
);
|
||||
|
||||
// Convert http:request::Parts from hyper to reqwest's RequestBuilder.
|
||||
let body = match hyper::body::to_bytes(body).await {
|
||||
Err(err) => {
|
||||
log::info!("Error handling request body: {:#?}", err);
|
||||
return Ok(create_json_response(
|
||||
r#"{"error":"Failed to handle request body."}"#,
|
||||
StatusCode::BAD_REQUEST,
|
||||
));
|
||||
}
|
||||
Ok(bytes) => bytes,
|
||||
};
|
||||
|
||||
let req_builder = RIOT_API.request(method, route.into(), req_path).body(body);
|
||||
|
||||
// Send request to Riot API.
|
||||
let resp_result = RIOT_API
|
||||
.execute_raw(method_id, route.into(), req_builder)
|
||||
.await;
|
||||
let resp_info = match resp_result {
|
||||
Err(err) => {
|
||||
log::info!("Riot API error: {:#?}", err.source_reqwest_error());
|
||||
return Ok(create_json_response(
|
||||
r#"{"error":"Riot API request failed."}"#,
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
));
|
||||
}
|
||||
Ok(resp_info) => resp_info,
|
||||
};
|
||||
|
||||
// Write output.
|
||||
let api_response = resp_info.response;
|
||||
let mut out_response = Response::default();
|
||||
*out_response.headers_mut() = api_response.headers().clone();
|
||||
|
||||
// If None, write "null" to body to be extra nice.
|
||||
if resp_info.status_none {
|
||||
*out_response.body_mut() = Body::from("null");
|
||||
}
|
||||
// Otherwise copy body.
|
||||
else {
|
||||
*out_response.status_mut() = api_response.status();
|
||||
|
||||
// Using streams would be faster.
|
||||
let bytes_result = api_response.bytes().await;
|
||||
let bytes = match bytes_result {
|
||||
Err(_err) => {
|
||||
return Ok(create_json_response(
|
||||
r#"{"error":"Failed to get body from Riot API response."}"#,
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
))
|
||||
}
|
||||
Ok(bytes) => bytes,
|
||||
};
|
||||
*out_response.body_mut() = Body::from(bytes);
|
||||
}
|
||||
Ok(out_response)
|
||||
}
|
||||
|
||||
/// Gets the region, method_id, and Riot API path based on the given http method and path.
|
||||
fn parse_path<'a>(
|
||||
http_method: &Method,
|
||||
req_path: &'a str,
|
||||
) -> Option<(Route, &'static str, &'a str)> {
|
||||
// Split URI into region and rest of path.
|
||||
let req_path = req_path.trim_start_matches('/');
|
||||
let (route, req_path) = req_path.split_at(req_path.find('/')?);
|
||||
let route: Route = route.to_uppercase().parse().ok()?;
|
||||
|
||||
// Find method_id for given path.
|
||||
let method_id = find_matching_method_id(http_method, req_path)?;
|
||||
|
||||
Some((route, method_id, req_path))
|
||||
}
|
||||
|
||||
/// Finds the method_id given the request path.
|
||||
fn find_matching_method_id(http_method: &Method, req_path: &str) -> Option<&'static str> {
|
||||
for (endpoint_http_method, ref_path, method_id) in &riven::meta::ALL_ENDPOINTS {
|
||||
if http_method == endpoint_http_method && paths_match(ref_path, req_path) {
|
||||
return Some(method_id);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Checks if the request path (req_path) matches the reference path (ref_path).
|
||||
fn paths_match(ref_path: &str, req_path: &str) -> bool {
|
||||
let mut ref_iter = ref_path.split('/');
|
||||
let mut req_iter = req_path.split('/');
|
||||
loop {
|
||||
match (ref_iter.next(), req_iter.next()) {
|
||||
(None, None) => return true,
|
||||
(Some(ref_seg), Some(req_seg)) => {
|
||||
if ref_seg.starts_with('{') || ref_seg == req_seg {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Setup loggers.
|
||||
// env_logger::init();
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Trigger lazy_static.
|
||||
let _ = &*RIOT_API;
|
||||
|
||||
// For every connection, we must make a `Service` to handle all
|
||||
// incoming HTTP requests on said connection.
|
||||
let make_svc = make_service_fn(|_conn| {
|
||||
// This is the `Service` that will handle the connection.
|
||||
// `service_fn` is a helper to convert a function that
|
||||
// returns a Response into a `Service`.
|
||||
async { Ok::<_, Infallible>(service_fn(handle_request)) }
|
||||
});
|
||||
|
||||
let addr = ([127, 0, 0, 1], 3000).into();
|
||||
|
||||
let server = Server::bind(&addr).serve(make_svc);
|
||||
|
||||
log::info!("Listening on http://{}", addr);
|
||||
|
||||
server.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
//! Configuration of RiotApi.
|
||||
use std::time::Duration;
|
||||
|
||||
use reqwest::header::{HeaderMap, HeaderValue};
|
||||
use reqwest::ClientBuilder;
|
||||
|
||||
/// Configuration for instantiating RiotApi.
|
||||
#[derive(Debug)]
|
||||
pub struct RiotApiConfig {
|
||||
pub(crate) base_url: String,
|
||||
pub(crate) retries: u8,
|
||||
pub(crate) app_rate_usage_factor: f32,
|
||||
pub(crate) method_rate_usage_factor: f32,
|
||||
pub(crate) burst_factor: f32,
|
||||
pub(crate) duration_overhead: Duration,
|
||||
pub(crate) client_builder: Option<ClientBuilder>,
|
||||
}
|
||||
|
||||
impl RiotApiConfig {
|
||||
/// Request header name for the Riot API key, `"X-Riot-Token"`.
|
||||
///
|
||||
/// When using `set_client_builder`, the supplied builder should include
|
||||
/// this default header with the Riot API key as the value.
|
||||
pub const RIOT_KEY_HEADER: &'static str = "X-Riot-Token";
|
||||
|
||||
/// `"https://{}.api.riotgames.com"`
|
||||
///
|
||||
/// Default base URL, including `{}` placeholder for region platform.
|
||||
pub const DEFAULT_BASE_URL: &'static str = "https://{}.api.riotgames.com";
|
||||
|
||||
/// `3`
|
||||
///
|
||||
/// Default number of retries.
|
||||
pub const DEFAULT_RETRIES: u8 = 3;
|
||||
|
||||
/// `1.0`
|
||||
///
|
||||
/// Default rate limit usage factor.
|
||||
pub const DEFAULT_RATE_USAGE_FACTOR: f32 = 1.0;
|
||||
|
||||
/// `0.99`
|
||||
///
|
||||
/// Default `burst_factor`, also used by `preconfig_burst`.
|
||||
pub const PRECONFIG_BURST_BURST_FACTOR: f32 = 0.99;
|
||||
/// `989` ms
|
||||
///
|
||||
/// Default `duration_overhead`, also used by `preconfig_burst`.
|
||||
pub const PRECONFIG_BURST_DURATION_OVERHEAD: Duration = Duration::from_millis(989);
|
||||
|
||||
/// `0.47`
|
||||
///
|
||||
/// `burst_factor` used by `preconfig_throughput`.
|
||||
pub const PRECONFIG_THROUGHPUT_BURST_FACTOR: f32 = 0.47;
|
||||
/// `10` ms.
|
||||
///
|
||||
/// `duration_overhead` used by `preconfig_throughput`.
|
||||
pub const PRECONFIG_THROUGHPUT_DURATION_OVERHEAD: Duration = Duration::from_millis(10);
|
||||
|
||||
/// Creates a new `RiotApiConfig` with the given `api_key` with the following
|
||||
/// configuration:
|
||||
///
|
||||
/// * `retries = 3` (`RiotApiConfig::DEFAULT_RETRIES`).
|
||||
/// * `burst_factor = 0.99` (`preconfig_burst`).
|
||||
/// * `duration_overhead = 989 ms` (`preconfig_burst`).
|
||||
///
|
||||
/// `api_key` should be a Riot Games API key from
|
||||
/// [https://developer.riotgames.com/](https://developer.riotgames.com/),
|
||||
/// and should look like `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`.
|
||||
pub fn with_key(api_key: impl AsRef<[u8]>) -> Self {
|
||||
let mut default_headers = HeaderMap::new();
|
||||
default_headers.insert(
|
||||
Self::RIOT_KEY_HEADER,
|
||||
HeaderValue::from_bytes(api_key.as_ref()).unwrap(),
|
||||
);
|
||||
|
||||
Self {
|
||||
base_url: Self::DEFAULT_BASE_URL.into(),
|
||||
retries: Self::DEFAULT_RETRIES,
|
||||
app_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
|
||||
method_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
|
||||
burst_factor: Self::PRECONFIG_BURST_BURST_FACTOR,
|
||||
duration_overhead: Self::PRECONFIG_BURST_DURATION_OVERHEAD,
|
||||
client_builder: Some(ClientBuilder::new().default_headers(default_headers)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `RiotApiConfig` with the given client builder.
|
||||
///
|
||||
/// The client builder default headers should include a value for
|
||||
/// [`RiotApiConfig::RIOT_KEY_HEADER`] (`"X-Riot-Token"`), otherwise authentication will fail.
|
||||
///
|
||||
/// * `retries = 3` (`RiotApiConfig::DEFAULT_RETRIES`).
|
||||
/// * `burst_factor = 0.99` (`preconfig_burst`).
|
||||
/// * `duration_overhead = 989 ms` (`preconfig_burst`).
|
||||
pub fn with_client_builder(client_builder: ClientBuilder) -> Self {
|
||||
Self {
|
||||
base_url: Self::DEFAULT_BASE_URL.to_owned(),
|
||||
retries: Self::DEFAULT_RETRIES,
|
||||
app_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
|
||||
method_rate_usage_factor: Self::DEFAULT_RATE_USAGE_FACTOR,
|
||||
burst_factor: Self::PRECONFIG_BURST_BURST_FACTOR,
|
||||
duration_overhead: Self::PRECONFIG_BURST_DURATION_OVERHEAD,
|
||||
client_builder: Some(client_builder),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets rate limiting settings to preconfigured values optimized for burst,
|
||||
/// low latency:
|
||||
///
|
||||
/// * `burst_factor = 0.99` (`PRECONFIG_BURST_BURST_FACTOR`).
|
||||
/// * `duration_overhead = 989 ms` (`PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS`).
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn preconfig_burst(mut self) -> Self {
|
||||
self.burst_factor = Self::PRECONFIG_BURST_BURST_FACTOR;
|
||||
self.duration_overhead = Self::PRECONFIG_BURST_DURATION_OVERHEAD;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the rate limiting settings to preconfigured values optimized for
|
||||
/// high throughput:
|
||||
///
|
||||
/// * `burst_factor = 0.47` (`PRECONFIG_THROUGHPUT_BURST_FACTOR`).
|
||||
/// * `duration_overhead = 10 ms` (`PRECONFIG_THROUGHPUT_DURATION_OVERHEAD_MILLIS`).
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn preconfig_throughput(mut self) -> Self {
|
||||
self.burst_factor = Self::PRECONFIG_THROUGHPUT_BURST_FACTOR;
|
||||
self.duration_overhead = Self::PRECONFIG_THROUGHPUT_DURATION_OVERHEAD;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the base url for requests. The string should contain a `"{}"`
|
||||
/// literal which will be replaced with the region platform name. (However
|
||||
/// multiple or zero `"{}"`s may be included if needed).
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn set_base_url(mut self, base_url: impl Into<String>) -> Self {
|
||||
self.base_url = base_url.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set number of times to retry requests. Naturally, only retryable requests
|
||||
/// will be retried: responses with status codes 5xx or 429 (after waiting
|
||||
/// for retry-after headers). A value of `0` means one request will be sent
|
||||
/// and it will not be retried if it fails.
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn set_retries(mut self, retries: u8) -> Self {
|
||||
self.retries = retries;
|
||||
self
|
||||
}
|
||||
|
||||
/// The rate limit usage percentage controls how much of the API key's rate
|
||||
/// limit will be used. The default value of `1.0` means the entirety of
|
||||
/// the rate limit may be used if it is needed. This applies to both the
|
||||
/// API key's rate limit (per route) _and_ to endpoint method rate limits.
|
||||
///
|
||||
/// Setting a value lower than `1.0` can be useful if you are running
|
||||
/// multiple API instances on the same API key.
|
||||
///
|
||||
/// For example, four instances, possibly running on different machines,
|
||||
/// could each have a value of `0.25` to share an API key's rate limit
|
||||
/// evenly.
|
||||
///
|
||||
/// Note that if you have multiple instances hitting _different_ methods,
|
||||
/// you should use [Self::set_app_rate_usage_factor()] and [Self::set_method_rate_usage_factor()]
|
||||
/// separately, as this sets both.
|
||||
///
|
||||
/// This also can be used to reduce the chance of hitting 429s, although
|
||||
/// 429s should be rare even with this set to `1.0`.
|
||||
///
|
||||
/// # Panics
|
||||
/// If `rate_usage_factor` is not in range (0, 1].
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn set_rate_usage_factor(mut self, rate_usage_factor: f32) -> Self {
|
||||
// Use inverted check to handle NaN.
|
||||
if 0.0 < rate_usage_factor && rate_usage_factor <= 1.0 {
|
||||
self.app_rate_usage_factor = rate_usage_factor;
|
||||
self.method_rate_usage_factor = rate_usage_factor;
|
||||
return self;
|
||||
}
|
||||
panic!(
|
||||
"rate_usage_factor \"{}\" not in range (0, 1].",
|
||||
rate_usage_factor
|
||||
);
|
||||
}
|
||||
|
||||
/// See [Self::set_rate_usage_factor]. Setting this is useful if you have multiple
|
||||
/// instances sharing the app rate limit, but are hitting distinct methods
|
||||
/// and therefore do not need their method usage decreased.
|
||||
///
|
||||
/// # Panics
|
||||
/// If `app_rate_usage_factor` is not in range (0, 1\].
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn set_app_rate_usage_factor(mut self, app_rate_usage_factor: f32) -> Self {
|
||||
// Use inverted check to handle NaN.
|
||||
if 0.0 < app_rate_usage_factor && app_rate_usage_factor <= 1.0 {
|
||||
self.app_rate_usage_factor = app_rate_usage_factor;
|
||||
return self;
|
||||
}
|
||||
panic!(
|
||||
"app_rate_usage_factor \"{}\" not in range (0, 1].",
|
||||
app_rate_usage_factor
|
||||
);
|
||||
}
|
||||
|
||||
/// See [Self::set_rate_usage_factor] and [Self::set_app_rate_usage_factor].
|
||||
/// This method is mainly provided for completeness, though it may be
|
||||
/// useful in advanced use cases.
|
||||
///
|
||||
/// # Panics
|
||||
/// If `method_rate_usage_factor` is not in range (0, 1\].
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn set_method_rate_usage_factor(mut self, method_rate_usage_factor: f32) -> Self {
|
||||
// Use inverted check to handle NaN.
|
||||
if 0.0 < method_rate_usage_factor && method_rate_usage_factor <= 1.0 {
|
||||
self.method_rate_usage_factor = method_rate_usage_factor;
|
||||
return self;
|
||||
}
|
||||
panic!(
|
||||
"method_rate_usage_factor \"{}\" not in range (0, 1].",
|
||||
method_rate_usage_factor
|
||||
);
|
||||
}
|
||||
|
||||
/// Burst percentage controls how many burst requests are allowed and
|
||||
/// therefore how requests are spread out. Higher equals more burst,
|
||||
/// less spread. Lower equals less burst, more spread.
|
||||
///
|
||||
/// The value must be in the range (0, 1];
|
||||
/// Between 0, exclusive, and 1, inclusive. However values should generally
|
||||
/// be larger than 0.25.
|
||||
///
|
||||
/// Burst percentage behaves as follows:<br>
|
||||
/// A burst percentage of x% means, for each token bucket, "x% of the
|
||||
/// tokens can be used in x% of the bucket duration." So, for example, if x
|
||||
/// is 90%, a bucket would allow 90% of the requests to be made without
|
||||
/// any delay. Then, after waiting 90% of the bucket's duration, the
|
||||
/// remaining 10% of requests could be made.
|
||||
///
|
||||
/// A burst percentage of 100% results in no request spreading, which would
|
||||
/// allow for the largest bursts and lowest latency, but could result in
|
||||
/// 429s as bucket boundaries occur.
|
||||
///
|
||||
/// A burst percentage of near 0% results in high spreading causing
|
||||
/// temporally equidistant requests. This prevents 429s but has the highest
|
||||
/// latency. Additionally, if the number of tokens is high, this may lower
|
||||
/// the overall throughput due to the rate at which requests can be
|
||||
/// scheduled.
|
||||
///
|
||||
/// Therefore, for interactive applications like summoner & match history
|
||||
/// lookup, a higher percentage may be better. For data-collection apps
|
||||
/// like champion winrate aggregation, a medium-low percentage may be
|
||||
/// better.
|
||||
///
|
||||
/// # Panics
|
||||
/// If `burst_factor` is not in range (0, 1\].
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn set_burst_factor(mut self, burst_factor: f32) -> Self {
|
||||
// Use inverted check to handle NaN.
|
||||
if 0.0 < burst_factor && burst_factor <= 1.0 {
|
||||
self.burst_factor = burst_factor;
|
||||
return self;
|
||||
}
|
||||
panic!("burst_factor \"{}\" not in range (0, 1].", burst_factor);
|
||||
}
|
||||
|
||||
/// Sets the additional bucket duration to consider when rate limiting.
|
||||
/// Increasing this value will decrease the chances of 429s, but will lower
|
||||
/// the overall throughput.
|
||||
///
|
||||
/// In a sense, the `duration_overhead` is how much to "widen" the temporal
|
||||
/// width of buckets.
|
||||
///
|
||||
/// Given a particular Riot Game API rate limit bucket that allows N requests
|
||||
/// per D duration, when counting requests this library will consider requests
|
||||
/// sent in the past `D + duration_overhead` duration.
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn set_duration_overhead(mut self, duration_overhead: Duration) -> Self {
|
||||
self.duration_overhead = duration_overhead;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> From<T> for RiotApiConfig {
|
||||
fn from(api_key: T) -> Self {
|
||||
Self::with_key(api_key)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,60 +1,77 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||
|
||||
/// LoL and TFT rank divisions, I, II, III, IV, and (deprecated) V.
|
||||
///
|
||||
/// Ordered such that "higher" divisions are greater than "lower" ones: `Division::I > Division::IV`.
|
||||
///
|
||||
/// Repr'd as equivalent numeric values, (1, 2, 3, 4, 5).
|
||||
///
|
||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator). Iterator excludes deprecated `Division::V`.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum Division {
|
||||
I = 1,
|
||||
II = 2,
|
||||
III = 3,
|
||||
IV = 4,
|
||||
#[deprecated(note="Removed for 2019.")]
|
||||
V = 5,
|
||||
}
|
||||
|
||||
serde_string!(Division);
|
||||
|
||||
/// Returns a DoubleEndedIterator of I, II, III, IV.
|
||||
/// Ordered from high rank (I) to low (IV).
|
||||
/// Excludes V, which is deprecated.
|
||||
impl IntoEnumIterator for Division {
|
||||
type Iterator = std::slice::Iter<'static, Self>;
|
||||
fn iter() -> Self::Iterator {
|
||||
[ Self::I, Self::II, Self::III, Self::IV ].iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Division {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
u8::from(*self).cmp(&u8::from(*other)).reverse()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Division {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sort() {
|
||||
assert!(Division::IV < Division::I);
|
||||
}
|
||||
}
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||
|
||||
/// LoL and TFT rank divisions, I, II, III, IV, and (deprecated) V.
|
||||
///
|
||||
/// Ordered such that "higher" divisions are greater than "lower" ones: `Division::I > Division::IV`.
|
||||
///
|
||||
/// Repr'd as equivalent numeric values, (1, 2, 3, 4, 5).
|
||||
///
|
||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator). Iterator excludes deprecated `Division::V`.
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Hash,
|
||||
EnumString,
|
||||
Display,
|
||||
AsRefStr,
|
||||
IntoStaticStr,
|
||||
IntoPrimitive,
|
||||
TryFromPrimitive,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum Division {
|
||||
/// Division 1, the best/highest division in a [`Tier`](crate::consts::Tier), or the only division in
|
||||
/// [apex tiers](crate::consts::Tier::is_apex).
|
||||
I = 1,
|
||||
/// Division 2, the second highest division.
|
||||
II = 2,
|
||||
/// Division 3, the third highest division.
|
||||
III = 3,
|
||||
/// Division 4, the fourth and lowest division since 2019.
|
||||
IV = 4,
|
||||
/// Division 5, the lowest division, only used before 2019.
|
||||
#[deprecated(note = "Removed for 2019.")]
|
||||
V = 5,
|
||||
}
|
||||
|
||||
/// Returns a DoubleEndedIterator of I, II, III, IV.
|
||||
/// Ordered from high rank (I) to low (IV).
|
||||
/// Excludes V, which is deprecated.
|
||||
impl IntoEnumIterator for Division {
|
||||
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
||||
fn iter() -> Self::Iterator {
|
||||
[Self::I, Self::II, Self::III, Self::IV].iter().copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Division {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
u8::from(*self).cmp(&u8::from(*other)).reverse()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Division {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sort() {
|
||||
assert!(Division::IV < Division::I);
|
||||
}
|
||||
}
|
|
@ -1,57 +1,78 @@
|
|||
///////////////////////////////////////////////
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// League of Legends game mode, such as Classic,
|
||||
/// ARAM, URF, One For All, Ascension, etc.
|
||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||
#[repr(u8)]
|
||||
pub enum GameMode {
|
||||
/// Classic Summoner's Rift and Twisted Treeline games
|
||||
CLASSIC,
|
||||
/// Dominion/Crystal Scar games
|
||||
ODIN,
|
||||
/// ARAM games
|
||||
ARAM,
|
||||
/// Tutorial games
|
||||
TUTORIAL,
|
||||
/// URF games
|
||||
URF,
|
||||
/// Doom Bot games
|
||||
DOOMBOTSTEEMO,
|
||||
/// One for All games
|
||||
ONEFORALL,
|
||||
/// Ascension games
|
||||
ASCENSION,
|
||||
/// Snowdown Showdown games
|
||||
FIRSTBLOOD,
|
||||
/// Legend of the Poro King games
|
||||
KINGPORO,
|
||||
/// Nexus Siege games
|
||||
SIEGE,
|
||||
/// Blood Hunt Assassin games
|
||||
ASSASSINATE,
|
||||
/// All Random Summoner's Rift games
|
||||
ARSR,
|
||||
/// Dark Star: Singularity games
|
||||
DARKSTAR,
|
||||
/// Star Guardian Invasion games
|
||||
STARGUARDIAN,
|
||||
/// PROJECT: Hunters games
|
||||
PROJECT,
|
||||
/// Nexus Blitz games
|
||||
GAMEMODEX,
|
||||
/// Odyssey: Extraction games
|
||||
ODYSSEY,
|
||||
}
|
||||
|
||||
serde_string!(GameMode);
|
||||
///////////////////////////////////////////////
|
||||
|
||||
use strum_macros::{ EnumString, EnumVariantNames, IntoStaticStr };
|
||||
|
||||
/// League of Legends game mode, such as Classic,
|
||||
/// ARAM, URF, One For All, Ascension, etc.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, EnumVariantNames, IntoStaticStr)]
|
||||
#[repr(u8)]
|
||||
pub enum GameMode {
|
||||
/// Catch-all variant for new, unknown game modes.
|
||||
#[strum(default)]
|
||||
UNKNOWN(String),
|
||||
|
||||
/// ARAM games
|
||||
ARAM,
|
||||
/// All Random Summoner's Rift games
|
||||
ARSR,
|
||||
/// Ascension games
|
||||
ASCENSION,
|
||||
/// Blood Hunt Assassin games
|
||||
ASSASSINATE,
|
||||
/// 2v2v2v2
|
||||
CHERRY,
|
||||
/// Classic Summoner's Rift and Twisted Treeline games
|
||||
CLASSIC,
|
||||
/// Dark Star: Singularity games
|
||||
DARKSTAR,
|
||||
/// Doom Bot games
|
||||
DOOMBOTSTEEMO,
|
||||
/// Snowdown Showdown games
|
||||
FIRSTBLOOD,
|
||||
/// Nexus Blitz games
|
||||
GAMEMODEX,
|
||||
/// Legend of the Poro King games
|
||||
KINGPORO,
|
||||
/// Nexus Blitz games
|
||||
NEXUSBLITZ,
|
||||
/// Dominion/Crystal Scar games
|
||||
ODIN,
|
||||
/// Odyssey: Extraction games
|
||||
ODYSSEY,
|
||||
/// One for All games
|
||||
ONEFORALL,
|
||||
/// Practice tool training games.
|
||||
PRACTICETOOL,
|
||||
/// PROJECT: Hunters games
|
||||
PROJECT,
|
||||
/// Nexus Siege games
|
||||
SIEGE,
|
||||
/// Star Guardian Invasion games
|
||||
STARGUARDIAN,
|
||||
/// Teamfight Tactics, used in `spectator-v4` endpoints.
|
||||
TFT,
|
||||
/// Tutorial games
|
||||
TUTORIAL,
|
||||
/// Tutorial: Welcome to League.
|
||||
TUTORIAL_MODULE_1,
|
||||
/// Tutorial: Power Up.
|
||||
TUTORIAL_MODULE_2,
|
||||
/// Tutorial: Shop for Gear.
|
||||
TUTORIAL_MODULE_3,
|
||||
/// Ultimate Spellbook games
|
||||
ULTBOOK,
|
||||
/// URF games
|
||||
URF,
|
||||
}
|
||||
|
||||
serde_strum_unknown!(GameMode);
|
|
@ -1,25 +1,35 @@
|
|||
///////////////////////////////////////////////
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// League of Legends game type: matched game, custom game, or tutorial game.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||
#[repr(u8)]
|
||||
pub enum GameType {
|
||||
/// Custom games
|
||||
CUSTOM_GAME,
|
||||
/// Tutorial games
|
||||
TUTORIAL_GAME,
|
||||
/// all other games
|
||||
MATCHED_GAME,
|
||||
}
|
||||
|
||||
serde_string!(GameType);
|
||||
///////////////////////////////////////////////
|
||||
|
||||
use serde::{ Serialize, Deserialize };
|
||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// League of Legends game type: matched game, custom game, or tutorial game.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum GameType {
|
||||
/// Custom games
|
||||
#[strum(to_string = "CUSTOM_GAME", serialize = "CUSTOM")]
|
||||
#[serde(alias = "CUSTOM")]
|
||||
CUSTOM_GAME,
|
||||
/// all other games
|
||||
#[strum(to_string = "MATCHED_GAME", serialize = "MATCHED")]
|
||||
#[serde(alias = "MATCHED")]
|
||||
MATCHED_GAME,
|
||||
/// Tutorial games
|
||||
#[strum(to_string = "TUTORIAL_GAME", serialize = "TUTORIAL")]
|
||||
#[serde(alias = "TUTORIAL")]
|
||||
TUTORIAL_GAME,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
|
@ -0,0 +1,24 @@
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_as_ref() {
|
||||
assert_eq!("MATCHED_GAME", GameType::MATCHED_GAME.as_ref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_to_string() {
|
||||
assert_eq!("MATCHED_GAME", GameType::MATCHED_GAME.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_from_string() {
|
||||
assert_eq!(Ok(GameType::MATCHED_GAME), "MATCHED_GAME".parse());
|
||||
assert_eq!(Ok(GameType::MATCHED_GAME), "MATCHED".parse());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_serialize() {
|
||||
assert_eq!(Some("\"MATCHED_GAME\""),
|
||||
serde_json::to_string(&GameType::MATCHED_GAME)
|
||||
.ok().as_deref());
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
#![macro_use]
|
||||
|
||||
/// Macro for deriving `Serialize` and `Deserialize` for string enums with an
|
||||
/// `UNKNOWN(String)` variant.
|
||||
///
|
||||
/// Enum should have `#[derive(EnumString, EnumVariantNames, IntoStaticStr)]` included.
|
||||
///
|
||||
/// Also implements `AsRef<str>`, `Display`, and `From<&str>`.
|
||||
macro_rules! serde_strum_unknown {
|
||||
($name:ident) => {
|
||||
impl AsRef<str> for $name {
|
||||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
Self::UNKNOWN(string) => &*string,
|
||||
known => known.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
self.as_ref().fmt(f)
|
||||
}
|
||||
}
|
||||
impl serde::ser::Serialize for $name {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::ser::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for $name {
|
||||
fn from(item: &str) -> Self {
|
||||
item.parse().unwrap()
|
||||
}
|
||||
}
|
||||
impl<'de> serde::de::Deserialize<'de> for $name {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
#[cfg(not(feature = "deny-unknown-enum-variants-strings"))]
|
||||
{
|
||||
<&str>::deserialize(deserializer).map(Into::into)
|
||||
}
|
||||
#[cfg(feature = "deny-unknown-enum-variants-strings")]
|
||||
{
|
||||
<&str>::deserialize(deserializer)
|
||||
.map(Into::into)
|
||||
.and_then(|item| match item {
|
||||
Self::UNKNOWN(unknown) => Err(serde::de::Error::unknown_variant(
|
||||
&*unknown,
|
||||
<Self as strum::VariantNames>::VARIANTS,
|
||||
)),
|
||||
other => Ok(other),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! arr {
|
||||
(
|
||||
$( #[$attr:meta] )*
|
||||
$v:vis $id:ident $name:ident: [$ty:ty; _] = $value:expr
|
||||
) => {
|
||||
$( #[$attr] )*
|
||||
$v $id $name: [$ty; $value.len()] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro for newtype "enums" with integer values.
|
||||
///
|
||||
/// For serde, use the following:
|
||||
/// ```ignore
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// #[serde(from = "$repr", into = "$repr")]
|
||||
/// ```
|
||||
macro_rules! newtype_enum {
|
||||
{
|
||||
$( #[$attr:meta] )*
|
||||
$v:vis newtype_enum $name:ident($repr:ty) {
|
||||
$(
|
||||
$( #[$var_attr:meta] )*
|
||||
$var_name:ident = $var_val:expr,
|
||||
)*
|
||||
}
|
||||
} => {
|
||||
$( #[$attr] )*
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
$v struct $name($v $repr);
|
||||
impl $name {
|
||||
$(
|
||||
$( #[$var_attr] )*
|
||||
$v const $var_name: Self = Self($var_val);
|
||||
)*
|
||||
}
|
||||
|
||||
impl $name {
|
||||
arr!{
|
||||
#[doc = "Array containing all known variants."]
|
||||
pub const ALL_KNOWN: [Self; _] = [
|
||||
$( Self::$var_name, )*
|
||||
]
|
||||
}
|
||||
|
||||
#[doc = "If this is one of the known variants."]
|
||||
$v const fn is_known(self) -> bool {
|
||||
match self {
|
||||
$(
|
||||
Self::$var_name => true,
|
||||
)*
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<$name> for $repr {
|
||||
fn from(value: $name ) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl serde::ser::Serialize for $name {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::ser::Serializer,
|
||||
{
|
||||
<$repr>::serialize(&self.0, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<$repr> for $name {
|
||||
fn from(value: $repr ) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl<'de> serde::de::Deserialize<'de> for $name {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>
|
||||
{
|
||||
#[cfg(not(feature = "deny-unknown-enum-variants-integers"))]
|
||||
{
|
||||
<$repr>::deserialize(deserializer).map(Into::into)
|
||||
}
|
||||
#[cfg(feature = "deny-unknown-enum-variants-integers")]
|
||||
{
|
||||
<$repr>::deserialize(deserializer).map(Into::into)
|
||||
.and_then(|item: Self| {
|
||||
if !item.is_known() {
|
||||
Err(serde::de::Error::custom(format!(
|
||||
"Unknown integer enum variant: {} (\"deny-unknown-enum-variants-integers\" feature is enabled).\nExpected one of the following: {:?}",
|
||||
item, Self::ALL_KNOWN
|
||||
)))
|
||||
}
|
||||
else {
|
||||
Ok(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
impl std::fmt::Debug for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}({}{})", stringify!($name), self.0, if self.is_known() { "" } else { "?" })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
newtype_enum! {
|
||||
/// A League of Legends map.
|
||||
pub newtype_enum Map(u8) {
|
||||
/// `1`.
|
||||
/// Summoner's Rift
|
||||
/// Original Summer variant
|
||||
SUMMONERS_RIFT_ORIGINAL_SUMMER_VARIANT = 1,
|
||||
/// `2`.
|
||||
/// Summoner's Rift
|
||||
/// Original Autumn variant
|
||||
SUMMONERS_RIFT_ORIGINAL_AUTUMN_VARIANT = 2,
|
||||
/// `3`.
|
||||
/// The Proving Grounds
|
||||
/// Tutorial Map
|
||||
THE_PROVING_GROUNDS = 3,
|
||||
/// `4`.
|
||||
/// Twisted Treeline
|
||||
/// Original Version
|
||||
TWISTED_TREELINE_ORIGINAL_VERSION = 4,
|
||||
/// `8`.
|
||||
/// The Crystal Scar
|
||||
/// Dominion map
|
||||
THE_CRYSTAL_SCAR = 8,
|
||||
/// `10`.
|
||||
/// Twisted Treeline
|
||||
/// Last TT map
|
||||
TWISTED_TREELINE = 10,
|
||||
/// `11`.
|
||||
/// Summoner's Rift
|
||||
/// Current Version
|
||||
SUMMONERS_RIFT = 11,
|
||||
/// `12`.
|
||||
/// Howling Abyss
|
||||
/// ARAM map
|
||||
HOWLING_ABYSS = 12,
|
||||
/// `14`.
|
||||
/// Butcher's Bridge
|
||||
/// Alternate ARAM map
|
||||
BUTCHERS_BRIDGE = 14,
|
||||
/// `16`.
|
||||
/// Cosmic Ruins
|
||||
/// Dark Star: Singularity map
|
||||
COSMIC_RUINS = 16,
|
||||
/// `18`.
|
||||
/// Valoran City Park
|
||||
/// Star Guardian Invasion map
|
||||
VALORAN_CITY_PARK = 18,
|
||||
/// `19`.
|
||||
/// Substructure 43
|
||||
/// PROJECT: Hunters map
|
||||
SUBSTRUCTURE_43 = 19,
|
||||
/// `20`.
|
||||
/// Crash Site
|
||||
/// Odyssey: Extraction map
|
||||
CRASH_SITE = 20,
|
||||
/// `21`.
|
||||
/// Nexus Blitz
|
||||
/// Nexus Blitz map
|
||||
NEXUS_BLITZ = 21,
|
||||
/// `22`.
|
||||
/// Convergence
|
||||
/// Teamfight Tactics map
|
||||
CONVERGENCE = 22,
|
||||
/// `30`.
|
||||
/// Arena
|
||||
/// Map for 2v2v2v2 (`CHERRY`). Team up with a friend or venture solo in this new game mode. Face against multiple teams in chaotic battles across diverse arenas
|
||||
ARENA = 30,
|
||||
}
|
||||
}
|
|
@ -1,50 +1,58 @@
|
|||
//! Constant data and Enums used with the Riot Games API.
|
||||
//!
|
||||
//! This module uses SCREAMING_SNAKE_CASE for enum variants, as enums in this
|
||||
//! crate should be considered collections of constants.
|
||||
|
||||
#![allow(deprecated)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
mod macro_serde_string;
|
||||
|
||||
mod champion;
|
||||
pub use champion::*;
|
||||
|
||||
mod division;
|
||||
pub use division::*;
|
||||
|
||||
mod game_mode;
|
||||
pub use game_mode::*;
|
||||
|
||||
mod game_type;
|
||||
pub use game_type::*;
|
||||
|
||||
mod map;
|
||||
pub use map::*;
|
||||
|
||||
mod queue_type;
|
||||
pub use queue_type::*;
|
||||
|
||||
mod queue;
|
||||
pub use queue::*;
|
||||
|
||||
pub mod ranks;
|
||||
|
||||
mod region;
|
||||
pub use region::*;
|
||||
|
||||
mod season;
|
||||
pub use season::*;
|
||||
|
||||
/// Trait allowing iteration of enum types, implemented by several enums in this module.
|
||||
/// Re-exported from strum.
|
||||
///
|
||||
///
|
||||
pub use strum::IntoEnumIterator;
|
||||
|
||||
mod team;
|
||||
pub use team::*;
|
||||
|
||||
mod tier;
|
||||
pub use tier::*;
|
||||
//! Constant data and Enums used with the Riot Games API.
|
||||
//!
|
||||
//! This module uses SCREAMING_SNAKE_CASE for enum variants, as enums in this
|
||||
//! crate should be considered collections of constants.
|
||||
|
||||
#![allow(deprecated)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
mod macros;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod champion;
|
||||
pub use champion::*;
|
||||
|
||||
mod division;
|
||||
pub use division::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod game_mode;
|
||||
pub use game_mode::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod game_type;
|
||||
pub use game_type::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod map;
|
||||
pub use map::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod queue_type;
|
||||
pub use queue_type::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod queue;
|
||||
pub use queue::*;
|
||||
|
||||
pub mod ranks;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod route;
|
||||
pub use route::*;
|
||||
|
||||
mod route_ext;
|
||||
pub use route_ext::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod season;
|
||||
pub use season::*;
|
||||
/// Trait allowing iteration of enum types, implemented by several enums in this module.
|
||||
/// Re-exported from strum.
|
||||
pub use strum::IntoEnumIterator;
|
||||
|
||||
mod team;
|
||||
pub use team::*;
|
||||
|
||||
mod tier;
|
||||
pub use tier::*;
|
|
@ -0,0 +1,397 @@
|
|||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
newtype_enum! {
|
||||
/// A League of Legends matchmaking queue.
|
||||
pub newtype_enum Queue(u16) {
|
||||
/// `0`.
|
||||
/// Games on Custom games
|
||||
CUSTOM = 0,
|
||||
/// `2`.
|
||||
/// 5v5 Blind Pick games on Summoner's Rift
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 430
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 430")]
|
||||
SUMMONERS_RIFT_5V5_BLIND_PICK_DEPRECATED_2 = 2,
|
||||
/// `4`.
|
||||
/// 5v5 Ranked Solo games on Summoner's Rift
|
||||
///
|
||||
/// Deprecated in favor of queueId 420
|
||||
#[deprecated(note="Deprecated in favor of queueId 420")]
|
||||
SUMMONERS_RIFT_5V5_RANKED_SOLO_DEPRECATED_4 = 4,
|
||||
/// `6`.
|
||||
/// 5v5 Ranked Premade games on Summoner's Rift
|
||||
///
|
||||
/// Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
SUMMONERS_RIFT_5V5_RANKED_PREMADE = 6,
|
||||
/// `7`.
|
||||
/// Co-op vs AI games on Summoner's Rift
|
||||
///
|
||||
/// Deprecated in favor of queueId 32 and 33
|
||||
#[deprecated(note="Deprecated in favor of queueId 32 and 33")]
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI = 7,
|
||||
/// `8`.
|
||||
/// 3v3 Normal games on Twisted Treeline
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 460
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 460")]
|
||||
TWISTED_TREELINE_3V3_NORMAL = 8,
|
||||
/// `9`.
|
||||
/// 3v3 Ranked Flex games on Twisted Treeline
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 470
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 470")]
|
||||
TWISTED_TREELINE_3V3_RANKED_FLEX_DEPRECATED_9 = 9,
|
||||
/// `14`.
|
||||
/// 5v5 Draft Pick games on Summoner's Rift
|
||||
///
|
||||
/// Deprecated in favor of queueId 400
|
||||
#[deprecated(note="Deprecated in favor of queueId 400")]
|
||||
SUMMONERS_RIFT_5V5_DRAFT_PICK_DEPRECATED_14 = 14,
|
||||
/// `16`.
|
||||
/// 5v5 Dominion Blind Pick games on Crystal Scar
|
||||
///
|
||||
/// Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
CRYSTAL_SCAR_5V5_DOMINION_BLIND_PICK = 16,
|
||||
/// `17`.
|
||||
/// 5v5 Dominion Draft Pick games on Crystal Scar
|
||||
///
|
||||
/// Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
CRYSTAL_SCAR_5V5_DOMINION_DRAFT_PICK = 17,
|
||||
/// `25`.
|
||||
/// Dominion Co-op vs AI games on Crystal Scar
|
||||
///
|
||||
/// Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
CRYSTAL_SCAR_DOMINION_CO_OP_VS_AI = 25,
|
||||
/// `31`.
|
||||
/// Co-op vs AI Intro Bot games on Summoner's Rift
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 830
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 830")]
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTRO_BOT_DEPRECATED_31 = 31,
|
||||
/// `32`.
|
||||
/// Co-op vs AI Beginner Bot games on Summoner's Rift
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 840
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 840")]
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_BEGINNER_BOT_DEPRECATED_32 = 32,
|
||||
/// `33`.
|
||||
/// Co-op vs AI Intermediate Bot games on Summoner's Rift
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 850
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 850")]
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTERMEDIATE_BOT_DEPRECATED_33 = 33,
|
||||
/// `41`.
|
||||
/// 3v3 Ranked Team games on Twisted Treeline
|
||||
///
|
||||
/// Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
TWISTED_TREELINE_3V3_RANKED_TEAM = 41,
|
||||
/// `42`.
|
||||
/// 5v5 Ranked Team games on Summoner's Rift
|
||||
///
|
||||
/// Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
SUMMONERS_RIFT_5V5_RANKED_TEAM = 42,
|
||||
/// `52`.
|
||||
/// Co-op vs AI games on Twisted Treeline
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 800
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 800")]
|
||||
TWISTED_TREELINE_CO_OP_VS_AI = 52,
|
||||
/// `61`.
|
||||
/// 5v5 Team Builder games on Summoner's Rift
|
||||
///
|
||||
/// Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
SUMMONERS_RIFT_5V5_TEAM_BUILDER = 61,
|
||||
/// `65`.
|
||||
/// 5v5 ARAM games on Howling Abyss
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 450
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 450")]
|
||||
HOWLING_ABYSS_5V5_ARAM_DEPRECATED_65 = 65,
|
||||
/// `67`.
|
||||
/// ARAM Co-op vs AI games on Howling Abyss
|
||||
///
|
||||
/// Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
HOWLING_ABYSS_ARAM_CO_OP_VS_AI = 67,
|
||||
/// `70`.
|
||||
/// One for All games on Summoner's Rift
|
||||
///
|
||||
/// Deprecated in patch 8.6 in favor of queueId 1020
|
||||
#[deprecated(note="Deprecated in patch 8.6 in favor of queueId 1020")]
|
||||
SUMMONERS_RIFT_ONE_FOR_ALL_DEPRECATED_70 = 70,
|
||||
/// `72`.
|
||||
/// 1v1 Snowdown Showdown games on Howling Abyss
|
||||
HOWLING_ABYSS_1V1_SNOWDOWN_SHOWDOWN = 72,
|
||||
/// `73`.
|
||||
/// 2v2 Snowdown Showdown games on Howling Abyss
|
||||
HOWLING_ABYSS_2V2_SNOWDOWN_SHOWDOWN = 73,
|
||||
/// `75`.
|
||||
/// 6v6 Hexakill games on Summoner's Rift
|
||||
SUMMONERS_RIFT_6V6_HEXAKILL = 75,
|
||||
/// `76`.
|
||||
/// Ultra Rapid Fire games on Summoner's Rift
|
||||
SUMMONERS_RIFT_ULTRA_RAPID_FIRE = 76,
|
||||
/// `78`.
|
||||
/// One For All: Mirror Mode games on Howling Abyss
|
||||
HOWLING_ABYSS_ONE_FOR_ALL_MIRROR_MODE = 78,
|
||||
/// `83`.
|
||||
/// Co-op vs AI Ultra Rapid Fire games on Summoner's Rift
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_ULTRA_RAPID_FIRE = 83,
|
||||
/// `91`.
|
||||
/// Doom Bots Rank 1 games on Summoner's Rift
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 950
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
||||
SUMMONERS_RIFT_DOOM_BOTS_RANK_1 = 91,
|
||||
/// `92`.
|
||||
/// Doom Bots Rank 2 games on Summoner's Rift
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 950
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
||||
SUMMONERS_RIFT_DOOM_BOTS_RANK_2 = 92,
|
||||
/// `93`.
|
||||
/// Doom Bots Rank 5 games on Summoner's Rift
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 950
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
||||
SUMMONERS_RIFT_DOOM_BOTS_RANK_5 = 93,
|
||||
/// `96`.
|
||||
/// Ascension games on Crystal Scar
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 910
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 910")]
|
||||
CRYSTAL_SCAR_ASCENSION_DEPRECATED_96 = 96,
|
||||
/// `98`.
|
||||
/// 6v6 Hexakill games on Twisted Treeline
|
||||
TWISTED_TREELINE_6V6_HEXAKILL = 98,
|
||||
/// `100`.
|
||||
/// 5v5 ARAM games on Butcher's Bridge
|
||||
BUTCHERS_BRIDGE_5V5_ARAM = 100,
|
||||
/// `300`.
|
||||
/// Legend of the Poro King games on Howling Abyss
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 920
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 920")]
|
||||
HOWLING_ABYSS_LEGEND_OF_THE_PORO_KING_DEPRECATED_300 = 300,
|
||||
/// `310`.
|
||||
/// Nemesis games on Summoner's Rift
|
||||
SUMMONERS_RIFT_NEMESIS = 310,
|
||||
/// `313`.
|
||||
/// Black Market Brawlers games on Summoner's Rift
|
||||
SUMMONERS_RIFT_BLACK_MARKET_BRAWLERS = 313,
|
||||
/// `315`.
|
||||
/// Nexus Siege games on Summoner's Rift
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 940
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 940")]
|
||||
SUMMONERS_RIFT_NEXUS_SIEGE_DEPRECATED_315 = 315,
|
||||
/// `317`.
|
||||
/// Definitely Not Dominion games on Crystal Scar
|
||||
CRYSTAL_SCAR_DEFINITELY_NOT_DOMINION = 317,
|
||||
/// `318`.
|
||||
/// ARURF games on Summoner's Rift
|
||||
///
|
||||
/// Deprecated in patch 7.19 in favor of queueId 900
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 900")]
|
||||
SUMMONERS_RIFT_ARURF_DEPRECATED_318 = 318,
|
||||
/// `325`.
|
||||
/// All Random games on Summoner's Rift
|
||||
SUMMONERS_RIFT_ALL_RANDOM = 325,
|
||||
/// `400`.
|
||||
/// 5v5 Draft Pick games on Summoner's Rift
|
||||
SUMMONERS_RIFT_5V5_DRAFT_PICK = 400,
|
||||
/// `410`.
|
||||
/// 5v5 Ranked Dynamic games on Summoner's Rift
|
||||
///
|
||||
/// Game mode deprecated in patch 6.22
|
||||
#[deprecated(note="Game mode deprecated in patch 6.22")]
|
||||
SUMMONERS_RIFT_5V5_RANKED_DYNAMIC = 410,
|
||||
/// `420`.
|
||||
/// 5v5 Ranked Solo games on Summoner's Rift
|
||||
SUMMONERS_RIFT_5V5_RANKED_SOLO = 420,
|
||||
/// `430`.
|
||||
/// 5v5 Blind Pick games on Summoner's Rift
|
||||
SUMMONERS_RIFT_5V5_BLIND_PICK = 430,
|
||||
/// `440`.
|
||||
/// 5v5 Ranked Flex games on Summoner's Rift
|
||||
SUMMONERS_RIFT_5V5_RANKED_FLEX = 440,
|
||||
/// `450`.
|
||||
/// 5v5 ARAM games on Howling Abyss
|
||||
HOWLING_ABYSS_5V5_ARAM = 450,
|
||||
/// `460`.
|
||||
/// 3v3 Blind Pick games on Twisted Treeline
|
||||
///
|
||||
/// Deprecated in patch 9.23
|
||||
#[deprecated(note="Deprecated in patch 9.23")]
|
||||
TWISTED_TREELINE_3V3_BLIND_PICK = 460,
|
||||
/// `470`.
|
||||
/// 3v3 Ranked Flex games on Twisted Treeline
|
||||
///
|
||||
/// Deprecated in patch 9.23
|
||||
#[deprecated(note="Deprecated in patch 9.23")]
|
||||
TWISTED_TREELINE_3V3_RANKED_FLEX_DEPRECATED_470 = 470,
|
||||
/// `490`.
|
||||
/// Normal (Quickplay) games on Summoner's Rift
|
||||
///
|
||||
/// https://github.com/RiotGames/developer-relations/issues/846
|
||||
SUMMONERS_RIFT_NORMAL_QUICKPLAY_ = 490,
|
||||
/// `600`.
|
||||
/// Blood Hunt Assassin games on Summoner's Rift
|
||||
SUMMONERS_RIFT_BLOOD_HUNT_ASSASSIN = 600,
|
||||
/// `610`.
|
||||
/// Dark Star: Singularity games on Cosmic Ruins
|
||||
COSMIC_RUINS_DARK_STAR_SINGULARITY = 610,
|
||||
/// `700`.
|
||||
/// Summoner's Rift Clash games on Summoner's Rift
|
||||
SUMMONERS_RIFT_CLASH = 700,
|
||||
/// `720`.
|
||||
/// ARAM Clash games on Howling Abyss
|
||||
HOWLING_ABYSS_ARAM_CLASH = 720,
|
||||
/// `800`.
|
||||
/// Co-op vs. AI Intermediate Bot games on Twisted Treeline
|
||||
///
|
||||
/// Deprecated in patch 9.23
|
||||
#[deprecated(note="Deprecated in patch 9.23")]
|
||||
TWISTED_TREELINE_CO_OP_VS_AI_INTERMEDIATE_BOT = 800,
|
||||
/// `810`.
|
||||
/// Co-op vs. AI Intro Bot games on Twisted Treeline
|
||||
///
|
||||
/// Deprecated in patch 9.23
|
||||
#[deprecated(note="Deprecated in patch 9.23")]
|
||||
TWISTED_TREELINE_CO_OP_VS_AI_INTRO_BOT = 810,
|
||||
/// `820`.
|
||||
/// Co-op vs. AI Beginner Bot games on Twisted Treeline
|
||||
TWISTED_TREELINE_CO_OP_VS_AI_BEGINNER_BOT = 820,
|
||||
/// `830`.
|
||||
/// Co-op vs. AI Intro Bot games on Summoner's Rift
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTRO_BOT = 830,
|
||||
/// `840`.
|
||||
/// Co-op vs. AI Beginner Bot games on Summoner's Rift
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_BEGINNER_BOT = 840,
|
||||
/// `850`.
|
||||
/// Co-op vs. AI Intermediate Bot games on Summoner's Rift
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTERMEDIATE_BOT = 850,
|
||||
/// `900`.
|
||||
/// ARURF games on Summoner's Rift
|
||||
SUMMONERS_RIFT_ARURF = 900,
|
||||
/// `910`.
|
||||
/// Ascension games on Crystal Scar
|
||||
CRYSTAL_SCAR_ASCENSION = 910,
|
||||
/// `920`.
|
||||
/// Legend of the Poro King games on Howling Abyss
|
||||
HOWLING_ABYSS_LEGEND_OF_THE_PORO_KING = 920,
|
||||
/// `940`.
|
||||
/// Nexus Siege games on Summoner's Rift
|
||||
SUMMONERS_RIFT_NEXUS_SIEGE = 940,
|
||||
/// `950`.
|
||||
/// Doom Bots Voting games on Summoner's Rift
|
||||
SUMMONERS_RIFT_DOOM_BOTS_VOTING = 950,
|
||||
/// `960`.
|
||||
/// Doom Bots Standard games on Summoner's Rift
|
||||
SUMMONERS_RIFT_DOOM_BOTS_STANDARD = 960,
|
||||
/// `980`.
|
||||
/// Star Guardian Invasion: Normal games on Valoran City Park
|
||||
VALORAN_CITY_PARK_STAR_GUARDIAN_INVASION_NORMAL = 980,
|
||||
/// `990`.
|
||||
/// Star Guardian Invasion: Onslaught games on Valoran City Park
|
||||
VALORAN_CITY_PARK_STAR_GUARDIAN_INVASION_ONSLAUGHT = 990,
|
||||
/// `1000`.
|
||||
/// PROJECT: Hunters games on Overcharge
|
||||
OVERCHARGE_PROJECT_HUNTERS = 1000,
|
||||
/// `1010`.
|
||||
/// Snow ARURF games on Summoner's Rift
|
||||
SUMMONERS_RIFT_SNOW_ARURF = 1010,
|
||||
/// `1020`.
|
||||
/// One for All games on Summoner's Rift
|
||||
SUMMONERS_RIFT_ONE_FOR_ALL = 1020,
|
||||
/// `1030`.
|
||||
/// Odyssey Extraction: Intro games on Crash Site
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_INTRO = 1030,
|
||||
/// `1040`.
|
||||
/// Odyssey Extraction: Cadet games on Crash Site
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_CADET = 1040,
|
||||
/// `1050`.
|
||||
/// Odyssey Extraction: Crewmember games on Crash Site
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_CREWMEMBER = 1050,
|
||||
/// `1060`.
|
||||
/// Odyssey Extraction: Captain games on Crash Site
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_CAPTAIN = 1060,
|
||||
/// `1070`.
|
||||
/// Odyssey Extraction: Onslaught games on Crash Site
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_ONSLAUGHT = 1070,
|
||||
/// `1090`.
|
||||
/// Teamfight Tactics games on Convergence
|
||||
CONVERGENCE_TEAMFIGHT_TACTICS = 1090,
|
||||
/// `1091`.
|
||||
/// Teamfight Tactics 1v0 games on Convergence
|
||||
CONVERGENCE_TEAMFIGHT_TACTICS_1V0 = 1091,
|
||||
/// `1092`.
|
||||
/// Teamfight Tactics 2v0 games on Convergence
|
||||
CONVERGENCE_TEAMFIGHT_TACTICS_2V0 = 1092,
|
||||
/// `1100`.
|
||||
/// Ranked Teamfight Tactics games on Convergence
|
||||
CONVERGENCE_RANKED_TEAMFIGHT_TACTICS = 1100,
|
||||
/// `1110`.
|
||||
/// Teamfight Tactics Tutorial games on Convergence
|
||||
CONVERGENCE_TEAMFIGHT_TACTICS_TUTORIAL = 1110,
|
||||
/// `1111`.
|
||||
/// Teamfight Tactics Simluation games on Convergence
|
||||
CONVERGENCE_TEAMFIGHT_TACTICS_SIMLUATION = 1111,
|
||||
/// `1130`.
|
||||
/// Ranked Teamfight Tactics (Hyper Roll) games on Convergence
|
||||
CONVERGENCE_RANKED_TEAMFIGHT_TACTICS_HYPER_ROLL_ = 1130,
|
||||
/// `1150`.
|
||||
/// Ranked Teamfight Tactics (Double Up Workshop) games on Convergence
|
||||
///
|
||||
/// Deprecated in patch 12.11 in favor of queueId 1160
|
||||
#[deprecated(note="Deprecated in patch 12.11 in favor of queueId 1160")]
|
||||
CONVERGENCE_RANKED_TEAMFIGHT_TACTICS_DOUBLE_UP_WORKSHOP__DEPRECATED_1150 = 1150,
|
||||
/// `1160`.
|
||||
/// Ranked Teamfight Tactics (Double Up Workshop) games on Convergence
|
||||
CONVERGENCE_RANKED_TEAMFIGHT_TACTICS_DOUBLE_UP_WORKSHOP_ = 1160,
|
||||
/// `1200`.
|
||||
/// Nexus Blitz games on Nexus Blitz
|
||||
///
|
||||
/// Deprecated in patch 9.2 in favor of queueId 1300
|
||||
#[deprecated(note="Deprecated in patch 9.2 in favor of queueId 1300")]
|
||||
NEXUS_BLITZ_DEPRECATED_1200 = 1200,
|
||||
/// `1300`.
|
||||
/// Nexus Blitz games on Nexus Blitz
|
||||
NEXUS_BLITZ = 1300,
|
||||
/// `1400`.
|
||||
/// Ultimate Spellbook games on Summoner's Rift
|
||||
SUMMONERS_RIFT_ULTIMATE_SPELLBOOK = 1400,
|
||||
/// `1700`.
|
||||
/// 2v2v2v2 `CHERRY` games on Arena
|
||||
ARENA_2V2V2V2_CHERRY_ = 1700,
|
||||
/// `1900`.
|
||||
/// Pick URF games on Summoner's Rift
|
||||
SUMMONERS_RIFT_PICK_URF = 1900,
|
||||
/// `2000`.
|
||||
/// Tutorial 1 games on Summoner's Rift
|
||||
SUMMONERS_RIFT_TUTORIAL_1 = 2000,
|
||||
/// `2010`.
|
||||
/// Tutorial 2 games on Summoner's Rift
|
||||
SUMMONERS_RIFT_TUTORIAL_2 = 2010,
|
||||
/// `2020`.
|
||||
/// Tutorial 3 games on Summoner's Rift
|
||||
SUMMONERS_RIFT_TUTORIAL_3 = 2020,
|
||||
/// `6000`.
|
||||
/// Teamfight Tactics Set 3.5 Revival games on Convergence
|
||||
CONVERGENCE_TEAMFIGHT_TACTICS_SET_3_5_REVIVAL = 6000,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
use strum_macros::{ EnumString, EnumVariantNames, IntoStaticStr };
|
||||
|
||||
/// LoL or TFT ranked queue types.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, EnumVariantNames, IntoStaticStr)]
|
||||
#[repr(u8)]
|
||||
pub enum QueueType {
|
||||
/// Catch-all variant for new, unknown queue types.
|
||||
#[strum(default)]
|
||||
UNKNOWN(String),
|
||||
|
||||
/// 5v5 Ranked Solo games
|
||||
RANKED_SOLO_5x5,
|
||||
/// 5v5 Ranked Flex games
|
||||
RANKED_FLEX_SR,
|
||||
/// 3v3 Ranked Flex games
|
||||
/// Deprecated in patch 9.23
|
||||
#[deprecated(note="Deprecated in patch 9.23")]
|
||||
RANKED_FLEX_TT,
|
||||
/// Ranked Teamfight Tactics games
|
||||
RANKED_TFT,
|
||||
/// Ranked Teamfight Tactics (Hyper Roll) games
|
||||
RANKED_TFT_TURBO,
|
||||
/// Ranked Teamfight Tactics (Double Up Workshop) games
|
||||
/// Deprecated in patch 12.11 in favor of queueId 1160 (`RANKED_TFT_DOUBLE_UP`)
|
||||
#[deprecated(note="Deprecated in patch 12.11 in favor of queueId 1160 (`RANKED_TFT_DOUBLE_UP`)")]
|
||||
RANKED_TFT_PAIRS,
|
||||
/// Ranked Teamfight Tactics (Double Up Workshop) games
|
||||
RANKED_TFT_DOUBLE_UP,
|
||||
/// 2v2v2v2 "Arena" games
|
||||
CHERRY,
|
||||
}
|
||||
|
||||
serde_strum_unknown!(QueueType);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
|
@ -0,0 +1,52 @@
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_as_ref() {
|
||||
assert_eq!("RANKED_SOLO_5x5", QueueType::RANKED_SOLO_5x5.as_ref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_to_string() {
|
||||
assert_eq!("RANKED_SOLO_5x5", QueueType::RANKED_SOLO_5x5.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_from_string() {
|
||||
assert_eq!(QueueType::RANKED_SOLO_5x5, "RANKED_SOLO_5x5".into());
|
||||
assert_eq!(QueueType::UNKNOWN("RANKED_MYSTERY_UNKNOWN".to_owned()), "RANKED_MYSTERY_UNKNOWN".into());
|
||||
assert_eq!("RANKED_MYSTERY_UNKNOWN", QueueType::UNKNOWN("RANKED_MYSTERY_UNKNOWN".to_owned()).as_ref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_serialize() {
|
||||
assert_eq!(Some("\"RANKED_TFT_DOUBLE_UP\""),
|
||||
serde_json::to_string(&QueueType::RANKED_TFT_DOUBLE_UP)
|
||||
.ok().as_deref());
|
||||
assert_eq!(Some("\"RANKED_MYSTERY_UNKNOWN\""),
|
||||
serde_json::to_string(&QueueType::UNKNOWN("RANKED_MYSTERY_UNKNOWN".to_owned()))
|
||||
.ok().as_deref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Note: this test is often not run due to this condition below.
|
||||
#[cfg(not(feature = "deny-unknown-enum-variants-strings"))]
|
||||
fn check_deserialize() {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let dict: BTreeMap<usize, QueueType> = serde_json::from_str(
|
||||
r#"{
|
||||
"100": "RANKED_SOLO_5x5",
|
||||
"200": "RANKED_TFT_TURBO",
|
||||
"210": "RANKED_TFT_DOUBLE_UP",
|
||||
"211": "RANKED_TFT_PAIRS",
|
||||
"900": "RANKED_MYSTERY_UNKNOWN"
|
||||
}"#
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(Some(&QueueType::RANKED_SOLO_5x5), dict.get(&100));
|
||||
assert_eq!(Some(&QueueType::RANKED_TFT_TURBO), dict.get(&200));
|
||||
assert_eq!(Some(&QueueType::RANKED_TFT_DOUBLE_UP), dict.get(&210));
|
||||
assert_eq!(Some(&QueueType::RANKED_TFT_PAIRS), dict.get(&211));
|
||||
assert_eq!(Some(&QueueType::UNKNOWN("RANKED_MYSTERY_UNKNOWN".to_owned())), dict.get(&900));
|
||||
|
||||
}
|
|
@ -4,7 +4,10 @@ use std::iter::Peekable;
|
|||
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use super::{ Tier, Division };
|
||||
use super::{Division, Tier};
|
||||
|
||||
/// (Tier, Division) tuple representing a rank.
|
||||
pub type Rank = (Tier, Division);
|
||||
|
||||
/// Iterator for iterating `(Tier, Division)` rank tuples.
|
||||
pub struct Iter {
|
||||
|
@ -13,18 +16,17 @@ pub struct Iter {
|
|||
}
|
||||
|
||||
impl Iterator for Iter {
|
||||
type Item = (Tier, Division);
|
||||
type Item = Rank;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// First find the tier (innermost loop).
|
||||
// If none found, we go to next tier (in unwrap_or_else case).
|
||||
let div = *self.div_iter.next()
|
||||
.unwrap_or_else(|| {
|
||||
// If no divisions available, go to next tier, reset the divisions, and return I.
|
||||
self.tier_iter.next();
|
||||
self.div_iter = Division::iter();
|
||||
self.div_iter.next().unwrap()
|
||||
});
|
||||
|
||||
let div = self.div_iter.next().unwrap_or_else(|| {
|
||||
// If no divisions available, go to next tier, reset the divisions, and return I.
|
||||
self.tier_iter.next();
|
||||
self.div_iter = Division::iter();
|
||||
self.div_iter.next().unwrap()
|
||||
});
|
||||
|
||||
// Then find the tier.
|
||||
let tier = *self.tier_iter.peek()?;
|
||||
// If its an apex tier go to next tier and reset the divisions.
|
||||
|
@ -56,23 +58,23 @@ pub fn non_apex_iter() -> Iter {
|
|||
tier_iter.next();
|
||||
}
|
||||
Iter {
|
||||
tier_iter: tier_iter,
|
||||
tier_iter,
|
||||
div_iter: Division::iter(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ Tier, Division };
|
||||
use super::{Division, Tier};
|
||||
|
||||
#[test]
|
||||
fn iter() {
|
||||
let mut it = super::iter();
|
||||
assert_eq!(Some((Tier::CHALLENGER, Division::I)), it.next());
|
||||
assert_eq!(Some((Tier::GRANDMASTER, Division::I)), it.next());
|
||||
assert_eq!(Some((Tier::MASTER, Division::I)), it.next());
|
||||
assert_eq!(Some((Tier::DIAMOND, Division::I)), it.next());
|
||||
assert_eq!(Some((Tier::DIAMOND, Division::II)), it.next());
|
||||
assert_eq!(Some((Tier::CHALLENGER, Division::I)), it.next());
|
||||
assert_eq!(Some((Tier::GRANDMASTER, Division::I)), it.next());
|
||||
assert_eq!(Some((Tier::MASTER, Division::I)), it.next());
|
||||
assert_eq!(Some((Tier::DIAMOND, Division::I)), it.next());
|
||||
assert_eq!(Some((Tier::DIAMOND, Division::II)), it.next());
|
||||
let mut last = None;
|
||||
for next in &mut it {
|
||||
last = Some(next);
|
||||
|
@ -81,20 +83,19 @@ mod tests {
|
|||
assert_eq!(None, it.next());
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn non_apex_iter() {
|
||||
let mut it = super::non_apex_iter();
|
||||
assert_eq!(Some((Tier::DIAMOND, Division::I)), 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::IV)), it.next());
|
||||
assert_eq!(Some((Tier::PLATINUM, Division::I)), it.next());
|
||||
let mut last = None;
|
||||
for next in &mut it {
|
||||
last = Some(next);
|
||||
}
|
||||
assert_eq!(Some((Tier::DIAMOND, Division::I)), it.next());
|
||||
assert_eq!(Some((Tier::DIAMOND, Division::II)), it.next());
|
||||
assert_eq!(Some((Tier::DIAMOND, Division::III)), it.next());
|
||||
assert_eq!(Some((Tier::DIAMOND, Division::IV)), it.next());
|
||||
assert_eq!(Some((Tier::EMERALD, Division::I)), it.next());
|
||||
assert_eq!(Some((Tier::EMERALD, Division::II)), it.next());
|
||||
assert_eq!(Some((Tier::EMERALD, Division::III)), it.next());
|
||||
assert_eq!(Some((Tier::EMERALD, Division::IV)), it.next());
|
||||
assert_eq!(Some((Tier::PLATINUM, Division::I)), it.next());
|
||||
let last = it.last();
|
||||
assert_eq!(Some((Tier::IRON, Division::IV)), last);
|
||||
assert_eq!(None, it.next());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,345 @@
|
|||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||
use strum_macros::{ EnumString, EnumIter, Display, IntoStaticStr };
|
||||
|
||||
/// Regional routes, used in tournament services, Legends of Runeterra (LoR), and other some endpoints.
|
||||
#[derive(Debug)]
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
pub enum RegionalRoute {
|
||||
/// North and South America.
|
||||
///
|
||||
/// `1` (riotapi-schema ID/repr)
|
||||
AMERICAS = 1,
|
||||
|
||||
/// Asia, used for LoL matches (`match-v5`) and TFT matches (`tft-match-v1`).
|
||||
///
|
||||
/// `2` (riotapi-schema ID/repr)
|
||||
ASIA = 2,
|
||||
|
||||
/// Europe.
|
||||
///
|
||||
/// `3` (riotapi-schema ID/repr)
|
||||
EUROPE = 3,
|
||||
|
||||
/// South East Asia, used for LoR, LoL matches (`match-v5`), and TFT matches (`tft-match-v1`).
|
||||
///
|
||||
/// `4` (riotapi-schema ID/repr)
|
||||
SEA = 4,
|
||||
|
||||
/// Asia-Pacific, deprecated, for some old matches in `lor-match-v1`.
|
||||
///
|
||||
/// `10` (riotapi-schema ID/repr)
|
||||
#[deprecated]
|
||||
APAC = 10,
|
||||
|
||||
/// Special esports platform for `account-v1`. Do not confuse with the `esports` Valorant platform route.
|
||||
///
|
||||
/// `11` (riotapi-schema ID/repr)
|
||||
ESPORTS = 11,
|
||||
|
||||
}
|
||||
|
||||
/// Platform routes for League of Legends (LoL), Teamfight Tactics (TFT), and Legends of Runeterra (LoR).
|
||||
#[derive(Debug)]
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
// Note: strum(serialize = ...) actually specifies extra DEserialization values.
|
||||
pub enum PlatformRoute {
|
||||
/// Brazil.
|
||||
///
|
||||
/// `16` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="BR1", serialize="BR")]
|
||||
BR1 = 16,
|
||||
|
||||
/// Europe, Northeast.
|
||||
///
|
||||
/// `17` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="EUN1", serialize="EUNE")]
|
||||
EUN1 = 17,
|
||||
|
||||
/// Europe, West.
|
||||
///
|
||||
/// `18` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="EUW1", serialize="EUW")]
|
||||
EUW1 = 18,
|
||||
|
||||
/// Japan.
|
||||
///
|
||||
/// `19` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="JP1", serialize="JP")]
|
||||
JP1 = 19,
|
||||
|
||||
/// Korea.
|
||||
///
|
||||
/// `20` (riotapi-schema ID/repr)
|
||||
KR = 20,
|
||||
|
||||
/// Latin America, North.
|
||||
///
|
||||
/// `21` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="LA1", serialize="LAN")]
|
||||
LA1 = 21,
|
||||
|
||||
/// Latin America, South.
|
||||
///
|
||||
/// `22` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="LA2", serialize="LAS")]
|
||||
LA2 = 22,
|
||||
|
||||
/// North America.
|
||||
///
|
||||
/// `23` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="NA1", serialize="NA")]
|
||||
NA1 = 23,
|
||||
|
||||
/// Oceania.
|
||||
///
|
||||
/// `24` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="OC1", serialize="OCE")]
|
||||
OC1 = 24,
|
||||
|
||||
/// Philippines
|
||||
///
|
||||
/// `32` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="PH2", serialize="PH")]
|
||||
PH2 = 32,
|
||||
|
||||
/// Russia
|
||||
///
|
||||
/// `25` (riotapi-schema ID/repr)
|
||||
RU = 25,
|
||||
|
||||
/// Singapore
|
||||
///
|
||||
/// `33` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="SG2", serialize="SG")]
|
||||
SG2 = 33,
|
||||
|
||||
/// Thailand
|
||||
///
|
||||
/// `34` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="TH2", serialize="TH")]
|
||||
TH2 = 34,
|
||||
|
||||
/// Turkey
|
||||
///
|
||||
/// `26` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="TR1", serialize="TR")]
|
||||
TR1 = 26,
|
||||
|
||||
/// Taiwan
|
||||
///
|
||||
/// `35` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="TW2", serialize="TW")]
|
||||
TW2 = 35,
|
||||
|
||||
/// Vietnam
|
||||
///
|
||||
/// `36` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="VN2", serialize="VN")]
|
||||
VN2 = 36,
|
||||
|
||||
/// Public Beta Environment, special beta testing platform. Located in North America.
|
||||
///
|
||||
/// `31` (riotapi-schema ID/repr)
|
||||
#[strum(to_string="PBE1", serialize="PBE")]
|
||||
PBE1 = 31,
|
||||
|
||||
}
|
||||
|
||||
impl PlatformRoute {
|
||||
/// Converts this [`PlatformRoute`] into its corresponding
|
||||
/// [`RegionalRoute`] for LoL and TFT match endpoints.
|
||||
/// For example, [`match-v5`](crate::endpoints::MatchV5).
|
||||
pub fn to_regional(self) -> RegionalRoute {
|
||||
match self {
|
||||
Self::BR1 => RegionalRoute::AMERICAS,
|
||||
Self::EUN1 => RegionalRoute::EUROPE,
|
||||
Self::EUW1 => RegionalRoute::EUROPE,
|
||||
Self::JP1 => RegionalRoute::ASIA,
|
||||
Self::KR => RegionalRoute::ASIA,
|
||||
Self::LA1 => RegionalRoute::AMERICAS,
|
||||
Self::LA2 => RegionalRoute::AMERICAS,
|
||||
Self::NA1 => RegionalRoute::AMERICAS,
|
||||
Self::OC1 => RegionalRoute::SEA,
|
||||
Self::PH2 => RegionalRoute::SEA,
|
||||
Self::RU => RegionalRoute::EUROPE,
|
||||
Self::SG2 => RegionalRoute::SEA,
|
||||
Self::TH2 => RegionalRoute::SEA,
|
||||
Self::TR1 => RegionalRoute::EUROPE,
|
||||
Self::TW2 => RegionalRoute::SEA,
|
||||
Self::VN2 => RegionalRoute::SEA,
|
||||
Self::PBE1 => RegionalRoute::AMERICAS,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this [`PlatformRoute`] into its corresponding
|
||||
/// [`RegionalRoute`] for LoR endpoints.
|
||||
/// For example, [`lor-match-v1`](crate::endpoints::LorMatchV1).
|
||||
pub fn to_regional_lor(self) -> RegionalRoute {
|
||||
match self {
|
||||
Self::BR1 => RegionalRoute::AMERICAS,
|
||||
Self::EUN1 => RegionalRoute::EUROPE,
|
||||
Self::EUW1 => RegionalRoute::EUROPE,
|
||||
Self::JP1 => RegionalRoute::ASIA,
|
||||
Self::KR => RegionalRoute::ASIA,
|
||||
Self::LA1 => RegionalRoute::AMERICAS,
|
||||
Self::LA2 => RegionalRoute::AMERICAS,
|
||||
Self::NA1 => RegionalRoute::AMERICAS,
|
||||
Self::OC1 => RegionalRoute::SEA,
|
||||
Self::PH2 => RegionalRoute::SEA,
|
||||
Self::RU => RegionalRoute::SEA,
|
||||
Self::SG2 => RegionalRoute::SEA,
|
||||
Self::TH2 => RegionalRoute::SEA,
|
||||
Self::TR1 => RegionalRoute::SEA,
|
||||
Self::TW2 => RegionalRoute::SEA,
|
||||
Self::VN2 => RegionalRoute::SEA,
|
||||
Self::PBE1 => RegionalRoute::AMERICAS,
|
||||
}
|
||||
}
|
||||
|
||||
/// Used in the LoL Tournament API. Specifically
|
||||
/// [`tournament-stub-v4.registerProviderData`](crate::endpoints::TournamentStubV4::register_provider_data)
|
||||
/// and [`tournament-v4.registerProviderData`](crate::endpoints::TournamentV4::register_provider_data).
|
||||
pub fn to_tournament_region(self) -> Option<TournamentRegion> {
|
||||
match self {
|
||||
Self::BR1 => Some(TournamentRegion::BR),
|
||||
Self::EUN1 => Some(TournamentRegion::EUNE),
|
||||
Self::EUW1 => Some(TournamentRegion::EUW),
|
||||
Self::JP1 => Some(TournamentRegion::JP),
|
||||
Self::LA1 => Some(TournamentRegion::LAN),
|
||||
Self::LA2 => Some(TournamentRegion::LAS),
|
||||
Self::NA1 => Some(TournamentRegion::NA),
|
||||
Self::OC1 => Some(TournamentRegion::OCE),
|
||||
Self::TR1 => Some(TournamentRegion::TR),
|
||||
Self::PBE1 => Some(TournamentRegion::PBE),
|
||||
_other => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the slightly more human-friendly alternate name for this `PlatformRoute`. Specifically
|
||||
/// excludes any trailing numbers and appends extra N(orth), S(outh), E(ast), and/or W(est)
|
||||
/// suffixes to some names. Some of these are old region names which are often still used as
|
||||
/// user-facing names, e.g. on op.gg.
|
||||
///
|
||||
/// Note these strings *are* handled by the `FromStr` implementation, if you wish to parse them
|
||||
/// back into `PlatformRoute`s.
|
||||
pub fn as_region_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::BR1 => "BR",
|
||||
Self::EUN1 => "EUNE",
|
||||
Self::EUW1 => "EUW",
|
||||
Self::JP1 => "JP",
|
||||
Self::LA1 => "LAN",
|
||||
Self::LA2 => "LAS",
|
||||
Self::NA1 => "NA",
|
||||
Self::OC1 => "OCE",
|
||||
Self::PH2 => "PH",
|
||||
Self::SG2 => "SG",
|
||||
Self::TH2 => "TH",
|
||||
Self::TR1 => "TR",
|
||||
Self::TW2 => "TW",
|
||||
Self::VN2 => "VN",
|
||||
Self::PBE1 => "PBE",
|
||||
other => other.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform routes for Valorant.
|
||||
#[derive(Debug)]
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
pub enum ValPlatformRoute {
|
||||
/// Asia-Pacific.
|
||||
///
|
||||
/// `64` (riotapi-schema ID/repr)
|
||||
AP = 64,
|
||||
|
||||
/// Brazil.
|
||||
///
|
||||
/// `65` (riotapi-schema ID/repr)
|
||||
BR = 65,
|
||||
|
||||
/// Europe.
|
||||
///
|
||||
/// `66` (riotapi-schema ID/repr)
|
||||
EU = 66,
|
||||
|
||||
/// Korea.
|
||||
///
|
||||
/// `70` (riotapi-schema ID/repr)
|
||||
KR = 70,
|
||||
|
||||
/// Latin America.
|
||||
///
|
||||
/// `68` (riotapi-schema ID/repr)
|
||||
LATAM = 68,
|
||||
|
||||
/// North America.
|
||||
///
|
||||
/// `69` (riotapi-schema ID/repr)
|
||||
NA = 69,
|
||||
|
||||
/// Special esports platform.
|
||||
///
|
||||
/// `95` (riotapi-schema ID/repr)
|
||||
ESPORTS = 95,
|
||||
|
||||
}
|
||||
|
||||
/// Tournament regions for League of Legends (LoL) used in
|
||||
/// [`tournament-stub-v4.registerProviderData`](crate::endpoints::TournamentStubV4::register_provider_data)
|
||||
/// and [`tournament-v4.registerProviderData`](crate::endpoints::TournamentV4::register_provider_data).
|
||||
#[derive(Debug)]
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
// Note: strum(serialize = ...) actually specifies extra DEserialization values.
|
||||
pub enum TournamentRegion {
|
||||
/// Brazil.
|
||||
BR = 16,
|
||||
/// Europe, Northeast.
|
||||
EUNE = 17,
|
||||
/// Europe, West.
|
||||
EUW = 18,
|
||||
/// Japan.
|
||||
JP = 19,
|
||||
/// Latin America, North.
|
||||
LAN = 21,
|
||||
/// Latin America, South.
|
||||
LAS = 22,
|
||||
/// North America.
|
||||
NA = 23,
|
||||
/// Oceania.
|
||||
OCE = 24,
|
||||
/// Turkey
|
||||
TR = 26,
|
||||
/// Public Beta Environment, special beta testing platform. Located in North America.
|
||||
PBE = 31,
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
use super::{PlatformRoute, RegionalRoute, ValPlatformRoute};
|
||||
|
||||
/// Utility enum containing all routing variants.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
pub enum Route {
|
||||
/// Sub-variant for [`RegionalRoute`]s.
|
||||
Regional(RegionalRoute),
|
||||
/// Sub-variant for [`PlatformRoute`]s.
|
||||
Platform(PlatformRoute),
|
||||
/// Sub-variant for [`ValPlatformRoute`]s.
|
||||
ValPlatform(ValPlatformRoute),
|
||||
}
|
||||
|
||||
impl From<Route> for &'static str {
|
||||
fn from(route: Route) -> Self {
|
||||
match route {
|
||||
Route::Regional(r) => r.into(),
|
||||
Route::Platform(r) => r.into(),
|
||||
Route::ValPlatform(r) => r.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Route> for u8 {
|
||||
fn from(route: Route) -> Self {
|
||||
match route {
|
||||
Route::Regional(r) => r.into(),
|
||||
Route::Platform(r) => r.into(),
|
||||
Route::ValPlatform(r) => r.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl num_enum::TryFromPrimitive for Route {
|
||||
type Primitive = u8;
|
||||
|
||||
const NAME: &'static str = stringify!(Route);
|
||||
|
||||
fn try_from_primitive(
|
||||
number: Self::Primitive,
|
||||
) -> Result<Self, num_enum::TryFromPrimitiveError<Self>> {
|
||||
RegionalRoute::try_from_primitive(number)
|
||||
.map(Route::Regional)
|
||||
.or_else(|_| PlatformRoute::try_from_primitive(number).map(Route::Platform))
|
||||
.or_else(|_| ValPlatformRoute::try_from_primitive(number).map(Route::ValPlatform))
|
||||
.map_err(|_| num_enum::TryFromPrimitiveError { number })
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<u8> for Route {
|
||||
type Error = num_enum::TryFromPrimitiveError<Self>;
|
||||
fn try_from(number: u8) -> Result<Self, num_enum::TryFromPrimitiveError<Self>> {
|
||||
<Self as num_enum::TryFromPrimitive>::try_from_primitive(number)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Route {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Regional(r) => r.fmt(f),
|
||||
Self::Platform(r) => r.fmt(f),
|
||||
Self::ValPlatform(r) => r.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Route {
|
||||
type Err = strum::ParseError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
RegionalRoute::from_str(s)
|
||||
.map(Self::Regional)
|
||||
.or_else(|_| PlatformRoute::from_str(s).map(Self::Platform))
|
||||
.or_else(|_| ValPlatformRoute::from_str(s).map(Self::ValPlatform))
|
||||
.map_err(|_| strum::ParseError::VariantNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
impl Route {
|
||||
/// Returns an iterator over all routes. Starts with [`Self::Regional`],
|
||||
/// then [`Self::Platform`], and finally [`Self::ValPlatform`].
|
||||
pub fn iter() -> impl Iterator<Item = Self> {
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
let regional = RegionalRoute::iter().map(Self::Regional);
|
||||
let platform = PlatformRoute::iter().map(Self::Platform);
|
||||
let val_platform = ValPlatformRoute::iter().map(Self::ValPlatform);
|
||||
|
||||
regional.chain(platform).chain(val_platform)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_route_tostring() {
|
||||
assert_eq!(
|
||||
"AMERICAS",
|
||||
Into::<&'static str>::into(Route::Regional(RegionalRoute::AMERICAS))
|
||||
);
|
||||
assert_eq!(
|
||||
"KR",
|
||||
Into::<&'static str>::into(Route::Platform(PlatformRoute::KR))
|
||||
);
|
||||
assert_eq!(
|
||||
"KR",
|
||||
Into::<&'static str>::into(Route::ValPlatform(ValPlatformRoute::KR))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_route_iter() {
|
||||
for (i, route) in Route::iter().enumerate() {
|
||||
println!("{:>2} {:<10} {:>3}", i, route, u8::from(route));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_route_tryfrom() {
|
||||
for x in u8::MIN..=u8::MAX {
|
||||
if let Ok(route) = std::convert::TryInto::<Route>::try_into(x) {
|
||||
println!("{:>3} {:<8}", x, route);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_regional_tostring() {
|
||||
assert_eq!("AMERICAS", RegionalRoute::AMERICAS.to_string());
|
||||
assert_eq!("SEA", RegionalRoute::SEA.to_string());
|
||||
|
||||
assert_eq!(
|
||||
"AMERICAS",
|
||||
Into::<&'static str>::into(RegionalRoute::AMERICAS)
|
||||
);
|
||||
assert_eq!("SEA", Into::<&'static str>::into(RegionalRoute::SEA));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_regional_parse() {
|
||||
assert_eq!(Ok(RegionalRoute::AMERICAS), "AMERICAS".parse());
|
||||
assert_eq!(Ok(RegionalRoute::SEA), "SEA".parse());
|
||||
assert!("NA".parse::<RegionalRoute>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_platform_tostring() {
|
||||
assert_eq!("BR1", PlatformRoute::BR1.to_string());
|
||||
assert_eq!("KR", PlatformRoute::KR.to_string());
|
||||
|
||||
assert_eq!("BR1", Into::<&'static str>::into(PlatformRoute::BR1));
|
||||
assert_eq!("KR", Into::<&'static str>::into(PlatformRoute::KR));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_platform_parse() {
|
||||
assert_eq!(Ok(PlatformRoute::BR1), "BR1".parse());
|
||||
assert_eq!(Ok(PlatformRoute::KR), "KR".parse());
|
||||
assert_eq!(Ok(PlatformRoute::JP1), "JP1".parse());
|
||||
assert_eq!(Ok(PlatformRoute::JP1), "JP".parse());
|
||||
assert_eq!(Ok(PlatformRoute::NA1), "NA1".parse());
|
||||
assert_eq!(Ok(PlatformRoute::NA1), "NA".parse());
|
||||
assert!("LA".parse::<PlatformRoute>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valplatform_tostring() {
|
||||
assert_eq!("AP", ValPlatformRoute::AP.to_string());
|
||||
assert_eq!("KR", ValPlatformRoute::KR.to_string());
|
||||
assert_eq!("ESPORTS", ValPlatformRoute::ESPORTS.to_string());
|
||||
|
||||
assert_eq!("AP", Into::<&'static str>::into(ValPlatformRoute::AP));
|
||||
assert_eq!("KR", Into::<&'static str>::into(ValPlatformRoute::KR));
|
||||
assert_eq!(
|
||||
"ESPORTS",
|
||||
Into::<&'static str>::into(ValPlatformRoute::ESPORTS)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valplatform_parse() {
|
||||
assert_eq!(Ok(ValPlatformRoute::AP), "AP".parse());
|
||||
assert_eq!(Ok(ValPlatformRoute::KR), "KR".parse());
|
||||
assert_eq!(Ok(ValPlatformRoute::ESPORTS), "ESPORTS".parse());
|
||||
assert!("SEA".parse::<ValPlatformRoute>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tournament_region_serde() {
|
||||
use crate::consts::TournamentRegion;
|
||||
let json = serde_json::to_string(&TournamentRegion::EUNE);
|
||||
assert!(json.is_ok());
|
||||
assert_eq!("\"EUNE\"", &*json.unwrap());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
newtype_enum! {
|
||||
/// A League of Legends season for competitive matchmaking.
|
||||
pub newtype_enum Season(u8) {
|
||||
/// `0`.
|
||||
PRESEASON_3 = 0,
|
||||
/// `1`.
|
||||
SEASON_3 = 1,
|
||||
/// `2`.
|
||||
PRESEASON_2014 = 2,
|
||||
/// `3`.
|
||||
SEASON_2014 = 3,
|
||||
/// `4`.
|
||||
PRESEASON_2015 = 4,
|
||||
/// `5`.
|
||||
SEASON_2015 = 5,
|
||||
/// `6`.
|
||||
PRESEASON_2016 = 6,
|
||||
/// `7`.
|
||||
SEASON_2016 = 7,
|
||||
/// `8`.
|
||||
PRESEASON_2017 = 8,
|
||||
/// `9`.
|
||||
SEASON_2017 = 9,
|
||||
/// `10`.
|
||||
PRESEASON_2018 = 10,
|
||||
/// `11`.
|
||||
SEASON_2018 = 11,
|
||||
/// `12`.
|
||||
PRESEASON_2019 = 12,
|
||||
/// `13`.
|
||||
SEASON_2019 = 13,
|
||||
/// `14`.
|
||||
PRESEASON_2020 = 14,
|
||||
/// `15`.
|
||||
SEASON_2020 = 15,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
/// League of Legends team.
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Hash,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Serialize_repr,
|
||||
Deserialize_repr,
|
||||
IntoPrimitive,
|
||||
TryFromPrimitive,
|
||||
)]
|
||||
#[repr(u16)]
|
||||
pub enum Team {
|
||||
/// Team ID zero for 2v2v2v2 Arena `CHERRY` game mode. (TODO: SUBJECT TO CHANGE?)
|
||||
ZERO = 0,
|
||||
|
||||
/// Blue team (bottom left on Summoner's Rift).
|
||||
BLUE = 100,
|
||||
/// Red team (top right on Summoner's Rift).
|
||||
RED = 200,
|
||||
|
||||
/// "killerTeamId" when Baron Nashor spawns and kills Rift Herald.
|
||||
OTHER = 300,
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||
|
||||
/// LoL and TFT ranked tiers, such as gold, diamond, challenger, etc.
|
||||
///
|
||||
/// Sorts from lowest rank to highest rank.
|
||||
///
|
||||
/// Repr'd as arbitrary `u8` values.
|
||||
///
|
||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator).
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
IntoPrimitive,
|
||||
TryFromPrimitive,
|
||||
EnumString,
|
||||
Display,
|
||||
AsRefStr,
|
||||
IntoStaticStr,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum Tier {
|
||||
/// Challenger, the highest tier, an apex tier. Repr: `220_u8`.
|
||||
CHALLENGER = 220,
|
||||
/// Grand Master, an apex tier. Repr: `200_u8`.
|
||||
GRANDMASTER = 200,
|
||||
/// Master, an apex tier. Repr: `180_u8`.
|
||||
MASTER = 180,
|
||||
/// Diamond, the higest non-apex tier. Repr: `140_u8`.
|
||||
DIAMOND = 140,
|
||||
/// Emerald. Added in 2023. Repr: `130_u8`.
|
||||
EMERALD = 130,
|
||||
/// Platinum. Repr: `120_u8`.
|
||||
PLATINUM = 120,
|
||||
/// Gold. Repr: `100_u8`.
|
||||
GOLD = 100,
|
||||
/// Silver. Repr: `80_u8`.
|
||||
SILVER = 80,
|
||||
/// Bronze. Repr: `60_u8`.
|
||||
BRONZE = 60,
|
||||
/// Iron, the lowest tier. Repr: `40_u8`.
|
||||
IRON = 40,
|
||||
|
||||
/// Unranked, no tier. Repr: `0_u8`.
|
||||
/// Also deserializes from "NONE" returned by `lol-challenges-v1.getChallengePercentiles`.
|
||||
#[serde(alias = "NONE")]
|
||||
UNRANKED = 0,
|
||||
}
|
||||
|
||||
impl Tier {
|
||||
/// If this tier is an apex tier: [`Self::MASTER`], [`Self::GRANDMASTER`],
|
||||
/// or [`Self::CHALLENGER`]. Returns false for [`Self::UNRANKED`].
|
||||
///
|
||||
/// These tiers are NOT queryable by LeagueV4Endpoints::get_league_entries(...).
|
||||
pub const fn is_apex(self) -> bool {
|
||||
// Casts needed for const.
|
||||
(Self::MASTER as u8) <= (self as u8)
|
||||
}
|
||||
|
||||
/// If this tier is a "standard" tier: iron through diamond.
|
||||
/// Returns false for unranked.
|
||||
///
|
||||
/// ONLY these tiers are queryable by [`LeagueV4::get_league_entries(...)`](crate::endpoints::LeagueV4::get_league_entries).
|
||||
pub fn is_standard(self) -> bool {
|
||||
// Casts needed for const.
|
||||
((Self::UNRANKED as u8) < (self as u8)) && ((self as u8) < (Self::MASTER as u8))
|
||||
}
|
||||
|
||||
/// If this tier is ranked. Returns true for iron through challenger, false for unranked.
|
||||
pub const fn is_ranked(self) -> bool {
|
||||
// Casts needed for const.
|
||||
(Self::UNRANKED as u8) < (self as u8)
|
||||
}
|
||||
|
||||
/// If this tier is unranked (`Tier::UNRANKED`).
|
||||
///
|
||||
/// UNRANKED is returned by `Participant.highest_achieved_season_tier`.
|
||||
pub const fn is_unranked(self) -> bool {
|
||||
// Casts needed for const.
|
||||
(self as u8) <= (Self::UNRANKED as u8)
|
||||
}
|
||||
|
||||
/// Converts UNRANKED to None and all ranked tiers to Some(...).
|
||||
pub fn to_ranked(self) -> Option<Self> {
|
||||
if self.is_unranked() {
|
||||
None
|
||||
} else {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a DoubleEndedIterator of I, II, III, IV.
|
||||
/// Ordered from high rank (I) to low (IV).
|
||||
/// Excludes V, which is deprecated.
|
||||
impl IntoEnumIterator for Tier {
|
||||
type Iterator = std::iter::Copied<std::slice::Iter<'static, Self>>;
|
||||
fn iter() -> Self::Iterator {
|
||||
[
|
||||
Self::CHALLENGER,
|
||||
Self::GRANDMASTER,
|
||||
Self::MASTER,
|
||||
Self::DIAMOND,
|
||||
Self::EMERALD,
|
||||
Self::PLATINUM,
|
||||
Self::GOLD,
|
||||
Self::SILVER,
|
||||
Self::BRONZE,
|
||||
Self::IRON,
|
||||
]
|
||||
.iter()
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ord() {
|
||||
assert!(Tier::GOLD < Tier::DIAMOND);
|
||||
assert!(Tier::UNRANKED < Tier::IRON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_apex() {
|
||||
assert!(Tier::GRANDMASTER.is_apex());
|
||||
assert!(!Tier::DIAMOND.is_apex());
|
||||
assert!(!Tier::UNRANKED.is_apex());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_ranked() {
|
||||
assert!(Tier::GRANDMASTER.is_ranked());
|
||||
assert!(Tier::DIAMOND.is_ranked());
|
||||
assert!(!Tier::UNRANKED.is_ranked());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_unranked() {
|
||||
assert!(!Tier::GRANDMASTER.is_unranked());
|
||||
assert!(!Tier::DIAMOND.is_unranked());
|
||||
assert!(Tier::UNRANKED.is_unranked());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_ranked() {
|
||||
assert_eq!(Some(Tier::GRANDMASTER), Tier::GRANDMASTER.to_ranked());
|
||||
assert_eq!(Some(Tier::DIAMOND), Tier::DIAMOND.to_ranked());
|
||||
assert_eq!(None, Tier::UNRANKED.to_ranked());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_standard() {
|
||||
assert!(!Tier::GRANDMASTER.is_standard());
|
||||
assert!(Tier::DIAMOND.is_standard());
|
||||
assert!(!Tier::UNRANKED.is_standard());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
assert_eq!("GRANDMASTER", Tier::GRANDMASTER.as_ref());
|
||||
assert_eq!("GRANDMASTER", Tier::GRANDMASTER.to_string());
|
||||
assert_eq!("UNRANKED", Tier::UNRANKED.as_ref());
|
||||
assert_eq!("UNRANKED", Tier::UNRANKED.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_string() {
|
||||
assert_eq!(Ok(Tier::GRANDMASTER), "GRANDMASTER".parse());
|
||||
assert_eq!(Ok(Tier::UNRANKED), "UNRANKED".parse());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter() {
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
let mut iter = Tier::iter();
|
||||
assert_eq!(Some(Tier::CHALLENGER), iter.next());
|
||||
iter.next();
|
||||
iter.next();
|
||||
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
||||
assert_eq!(Some(Tier::EMERALD), iter.next());
|
||||
iter.next();
|
||||
iter.next();
|
||||
iter.next();
|
||||
iter.next();
|
||||
assert_eq!(Some(Tier::IRON), iter.next());
|
||||
assert_eq!(None, iter.next());
|
||||
assert_eq!(None, iter.next_back());
|
||||
|
||||
let mut iter = Tier::iter().rev();
|
||||
assert_eq!(Some(Tier::IRON), iter.next());
|
||||
iter.next();
|
||||
iter.next();
|
||||
iter.next();
|
||||
iter.next();
|
||||
assert_eq!(Some(Tier::EMERALD), iter.next());
|
||||
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
||||
iter.next();
|
||||
iter.next();
|
||||
assert_eq!(Some(Tier::CHALLENGER), iter.next());
|
||||
assert_eq!(None, iter.next());
|
||||
assert_eq!(None, iter.next_back());
|
||||
|
||||
let mut iter = Tier::iter();
|
||||
assert_eq!(Some(Tier::CHALLENGER), iter.next());
|
||||
assert_eq!(Some(Tier::IRON), iter.next_back());
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,65 +1,69 @@
|
|||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
|
||||
/// Re-exported `reqwest` types.
|
||||
pub mod reqwest {
|
||||
pub use reqwest::{
|
||||
Error, Response, StatusCode, Url
|
||||
};
|
||||
}
|
||||
use ::reqwest::*;
|
||||
|
||||
/// Result containing RiotApiError on failure.
|
||||
pub type Result<T> = std::result::Result<T, RiotApiError>;
|
||||
|
||||
/// An error that occurred while processing a Riot API request.
|
||||
///
|
||||
/// Although Riven may make multiple requests due to retries, this will always
|
||||
/// contain exactly one reqwest::Error for the final request which failed.
|
||||
#[derive(Debug)]
|
||||
pub struct RiotApiError {
|
||||
reqwest_error: Error,
|
||||
retries: u8,
|
||||
response: Option<Response>,
|
||||
status_code: Option<StatusCode>,
|
||||
}
|
||||
impl RiotApiError {
|
||||
pub(crate) fn new(reqwest_error: Error, retries: u8, response: Option<Response>, status_code: Option<StatusCode>) -> Self {
|
||||
Self {
|
||||
reqwest_error: reqwest_error,
|
||||
retries: retries,
|
||||
response: response,
|
||||
status_code: status_code,
|
||||
}
|
||||
}
|
||||
/// The reqwest::Error for the final failed request.
|
||||
pub fn source_reqwest_error(&self) -> &Error {
|
||||
&self.reqwest_error
|
||||
}
|
||||
/// The number of retires attempted. Zero means exactly one request, zero retries.
|
||||
pub fn retries(&self) -> u8 {
|
||||
self.retries
|
||||
}
|
||||
/// 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 response(&self) -> Option<&Response> {
|
||||
self.response.as_ref()
|
||||
}
|
||||
/// The failed response's HTTP status code.
|
||||
/// `Some(reqwest::StatusCode)` if the request was sent and failed, OR if parsing the response JSON failed.
|
||||
/// `None` if the request was not sent.
|
||||
pub fn status_code(&self) -> Option<StatusCode> {
|
||||
self.status_code
|
||||
}
|
||||
}
|
||||
impl fmt::Display for RiotApiError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:#?}", self)
|
||||
}
|
||||
}
|
||||
impl StdError for RiotApiError {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
Some(&self.reqwest_error)
|
||||
}
|
||||
}
|
||||
use std::fmt;
|
||||
|
||||
use reqwest::{Error, Response, StatusCode};
|
||||
|
||||
/// Result containing RiotApiError on failure.
|
||||
pub type Result<T> = std::result::Result<T, RiotApiError>;
|
||||
|
||||
/// An error that occurred while processing a Riot API request.
|
||||
///
|
||||
/// Although Riven may make multiple requests due to retries, this will always
|
||||
/// contain exactly one reqwest::Error for the final request which failed.
|
||||
#[derive(Debug)]
|
||||
pub struct RiotApiError {
|
||||
reqwest_error: Error,
|
||||
retries: u8,
|
||||
response: Option<Response>,
|
||||
status_code: Option<StatusCode>,
|
||||
}
|
||||
impl RiotApiError {
|
||||
pub(crate) fn new(
|
||||
reqwest_error: Error,
|
||||
retries: u8,
|
||||
response: Option<Response>,
|
||||
status_code: Option<StatusCode>,
|
||||
) -> Self {
|
||||
Self {
|
||||
reqwest_error,
|
||||
retries,
|
||||
response,
|
||||
status_code,
|
||||
}
|
||||
}
|
||||
/// The reqwest::Error for the final failed request.
|
||||
pub fn source_reqwest_error(&self) -> &Error {
|
||||
&self.reqwest_error
|
||||
}
|
||||
/// The number of retires attempted. Zero means exactly one request, zero retries.
|
||||
pub fn retries(&self) -> u8 {
|
||||
self.retries
|
||||
}
|
||||
/// 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 response(&self) -> Option<&Response> {
|
||||
self.response.as_ref()
|
||||
}
|
||||
/// The failed response.
|
||||
/// `Some(reqwest::Response)` if the request was sent and failed.
|
||||
/// `None` if the request was not sent, OR if parsing the response JSON failed.
|
||||
pub fn take_response(&mut self) -> Option<Response> {
|
||||
self.response.take()
|
||||
}
|
||||
/// The failed response's HTTP status code.
|
||||
/// `Some(reqwest::StatusCode)` if the request was sent and failed, OR if parsing the response JSON failed.
|
||||
/// `None` if the request was not sent.
|
||||
pub fn status_code(&self) -> Option<StatusCode> {
|
||||
self.status_code
|
||||
}
|
||||
}
|
||||
impl fmt::Display for RiotApiError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:#?}", self)
|
||||
}
|
||||
}
|
||||
impl std::error::Error for RiotApiError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
Some(&self.reqwest_error)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! <h1 align="center">
|
||||
//! Riven<br>
|
||||
//! </h1>
|
||||
//! <p align="center">
|
||||
//! <a href="https://github.com/MingweiSamuel/Riven/"><img src="https://cdn.communitydragon.org/latest/champion/Riven/square" width="20" height="20" alt="Riven Github"></a>
|
||||
//! <a href="https://crates.io/crates/riven"><img src="https://img.shields.io/crates/v/riven?style=flat-square&logo=rust" alt="Crates.io"></a>
|
||||
//! <a href="https://docs.rs/riven/"><img src="https://img.shields.io/badge/docs.rs-Riven-blue?style=flat-square&logo=read-the-docs&logoColor=white" alt="Docs.rs"></a>
|
||||
//! <!--<a href="https://travis-ci.com/MingweiSamuel/Riven"><img src="https://img.shields.io/travis/com/mingweisamuel/riven?style=flat-square" alt="Travis CI"></a>-->
|
||||
//! <a href="https://github.com/rust-secure-code/safety-dance/"><img src="https://img.shields.io/badge/unsafe-forbidden-green.svg?style=flat-square" alt="unsafe forbidden"></a>
|
||||
//! </p>
|
||||
//!
|
||||
//! Rust Library for the [Riot Games API](https://developer.riotgames.com/).
|
||||
//!
|
||||
//! Riven's goals are _speed_, _reliability_, and _maintainability_. Riven handles rate limits and large requests with ease.
|
||||
//! Data structs and endpoints are automatically generated from the
|
||||
//! [Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)).
|
||||
//!
|
||||
//! # Design
|
||||
//!
|
||||
//! * Fast, asynchronous, thread-safe.
|
||||
//! * Automatically retries failed requests.
|
||||
//! * Supports all endpoints, kept up-to-date using [riotapi-schema](https://github.com/MingweiSamuel/riotapi-schema).
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! ```rust
|
||||
//! use riven::RiotApi;
|
||||
//! use riven::consts::PlatformRoute;
|
||||
//!
|
||||
//! // Enter tokio async runtime.
|
||||
//! let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
//! rt.block_on(async {
|
||||
//! // Create RiotApi instance from key string.
|
||||
//! let api_key = std::env!("RGAPI_KEY"); // "RGAPI-01234567-89ab-cdef-0123-456789abcdef";
|
||||
//! let riot_api = RiotApi::new(api_key);
|
||||
//!
|
||||
//! // Get summoner data.
|
||||
//! let summoner = riot_api.summoner_v4()
|
||||
//! .get_by_summoner_name(PlatformRoute::NA1, "잘 못").await
|
||||
//! .expect("Get summoner failed.")
|
||||
//! .expect("There is no summoner with that name.");
|
||||
//!
|
||||
//! // Print summoner name.
|
||||
//! println!("{} Champion Masteries:", summoner.name);
|
||||
//!
|
||||
//! // Get champion mastery data.
|
||||
//! let masteries = riot_api.champion_mastery_v4()
|
||||
//! .get_all_champion_masteries_by_puuid(PlatformRoute::NA1, &summoner.puuid).await
|
||||
//! .expect("Get champion masteries failed.");
|
||||
//!
|
||||
//! // Print champion masteries.
|
||||
//! for (i, mastery) in masteries.iter().take(10).enumerate() {
|
||||
//! println!("{: >2}) {: <9} {: >7} ({})", i + 1,
|
||||
//! mastery.champion_id.name().unwrap_or("UNKNOWN"),
|
||||
//! mastery.champion_points, mastery.champion_level);
|
||||
//! }
|
||||
//! });
|
||||
//! ```
|
||||
//! Output:
|
||||
//! ```text
|
||||
//! 잘 못 Champion Masteries:
|
||||
//! 1) Riven 1236866 (7)
|
||||
//! 2) Fiora 230679 (5)
|
||||
//! 3) Katarina 175985 (5)
|
||||
//! 4) Lee Sin 156070 (7)
|
||||
//! 5) Jax 102662 (5)
|
||||
//! 6) Gnar 76373 (6)
|
||||
//! 7) Kai'Sa 64271 (5)
|
||||
//! 8) Caitlyn 46614 (5)
|
||||
//! 9) Irelia 46465 (5)
|
||||
//! 10) Vladimir 37176 (5)
|
||||
//! ```
|
||||
//! The [`RiotApi` struct documentation](https://docs.rs/riven/latest/riven/struct.RiotApi.html)
|
||||
//! contains additional usage information. The [tests](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/tests)
|
||||
//! and [example proxy](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/examples/proxy)
|
||||
//! provide more example usage.
|
||||
//!
|
||||
//! ## Feature Flags
|
||||
//!
|
||||
//! ### Nightly vs Stable
|
||||
//!
|
||||
//! Enable the `nightly` feature to use nightly-only functionality. This enables
|
||||
//! [nightly optimizations in the `parking_lot` crate](https://github.com/Amanieu/parking_lot#nightly-vs-stable).
|
||||
//!
|
||||
//! ```toml
|
||||
//! riven = { version = "...", features = [ "nightly" ] }
|
||||
//! ```
|
||||
//!
|
||||
//! ### rustls
|
||||
//!
|
||||
//! Riven uses [reqwest](https://github.com/seanmonstar/reqwest) for making requests. By default, reqwest uses the native TLS library.
|
||||
//! If you prefer using [rustls](https://github.com/ctz/rustls) you can do so by turning off the Riven default features
|
||||
//! and specifying the `rustls-tls` feature:
|
||||
//!
|
||||
//! ```toml
|
||||
//! riven = { version = "...", default-features = false, features = [ "rustls-tls" ] }
|
||||
//! ```
|
||||
//!
|
||||
//! Riven is additionally able to produce [tracing](https://docs.rs/tracing) spans for requests if the `tracing` feature is enabled. This feature is disabled by default.
|
||||
//!
|
||||
//! ## Docs
|
||||
//!
|
||||
//! [On docs.rs](https://docs.rs/riven/).
|
||||
//!
|
||||
//! ## Error Handling
|
||||
//!
|
||||
//! Riven returns either `Result<T>` or `Result<Option<T>>` within futures.
|
||||
//!
|
||||
//! If the `Result` is errored, this indicates that the API request failed to
|
||||
//! complete successfully, which may be due to bad user input, Riot server errors,
|
||||
//! incorrect API key, etc.
|
||||
//!
|
||||
//! If the `Option` is `None`, this indicates that the request completed
|
||||
//! successfully but no data was returned. This happens in several situations, such
|
||||
//! as getting a summoner (by name) or match (by id) that doesn't exist, or getting
|
||||
//! spectator data for a summoner who is not in-game.
|
||||
//! Specifically, the API returned a 404 HTTP status code in this situation.
|
||||
//!
|
||||
//! The error type used by Riven is `riven::RiotApiError`. It provides some basic
|
||||
//! diagnostic information, such as the source Reqwest error, the number of retries
|
||||
//! attempted, and the Reqwest `Response` object.
|
||||
//!
|
||||
//! You can configure the number of time Riven retries using
|
||||
//! `RiotApiConfig::set_retries(...)` and the `RiotApi::from_config(config)`
|
||||
//! constructor. By default, Riven retries up to 3 times (4 requests total).
|
||||
//! Some errors, such as 400 client errors, are not retried as they would
|
||||
//! inevitably fail again.
|
||||
//!
|
||||
//! ## Semantic Versioning
|
||||
//!
|
||||
//! This package follows semantic versioning to an extent. However, the Riot API
|
||||
//! itself changes often and does not follow semantic versioning, which makes
|
||||
//! things difficult. Out-of-date versions will slowly partially cease to work due
|
||||
//! to this.
|
||||
//!
|
||||
//! When the API changes, this may result in breaking changes in the `models`
|
||||
//! module, `endpoints` module, and some of the `consts` module. "Handle accessor"
|
||||
//! methods may be removed from `RiotApi` if the corresponding endpoint is removed
|
||||
//! from the Riot API. These breaking changes will increment the **MINOR** version,
|
||||
//! not the major version.
|
||||
//!
|
||||
//! Parts of Riven that do not depend on Riot API changes do follow semantic
|
||||
//! versioning.
|
||||
//!
|
||||
//! ## Additional Help
|
||||
//!
|
||||
//! Feel free to [make an issue](https://github.com/MingweiSamuel/Riven/issues/new)
|
||||
//! if you are have any questions or trouble with Riven.
|
||||
//!
|
||||
//! # Development
|
||||
//!
|
||||
//! NodeJS is used to generate code for Riven. The
|
||||
//! [`riven/srcgen`](https://github.com/MingweiSamuel/Riven/tree/v/2.x.x/riven/srcgen)
|
||||
//! folder contains the code and [doT.js](https://olado.github.io/doT/index.html)
|
||||
//! templates. `index.js` lists the JSON files downloaded and used to generate the
|
||||
//! code.
|
||||
//!
|
||||
//! To set up the srcgen, you will first need to install NodeJS. Then enter the
|
||||
//! `riven/srcgen` folder and run `npm ci` (or `npm install`) to install
|
||||
//! dependencies.
|
||||
//!
|
||||
//! To run the srcgen use `node riven/srcgen` from the repository root.
|
||||
//!
|
||||
//!
|
||||
|
||||
// Re-exported reqwest types.
|
||||
pub use reqwest;
|
||||
|
||||
mod config;
|
||||
pub use config::RiotApiConfig;
|
||||
|
||||
pub mod consts;
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub mod endpoints;
|
||||
|
||||
mod error;
|
||||
pub use error::*;
|
||||
|
||||
pub mod meta;
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub mod models;
|
||||
mod models_impls;
|
||||
|
||||
mod req;
|
||||
|
||||
mod response_info;
|
||||
pub use response_info::*;
|
||||
|
||||
mod riot_api;
|
||||
pub use riot_api::*;
|
||||
|
||||
mod util;
|
|
@ -0,0 +1,100 @@
|
|||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
||||
// Version d4f02b20da80dd2c869da349ba774ef6eddc22fa
|
||||
|
||||
//! Metadata about the Riot API and Riven.
|
||||
//!
|
||||
//! Note: this modules is automatically generated.
|
||||
|
||||
/// Metadata for endpoints. Each tuple corresponds to one endpoint and contains
|
||||
/// the HTTP [`Method`](reqwest::Method), `str` path, and the method's `str` ID.
|
||||
pub static ALL_ENDPOINTS: [(reqwest::Method, &str, &str); 80] = [
|
||||
(reqwest::Method::GET, "/riot/account/v1/accounts/by-puuid/{puuid}", "account-v1.getByPuuid"),
|
||||
(reqwest::Method::GET, "/riot/account/v1/accounts/by-riot-id/{gameName}/{tagLine}", "account-v1.getByRiotId"),
|
||||
(reqwest::Method::GET, "/riot/account/v1/accounts/me", "account-v1.getByAccessToken"),
|
||||
(reqwest::Method::GET, "/riot/account/v1/active-shards/by-game/{game}/by-puuid/{puuid}", "account-v1.getActiveShard"),
|
||||
(reqwest::Method::GET, "/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}", "champion-mastery-v4.getAllChampionMasteriesByPUUID"),
|
||||
(reqwest::Method::GET, "/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}/by-champion/{championId}", "champion-mastery-v4.getChampionMasteryByPUUID"),
|
||||
(reqwest::Method::GET, "/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}/top", "champion-mastery-v4.getTopChampionMasteriesByPUUID"),
|
||||
(reqwest::Method::GET, "/lol/champion-mastery/v4/scores/by-puuid/{encryptedPUUID}", "champion-mastery-v4.getChampionMasteryScoreByPUUID"),
|
||||
(reqwest::Method::GET, "/lol/platform/v3/champion-rotations", "champion-v3.getChampionInfo"),
|
||||
(reqwest::Method::GET, "/lol/clash/v1/players/by-summoner/{summonerId}", "clash-v1.getPlayersBySummoner"),
|
||||
(reqwest::Method::GET, "/lol/clash/v1/teams/{teamId}", "clash-v1.getTeamById"),
|
||||
(reqwest::Method::GET, "/lol/clash/v1/tournaments", "clash-v1.getTournaments"),
|
||||
(reqwest::Method::GET, "/lol/clash/v1/tournaments/by-team/{teamId}", "clash-v1.getTournamentByTeam"),
|
||||
(reqwest::Method::GET, "/lol/clash/v1/tournaments/{tournamentId}", "clash-v1.getTournamentById"),
|
||||
(reqwest::Method::GET, "/lol/league-exp/v4/entries/{queue}/{tier}/{division}", "league-exp-v4.getLeagueEntries"),
|
||||
(reqwest::Method::GET, "/lol/league/v4/challengerleagues/by-queue/{queue}", "league-v4.getChallengerLeague"),
|
||||
(reqwest::Method::GET, "/lol/league/v4/entries/by-summoner/{encryptedSummonerId}", "league-v4.getLeagueEntriesForSummoner"),
|
||||
(reqwest::Method::GET, "/lol/league/v4/entries/{queue}/{tier}/{division}", "league-v4.getLeagueEntries"),
|
||||
(reqwest::Method::GET, "/lol/league/v4/grandmasterleagues/by-queue/{queue}", "league-v4.getGrandmasterLeague"),
|
||||
(reqwest::Method::GET, "/lol/league/v4/leagues/{leagueId}", "league-v4.getLeagueById"),
|
||||
(reqwest::Method::GET, "/lol/league/v4/masterleagues/by-queue/{queue}", "league-v4.getMasterLeague"),
|
||||
(reqwest::Method::GET, "/lol/challenges/v1/challenges/config", "lol-challenges-v1.getAllChallengeConfigs"),
|
||||
(reqwest::Method::GET, "/lol/challenges/v1/challenges/percentiles", "lol-challenges-v1.getAllChallengePercentiles"),
|
||||
(reqwest::Method::GET, "/lol/challenges/v1/challenges/{challengeId}/config", "lol-challenges-v1.getChallengeConfigs"),
|
||||
(reqwest::Method::GET, "/lol/challenges/v1/challenges/{challengeId}/leaderboards/by-level/{level}", "lol-challenges-v1.getChallengeLeaderboards"),
|
||||
(reqwest::Method::GET, "/lol/challenges/v1/challenges/{challengeId}/percentiles", "lol-challenges-v1.getChallengePercentiles"),
|
||||
(reqwest::Method::GET, "/lol/challenges/v1/player-data/{puuid}", "lol-challenges-v1.getPlayerData"),
|
||||
(reqwest::Method::GET, "/lol/status/v3/shard-data", "lol-status-v3.getShardData"),
|
||||
(reqwest::Method::GET, "/lol/status/v4/platform-data", "lol-status-v4.getPlatformData"),
|
||||
(reqwest::Method::GET, "/lor/deck/v1/decks/me", "lor-deck-v1.getDecks"),
|
||||
(reqwest::Method::POST, "/lor/deck/v1/decks/me", "lor-deck-v1.createDeck"),
|
||||
(reqwest::Method::GET, "/lor/inventory/v1/cards/me", "lor-inventory-v1.getCards"),
|
||||
(reqwest::Method::GET, "/lor/match/v1/matches/by-puuid/{puuid}/ids", "lor-match-v1.getMatchIdsByPUUID"),
|
||||
(reqwest::Method::GET, "/lor/match/v1/matches/{matchId}", "lor-match-v1.getMatch"),
|
||||
(reqwest::Method::GET, "/lor/ranked/v1/leaderboards", "lor-ranked-v1.getLeaderboards"),
|
||||
(reqwest::Method::GET, "/lor/status/v1/platform-data", "lor-status-v1.getPlatformData"),
|
||||
(reqwest::Method::GET, "/lol/match/v5/matches/by-puuid/{puuid}/ids", "match-v5.getMatchIdsByPUUID"),
|
||||
(reqwest::Method::GET, "/lol/match/v5/matches/{matchId}", "match-v5.getMatch"),
|
||||
(reqwest::Method::GET, "/lol/match/v5/matches/{matchId}/timeline", "match-v5.getTimeline"),
|
||||
(reqwest::Method::GET, "/lol/spectator/v4/active-games/by-summoner/{encryptedSummonerId}", "spectator-v4.getCurrentGameInfoBySummoner"),
|
||||
(reqwest::Method::GET, "/lol/spectator/v4/featured-games", "spectator-v4.getFeaturedGames"),
|
||||
(reqwest::Method::GET, "/fulfillment/v1/summoners/by-puuid/{rsoPUUID}", "summoner-v4.getByRSOPUUID"),
|
||||
(reqwest::Method::GET, "/lol/summoner/v4/summoners/by-account/{encryptedAccountId}", "summoner-v4.getByAccountId"),
|
||||
(reqwest::Method::GET, "/lol/summoner/v4/summoners/by-name/{summonerName}", "summoner-v4.getBySummonerName"),
|
||||
(reqwest::Method::GET, "/lol/summoner/v4/summoners/by-puuid/{encryptedPUUID}", "summoner-v4.getByPUUID"),
|
||||
(reqwest::Method::GET, "/lol/summoner/v4/summoners/me", "summoner-v4.getByAccessToken"),
|
||||
(reqwest::Method::GET, "/lol/summoner/v4/summoners/{encryptedSummonerId}", "summoner-v4.getBySummonerId"),
|
||||
(reqwest::Method::GET, "/tft/league/v1/challenger", "tft-league-v1.getChallengerLeague"),
|
||||
(reqwest::Method::GET, "/tft/league/v1/entries/by-summoner/{summonerId}", "tft-league-v1.getLeagueEntriesForSummoner"),
|
||||
(reqwest::Method::GET, "/tft/league/v1/entries/{tier}/{division}", "tft-league-v1.getLeagueEntries"),
|
||||
(reqwest::Method::GET, "/tft/league/v1/grandmaster", "tft-league-v1.getGrandmasterLeague"),
|
||||
(reqwest::Method::GET, "/tft/league/v1/leagues/{leagueId}", "tft-league-v1.getLeagueById"),
|
||||
(reqwest::Method::GET, "/tft/league/v1/master", "tft-league-v1.getMasterLeague"),
|
||||
(reqwest::Method::GET, "/tft/league/v1/rated-ladders/{queue}/top", "tft-league-v1.getTopRatedLadder"),
|
||||
(reqwest::Method::GET, "/tft/match/v1/matches/by-puuid/{puuid}/ids", "tft-match-v1.getMatchIdsByPUUID"),
|
||||
(reqwest::Method::GET, "/tft/match/v1/matches/{matchId}", "tft-match-v1.getMatch"),
|
||||
(reqwest::Method::GET, "/tft/status/v1/platform-data", "tft-status-v1.getPlatformData"),
|
||||
(reqwest::Method::GET, "/tft/summoner/v1/summoners/by-account/{encryptedAccountId}", "tft-summoner-v1.getByAccountId"),
|
||||
(reqwest::Method::GET, "/tft/summoner/v1/summoners/by-name/{summonerName}", "tft-summoner-v1.getBySummonerName"),
|
||||
(reqwest::Method::GET, "/tft/summoner/v1/summoners/by-puuid/{encryptedPUUID}", "tft-summoner-v1.getByPUUID"),
|
||||
(reqwest::Method::GET, "/tft/summoner/v1/summoners/me", "tft-summoner-v1.getByAccessToken"),
|
||||
(reqwest::Method::GET, "/tft/summoner/v1/summoners/{encryptedSummonerId}", "tft-summoner-v1.getBySummonerId"),
|
||||
(reqwest::Method::POST, "/lol/tournament-stub/v5/codes", "tournament-stub-v5.createTournamentCode"),
|
||||
(reqwest::Method::GET, "/lol/tournament-stub/v5/codes/{tournamentCode}", "tournament-stub-v5.getTournamentCode"),
|
||||
(reqwest::Method::GET, "/lol/tournament-stub/v5/lobby-events/by-code/{tournamentCode}", "tournament-stub-v5.getLobbyEventsByCode"),
|
||||
(reqwest::Method::POST, "/lol/tournament-stub/v5/providers", "tournament-stub-v5.registerProviderData"),
|
||||
(reqwest::Method::POST, "/lol/tournament-stub/v5/tournaments", "tournament-stub-v5.registerTournament"),
|
||||
(reqwest::Method::POST, "/lol/tournament/v5/codes", "tournament-v5.createTournamentCode"),
|
||||
(reqwest::Method::GET, "/lol/tournament/v5/codes/{tournamentCode}", "tournament-v5.getTournamentCode"),
|
||||
(reqwest::Method::PUT, "/lol/tournament/v5/codes/{tournamentCode}", "tournament-v5.updateCode"),
|
||||
(reqwest::Method::GET, "/lol/tournament/v5/games/by-code/{tournamentCode}", "tournament-v5.getGames"),
|
||||
(reqwest::Method::GET, "/lol/tournament/v5/lobby-events/by-code/{tournamentCode}", "tournament-v5.getLobbyEventsByCode"),
|
||||
(reqwest::Method::POST, "/lol/tournament/v5/providers", "tournament-v5.registerProviderData"),
|
||||
(reqwest::Method::POST, "/lol/tournament/v5/tournaments", "tournament-v5.registerTournament"),
|
||||
(reqwest::Method::GET, "/val/content/v1/contents", "val-content-v1.getContent"),
|
||||
(reqwest::Method::GET, "/val/match/v1/matches/{matchId}", "val-match-v1.getMatch"),
|
||||
(reqwest::Method::GET, "/val/match/v1/matchlists/by-puuid/{puuid}", "val-match-v1.getMatchlist"),
|
||||
(reqwest::Method::GET, "/val/match/v1/recent-matches/by-queue/{queue}", "val-match-v1.getRecent"),
|
||||
(reqwest::Method::GET, "/val/ranked/v1/leaderboards/by-act/{actId}", "val-ranked-v1.getLeaderboard"),
|
||||
(reqwest::Method::GET, "/val/status/v1/platform-data", "val-status-v1.getPlatformData"),
|
||||
];
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,21 @@
|
|||
use crate::consts::Champion;
|
||||
use crate::models::match_v5::Participant;
|
||||
|
||||
impl Participant {
|
||||
/// This method takes the [`Self::champion_id`] field if it is valid
|
||||
/// (`Ok`), otherwise it attempts to parse [`Self::champion_name`] and
|
||||
/// returns the `Result`.
|
||||
///
|
||||
/// This is needed because some of Riot's [`Self::champion_id`] data is
|
||||
/// corrupted, as they describe in the docs:
|
||||
///
|
||||
/// > Prior to patch 11.4, on Feb 18th, 2021, this field returned invalid
|
||||
/// > championIds. We recommend determining the champion based on the
|
||||
/// > championName field for matches played prior to patch 11.4.
|
||||
///
|
||||
/// This issue is reported here: <https://github.com/RiotGames/developer-relations/issues/553>.
|
||||
pub fn champion(&self) -> Result<Champion, <Champion as std::str::FromStr>::Err> {
|
||||
#[allow(deprecated)]
|
||||
self.champion_id.or_else(|_| self.champion_name.parse())
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
//! Module containing rate limiting and requesting types.
|
||||
|
||||
mod rate_limit;
|
||||
pub use rate_limit::*;
|
||||
|
||||
mod rate_limit_type;
|
||||
pub use rate_limit_type::*;
|
||||
|
||||
use std::time::Instant; // Hack for token_bucket_test.rs.
|
||||
mod token_bucket;
|
||||
pub use token_bucket::*;
|
||||
|
||||
mod regional_requester;
|
||||
pub use regional_requester::*;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "token_bucket.test.rs"]
|
||||
mod token_bucket_test;
|
||||
//! Module containing rate limiting and requesting types.
|
||||
|
||||
mod rate_limit;
|
||||
pub use rate_limit::*;
|
||||
|
||||
mod rate_limit_type;
|
||||
use std::time::Instant;
|
||||
|
||||
pub use rate_limit_type::*; // Hack for token_bucket_test.rs.
|
||||
mod token_bucket;
|
||||
pub use token_bucket::*;
|
||||
|
||||
mod regional_requester;
|
||||
pub use regional_requester::*;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "token_bucket.test.rs"]
|
||||
mod token_bucket_test;
|
|
@ -0,0 +1,307 @@
|
|||
use std::cmp;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||
use reqwest::{Response, StatusCode};
|
||||
use scan_fmt::scan_fmt;
|
||||
use tokio::sync::Notify;
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing as log;
|
||||
|
||||
use super::{RateLimitType, TokenBucket, VectorTokenBucket};
|
||||
use crate::RiotApiConfig;
|
||||
|
||||
pub struct RateLimit {
|
||||
rate_limit_type: RateLimitType,
|
||||
// Buckets for this rate limit (synchronized).
|
||||
// Almost always read, written only when rate limit rates are updated
|
||||
// from API response.
|
||||
buckets: RwLock<Vec<VectorTokenBucket>>,
|
||||
// Set to when we can retry if a retry-after header is received.
|
||||
retry_after: RwLock<Option<Instant>>,
|
||||
// Notifies waiters when rate limits are updated.
|
||||
update_notify: Notify,
|
||||
}
|
||||
|
||||
impl RateLimit {
|
||||
/// Header specifying which RateLimitType caused a 429.
|
||||
/// This header specifies which rate limit is violated in a 429 (if any).
|
||||
/// There are three possible values, see [HEADER_XRATELIMITTYPE_APPLICATION],
|
||||
/// [HEADER_XRATELIMITTYPE_METHOD], and [HEADER_XRATELIMITTYPE_SERVICE].
|
||||
const HEADER_XRATELIMITTYPE: &'static str = "X-Rate-Limit-Type";
|
||||
|
||||
/// `"application"` - Entire app/key is rate limited due to violation.
|
||||
const HEADER_XRATELIMITTYPE_APPLICATION: &'static str = "application";
|
||||
/// `"method"` - App/key is rate limited on a specific method due to violation.
|
||||
const HEADER_XRATELIMITTYPE_METHOD: &'static str = "method";
|
||||
/// `"service"` - Service backend is rate-limiting (no violation).
|
||||
const HEADER_XRATELIMITTYPE_SERVICE: &'static str = "service";
|
||||
|
||||
pub fn new(rate_limit_type: RateLimitType) -> Self {
|
||||
let initial_bucket =
|
||||
VectorTokenBucket::new(Duration::from_secs(1), 1, Duration::new(0, 0), 1.0, 1.0);
|
||||
RateLimit {
|
||||
rate_limit_type,
|
||||
// Rate limit before getting from response: 1/s.
|
||||
buckets: RwLock::new(vec![initial_bucket]),
|
||||
retry_after: RwLock::new(None),
|
||||
update_notify: Notify::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn acquire_both(app_rate_limit: &Self, method_rate_limit: &Self) {
|
||||
while let Some(delay) = Self::acquire_both_or_duration(app_rate_limit, method_rate_limit) {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = tokio::time::sleep(delay) => { continue }
|
||||
_ = app_rate_limit.update_notify.notified() => {}
|
||||
_ = method_rate_limit.update_notify.notified() => {}
|
||||
};
|
||||
log::trace!("Task awoken due to rate limit update.");
|
||||
}
|
||||
}
|
||||
|
||||
fn acquire_both_or_duration(
|
||||
app_rate_limit: &Self,
|
||||
method_rate_limit: &Self,
|
||||
) -> Option<Duration> {
|
||||
// Check retry after.
|
||||
{
|
||||
let retry_after_delay = app_rate_limit.get_retry_after_delay().and_then(|a| {
|
||||
method_rate_limit
|
||||
.get_retry_after_delay()
|
||||
.map(|m| cmp::max(a, m))
|
||||
});
|
||||
if retry_after_delay.is_some() {
|
||||
return retry_after_delay;
|
||||
}
|
||||
}
|
||||
// Check buckets.
|
||||
let app_buckets = app_rate_limit.buckets.read();
|
||||
let method_buckets = method_rate_limit.buckets.read();
|
||||
for bucket in app_buckets.iter().chain(method_buckets.iter()) {
|
||||
let delay = bucket.get_delay();
|
||||
if delay.is_some() {
|
||||
return delay;
|
||||
}
|
||||
}
|
||||
// Success.
|
||||
for bucket in app_buckets.iter().chain(method_buckets.iter()) {
|
||||
bucket.get_tokens(1);
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
"Tokens obtained, buckets: APP {:?} METHOD {:?}",
|
||||
app_buckets,
|
||||
method_buckets
|
||||
);
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_retry_after_delay(&self) -> Option<Duration> {
|
||||
self.retry_after
|
||||
.read()
|
||||
.and_then(|i| Instant::now().checked_duration_since(i))
|
||||
}
|
||||
|
||||
/// Update retry-after and rate limits based on an API response.
|
||||
/// Returns the retry-after delay if set.
|
||||
pub fn on_response(&self, config: &RiotApiConfig, response: &Response) -> Option<Duration> {
|
||||
let retry_after = self.on_response_retry_after(response);
|
||||
self.on_response_rate_limits(config, response);
|
||||
retry_after
|
||||
}
|
||||
|
||||
/// `on_response` helper for retry after check.
|
||||
/// Returns the retry-after delay if set.
|
||||
#[inline]
|
||||
fn on_response_retry_after(&self, response: &Response) -> Option<Duration> {
|
||||
// Only care about 429s.
|
||||
if StatusCode::TOO_MANY_REQUESTS != response.status() {
|
||||
return None;
|
||||
}
|
||||
|
||||
{
|
||||
// Get the X-Rate-Limit-Type header, `Some("application" | "method" | "service")` or `None`.
|
||||
let header_opt = response.headers()
|
||||
.get(Self::HEADER_XRATELIMITTYPE)
|
||||
.or_else(|| {
|
||||
log::info!("429 response missing {} header.", Self::HEADER_XRATELIMITTYPE);
|
||||
None
|
||||
})
|
||||
.and_then(|header_value| header_value.to_str()
|
||||
.map_err(|e| log::info!("429 response, error parsing '{}' header as string: {}. Header value: {:#?}",
|
||||
Self::HEADER_XRATELIMITTYPE, e, header_value))
|
||||
.ok());
|
||||
|
||||
// This match checks the valid possibilities. Otherwise returns none to ignore.
|
||||
// `Application` handles "application", `Method` handles all other values.
|
||||
let is_responsible = match header_opt {
|
||||
Some(Self::HEADER_XRATELIMITTYPE_APPLICATION) => {
|
||||
self.rate_limit_type == RateLimitType::Application
|
||||
}
|
||||
Some(Self::HEADER_XRATELIMITTYPE_METHOD | Self::HEADER_XRATELIMITTYPE_SERVICE) => {
|
||||
self.rate_limit_type == RateLimitType::Method
|
||||
}
|
||||
other => {
|
||||
// RateLimitType::Method handles unknown values.
|
||||
if self.rate_limit_type == RateLimitType::Method {
|
||||
log::warn!(
|
||||
"429 response has None (missing or invalid) or unknown {} header value {:?}, {:?} rate limit obeying retry-after.",
|
||||
Self::HEADER_XRATELIMITTYPE, other, self.rate_limit_type);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
if !is_responsible {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// Get retry after header. Only care if it exists.
|
||||
let retry_after_header = response
|
||||
.headers()
|
||||
.get(reqwest::header::RETRY_AFTER)
|
||||
.and_then(|h| {
|
||||
h.to_str()
|
||||
.map_err(|e| {
|
||||
log::error!(
|
||||
"Failed to read retry-after header as visible ASCII string: {:?}.",
|
||||
e
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
})?;
|
||||
|
||||
log::info!(
|
||||
"429 response, rate limit {:?}, retry-after {} secs.",
|
||||
self.rate_limit_type,
|
||||
retry_after_header
|
||||
);
|
||||
|
||||
// Header currently only returns ints, but float is more general. Can be zero.
|
||||
let retry_after_secs = retry_after_header
|
||||
.parse::<f32>()
|
||||
.map_err(|e| log::error!("Failed to parse read-after header as f32: {:?}.", e))
|
||||
.ok()?;
|
||||
|
||||
// Add 0.5 seconds to account for rounding, cases when response is zero.
|
||||
let delay = Duration::from_secs_f32(0.5 + retry_after_secs);
|
||||
|
||||
// Set `self.retry_after`.
|
||||
*self.retry_after.write() = Some(Instant::now() + delay);
|
||||
Some(delay)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn on_response_rate_limits(&self, config: &RiotApiConfig, response: &Response) {
|
||||
// Check if rate limits changed.
|
||||
let headers = response.headers();
|
||||
let limit_header_opt = headers
|
||||
.get(self.rate_limit_type.limit_header())
|
||||
.and_then(|h| {
|
||||
h.to_str()
|
||||
.map_err(|e| {
|
||||
log::error!(
|
||||
"Failed to read limit header as visible ASCII string: {:?}.",
|
||||
e
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
let count_header_opt = headers
|
||||
.get(self.rate_limit_type.count_header())
|
||||
.and_then(|h| {
|
||||
h.to_str()
|
||||
.map_err(|e| {
|
||||
log::error!(
|
||||
"Failed to read count header as visible ASCII string: {:?}.",
|
||||
e
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
|
||||
if let (Some(limit_header), Some(count_header)) = (limit_header_opt, count_header_opt) {
|
||||
{
|
||||
let buckets = self.buckets.upgradable_read();
|
||||
if !buckets_require_updating(limit_header, &buckets) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Buckets require updating. Upgrade to write lock.
|
||||
let mut buckets = RwLockUpgradableReadGuard::upgrade(buckets);
|
||||
*buckets =
|
||||
buckets_from_header(config, limit_header, count_header, self.rate_limit_type);
|
||||
}
|
||||
// Notify waiters that buckets have updated (after unlocking).
|
||||
self.update_notify.notify_waiters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn buckets_require_updating(limit_header: &str, buckets: &[VectorTokenBucket]) -> bool {
|
||||
if buckets.len() != limit_header.split(',').count() {
|
||||
return true;
|
||||
}
|
||||
for (limit_header_entry, bucket) in limit_header.split(',').zip(buckets) {
|
||||
// limit_header_entry "100:60" means 100 req per 60 sec.
|
||||
let bucket_entry = format!(
|
||||
"{}:{}",
|
||||
bucket.get_total_limit(),
|
||||
bucket.get_bucket_duration().as_secs()
|
||||
);
|
||||
if limit_header_entry != bucket_entry {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn buckets_from_header(
|
||||
config: &RiotApiConfig,
|
||||
limit_header: &str,
|
||||
count_header: &str,
|
||||
rate_limit_type: RateLimitType,
|
||||
) -> Vec<VectorTokenBucket> {
|
||||
// Limits: "20000:10,1200000:600"
|
||||
// Counts: "7:10,58:600"
|
||||
let size = limit_header.split(',').count();
|
||||
debug_assert!(size == count_header.split(',').count());
|
||||
let mut out = Vec::with_capacity(size);
|
||||
|
||||
for (limit_entry, count_entry) in limit_header.split(',').zip(count_header.split(',')) {
|
||||
let (limit, limit_secs) = scan_fmt!(limit_entry, "{d}:{d}", usize, u64)
|
||||
.unwrap_or_else(|_| panic!("Failed to parse limit entry \"{}\".", limit_entry));
|
||||
let (count, count_secs) = scan_fmt!(count_entry, "{d}:{d}", usize, u64)
|
||||
.unwrap_or_else(|_| panic!("Failed to parse count entry \"{}\".", count_entry));
|
||||
debug_assert!(limit_secs == count_secs);
|
||||
|
||||
let rate_usage_factor = if RateLimitType::Application == rate_limit_type {
|
||||
config.app_rate_usage_factor
|
||||
} else {
|
||||
config.method_rate_usage_factor
|
||||
};
|
||||
|
||||
let limit_f32 = limit as f32;
|
||||
let scaled_burst_factor = config.burst_factor * limit_f32 / (limit_f32 + 1.0);
|
||||
|
||||
let bucket = VectorTokenBucket::new(
|
||||
Duration::from_secs(limit_secs),
|
||||
limit,
|
||||
config.duration_overhead,
|
||||
scaled_burst_factor,
|
||||
rate_usage_factor,
|
||||
);
|
||||
bucket.get_tokens(count);
|
||||
out.push(bucket);
|
||||
}
|
||||
log::debug!(
|
||||
"Set buckets to {} limit, {} count.",
|
||||
limit_header,
|
||||
count_header
|
||||
);
|
||||
out
|
||||
}
|
|
@ -1,28 +1,24 @@
|
|||
#[derive(Copy, Clone)]
|
||||
pub enum RateLimitType {
|
||||
Application,
|
||||
Method,
|
||||
}
|
||||
|
||||
impl RateLimitType {
|
||||
pub fn type_name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Application => "application",
|
||||
Self::Method => "method",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn limit_header(self) -> &'static str {
|
||||
match self {
|
||||
Self::Application => "X-App-Rate-Limit",
|
||||
Self::Method => "X-Method-Rate-Limit",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn count_header(self) -> &'static str {
|
||||
match self {
|
||||
Self::Application => "X-App-Rate-Limit-Count",
|
||||
Self::Method => "X-Method-Rate-Limit-Count",
|
||||
}
|
||||
}
|
||||
}
|
||||
/// The type for a [RateLimit](super::RateLimit). Either a rate limit for the
|
||||
/// entire app (`Application`) or for a specific method (`Method`).
|
||||
/// Method rate limit will handle service violations.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum RateLimitType {
|
||||
Application,
|
||||
Method,
|
||||
}
|
||||
|
||||
impl RateLimitType {
|
||||
pub const fn limit_header(self) -> &'static str {
|
||||
match self {
|
||||
Self::Application => "X-App-Rate-Limit",
|
||||
Self::Method => "X-Method-Rate-Limit",
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn count_header(self) -> &'static str {
|
||||
match self {
|
||||
Self::Application => "X-App-Rate-Limit-Count",
|
||||
Self::Method => "X-Method-Rate-Limit-Count",
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
use reqwest::{RequestBuilder, StatusCode};
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing as log;
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing::Instrument;
|
||||
|
||||
use super::{RateLimit, RateLimitType};
|
||||
use crate::util::InsertOnlyCHashMap;
|
||||
use crate::{ResponseInfo, Result, RiotApiConfig, RiotApiError};
|
||||
|
||||
pub struct RegionalRequester {
|
||||
/// The app rate limit.
|
||||
app_rate_limit: RateLimit,
|
||||
/// Method rate limits.
|
||||
method_rate_limits: InsertOnlyCHashMap<&'static str, RateLimit>,
|
||||
}
|
||||
|
||||
impl RegionalRequester {
|
||||
/// HTTP status codes which are considered a success but will results in `None`.
|
||||
const NONE_STATUS_CODES: [StatusCode; 2] = [
|
||||
StatusCode::NO_CONTENT, // 204
|
||||
StatusCode::NOT_FOUND, // 404
|
||||
];
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
app_rate_limit: RateLimit::new(RateLimitType::Application),
|
||||
method_rate_limits: InsertOnlyCHashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute<'a>(
|
||||
self: Arc<Self>,
|
||||
config: &'a RiotApiConfig,
|
||||
method_id: &'static str,
|
||||
request: RequestBuilder,
|
||||
) -> impl Future<Output = Result<ResponseInfo>> + 'a {
|
||||
async move {
|
||||
let mut retries: u8 = 0;
|
||||
loop {
|
||||
let method_rate_limit: Arc<RateLimit> = self
|
||||
.method_rate_limits
|
||||
.get_or_insert_with(method_id, || RateLimit::new(RateLimitType::Method));
|
||||
|
||||
// Rate limit.
|
||||
let rate_limit = RateLimit::acquire_both(&self.app_rate_limit, &method_rate_limit);
|
||||
#[cfg(feature = "tracing")]
|
||||
let rate_limit = rate_limit.instrument(tracing::info_span!("rate_limit"));
|
||||
rate_limit.await;
|
||||
|
||||
// Send request.
|
||||
let request_clone = request
|
||||
.try_clone()
|
||||
.expect("Failed to clone request.")
|
||||
.send();
|
||||
#[cfg(feature = "tracing")]
|
||||
let request_clone = request_clone.instrument(tracing::info_span!("request"));
|
||||
let response = request_clone
|
||||
.await
|
||||
.map_err(|e| RiotApiError::new(e, retries, None, None))?;
|
||||
|
||||
// Maybe update rate limits (based on response headers).
|
||||
// Use single bar for no short circuiting.
|
||||
let retry_after_app = self.app_rate_limit.on_response(config, &response);
|
||||
let retry_after_method = method_rate_limit.on_response(config, &response);
|
||||
let retry_after = retry_after_app.or(retry_after_method); // Note: Edge case if both are Some(_) not handled.
|
||||
|
||||
let status = response.status();
|
||||
// Handle normal success / failure cases.
|
||||
let status_none = Self::NONE_STATUS_CODES.contains(&status);
|
||||
// Success case.
|
||||
if status.is_success() || status_none {
|
||||
log::trace!(
|
||||
"Response {} (retried {} times), success, returning result.",
|
||||
status,
|
||||
retries
|
||||
);
|
||||
break Ok(ResponseInfo {
|
||||
response,
|
||||
retries,
|
||||
status_none,
|
||||
});
|
||||
}
|
||||
let err = response.error_for_status_ref().err().unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Unhandlable response status code, neither success nor failure: {}.",
|
||||
status
|
||||
)
|
||||
});
|
||||
// Failure, may or may not be retryable.
|
||||
// Not-retryable: no more retries or 4xx or ? (3xx, redirects exceeded).
|
||||
// Retryable: retries remaining, and 429 or 5xx.
|
||||
if retries >= config.retries
|
||||
|| (StatusCode::TOO_MANY_REQUESTS != status && !status.is_server_error())
|
||||
{
|
||||
log::debug!(
|
||||
"Response {} (retried {} times), failure, returning error.",
|
||||
status,
|
||||
retries
|
||||
);
|
||||
break Err(RiotApiError::new(
|
||||
err,
|
||||
retries,
|
||||
Some(response),
|
||||
Some(status),
|
||||
));
|
||||
}
|
||||
|
||||
// Is retryable, do exponential backoff if retry-after wasn't specified.
|
||||
// 1 sec, 2 sec, 4 sec, 8 sec.
|
||||
match retry_after {
|
||||
None => {
|
||||
let delay = std::time::Duration::from_secs(2_u64.pow(retries as u32));
|
||||
log::debug!("Response {} (retried {} times), NO `retry-after`, using exponential backoff, retrying after {:?}.", status, retries, delay);
|
||||
let backoff = tokio::time::sleep(delay);
|
||||
#[cfg(feature = "tracing")]
|
||||
let backoff = backoff.instrument(tracing::info_span!("backoff"));
|
||||
backoff.await;
|
||||
}
|
||||
Some(delay) => {
|
||||
log::debug!("Response {} (retried {} times), `retry-after` set, retrying after {:?}.", status, retries, delay);
|
||||
}
|
||||
}
|
||||
retries += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// use super::*;
|
||||
}
|
|
@ -1,156 +1,195 @@
|
|||
use std::fmt;
|
||||
use std::collections::VecDeque;
|
||||
use std::time::Duration;
|
||||
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
|
||||
use super::Instant; // Hack for token_bucket_test.rs.
|
||||
|
||||
/// A `TokenBucket` keeps track of number of requests allowed per duration of
|
||||
/// time.
|
||||
///
|
||||
/// Respone headers contain descriptions of rate limits such as
|
||||
/// `"X-App-Rate-Limit": "20:1,100:120"`. Each `TokenBucket` corresponds to a
|
||||
/// single `"100:120"` (100 requests per 120 seconds).
|
||||
pub trait TokenBucket {
|
||||
/// Get the duration til the next available token, or None if a token
|
||||
/// is available.
|
||||
/// # Returns
|
||||
/// Duration or 0 duration.
|
||||
fn get_delay(&self) -> Option<Duration>;
|
||||
|
||||
/// Gets n tokens, regardless of whether they are available.
|
||||
/// # Parameters
|
||||
/// * `n` - Number of tokens to take.
|
||||
/// # Returns
|
||||
/// True if the tokens were obtained without violating limits, false
|
||||
/// otherwise.
|
||||
fn get_tokens(&self, n: usize) -> bool;
|
||||
|
||||
/// Get the duration of this bucket.
|
||||
/// # Returns
|
||||
/// Duration of the bucket.
|
||||
fn get_bucket_duration(&self) -> Duration;
|
||||
|
||||
/// Get the total limit of this bucket per timespan.
|
||||
/// # Returns
|
||||
/// Total limit per timespan.
|
||||
fn get_total_limit(&self) -> usize;
|
||||
}
|
||||
|
||||
pub struct VectorTokenBucket {
|
||||
/// Duration of this TokenBucket.
|
||||
duration: Duration,
|
||||
// Total tokens available from this TokenBucket.
|
||||
total_limit: usize,
|
||||
/// Extra duration to be considered on top of `duration`, to account for
|
||||
/// varying network latency.
|
||||
duration_overhead: Duration,
|
||||
|
||||
/// Duration considered for burst factor.
|
||||
burst_duration: Duration,
|
||||
/// Limit allowed per burst_duration, for burst factor.
|
||||
burst_limit: usize,
|
||||
|
||||
|
||||
/// Record of timestamps (synchronized).
|
||||
timestamps: Mutex<VecDeque<Instant>>,
|
||||
}
|
||||
|
||||
impl VectorTokenBucket {
|
||||
pub fn new(duration: Duration, total_limit: usize,
|
||||
duration_overhead: Duration, burst_pct: f32) -> Self
|
||||
{
|
||||
debug_assert!(0.0 < burst_pct && burst_pct <= 1.0,
|
||||
"BAD burst_pct {}.", burst_pct);
|
||||
// Float ops may lose precision, but nothing should be that precise.
|
||||
// API always uses round numbers, burst_pct is frac of 256.
|
||||
|
||||
// Effective duration.
|
||||
let d_eff = duration + duration_overhead;
|
||||
let burst_duration = Duration::new(
|
||||
(d_eff.as_secs() as f32 * burst_pct).ceil() as u64,
|
||||
(d_eff.subsec_nanos() as f32 * burst_pct).ceil() as u32);
|
||||
let burst_limit = std::cmp::max(1,
|
||||
(total_limit as f32 * burst_pct).floor() as usize);
|
||||
debug_assert!(burst_limit <= total_limit);
|
||||
|
||||
VectorTokenBucket {
|
||||
duration: duration,
|
||||
total_limit: total_limit,
|
||||
duration_overhead: duration_overhead,
|
||||
|
||||
burst_duration: burst_duration,
|
||||
burst_limit: burst_limit,
|
||||
|
||||
timestamps: Mutex::new(VecDeque::with_capacity(total_limit)),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_get_timestamps(&self) -> MutexGuard<VecDeque<Instant>> {
|
||||
let mut timestamps = self.timestamps.lock();
|
||||
let cutoff = Instant::now() - self.duration - self.duration_overhead;
|
||||
// We only need to trim the end of the queue to not leak memory.
|
||||
// We could do it lazily somehow if we wanted to be really fancy.
|
||||
while timestamps.back().map_or(false, |ts| *ts < cutoff) {
|
||||
timestamps.pop_back();
|
||||
}
|
||||
return timestamps;
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenBucket for VectorTokenBucket {
|
||||
|
||||
fn get_delay(&self) -> Option<Duration> {
|
||||
let timestamps = self.update_get_timestamps();
|
||||
|
||||
// The "?" means:
|
||||
// `if timestamps.len() < self.total_limit { return None }`
|
||||
// Timestamp that needs to be popped before
|
||||
// we can enter another timestamp.
|
||||
|
||||
// Full rate limit.
|
||||
if let Some(ts) = timestamps.get(self.total_limit - 1) {
|
||||
// Return amount of time needed for timestamp `ts` to go away.
|
||||
Instant::now().checked_duration_since(*ts)
|
||||
.and_then(|passed_dur| (self.duration + self.duration_overhead)
|
||||
.checked_sub(passed_dur))
|
||||
}
|
||||
// Otherwise burst rate limit.
|
||||
else if let Some(ts) = timestamps.get(self.burst_limit - 1) {
|
||||
// Return amount of time needed for timestamp `ts` to go away.
|
||||
Instant::now().checked_duration_since(*ts)
|
||||
.and_then(|passed_dur| self.burst_duration.checked_sub(passed_dur))
|
||||
}
|
||||
// No delay needed.
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tokens(&self, n: usize) -> bool {
|
||||
let mut timestamps = self.update_get_timestamps();
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
timestamps.reserve(n);
|
||||
for _ in 0..n {
|
||||
timestamps.push_front(now);
|
||||
}
|
||||
timestamps.len() <= self.total_limit
|
||||
}
|
||||
|
||||
fn get_bucket_duration(&self) -> Duration {
|
||||
self.duration
|
||||
}
|
||||
|
||||
fn get_total_limit(&self) -> usize {
|
||||
self.total_limit
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for VectorTokenBucket {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "({}/{}:{})", self.timestamps.lock().len(), self.total_limit, self.duration.as_secs())
|
||||
}
|
||||
}
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
|
||||
use super::Instant; // Hack for token_bucket_test.rs.
|
||||
|
||||
/// A `TokenBucket` keeps track of number of requests allowed per duration of
|
||||
/// time.
|
||||
///
|
||||
/// Respone headers contain descriptions of rate limits such as
|
||||
/// `"X-App-Rate-Limit": "20:1,100:120"`. Each `TokenBucket` corresponds to a
|
||||
/// single `"100:120"` (100 requests per 120 seconds).
|
||||
pub trait TokenBucket {
|
||||
/// Get the duration til the next available token, or None if a token
|
||||
/// is available.
|
||||
/// # Returns
|
||||
/// Duration or 0 duration.
|
||||
fn get_delay(&self) -> Option<Duration>;
|
||||
|
||||
/// Gets n tokens, regardless of whether they are available.
|
||||
/// # Parameters
|
||||
/// * `n` - Number of tokens to take.
|
||||
/// # Returns
|
||||
/// True if the tokens were obtained without violating limits, false
|
||||
/// otherwise.
|
||||
fn get_tokens(&self, n: usize) -> bool;
|
||||
|
||||
/// Get the duration of this bucket.
|
||||
/// # Returns
|
||||
/// Duration of the bucket.
|
||||
fn get_bucket_duration(&self) -> Duration;
|
||||
|
||||
/// Get the total limit of this bucket per timespan.
|
||||
/// # Returns
|
||||
/// Total limit per timespan.
|
||||
fn get_total_limit(&self) -> usize;
|
||||
}
|
||||
|
||||
pub struct VectorTokenBucket {
|
||||
/// The total limit supplied to the constructor, unadjusted by the [rate_usage_factor].
|
||||
_given_total_limit: usize,
|
||||
/// Additional factor to reduce rate limit usage, in range (0, 1\].
|
||||
_rate_usage_factor: f32,
|
||||
|
||||
/// Duration of this TokenBucket.
|
||||
duration: Duration,
|
||||
// Total tokens available from this TokenBucket.
|
||||
total_limit: usize,
|
||||
|
||||
/// Extra duration to be considered on top of `duration`, to account for
|
||||
/// varying network latency.
|
||||
duration_overhead: Duration,
|
||||
|
||||
/// Duration considered for burst factor.
|
||||
burst_duration: Duration,
|
||||
/// Limit allowed per burst_duration, for burst factor.
|
||||
burst_limit: usize,
|
||||
|
||||
/// Record of timestamps (synchronized).
|
||||
timestamps: Mutex<VecDeque<Instant>>,
|
||||
}
|
||||
|
||||
impl VectorTokenBucket {
|
||||
pub fn new(
|
||||
duration: Duration,
|
||||
given_total_limit: usize,
|
||||
duration_overhead: Duration,
|
||||
burst_factor: f32,
|
||||
rate_usage_factor: f32,
|
||||
) -> Self {
|
||||
debug_assert!(
|
||||
0.0 < rate_usage_factor && rate_usage_factor <= 1.0,
|
||||
"BAD rate_usage_factor {}.",
|
||||
rate_usage_factor
|
||||
);
|
||||
debug_assert!(
|
||||
0.0 < burst_factor && burst_factor <= 1.0,
|
||||
"BAD burst_factor {}.",
|
||||
burst_factor
|
||||
);
|
||||
// Float ops may lose precision, but nothing should be that precise.
|
||||
// API always uses round numbers, burst_factor is frac of 256.
|
||||
|
||||
// Adjust everything by rate_usage_factor.
|
||||
let total_limit = std::cmp::max(
|
||||
1,
|
||||
(given_total_limit as f32 * rate_usage_factor).floor() as usize,
|
||||
);
|
||||
|
||||
// Effective duration.
|
||||
let d_eff = duration + duration_overhead;
|
||||
let burst_duration = d_eff.mul_f32(burst_factor);
|
||||
let burst_limit = std::cmp::max(1, (total_limit as f32 * burst_factor).floor() as usize);
|
||||
debug_assert!(burst_limit <= total_limit);
|
||||
|
||||
VectorTokenBucket {
|
||||
_given_total_limit: given_total_limit,
|
||||
_rate_usage_factor: rate_usage_factor,
|
||||
|
||||
duration,
|
||||
total_limit,
|
||||
|
||||
duration_overhead,
|
||||
burst_duration,
|
||||
burst_limit,
|
||||
|
||||
timestamps: Mutex::new(VecDeque::with_capacity(total_limit)),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_get_timestamps(&self) -> MutexGuard<VecDeque<Instant>> {
|
||||
let mut timestamps = self.timestamps.lock();
|
||||
let cutoff = Instant::now() - self.duration - self.duration_overhead;
|
||||
// Pop off timestamps that are beyound the bucket duration.
|
||||
while timestamps.back().map_or(false, |ts| *ts < cutoff) {
|
||||
timestamps.pop_back();
|
||||
}
|
||||
timestamps
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenBucket for VectorTokenBucket {
|
||||
fn get_delay(&self) -> Option<Duration> {
|
||||
let timestamps = self.update_get_timestamps();
|
||||
|
||||
// Full rate limit.
|
||||
if let Some(ts) = timestamps.get(self.total_limit - 1) {
|
||||
// Return amount of time needed for timestamp `ts` to go away.
|
||||
Instant::now()
|
||||
.checked_duration_since(*ts)
|
||||
.and_then(|passed_dur| {
|
||||
(self.duration + self.duration_overhead).checked_sub(passed_dur)
|
||||
})
|
||||
}
|
||||
// Otherwise burst rate limit.
|
||||
else if let Some(ts) = timestamps.get(self.burst_limit - 1) {
|
||||
// Return amount of time needed for timestamp `ts` to go away.
|
||||
Instant::now()
|
||||
.checked_duration_since(*ts)
|
||||
.and_then(|passed_dur| self.burst_duration.checked_sub(passed_dur))
|
||||
}
|
||||
// No delay needed.
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tokens(&self, n: usize) -> bool {
|
||||
let mut timestamps = self.update_get_timestamps();
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
timestamps.reserve(n);
|
||||
for _ in 0..n {
|
||||
timestamps.push_front(now);
|
||||
}
|
||||
|
||||
// Check total limit.
|
||||
if self.total_limit < timestamps.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check burst limit.
|
||||
if let Some(burst_time) = timestamps.get(self.burst_limit) {
|
||||
let duration_since = now.duration_since(*burst_time); // `now` before `burst_time` will panic.
|
||||
if duration_since < self.burst_duration {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn get_bucket_duration(&self) -> Duration {
|
||||
self.duration
|
||||
}
|
||||
|
||||
fn get_total_limit(&self) -> usize {
|
||||
self.total_limit
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for VectorTokenBucket {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"({}/{}:{})",
|
||||
self.timestamps.lock().len(),
|
||||
self.total_limit,
|
||||
self.duration.as_secs()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
#![cfg(test)]
|
||||
|
||||
use fake_instant::FakeInstant as Instant;
|
||||
|
||||
/// This is a hack to test token bucket, substituting `FakeInstant` in place of `Instant`.
|
||||
mod token_bucket {
|
||||
include!("token_bucket.rs");
|
||||
|
||||
mod tests {
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use super::*;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref ZERO: Duration = Duration::new(0, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
Instant::set_time(50_000);
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 0.95, 1.0);
|
||||
assert!(bucket.get_tokens(50), "Should have not violated limit.");
|
||||
assert_eq!(None, bucket.get_delay(), "Can get stuff.");
|
||||
assert!(!bucket.get_tokens(51), "Should have violated limit.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_internal_constructor() {
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 1.0, 1.0);
|
||||
assert_eq!(100, bucket.burst_limit);
|
||||
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 1e-6, 1.0);
|
||||
assert_eq!(1, bucket.burst_limit);
|
||||
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 1.0, 1e-6);
|
||||
assert_eq!(1, bucket.total_limit);
|
||||
assert_eq!(1, bucket.burst_limit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saturated_100_burst() {
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 1.00, 1.0);
|
||||
|
||||
Instant::set_time(50_000);
|
||||
assert!(
|
||||
bucket.get_tokens(100),
|
||||
"All tokens should be immediately available."
|
||||
);
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(1001); // Extra buffer for Duration(0).
|
||||
assert!(
|
||||
bucket.get_tokens(100),
|
||||
"All tokens should be available after a bucket duration."
|
||||
);
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saturated_95_burst() {
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 0.95, 1.0);
|
||||
|
||||
Instant::set_time(50_000);
|
||||
assert!(
|
||||
bucket.get_tokens(95),
|
||||
"95 tokens should be immediately available."
|
||||
);
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(475);
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
Instant::advance_time(476); // Total 951. Extra buffer for Duration(0).
|
||||
|
||||
assert!(bucket.get_tokens(5), "Last 5 tokens should be available.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(51); // Total 1002.
|
||||
assert!(bucket.get_tokens(90), "90 tokens should be available.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(951);
|
||||
assert!(bucket.get_tokens(10), "Last 10 tokens should be available.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_violated_50_burst() {
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 0.50, 1.0);
|
||||
|
||||
Instant::set_time(50_000);
|
||||
assert!(!bucket.get_tokens(90), "Burst should be violated.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saturated_50_burst() {
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 0.50, 1.0);
|
||||
|
||||
Instant::set_time(50_000);
|
||||
assert!(
|
||||
bucket.get_tokens(50),
|
||||
"Half the tokens should be immediately available."
|
||||
);
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(501); // Extra buffer for Duration(0).
|
||||
assert!(
|
||||
bucket.get_tokens(50),
|
||||
"Half the tokens should be available after a half bucket duration."
|
||||
);
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(501);
|
||||
assert!(
|
||||
bucket.get_tokens(50),
|
||||
"Half the tokens should be available after a full bucket duration."
|
||||
);
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(501);
|
||||
assert!(bucket.get_tokens(50));
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saturated_90_burst_rate_usage_factor_50() {
|
||||
let rate_usage_factor = 0.5;
|
||||
let bucket = VectorTokenBucket::new(
|
||||
Duration::from_millis(1000),
|
||||
100,
|
||||
*ZERO,
|
||||
0.90,
|
||||
rate_usage_factor,
|
||||
);
|
||||
|
||||
Instant::set_time(50_000);
|
||||
assert!(
|
||||
bucket.get_tokens(45),
|
||||
"45 tokens should be immediately available."
|
||||
);
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(475);
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
Instant::advance_time(476); // Total 951. Extra buffer for Duration(0).
|
||||
|
||||
assert!(bucket.get_tokens(5), "Last 5 tokens should be available.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(51); // Total 1002.
|
||||
assert!(bucket.get_tokens(40), "45 tokens should be available.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(951);
|
||||
assert!(bucket.get_tokens(10), "Last 10 tokens should be available.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_many() {
|
||||
Instant::set_time(50_000);
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *ZERO, 0.5, 1.0);
|
||||
assert!(
|
||||
bucket.get_tokens(50),
|
||||
"Should have not violated limit. i=-1."
|
||||
);
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay. i=-1.");
|
||||
for i in 0..20_000 {
|
||||
Instant::advance_time(501);
|
||||
assert!(
|
||||
bucket.get_tokens(50),
|
||||
"Should have not violated limit. i={}.",
|
||||
i
|
||||
);
|
||||
assert_ne!(
|
||||
None,
|
||||
bucket.get_delay(),
|
||||
"Bucket should have delay. i={}.",
|
||||
i
|
||||
);
|
||||
Instant::advance_time(501);
|
||||
assert!(
|
||||
bucket.get_tokens(50),
|
||||
"Should have not violated limit. i={}.",
|
||||
i
|
||||
);
|
||||
assert_ne!(
|
||||
None,
|
||||
bucket.get_delay(),
|
||||
"Bucket should have delay. i={}.",
|
||||
i
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
bucket.timestamps.lock().len() < 110,
|
||||
"Should not memory leak."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
use reqwest::Response;
|
||||
|
||||
/// A "raw" unparsed successful response from the Riot API, for internal or advanced use cases.
|
||||
pub struct ResponseInfo {
|
||||
/// The reqwest response.
|
||||
pub response: Response,
|
||||
/// The number of retries used, zero for first-try success.
|
||||
pub retries: u8,
|
||||
/// If the response has an HTTP status code indicating a `None` response (i.e. 204, 404).
|
||||
pub status_none: bool,
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
use reqwest::{Client, Method, RequestBuilder};
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing as log;
|
||||
|
||||
use crate::req::RegionalRequester;
|
||||
use crate::util::InsertOnlyCHashMap;
|
||||
use crate::{ResponseInfo, Result, RiotApiConfig, RiotApiError};
|
||||
|
||||
/// For retrieving data from the Riot Games API.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// Construct an instance using [`RiotApi::new(api_key or config)`](RiotApi::new).
|
||||
/// The parameter may be a Riot API key string or a [`RiotApiConfig`]. Riot API
|
||||
/// keys are obtained from the [Riot Developer Portal](https://developer.riotgames.com/)
|
||||
/// and look like `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`.
|
||||
///
|
||||
/// An instance provides access to "endpoint handles" which in turn provide
|
||||
/// access to individual API method calls. For example, to get a summoner by
|
||||
/// name we first access the [`summoner_v4()`](RiotApi::summoner_v4) endpoints
|
||||
/// then call the [`get_by_summoner_name()`](crate::endpoints::SummonerV4::get_by_summoner_name)
|
||||
/// method:
|
||||
/// ```ignore
|
||||
/// riot_api.summoner_v4().get_by_summoner_name(Region::NA, "LugnutsK")
|
||||
/// ```
|
||||
///
|
||||
/// # Rate Limiting
|
||||
///
|
||||
/// The Riot Game API enforces _dynamic_ rate limiting, meaning that rate limits are
|
||||
/// specified in response headers and can change at any time.
|
||||
/// Riven keeps track of changing rate limits seamlessly, preventing you from
|
||||
/// getting blacklisted.
|
||||
///
|
||||
/// Riven's rate limiting is highly efficient; it can use the full throughput
|
||||
/// of your rate limit without triggering 429 errors.
|
||||
///
|
||||
/// To adjust rate limiting, see [RiotApiConfig] and use
|
||||
/// [`RiotApi::new(config)`](RiotApi::new) to construct an instance.
|
||||
pub struct RiotApi {
|
||||
/// Configuration settings.
|
||||
config: RiotApiConfig,
|
||||
/// Client for making requests.
|
||||
client: Client,
|
||||
|
||||
/// Per-region requesters.
|
||||
regional_requesters: InsertOnlyCHashMap<&'static str, RegionalRequester>,
|
||||
}
|
||||
|
||||
impl RiotApi {
|
||||
/// Constructs a new instance from an API key (e.g. `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`) or a [RiotApiConfig].
|
||||
pub fn new(config: impl Into<RiotApiConfig>) -> Self {
|
||||
let mut config = config.into();
|
||||
let client_builder = config
|
||||
.client_builder
|
||||
.take()
|
||||
.expect("CLIENT_BUILDER IN CONFIG SHOULD NOT BE NONE.");
|
||||
Self {
|
||||
config,
|
||||
client: client_builder
|
||||
.build()
|
||||
.expect("Failed to create client from builder."),
|
||||
regional_requesters: InsertOnlyCHashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||
///
|
||||
/// Creates a `RequestBuilder` instance with the given parameters, for use with the `execute*()` methods.
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `method` - The HTTP method for this request.
|
||||
/// * `region_platform` - The stringified platform, used to create the base URL.
|
||||
/// * `path` - The URL path, appended to the base URL.
|
||||
pub fn request(&self, method: Method, region_platform: &str, path: &str) -> RequestBuilder {
|
||||
let base_url_platform = self.config.base_url.replace("{}", region_platform);
|
||||
self.client
|
||||
.request(method, format!("{}{}", base_url_platform, path))
|
||||
}
|
||||
|
||||
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||
///
|
||||
/// This sends a request based on the given parameters and returns a parsed result.
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
|
||||
/// * `region_platform` - The stringified platform, used in rate limiting.
|
||||
/// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
|
||||
///
|
||||
/// # Returns
|
||||
/// A future resolving to a `Result` containg either a `T` (success) or a `RiotApiError` (failure).
|
||||
pub async fn execute_val<'a, T: serde::de::DeserializeOwned + 'a>(
|
||||
&'a self,
|
||||
method_id: &'static str,
|
||||
region_platform: &'static str,
|
||||
request: RequestBuilder,
|
||||
) -> Result<T> {
|
||||
let rinfo = self
|
||||
.execute_raw(method_id, region_platform, request)
|
||||
.await?;
|
||||
let retries = rinfo.retries;
|
||||
let status = rinfo.response.status();
|
||||
let value = rinfo.response.json::<T>().await;
|
||||
value.map_err(|e| RiotApiError::new(e, retries, None, Some(status)))
|
||||
}
|
||||
|
||||
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||
///
|
||||
/// This sends a request based on the given parameters and returns an optional parsed result.
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
|
||||
/// * `region_platform` - The stringified platform, used in rate limiting.
|
||||
/// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
|
||||
///
|
||||
/// # Returns
|
||||
/// A future resolving to a `Result` containg either an `Option<T>` (success) or a `RiotApiError` (failure).
|
||||
pub async fn execute_opt<'a, T: serde::de::DeserializeOwned + 'a>(
|
||||
&'a self,
|
||||
method_id: &'static str,
|
||||
region_platform: &'static str,
|
||||
request: RequestBuilder,
|
||||
) -> Result<Option<T>> {
|
||||
let rinfo = self
|
||||
.execute_raw(method_id, region_platform, request)
|
||||
.await?;
|
||||
if rinfo.status_none {
|
||||
return Ok(None);
|
||||
}
|
||||
let retries = rinfo.retries;
|
||||
let status = rinfo.response.status();
|
||||
let value = rinfo.response.json::<Option<T>>().await;
|
||||
value.map_err(|e| RiotApiError::new(e, retries, None, Some(status)))
|
||||
}
|
||||
|
||||
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||
///
|
||||
/// This sends a request based on the given parameters but does not deserialize any response body.
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
|
||||
/// * `region_platform` - The stringified platform, used in rate limiting.
|
||||
/// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
|
||||
///
|
||||
/// # Returns
|
||||
/// A future resolving to a `Result` containg either `()` (success) or a `RiotApiError` (failure).
|
||||
pub async fn execute(
|
||||
&self,
|
||||
method_id: &'static str,
|
||||
region_platform: &'static str,
|
||||
request: RequestBuilder,
|
||||
) -> Result<()> {
|
||||
let rinfo = self
|
||||
.execute_raw(method_id, region_platform, request)
|
||||
.await?;
|
||||
let retries = rinfo.retries;
|
||||
let status = rinfo.response.status();
|
||||
rinfo
|
||||
.response
|
||||
.error_for_status()
|
||||
.map(|_| ())
|
||||
.map_err(|e| RiotApiError::new(e, retries, None, Some(status)))
|
||||
}
|
||||
|
||||
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||
///
|
||||
/// This sends a request based on the given parameters and returns a raw `ResponseInfo`.
|
||||
///
|
||||
/// This can be used to implement a Riot API proxy without needing to deserialize and reserialize JSON responses.
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
|
||||
/// * `region_platform` - The stringified platform, used in rate limiting.
|
||||
/// * `request` - The request information. Use `request()` to obtain a `RequestBuilder` instance.
|
||||
///
|
||||
/// # Returns
|
||||
/// A future resolving to a `Result` containg either a `ResponseInfo` (success) or a `RiotApiError` (failure).
|
||||
pub fn execute_raw(
|
||||
&self,
|
||||
method_id: &'static str,
|
||||
region_platform: &'static str,
|
||||
request: RequestBuilder,
|
||||
) -> impl Future<Output = Result<ResponseInfo>> + '_ {
|
||||
self.regional_requester(region_platform)
|
||||
.execute(&self.config, method_id, request)
|
||||
}
|
||||
|
||||
/// Get or create the RegionalRequester for the given region.
|
||||
fn regional_requester(&self, region_platform: &'static str) -> Arc<RegionalRequester> {
|
||||
self.regional_requesters
|
||||
.get_or_insert_with(region_platform, || {
|
||||
log::debug!(
|
||||
"Creating requester for region platform {}.",
|
||||
region_platform
|
||||
);
|
||||
RegionalRequester::new()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,36 +1,38 @@
|
|||
// use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
pub struct InsertOnlyCHashMap<K: Hash + Eq, V> {
|
||||
base: Mutex<HashMap<K, Arc<V>>>,
|
||||
}
|
||||
|
||||
impl<K: Hash + Eq, V> InsertOnlyCHashMap<K, V> {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base: Mutex::new(HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
// #[inline]
|
||||
// pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<Arc<V>>
|
||||
// where
|
||||
// K: Borrow<Q>,
|
||||
// Q: Hash + Eq,
|
||||
// {
|
||||
// self.base.lock().get(key).map(|v| Arc::clone(v))
|
||||
// }
|
||||
|
||||
#[inline]
|
||||
pub fn get_or_insert_with<F: FnOnce() -> V>(&self, key: K, default: F) -> Arc<V>
|
||||
{
|
||||
Arc::clone(self.base.lock()
|
||||
.entry(key)
|
||||
.or_insert_with(|| Arc::new(default())))
|
||||
}
|
||||
}
|
||||
// use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
pub struct InsertOnlyCHashMap<K: Hash + Eq, V> {
|
||||
base: Mutex<HashMap<K, Arc<V>>>,
|
||||
}
|
||||
|
||||
impl<K: Hash + Eq, V> InsertOnlyCHashMap<K, V> {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
// #[inline]
|
||||
// pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<Arc<V>>
|
||||
// where
|
||||
// K: Borrow<Q>,
|
||||
// Q: Hash + Eq,
|
||||
// {
|
||||
// self.base.lock().get(key).map(|v| Arc::clone(v))
|
||||
// }
|
||||
|
||||
#[inline]
|
||||
pub fn get_or_insert_with<F: FnOnce() -> V>(&self, key: K, default: F) -> Arc<V> {
|
||||
Arc::clone(
|
||||
self.base
|
||||
.lock()
|
||||
.entry(key)
|
||||
.or_insert_with(|| Arc::new(default())),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
mod insert_only_chashmap;
|
||||
|
||||
pub use insert_only_chashmap::*;
|
||||
mod insert_only_chashmap;
|
||||
|
||||
pub use insert_only_chashmap::*;
|
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
.*.json
|
|
@ -0,0 +1,166 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const champions = require('./.champion.json')
|
||||
.filter(({ id }) => id > 0)
|
||||
.sortBy(({ name }) => name);
|
||||
|
||||
const constName = name => dotUtils.changeCase.constantCase(name).replace(/[^_A-Z0-9]+/g, '');
|
||||
const constNamePad = 12;
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
newtype_enum! {
|
||||
/// A League of Legends champion.
|
||||
///
|
||||
/// This newtype acts as a C-like enum; each variant corresponds to an
|
||||
/// integer value. Using a newtype allows _unknown_ variants to be
|
||||
/// represented. This is important when Riot adds new champions.
|
||||
///
|
||||
/// Field | Name | Identifier | Id
|
||||
/// ---|---|---|---
|
||||
/// `NONE` | None (no ban) | | -1
|
||||
{{
|
||||
for (const { id, alias, name } of champions) {
|
||||
}}
|
||||
/// `{{= constName(name) }}` | "{{= name }}" | "{{= alias }}" | {{= id }}
|
||||
{{
|
||||
}
|
||||
}}
|
||||
pub newtype_enum Champion(i16) {
|
||||
/// `-1`, none. Appears when a champion ban is not used in champ select.
|
||||
NONE = -1,
|
||||
|
||||
{{
|
||||
for (const { id, alias, name } of champions) {
|
||||
}}
|
||||
/// `{{= id }}`.
|
||||
{{= constName(name) }} = {{= id }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
impl Champion {
|
||||
/// The champion's name (`en_US` localization).
|
||||
pub const fn name(self) -> Option<&'static str> {
|
||||
match self {
|
||||
{{
|
||||
for (const { name } of champions) {
|
||||
}}
|
||||
Self::{{= constName(name).padEnd(constNamePad) }} => Some("{{= name }}"),
|
||||
{{
|
||||
}
|
||||
}}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The champion's identifier key. Somtimes called "key", "identifier", or "alias".
|
||||
/// This is mainly used in DDragon paths.
|
||||
///
|
||||
/// This is generally the `en_US` name with spaces and punctuation removed,
|
||||
/// capitalization preserved, however the follow are exceptions:
|
||||
///
|
||||
/// Field | Name | Identifier | Id
|
||||
/// ---|---|---|---
|
||||
{{
|
||||
for (const { id, alias, name } of champions) {
|
||||
if (name.replace(/[^a-zA-Z0-9]+/, '') !== alias) {
|
||||
}}
|
||||
/// `{{= constName(name) }}` | "{{= name }}" | "{{= alias }}" | {{= id }}
|
||||
{{
|
||||
}
|
||||
}
|
||||
}}
|
||||
pub const fn identifier(self) -> Option<&'static str> {
|
||||
match self {
|
||||
{{
|
||||
for (const { name, alias } of champions) {
|
||||
}}
|
||||
Self::{{= constName(name).padEnd(constNamePad) }} => Some("{{= alias }}"),
|
||||
{{
|
||||
}
|
||||
}}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// https://github.com/MingweiSamuel/Riven/issues/36
|
||||
pub(crate) fn serialize_result<S>(
|
||||
val: &Result<Self, std::num::TryFromIntError>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::ser::Serializer,
|
||||
{
|
||||
use serde::ser::Serialize;
|
||||
val.unwrap_or(Champion(-1)).serialize(serializer)
|
||||
}
|
||||
|
||||
/// https://github.com/MingweiSamuel/Riven/issues/36
|
||||
pub(crate) fn deserialize_result<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Result<Self, std::num::TryFromIntError>, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
use std::convert::TryInto;
|
||||
<i64 as serde::de::Deserialize>::deserialize(deserializer).map(|id| id.try_into().map(Self))
|
||||
}
|
||||
}
|
||||
|
||||
/// The error used for failures in [`Champion`]'s
|
||||
/// [`FromStr`](std::str::FromStr) implementation.
|
||||
///
|
||||
/// Currently only internally stores the four characters used to parse the
|
||||
/// champion, but may change in the future.
|
||||
#[derive(Debug)]
|
||||
pub struct ParseChampionError([char; 4]);
|
||||
impl std::fmt::Display for ParseChampionError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let s: String = self.0.iter().copied().take_while(|&c| '\0' != c).collect();
|
||||
write!(f, "Failed to parse unknown champion prefix: {:?}", s)
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ParseChampionError {}
|
||||
|
||||
impl std::str::FromStr for Champion {
|
||||
type Err = ParseChampionError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut chars = ['\0'; 4];
|
||||
s.chars()
|
||||
.take(4)
|
||||
.filter(|c| c.is_ascii_alphanumeric())
|
||||
.map(|c| c.to_ascii_uppercase())
|
||||
.enumerate()
|
||||
.for_each(|(i, c)| chars[i] = c);
|
||||
match chars {
|
||||
{{
|
||||
const keyStrings = (name, alias) => new Set([].concat(...[ name, alias ].map(s => s.toUpperCase())
|
||||
.map(s => [
|
||||
s.replace(/[^A-Z0-9]+/, '').substring(0, 4),
|
||||
s.split(/[^A-Z0-9]/, 1)[0].substring(0, 4),
|
||||
s.split(/[^A-Z]/, 1)[0].substring(0, 4),
|
||||
])));
|
||||
for (const { id, alias, name } of champions) {
|
||||
for (const prefix of keyStrings(name, alias)) {
|
||||
const chars = Object.assign(Array(4).fill('\\0'), Array.from(prefix))
|
||||
.map(c => `'${c}'`)
|
||||
.map(c => c.padStart(4));
|
||||
}}
|
||||
/* {{= prefix.padEnd(4) }} */ [{{= chars.join(', ') }}] => Ok(Champion::{{= constName(name) }}),
|
||||
{{
|
||||
}
|
||||
}
|
||||
}}
|
||||
unknown => Err(ParseChampionError(unknown)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&str> for Champion {
|
||||
type Error = <Self as std::str::FromStr>::Err;
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
<Self as std::str::FromStr>::from_str(value)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const gameModes = require('./.gameModes.json');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
use strum_macros::{ EnumString, EnumVariantNames, IntoStaticStr };
|
||||
|
||||
/// League of Legends game mode, such as Classic,
|
||||
/// ARAM, URF, One For All, Ascension, etc.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, EnumVariantNames, IntoStaticStr)]
|
||||
#[repr(u8)]
|
||||
pub enum GameMode {
|
||||
/// Catch-all variant for new, unknown game modes.
|
||||
#[strum(default)]
|
||||
UNKNOWN(String),
|
||||
|
||||
{{
|
||||
for (const e of gameModes) {
|
||||
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||
}}
|
||||
{{~ desc :line }}
|
||||
/// {{= line }}
|
||||
{{~}}
|
||||
{{= e['x-name'] }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
serde_strum_unknown!(GameMode);
|
|
@ -0,0 +1,33 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const gameTypes = require('./.gameTypes.json');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
use serde::{ Serialize, Deserialize };
|
||||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// League of Legends game type: matched game, custom game, or tutorial game.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum GameType {
|
||||
{{
|
||||
for (const e of gameTypes) {
|
||||
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||
const nameNoGame = e['x-name'].replace(/_GAME$/, "");
|
||||
}}
|
||||
{{~ desc :line }}
|
||||
/// {{= line }}
|
||||
{{~}}
|
||||
#[strum(to_string = "{{= e['x-name'] }}", serialize = "{{= nameNoGame }}")]
|
||||
#[serde(alias = "{{= nameNoGame }}")]
|
||||
{{= e['x-name'] }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
|
@ -0,0 +1,22 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const maps = require('./.maps.json');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
newtype_enum! {
|
||||
/// A League of Legends map.
|
||||
pub newtype_enum Map(u8) {
|
||||
{{
|
||||
for (const e of maps) {
|
||||
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||
}}
|
||||
/// `{{= e['x-value'] }}`.
|
||||
{{~ desc :line }}
|
||||
/// {{= line }}
|
||||
{{~}}
|
||||
{{= e['x-name'] }} = {{= e['x-value'] }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const queues = require('./.queues.json');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
newtype_enum! {
|
||||
/// A League of Legends matchmaking queue.
|
||||
pub newtype_enum Queue(u16) {
|
||||
{{
|
||||
for (const e of queues) {
|
||||
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||
}}
|
||||
/// `{{= e['x-value'] }}`.
|
||||
{{~ desc :line }}
|
||||
/// {{= line }}
|
||||
{{~}}
|
||||
{{? e.notes }}
|
||||
///
|
||||
/// {{= e.notes }}
|
||||
{{?}}
|
||||
{{? e['x-deprecated'] }}
|
||||
#[deprecated(note="{{= e.notes }}")]
|
||||
{{?}}
|
||||
{{= e['x-name'] }} = {{= e['x-value'] }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const queueTypes = require('./.queueTypes.json');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
use strum_macros::{ EnumString, EnumVariantNames, IntoStaticStr };
|
||||
|
||||
/// LoL or TFT ranked queue types.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, EnumVariantNames, IntoStaticStr)]
|
||||
#[repr(u8)]
|
||||
pub enum QueueType {
|
||||
/// Catch-all variant for new, unknown queue types.
|
||||
#[strum(default)]
|
||||
UNKNOWN(String),
|
||||
|
||||
{{
|
||||
for (const e of queueTypes) {
|
||||
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||
}}
|
||||
{{~ desc :line }}
|
||||
/// {{= line }}
|
||||
{{~}}
|
||||
{{? e['x-deprecated'] }}
|
||||
/// {{= e.notes }}
|
||||
#[deprecated(note="{{= e.notes }}")]
|
||||
{{?}}
|
||||
{{= e['x-name'] }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
serde_strum_unknown!(QueueType);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
|
@ -0,0 +1,196 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const routesTable = require('./.routesTable.json');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||
use strum_macros::{ EnumString, EnumIter, Display, IntoStaticStr };
|
||||
|
||||
/// Regional routes, used in tournament services, Legends of Runeterra (LoR), and other some endpoints.
|
||||
#[derive(Debug)]
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
pub enum RegionalRoute {
|
||||
{{
|
||||
for (const [ name, { id, description, deprecated } ] of Object.entries(routesTable['regional'])) {
|
||||
const desc = description.split('\n');
|
||||
}}
|
||||
{{~ desc :line }}
|
||||
/// {{= line }}
|
||||
{{~}}
|
||||
///
|
||||
/// `{{= id }}` (riotapi-schema ID/repr)
|
||||
{{? deprecated }}
|
||||
#[deprecated]
|
||||
{{?}}
|
||||
{{= name.toUpperCase() }} = {{= id }},
|
||||
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
/// Platform routes for League of Legends (LoL), Teamfight Tactics (TFT), and Legends of Runeterra (LoR).
|
||||
#[derive(Debug)]
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
// Note: strum(serialize = ...) actually specifies extra DEserialization values.
|
||||
pub enum PlatformRoute {
|
||||
{{
|
||||
for (const [ name, { id, description, altName, deprecated } ] of Object.entries(routesTable['platform'])) {
|
||||
const desc = description.split('\n');
|
||||
}}
|
||||
{{~ desc :line }}
|
||||
/// {{= line }}
|
||||
{{~}}
|
||||
///
|
||||
/// `{{= id }}` (riotapi-schema ID/repr)
|
||||
{{? deprecated }}
|
||||
#[deprecated]
|
||||
{{?}}
|
||||
{{? altName }}
|
||||
#[strum(to_string="{{= name.toUpperCase() }}", serialize="{{= altName }}")]
|
||||
{{?}}
|
||||
{{= name.toUpperCase() }} = {{= id }},
|
||||
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
impl PlatformRoute {
|
||||
/// Converts this [`PlatformRoute`] into its corresponding
|
||||
/// [`RegionalRoute`] for LoL and TFT match endpoints.
|
||||
/// For example, [`match-v5`](crate::endpoints::MatchV5).
|
||||
pub fn to_regional(self) -> RegionalRoute {
|
||||
match self {
|
||||
{{
|
||||
for (const [ name, { regionalRoute } ] of Object.entries(routesTable['platform'])) {
|
||||
}}
|
||||
Self::{{= name.toUpperCase() }} => RegionalRoute::{{= regionalRoute.toUpperCase() }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this [`PlatformRoute`] into its corresponding
|
||||
/// [`RegionalRoute`] for LoR endpoints.
|
||||
/// For example, [`lor-match-v1`](crate::endpoints::LorMatchV1).
|
||||
pub fn to_regional_lor(self) -> RegionalRoute {
|
||||
match self {
|
||||
{{
|
||||
for (const [ name, { regionalRouteLor } ] of Object.entries(routesTable['platform'])) {
|
||||
}}
|
||||
Self::{{= name.toUpperCase() }} => RegionalRoute::{{= regionalRouteLor.toUpperCase() }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used in the LoL Tournament API. Specifically
|
||||
/// [`tournament-stub-v4.registerProviderData`](crate::endpoints::TournamentStubV4::register_provider_data)
|
||||
/// and [`tournament-v4.registerProviderData`](crate::endpoints::TournamentV4::register_provider_data).
|
||||
pub fn to_tournament_region(self) -> Option<TournamentRegion> {
|
||||
match self {
|
||||
{{
|
||||
for (const [ name, { tournamentRegion } ] of Object.entries(routesTable['platform'])) {
|
||||
if (!tournamentRegion) continue;
|
||||
}}
|
||||
Self::{{= name.toUpperCase() }} => Some(TournamentRegion::{{= tournamentRegion }}),
|
||||
{{
|
||||
}
|
||||
}}
|
||||
_other => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the slightly more human-friendly alternate name for this `PlatformRoute`. Specifically
|
||||
/// excludes any trailing numbers and appends extra N(orth), S(outh), E(ast), and/or W(est)
|
||||
/// suffixes to some names. Some of these are old region names which are often still used as
|
||||
/// user-facing names, e.g. on op.gg.
|
||||
///
|
||||
/// Note these strings *are* handled by the `FromStr` implementation, if you wish to parse them
|
||||
/// back into `PlatformRoute`s.
|
||||
pub fn as_region_str(self) -> &'static str {
|
||||
match self {
|
||||
{{
|
||||
for (const [ name, { altName } ] of Object.entries(routesTable['platform'])) {
|
||||
if (!altName) continue;
|
||||
}}
|
||||
Self::{{= name.toUpperCase() }} => "{{= altName }}",
|
||||
{{
|
||||
}
|
||||
}}
|
||||
other => other.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform routes for Valorant.
|
||||
#[derive(Debug)]
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
pub enum ValPlatformRoute {
|
||||
{{
|
||||
for (const [ name, { id, description, deprecated } ] of Object.entries(routesTable['val-platform'])) {
|
||||
const desc = description.split('\n');
|
||||
}}
|
||||
{{~ desc :line }}
|
||||
/// {{= line }}
|
||||
{{~}}
|
||||
///
|
||||
/// `{{= id }}` (riotapi-schema ID/repr)
|
||||
{{? deprecated }}
|
||||
#[deprecated]
|
||||
{{?}}
|
||||
{{= name.toUpperCase() }} = {{= id }},
|
||||
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
/// Tournament regions for League of Legends (LoL) used in
|
||||
/// [`tournament-stub-v4.registerProviderData`](crate::endpoints::TournamentStubV4::register_provider_data)
|
||||
/// and [`tournament-v4.registerProviderData`](crate::endpoints::TournamentV4::register_provider_data).
|
||||
#[derive(Debug)]
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
// Note: strum(serialize = ...) actually specifies extra DEserialization values.
|
||||
pub enum TournamentRegion {
|
||||
{{
|
||||
for (const [ name, { id, description, tournamentRegion, deprecated } ] of Object.entries(routesTable['platform'])) {
|
||||
if (tournamentRegion) {
|
||||
const desc = description.split('\n');
|
||||
}}
|
||||
{{~ desc :line }}
|
||||
/// {{= line }}
|
||||
{{~}}
|
||||
{{? deprecated }}
|
||||
#[deprecated]
|
||||
{{?}}
|
||||
{{= tournamentRegion }} = {{= id }},
|
||||
{{
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const seasons = require('./.seasons.json');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
newtype_enum! {
|
||||
/// A League of Legends season for competitive matchmaking.
|
||||
pub newtype_enum Season(u8) {
|
||||
{{
|
||||
for (const e of seasons) {
|
||||
const desc = e['x-desc'] ? e['x-desc'].split('\n') : [];
|
||||
}}
|
||||
/// `{{= e['x-value'] }}`.
|
||||
{{~ desc :line }}
|
||||
/// {{= line }}
|
||||
{{~}}
|
||||
{{= e['x-name'] }} = {{= e['x-value'] }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
|
@ -1,165 +1,161 @@
|
|||
const changeCase = require('change-case');
|
||||
|
||||
const enumTypeLookup = {
|
||||
champion: 'i16',
|
||||
gameMode: 'u8',
|
||||
gameType: 'u8',
|
||||
map: 'u8',
|
||||
queue: 'u16',
|
||||
season: 'u8',
|
||||
};
|
||||
|
||||
// flatMap: https://gist.github.com/samgiles/762ee337dff48623e729
|
||||
// [B](f: (A) ⇒ [B]): [B] ; Although the types in the arrays aren't strict (:
|
||||
Array.prototype.flatMap = function(lambda) {
|
||||
return Array.prototype.concat.apply([], this.map(lambda));
|
||||
};
|
||||
Array.prototype.groupBy = function(lambda) {
|
||||
return Object.entries(this.reduce((agg, x) => {
|
||||
const k = lambda(x);
|
||||
(agg[k] = agg[k] || []).push(x);
|
||||
return agg;
|
||||
}, {}));
|
||||
};
|
||||
Array.prototype.sortBy = function(lambda) {
|
||||
return this.sort((a, b) => {
|
||||
const va = lambda(a);
|
||||
const vb = lambda(b);
|
||||
if ((typeof va) !== (typeof vb))
|
||||
throw Error(`Mismatched sort types: ${typeof va}, ${typeof vb}.`);
|
||||
if (typeof va === 'number')
|
||||
return va - vb;
|
||||
if (typeof va === 'string')
|
||||
return va.localeCompare(vb);
|
||||
throw Error(`Unknown sort type: ${typeof va}.`);
|
||||
});
|
||||
};
|
||||
|
||||
function preamble() {
|
||||
return `\
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////`;
|
||||
}
|
||||
|
||||
function capitalize(input) {
|
||||
return input[0].toUpperCase() + input.slice(1);
|
||||
}
|
||||
|
||||
function decapitalize(input) {
|
||||
return input[0].toLowerCase() + input.slice(1);
|
||||
}
|
||||
|
||||
function normalizeSchemaName(name) {
|
||||
return name.replace(/DTO/ig, '');
|
||||
}
|
||||
|
||||
function normalizeArgName(name) {
|
||||
const tokens = name.split('_');
|
||||
const argName = decapitalize(tokens.map(capitalize).join(''));
|
||||
return 'base' === argName ? 'Base' : argName;
|
||||
}
|
||||
|
||||
function normalizePropName(propName) {
|
||||
const out = changeCase.snakeCase(propName);
|
||||
if ('type' === out)
|
||||
return 'r#' + out;
|
||||
return out;
|
||||
}
|
||||
|
||||
function stringifyType(prop, { endpoint = null, optional = false, fullpath = true, owned = true }) {
|
||||
if (prop.anyOf) {
|
||||
prop = prop.anyOf[0];
|
||||
}
|
||||
if (optional) {
|
||||
return `Option<${stringifyType(prop, { endpoint, fullpath })}>`;
|
||||
}
|
||||
|
||||
let enumType = prop['x-enum'];
|
||||
if (enumType && 'locale' !== enumType)
|
||||
return 'crate::consts::' + changeCase.pascalCase(enumType);
|
||||
|
||||
let refType = prop['$ref'];
|
||||
if (refType) {
|
||||
return (!endpoint ? '' : changeCase.snakeCase(endpoint) + '::') +
|
||||
normalizeSchemaName(refType.slice(refType.indexOf('.') + 1));
|
||||
}
|
||||
switch (prop.type) {
|
||||
case 'boolean': return 'bool';
|
||||
case 'integer': return ('int32' === prop.format ? 'i32' : 'i64');
|
||||
case 'number': return ('float' === prop.format ? 'f32' : 'f64');
|
||||
case 'array':
|
||||
const subprop = stringifyType(prop.items, { endpoint, optional, fullpath, owned });
|
||||
return (owned ? (fullpath ? 'std::vec::' : '') + `Vec<${subprop}>` : `&[${subprop}]`);
|
||||
case 'string': return (owned ? 'String' : '&str');
|
||||
case 'object':
|
||||
return 'std::collections::HashMap<' + stringifyType(prop['x-key'], { endpoint, optional, fullpath, owned }) + ', ' +
|
||||
stringifyType(prop.additionalProperties, { endpoint, optional, fullpath, owned }) + '>';
|
||||
default: return prop.type;
|
||||
}
|
||||
}
|
||||
|
||||
function formatJsonProperty(name) {
|
||||
return `#[serde(rename = "${name}")]`;
|
||||
}
|
||||
|
||||
function formatQueryParamStringify(name, prop, useOwned = false) {
|
||||
const own = useOwned ? '' : '&*';
|
||||
if (prop['x-enum']) {
|
||||
switch (prop.type) {
|
||||
case 'integer':
|
||||
return `${own}Into::<${enumTypeLookup[prop['x-enum']]}>::into(*${name}).to_string()`;
|
||||
default: throw new Error(`Enum not supported: ${JSON.stringify(prop)}.`)
|
||||
}
|
||||
}
|
||||
switch (prop.type) {
|
||||
case 'array': throw new Error(`Cannot formart array: ${JSON.stringify(prop)}.`);
|
||||
case 'boolean': return `${name} ? "true" : "false"`;
|
||||
case 'string': return name;
|
||||
default: return `${own}${name}.to_string()`;
|
||||
}
|
||||
}
|
||||
|
||||
function formatAddQueryParam(param) {
|
||||
let k = `"${param.name}"`;
|
||||
let name = changeCase.snakeCase(param.name);
|
||||
let nc = param.required ? '' : `if let Some(${name}) = ${name} `;
|
||||
let prop = param.schema;
|
||||
switch (prop.type) {
|
||||
case 'array': return `${nc}{ query_params.extend_pairs(${name}.iter()`
|
||||
+ `.map(|w| (${k}, ${formatQueryParamStringify("w", prop.items, true)}))); }`;
|
||||
case 'object': throw 'unsupported';
|
||||
default:
|
||||
return `${nc}{ query_params.append_pair(${k}, ${formatQueryParamStringify(name, prop)}); }`;
|
||||
}
|
||||
}
|
||||
|
||||
function formatRouteArgument(route, pathParams = []) {
|
||||
if (!pathParams.length)
|
||||
return `"${route}".to_owned()`;
|
||||
|
||||
route = route.replace(/\{\S+?\}/g, '{}');
|
||||
const args = pathParams
|
||||
.map(({name}) => name)
|
||||
.map(changeCase.snakeCase)
|
||||
.join(', ');
|
||||
return `format!("${route}", ${args})`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
changeCase,
|
||||
preamble,
|
||||
capitalize,
|
||||
decapitalize,
|
||||
normalizeSchemaName,
|
||||
normalizeArgName,
|
||||
normalizePropName,
|
||||
stringifyType,
|
||||
formatJsonProperty,
|
||||
formatAddQueryParam,
|
||||
formatRouteArgument,
|
||||
const changeCase = require('change-case');
|
||||
|
||||
// flatMap: https://gist.github.com/samgiles/762ee337dff48623e729
|
||||
// [B](f: (A) ⇒ [B]): [B] ; Although the types in the arrays aren't strict (:
|
||||
Array.prototype.flatMap = function(lambda) {
|
||||
return Array.prototype.concat.apply([], this.map(lambda));
|
||||
};
|
||||
Array.prototype.groupBy = function(lambda) {
|
||||
return Object.entries(this.reduce((agg, x) => {
|
||||
const k = lambda(x);
|
||||
(agg[k] = agg[k] || []).push(x);
|
||||
return agg;
|
||||
}, {}));
|
||||
};
|
||||
Array.prototype.sortBy = function(lambda) {
|
||||
return this.sort((a, b) => {
|
||||
const va = lambda(a);
|
||||
const vb = lambda(b);
|
||||
if ((typeof va) !== (typeof vb))
|
||||
throw Error(`Mismatched sort types: ${typeof va}, ${typeof vb}.`);
|
||||
if (typeof va === 'number')
|
||||
return va - vb;
|
||||
if (typeof va === 'string')
|
||||
return va.localeCompare(vb);
|
||||
throw Error(`Unknown sort type: ${typeof va}.`);
|
||||
});
|
||||
};
|
||||
|
||||
function preamble() {
|
||||
return `\
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////`;
|
||||
}
|
||||
|
||||
function capitalize(input) {
|
||||
return input[0].toUpperCase() + input.slice(1);
|
||||
}
|
||||
|
||||
function decapitalize(input) {
|
||||
return input[0].toLowerCase() + input.slice(1);
|
||||
}
|
||||
|
||||
function normalizeSchemaName(name) {
|
||||
return name.replace(/DTO/ig, '');
|
||||
}
|
||||
|
||||
function normalizeArgName(name) {
|
||||
const tokens = name.split('_');
|
||||
const argName = decapitalize(tokens.map(capitalize).join(''));
|
||||
return 'base' === argName ? 'Base' : argName;
|
||||
}
|
||||
|
||||
function normalizePropName(propName) {
|
||||
let out = changeCase.snakeCase(propName);
|
||||
if (/^\d/.test(out)) // No leading digits.
|
||||
out = 'x' + out;
|
||||
if ('type' === out)
|
||||
return 'r#' + out;
|
||||
return out;
|
||||
}
|
||||
|
||||
function stringifyType(prop, { endpoint = null, optional = false, fullpath = true, owned = true }) {
|
||||
if (prop.anyOf) {
|
||||
prop = prop.anyOf[0];
|
||||
}
|
||||
if (optional) {
|
||||
return `Option<${stringifyType(prop, { endpoint, fullpath, owned })}>`;
|
||||
}
|
||||
|
||||
let enumType = prop['x-enum'];
|
||||
if (enumType && 'locale' !== enumType)
|
||||
return 'crate::consts::' + changeCase.pascalCase(enumType);
|
||||
|
||||
let refType = prop['$ref'];
|
||||
if (refType) {
|
||||
return (!endpoint ? '' : changeCase.snakeCase(endpoint) + '::') +
|
||||
normalizeSchemaName(refType.slice(refType.indexOf('.') + 1));
|
||||
}
|
||||
switch (prop.type) {
|
||||
case 'boolean': return 'bool';
|
||||
case 'integer': return ('int32' === prop.format ? 'i32' : 'i64');
|
||||
case 'number': return ('float' === prop.format ? 'f32' : 'f64');
|
||||
case 'array':
|
||||
const subprop = stringifyType(prop.items, { endpoint, optional, fullpath, owned });
|
||||
return (owned ? (fullpath ? 'std::vec::' : '') + `Vec<${subprop}>` : `&[${subprop}]`);
|
||||
case 'string': return (owned ? 'String' : '&str');
|
||||
case 'object':
|
||||
return 'std::collections::HashMap<' + stringifyType(prop['x-key'], { endpoint, optional, fullpath, owned }) + ', ' +
|
||||
stringifyType(prop.additionalProperties, { endpoint, optional, fullpath, owned }) + '>';
|
||||
default: return prop.type;
|
||||
}
|
||||
}
|
||||
|
||||
function formatJsonProperty(name) {
|
||||
return `#[serde(rename = "${name}")]`;
|
||||
}
|
||||
|
||||
function formatAddQueryParam(param) {
|
||||
const k = `"${param.name}"`;
|
||||
const name = normalizePropName(param.name);
|
||||
const condStart = param.required ? '' : `if let Some(${name}) = ${name} { `;
|
||||
const condEnd = param.required ? '' : ' } else { request }'
|
||||
const prop = param.schema;
|
||||
switch (prop.type) {
|
||||
case 'array': return `let request = ${condStart}request.query(&*${name}.iter()`
|
||||
+ `.map(|w| ( ${k}, w )).collect::<Vec<_>>())${condEnd};`;
|
||||
case 'object':
|
||||
throw 'unsupported';
|
||||
default:
|
||||
return `let request = ${condStart}request.query(&[ (${k}, ${name}) ])${condEnd};`;
|
||||
}
|
||||
}
|
||||
|
||||
function formatAddHeaderParam(param) {
|
||||
const k = `"${param.name}"`;
|
||||
const name = changeCase.snakeCase(param.name);
|
||||
const condStart = param.required ? '' : `mut request = request; if let Some(${name}) = ${name} { `;
|
||||
const condEnd = param.required ? '' : ' }'
|
||||
const prop = param.schema;
|
||||
switch (prop.type) {
|
||||
case 'string':
|
||||
return `let ${condStart}request = request.header(${k}, ${name});${condEnd}`;
|
||||
case 'object':
|
||||
throw 'unsupported';
|
||||
default:
|
||||
return `let ${condStart}request = request.header(${k}, ${name}.to_string());${condEnd}`;
|
||||
}
|
||||
}
|
||||
|
||||
function formatRouteArgument(route, pathParams = []) {
|
||||
if (!pathParams.length)
|
||||
return `"${route}"`;
|
||||
|
||||
route = route.replace(/\{\S+?\}/g, '{}');
|
||||
const args = pathParams
|
||||
.map(({name}) => name)
|
||||
.map(changeCase.snakeCase)
|
||||
.join(', ');
|
||||
return `&format!("${route}", ${args})`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
changeCase,
|
||||
preamble,
|
||||
capitalize,
|
||||
decapitalize,
|
||||
normalizeSchemaName,
|
||||
normalizeArgName,
|
||||
normalizePropName,
|
||||
stringifyType,
|
||||
formatJsonProperty,
|
||||
formatAddQueryParam,
|
||||
formatAddHeaderParam,
|
||||
formatRouteArgument,
|
||||
};
|
|
@ -0,0 +1,216 @@
|
|||
{{
|
||||
const spec = require('./.spec.json');
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
||||
// Version {{= spec.info.version }}
|
||||
|
||||
//! Automatically generated endpoint handles.
|
||||
#![allow(clippy::let_and_return, clippy::too_many_arguments)]
|
||||
|
||||
use crate::models::*;
|
||||
|
||||
use std::future::Future;
|
||||
use std::vec::Vec;
|
||||
|
||||
#[cfg(feature="tracing")]
|
||||
use tracing::Instrument;
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::Result;
|
||||
use crate::consts::{ RegionalRoute, PlatformRoute, ValPlatformRoute };
|
||||
use crate::riot_api::RiotApi;
|
||||
|
||||
{{
|
||||
const endpointGroups = {};
|
||||
for (let path of Object.entries(spec.paths)) {
|
||||
let ep = path[1]['x-endpoint'];
|
||||
endpointGroups[ep] = endpointGroups[ep] || [];
|
||||
endpointGroups[ep].push(path);
|
||||
}
|
||||
}}
|
||||
impl RiotApi {
|
||||
{{
|
||||
for (const endpointName of Object.keys(endpointGroups)) {
|
||||
const method = dotUtils.changeCase.snakeCase(endpointName);
|
||||
const type = dotUtils.changeCase.pascalCase(endpointName);
|
||||
}}
|
||||
/// Returns a handle for accessing [{{= type }}](crate::endpoints::{{= type }}) endpoints.
|
||||
/// # Riot Developer API Reference
|
||||
/// <a href="https://developer.riotgames.com/apis#{{= endpointName }}" target="_blank">`{{= endpointName }}`</a>
|
||||
///
|
||||
/// Note: this method is automatically generated.
|
||||
#[inline]
|
||||
pub fn {{= method }}(&self) -> {{= type }} {
|
||||
{{= type }} { base: self }
|
||||
}
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
{{
|
||||
for (let [ endpointName, endpointMethods ] of Object.entries(endpointGroups))
|
||||
{
|
||||
let endpoint = dotUtils.changeCase.pascalCase(endpointName);
|
||||
const endpoint_snake_case = dotUtils.changeCase.snakeCase(endpointName);
|
||||
}}
|
||||
|
||||
/// {{= endpoint }} endpoints handle, accessed by calling [`{{= endpoint_snake_case }}()`](crate::RiotApi::{{= endpoint_snake_case }}) on a [`RiotApi`](crate::RiotApi) instance.
|
||||
/// # Riot Developer API Reference
|
||||
/// <a href="https://developer.riotgames.com/apis#{{= endpointName }}" target="_blank">`{{= endpointName }}`</a>
|
||||
///
|
||||
/// Note: this struct is automatically generated.
|
||||
#[repr(transparent)]
|
||||
pub struct {{= endpoint }}<'a> {
|
||||
base: &'a RiotApi,
|
||||
}
|
||||
impl<'a> {{= endpoint }}<'a> {
|
||||
{{
|
||||
for (const [ route, path ] of endpointMethods)
|
||||
{
|
||||
for (const [ verb, operation ] of Object.entries(path))
|
||||
{
|
||||
if (verb.startsWith('x-')) continue;
|
||||
|
||||
const operationId = operation.operationId;
|
||||
const method = dotUtils.changeCase.snakeCase(operationId.slice(operationId.indexOf('.') + 1));
|
||||
|
||||
const resp200 = operation.responses['200'];
|
||||
|
||||
/* Return type checks. */
|
||||
let hasReturn = false;
|
||||
let returnType = '()';
|
||||
let returnTypeTurbofish = '';
|
||||
let returnOptional = false;
|
||||
if (resp200 && resp200.content)
|
||||
{
|
||||
hasReturn = true;
|
||||
const jsonInfo = resp200.content['application/json'];
|
||||
|
||||
const parseType = dotUtils.stringifyType(jsonInfo.schema, { endpoint, fullpath: false });
|
||||
returnTypeTurbofish = `::<${parseType}>`;
|
||||
returnOptional = !!operation['x-nullable-404'];
|
||||
returnType = returnOptional ? `Option<${parseType}>` : parseType;
|
||||
}
|
||||
|
||||
/* Body content checks. */
|
||||
let bodyType = null;
|
||||
if (operation.requestBody)
|
||||
{
|
||||
const jsonInfo = operation.requestBody.content['application/json'];
|
||||
bodyType = dotUtils.stringifyType(jsonInfo.schema, { endpoint, fullpath: false });
|
||||
}
|
||||
|
||||
/* Description processing. */
|
||||
let descArr = operation.description.split('\n');
|
||||
|
||||
/* Build argument comment & string. */
|
||||
const argBuilder = [
|
||||
'route: ', dotUtils.changeCase.pascalCase(operation['x-route-enum']), 'Route'
|
||||
];
|
||||
|
||||
/* Add body params before path/query. */
|
||||
if (bodyType) {
|
||||
argBuilder.push(', body: &', bodyType);
|
||||
}
|
||||
|
||||
/* Path and query params. */
|
||||
const allParams = operation.parameters;
|
||||
let queryParams = [];
|
||||
let headerParams = [];
|
||||
let routeArgument;
|
||||
if (allParams && allParams.length)
|
||||
{
|
||||
const pathParams = allParams.filter(p => 'path' === p.in)
|
||||
.sortBy(({ name }) => route.indexOf(name));
|
||||
|
||||
const reqQueryParams = allParams.filter(p => 'query' === p.in && p.required);
|
||||
const optQueryParams = allParams.filter(p => 'query' === p.in && !p.required)
|
||||
.sortBy(({ name }) => {
|
||||
let match = /(^[a-z]+|[A-Z]+(?![a-z])|[A-Z][a-z]+)/.exec(name);
|
||||
return match.slice(1).reverse().join('');
|
||||
});
|
||||
queryParams = reqQueryParams.concat(optQueryParams);
|
||||
|
||||
headerParams = allParams.filter(p => 'header' === p.in);
|
||||
|
||||
for (let paramList of [ pathParams, reqQueryParams, optQueryParams, headerParams ])
|
||||
{
|
||||
const required = paramList === pathParams;
|
||||
for (const param of paramList)
|
||||
{
|
||||
argBuilder.push(', ', dotUtils.normalizePropName(param.name), ': ',
|
||||
dotUtils.stringifyType(param.schema, { endpoint, optional: !(required || param.required), owned: false }));
|
||||
}
|
||||
}
|
||||
|
||||
routeArgument = dotUtils.formatRouteArgument(route, pathParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
routeArgument = dotUtils.formatRouteArgument(route);
|
||||
}
|
||||
|
||||
for (var descLine of descArr)
|
||||
{
|
||||
}}
|
||||
///{{= descLine ? ' ' + descLine : '' }}
|
||||
{{
|
||||
}
|
||||
}}
|
||||
/// # Parameters
|
||||
/// * `route` - Route to query.
|
||||
{{
|
||||
if (allParams)
|
||||
{
|
||||
for (let param of allParams)
|
||||
{
|
||||
}}
|
||||
/// * `{{= dotUtils.changeCase.snakeCase(param.name) }}` ({{= param.required ? 'required' : 'optional' }}, in {{= param.in }}){{= param.description ? ' - ' + param.description : ''}}
|
||||
{{
|
||||
}
|
||||
}
|
||||
}}
|
||||
/// # Riot Developer API Reference
|
||||
/// <a href="{{= operation.externalDocs.url }}" target="_blank">`{{= operationId }}`</a>
|
||||
///
|
||||
/// Note: this method is automatically generated.
|
||||
pub fn {{= method }}(&self, {{= argBuilder.join('') }})
|
||||
-> impl Future<Output = Result<{{= returnType }}>> + 'a
|
||||
{
|
||||
let route_str = route.into();
|
||||
let request = self.base.request(Method::{{= verb.toUpperCase() }}, route_str, {{= routeArgument }});
|
||||
{{
|
||||
for (let queryParam of queryParams)
|
||||
{
|
||||
}}
|
||||
{{= dotUtils.formatAddQueryParam(queryParam) }}
|
||||
{{
|
||||
}
|
||||
}}
|
||||
{{
|
||||
for (const headerParam of headerParams)
|
||||
{
|
||||
}}
|
||||
{{= dotUtils.formatAddHeaderParam(headerParam) }}
|
||||
{{
|
||||
}
|
||||
}}
|
||||
{{? bodyType }}
|
||||
let request = request.body(serde_json::ser::to_vec(body).unwrap());
|
||||
{{?}}
|
||||
let future = self.base.execute{{= hasReturn ? (returnOptional ? '_opt' : '_val') : '' }}{{= returnTypeTurbofish }}("{{= operationId }}", route_str, request);
|
||||
#[cfg(feature = "tracing")]
|
||||
let future = future.instrument(tracing::info_span!("{{= operationId }}"));
|
||||
future
|
||||
}
|
||||
|
||||
{{
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
{{
|
||||
}
|
||||
}}
|
|
@ -1,80 +1,89 @@
|
|||
const util = require('util');
|
||||
const fs = require('fs');
|
||||
fs.readFileAsync = util.promisify(fs.readFile);
|
||||
fs.writeFileAsync = util.promisify(fs.writeFile);
|
||||
const req = require("request-promise-native");
|
||||
|
||||
process.chdir(__dirname);
|
||||
|
||||
const files = [
|
||||
[
|
||||
'http://raw.communitydragon.org/pbe/plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json',
|
||||
'.champion.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/openapi-3.0.0.json',
|
||||
'.spec.json'
|
||||
],
|
||||
[
|
||||
'http://static.developer.riotgames.com/docs/lol/seasons.json',
|
||||
'.seasons.json'
|
||||
],
|
||||
[
|
||||
'http://static.developer.riotgames.com/docs/lol/queues.json',
|
||||
'.queues.json'
|
||||
],
|
||||
[
|
||||
'http://static.developer.riotgames.com/docs/lol/gameTypes.json',
|
||||
'.gameTypes.json'
|
||||
],
|
||||
[
|
||||
'http://static.developer.riotgames.com/docs/lol/gameModes.json',
|
||||
'.gameModes.json'
|
||||
],
|
||||
[
|
||||
'http://static.developer.riotgames.com/docs/lol/maps.json',
|
||||
'.maps.json'
|
||||
]
|
||||
]
|
||||
|
||||
const downloadFilesPromise = Promise.all(files.map(([url, file]) => req(url)
|
||||
.then(body => fs.writeFileAsync(file, body, "utf8"))));
|
||||
|
||||
const doT = require('dot');
|
||||
const glob = require('glob-promise');
|
||||
|
||||
const log = a => { console.log(a); return a; };
|
||||
const suffix = '.dt';
|
||||
|
||||
doT.templateSettings = {
|
||||
evaluate: /\r?\n?\{\{([\s\S]+?)\}\}/g,
|
||||
interpolate: /\r?\n?\{\{=([\s\S]+?)\}\}/g,
|
||||
encode: /\r?\n?\{\{!([\s\S]+?)\}\}/g,
|
||||
use: /\r?\n?\{\{#([\s\S]+?)\}\}/g,
|
||||
define: /\r?\n?\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g,
|
||||
conditional: /\r?\n?\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g,
|
||||
iterate: /\r?\n?\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g,
|
||||
varname: 'it',
|
||||
strip: false,
|
||||
append: false,
|
||||
selfcontained: false
|
||||
};
|
||||
|
||||
global.require = require;
|
||||
|
||||
downloadFilesPromise.then(() => glob.promise("**/*" + suffix, { ignore: ["**/node_modules/**"] }))
|
||||
.then(files => Promise.all(files
|
||||
.map(log)
|
||||
.map(file => fs.readFileAsync(file, "utf8")
|
||||
.then(input => {
|
||||
try {
|
||||
return doT.template(input)({});
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`Error thrown while running "${file}":`, e);
|
||||
}
|
||||
})
|
||||
.then(output => fs.writeFileAsync("../src/" + file.slice(0, -suffix.length), output, "utf8"))
|
||||
)
|
||||
))
|
||||
const util = require('util');
|
||||
const fs = require('fs');
|
||||
fs.readFileAsync = util.promisify(fs.readFile);
|
||||
fs.writeFileAsync = util.promisify(fs.writeFile);
|
||||
const req = require("request-promise-native");
|
||||
|
||||
process.chdir(__dirname);
|
||||
|
||||
const files = [
|
||||
[
|
||||
'http://raw.communitydragon.org/pbe/plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json',
|
||||
'.champion.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/openapi-3.0.0.json',
|
||||
'.spec.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/enums/seasons.json',
|
||||
'.seasons.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/enums/queues.json',
|
||||
'.queues.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/enums/queueTypes.json',
|
||||
'.queueTypes.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/enums/gameTypes.json',
|
||||
'.gameTypes.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/enums/gameModes.json',
|
||||
'.gameModes.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/enums/maps.json',
|
||||
'.maps.json'
|
||||
],
|
||||
[
|
||||
'http://www.mingweisamuel.com/riotapi-schema/routesTable.json',
|
||||
'.routesTable.json'
|
||||
],
|
||||
];
|
||||
|
||||
const downloadFilesPromise = Promise.all(files.map(([url, file]) => req(url)
|
||||
.then(body => fs.writeFileAsync(file, body, "utf8"))));
|
||||
|
||||
const doT = require('dot');
|
||||
const glob = require('glob-promise');
|
||||
|
||||
const log = a => { console.log(a); return a; };
|
||||
const suffix = '.dt';
|
||||
|
||||
doT.templateSettings = {
|
||||
evaluate: /\r?\n?\{\{([\s\S]+?)\}\}/g,
|
||||
interpolate: /\r?\n?\{\{=([\s\S]+?)\}\}/g,
|
||||
encode: /\r?\n?\{\{!([\s\S]+?)\}\}/g,
|
||||
use: /\r?\n?\{\{#([\s\S]+?)\}\}/g,
|
||||
define: /\r?\n?\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g,
|
||||
conditional: /\r?\n?\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g,
|
||||
iterate: /\r?\n?\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g,
|
||||
varname: 'it',
|
||||
strip: false,
|
||||
append: false,
|
||||
selfcontained: false
|
||||
};
|
||||
|
||||
global.require = require;
|
||||
|
||||
downloadFilesPromise.then(() => glob.promise("**/*" + suffix, { ignore: ["**/node_modules/**"] }))
|
||||
.then(files => Promise.all(files
|
||||
.map(log)
|
||||
.map(file => fs.readFileAsync(file, "utf8")
|
||||
.then(input => {
|
||||
try {
|
||||
return doT.template(input)({});
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`Error thrown while running "${file}":`, e);
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
.then(output => fs.writeFileAsync("../src/" + file.slice(0, -suffix.length), output, "utf8"))
|
||||
)
|
||||
))
|
||||
.catch(console.error);
|
|
@ -0,0 +1,41 @@
|
|||
{{
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
const readme = require('fs').readFileSync('../../README.md', 'utf-8').split(/\r?\n/);
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
{{~ readme :line }}
|
||||
//!{{= line ? (' ' + line) : '' }}
|
||||
{{~}}
|
||||
|
||||
// Re-exported reqwest types.
|
||||
pub use reqwest;
|
||||
|
||||
mod config;
|
||||
pub use config::RiotApiConfig;
|
||||
|
||||
pub mod consts;
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub mod endpoints;
|
||||
|
||||
mod error;
|
||||
pub use error::*;
|
||||
|
||||
pub mod meta;
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub mod models;
|
||||
mod models_impls;
|
||||
|
||||
mod req;
|
||||
|
||||
mod response_info;
|
||||
pub use response_info::*;
|
||||
|
||||
mod riot_api;
|
||||
pub use riot_api::*;
|
||||
|
||||
mod util;
|
|
@ -0,0 +1,31 @@
|
|||
{{
|
||||
const spec = require('./.spec.json');
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
|
||||
const operations = [];
|
||||
for (const [ route, path ] of Object.entries(spec.paths)) {
|
||||
for (const [ method, operation ] of Object.entries(path)) {
|
||||
if (method.startsWith('x-')) continue;
|
||||
operations.push({ route, method, operation });
|
||||
}
|
||||
}
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
||||
// Version {{= spec.info.version }}
|
||||
|
||||
//! Metadata about the Riot API and Riven.
|
||||
//!
|
||||
//! Note: this modules is automatically generated.
|
||||
|
||||
/// Metadata for endpoints. Each tuple corresponds to one endpoint and contains
|
||||
/// the HTTP [`Method`](reqwest::Method), `str` path, and the method's `str` ID.
|
||||
pub static ALL_ENDPOINTS: [(reqwest::Method, &str, &str); {{= operations.length }}] = [
|
||||
{{
|
||||
for (const { route, method, operation } of operations) {
|
||||
}}
|
||||
(reqwest::Method::{{= method.toUpperCase() }}, "{{= route }}", "{{= operation.operationId }}"),
|
||||
{{
|
||||
}
|
||||
}}
|
||||
];
|
|
@ -1,70 +1,85 @@
|
|||
{{
|
||||
const spec = require('./.spec.json');
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
||||
// Version {{= spec.info.version }}
|
||||
|
||||
//! Data transfer structs.
|
||||
//!
|
||||
//! Separated into separate modules for each endpoint.
|
||||
//! Several modules contain structs with the same name, so be sure to use the right ones.
|
||||
//!
|
||||
//! Note: these modules are automatically generated.
|
||||
|
||||
{{
|
||||
let schemas = spec.components.schemas;
|
||||
let schemaKeyByEndpoint = Object.keys(schemas)
|
||||
.filter(schemaKey => 'Error' != schemaKey)
|
||||
.groupBy(schemaKey => schemaKey.split('.')[0]);
|
||||
|
||||
for (let [endpoint, schemaKeyGroup] of schemaKeyByEndpoint) {
|
||||
const endpoint_pascal_case = dotUtils.changeCase.pascalCase(endpoint);
|
||||
}}
|
||||
/// Data structs used by [`{{= endpoint_pascal_case }}`](crate::endpoints::{{= endpoint_pascal_case }}).
|
||||
///
|
||||
/// Note: this module is automatically generated.
|
||||
#[allow(dead_code)]
|
||||
pub mod {{= dotUtils.changeCase.snakeCase(endpoint) }} {
|
||||
{{
|
||||
for (let schemaKey of schemaKeyGroup) {
|
||||
const [, rawSchemaName] = schemaKey.split('.');
|
||||
const schemaName = dotUtils.normalizeSchemaName(rawSchemaName);
|
||||
const schema = schemas[schemaKey];
|
||||
const props = schema.properties;
|
||||
const requiredSet = new Set(schema.required);
|
||||
}}
|
||||
/// {{= schemaName }} data object.
|
||||
{{? schema.description }}
|
||||
/// # Description
|
||||
/// {{= schema.description }}
|
||||
///
|
||||
/// Note: This struct is automatically generated
|
||||
{{?}}
|
||||
#[derive(Debug)]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct {{= schemaName }} {
|
||||
{{
|
||||
for (let [ propKey, prop ] of Object.entries(props))
|
||||
{
|
||||
const name = dotUtils.normalizePropName(propKey);
|
||||
const required = requiredSet.has(propKey);
|
||||
}}
|
||||
{{? prop.description }}
|
||||
/// {{= prop.description.split('\n').map(x => x.trim()).join('<br>\r\n /// ') }}
|
||||
{{?}}
|
||||
{{= dotUtils.formatJsonProperty(propKey) }}
|
||||
pub {{= name }}: {{= dotUtils.stringifyType(prop, { optional: !required }) }},
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
{{
|
||||
}
|
||||
{{
|
||||
const spec = require('./.spec.json');
|
||||
const dotUtils = require('./dotUtils.js');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
|
||||
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
||||
// Version {{= spec.info.version }}
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
//! Data transfer structs.
|
||||
//!
|
||||
//! Separated into separate modules for each endpoint.
|
||||
//! Several modules contain structs with the same name, so be sure to use the right ones.
|
||||
//!
|
||||
//! Note: these modules are automatically generated.
|
||||
|
||||
{{
|
||||
let schemas = spec.components.schemas;
|
||||
let schemaKeyByEndpoint = Object.keys(schemas)
|
||||
.filter(schemaKey => 'Error' != schemaKey)
|
||||
.groupBy(schemaKey => schemaKey.split('.')[0]);
|
||||
|
||||
for (let [endpoint, schemaKeyGroup] of schemaKeyByEndpoint) {
|
||||
const endpoint_pascal_case = dotUtils.changeCase.pascalCase(endpoint);
|
||||
}}
|
||||
/// Data structs used by [`{{= endpoint_pascal_case }}`](crate::endpoints::{{= endpoint_pascal_case }}).
|
||||
///
|
||||
/// Note: this module is automatically generated.
|
||||
#[allow(dead_code)]
|
||||
pub mod {{= dotUtils.changeCase.snakeCase(endpoint) }} {
|
||||
{{
|
||||
for (let schemaKey of schemaKeyGroup) {
|
||||
const [, rawSchemaName] = schemaKey.split('.');
|
||||
const schemaName = dotUtils.normalizeSchemaName(rawSchemaName);
|
||||
const schema = schemas[schemaKey];
|
||||
const props = schema.properties;
|
||||
const requiredSet = new Set(schema.required);
|
||||
}}
|
||||
/// {{= schemaName }} data object.
|
||||
{{? schema.description }}
|
||||
/// # Description
|
||||
/// {{= schema.description.split('\n').map(x => x.trim()).join('<br>\r\n /// ') }}
|
||||
///
|
||||
/// Note: This struct is automatically generated
|
||||
{{?}}
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "deny-unknown-fields", serde(deny_unknown_fields))]
|
||||
pub struct {{= schemaName }} {
|
||||
{{
|
||||
for (let [ propKey, prop ] of Object.entries(props))
|
||||
{
|
||||
const name = dotUtils.normalizePropName(propKey);
|
||||
const optional = !requiredSet.has(propKey);
|
||||
}}
|
||||
{{? prop.description }}
|
||||
/// {{= prop.description.split('\n').map(x => x.trim()).join('<br>\r\n /// ') }}
|
||||
{{?}}
|
||||
{{= dotUtils.formatJsonProperty(propKey) }}
|
||||
{{? optional }}
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
{{?}}
|
||||
{{? 'championId' === propKey && (prop.description || '').includes('this field returned invalid championIds') }}
|
||||
///
|
||||
/// Instead use [`Self::champion()`] which checks this field then parses [`Self::champion_name`].
|
||||
#[deprecated(since = "2.5.0", note = "Use `Participant.champion()` instead. Riot sometimes returns corrupted data for this field: https://github.com/RiotGames/developer-relations/issues/553")]
|
||||
#[serde(serialize_with = "crate::consts::Champion::serialize_result")]
|
||||
#[serde(deserialize_with = "crate::consts::Champion::deserialize_result")]
|
||||
pub {{= name }}: Result<crate::consts::Champion, std::num::TryFromIntError>,
|
||||
{{??}}
|
||||
pub {{= name }}: {{= dotUtils.stringifyType(prop, { optional }) }},
|
||||
{{?}}
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
{{
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
{{
|
||||
}
|
||||
}}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,20 +1,20 @@
|
|||
{
|
||||
"name": "dot-gen",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"change-case": "^3.1.0",
|
||||
"dot": "^1.1.2",
|
||||
"glob": "^7.1.2",
|
||||
"glob-promise": "^3.3.0",
|
||||
"request": "^2.88.0",
|
||||
"request-promise-native": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "GPL-2.0"
|
||||
}
|
||||
{
|
||||
"name": "dot-gen",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"change-case": "^3.1.0",
|
||||
"dot": "^1.1.3",
|
||||
"glob": "^7.1.2",
|
||||
"glob-promise": "^3.3.0",
|
||||
"request": "^2.88.0",
|
||||
"request-promise-native": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "GPL-2.0"
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
Tests are divided up by region. This is because tests cannot share state, and therefore
|
||||
cannot share the rate limiting done within a `RiotApi` instance. However, rate limiting
|
||||
is separate for each region, so it is safe to send requests to different regions from
|
||||
different instances.
|
||||
|
||||
The tests within an individual file do share their `RiotApi` instance thanks to custom
|
||||
test runners and some macros I hacked together which are located in `async_tests.rs`.
|
||||
They are set up in a way to look like normal test output for fun and probably to
|
||||
confuse people.
|
||||
Tests are divided up by region. This is because tests cannot share state, and therefore
|
||||
cannot share the rate limiting done within a `RiotApi` instance. However, rate limiting
|
||||
is separate for each region, so it is safe to send requests to different regions from
|
||||
different instances.
|
||||
|
||||
The tests within an individual file do share their `RiotApi` instance thanks to custom
|
||||
test runners and some macros I hacked together which are located in `async_tests.rs`.
|
||||
They are set up in a way to look like normal test output for fun and probably to
|
||||
confuse people.
|
|
@ -1,86 +1,87 @@
|
|||
/// This is just a huge hack to make a test runner (with no test cases)
|
||||
/// look as if it's running a bunch of (async) test cases.
|
||||
#[macro_export]
|
||||
macro_rules! async_tests {
|
||||
( $runner:ident { $( $name:ident : async $eval:block, )* } ) => {
|
||||
fn $runner(_: &[()]) {
|
||||
env_logger::init();
|
||||
|
||||
std::process::exit({
|
||||
let mut rt = tokio::runtime::Runtime::new()
|
||||
.expect("Failed to create runtime.");
|
||||
|
||||
let (_, errs) = rt.block_on(async {
|
||||
println!();
|
||||
println!("running tests");
|
||||
println!();
|
||||
let mut oks: u32 = 0;
|
||||
let mut errs: u32 = 0;
|
||||
$(
|
||||
let $name = async {
|
||||
let result: std::result::Result<(), String> = async {
|
||||
$eval
|
||||
}.await;
|
||||
result
|
||||
};
|
||||
let $name = tokio::spawn($name);
|
||||
)*
|
||||
$(
|
||||
let $name = $name.await.expect("Failed to spawn.");
|
||||
)*
|
||||
$(
|
||||
print!("test {} ... ", stringify!($name));
|
||||
match $name {
|
||||
Ok(_) => {
|
||||
println!("{}", "ok".green());
|
||||
oks += 1;
|
||||
}
|
||||
Err(msg) => {
|
||||
println!("{}", "error".bright_red());
|
||||
println!("{}", msg);
|
||||
errs += 1;
|
||||
}
|
||||
}
|
||||
)*
|
||||
println!();
|
||||
print!("test result: {}. ", if errs > 0 { "error".bright_red() } else { "ok".green() });
|
||||
println!("{} passed; {} failed; 0 ignored; 0 measured; 0 filtered out", oks, errs);
|
||||
println!();
|
||||
(oks, errs)
|
||||
});
|
||||
// Just returns #errs as exit code.
|
||||
errs as i32
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! rassert {
|
||||
( $x:expr ) => {
|
||||
{
|
||||
if $x { Ok(()) } else { Err(stringify!($x)) }?
|
||||
}
|
||||
};
|
||||
( $x:expr, $format:expr $(, $arg:expr)* ) => {
|
||||
{
|
||||
if $x { Ok(()) } else { Err( format!($format, $( $arg )* ) ) }?
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! rassert_eq {
|
||||
( $a:expr, $b:expr ) => { rassert!($a == $b) };
|
||||
( $a:expr, $b:expr, $format:expr $(, $arg:expr)* ) => {
|
||||
rassert!($a == $b, $format $(, $arg )* )
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! rassert_ne {
|
||||
( $a:expr, $b:expr ) => { rassert!($a != $b) };
|
||||
( $a:expr, $b:expr, $format:expr $(, $arg:expr)* ) => {
|
||||
rassert!($a != $b, $format $(, $arg )* )
|
||||
};
|
||||
}
|
||||
/// This is just a huge hack to make a test runner (with no test cases)
|
||||
/// look as if it's running a bunch of (async) test cases.
|
||||
#[macro_export]
|
||||
macro_rules! async_tests {
|
||||
( $runner:ident { $( $name:ident : async $eval:block, )* } ) => {
|
||||
#[allow(dead_code)]
|
||||
fn $runner(_: &[()]) {
|
||||
env_logger::init();
|
||||
|
||||
std::process::exit({
|
||||
let rt = tokio::runtime::Runtime::new()
|
||||
.expect("Failed to create runtime.");
|
||||
|
||||
let (_, errs) = rt.block_on(async {
|
||||
println!();
|
||||
println!("running tests");
|
||||
println!();
|
||||
let mut oks: u32 = 0;
|
||||
let mut errs: u32 = 0;
|
||||
$(
|
||||
let $name = async {
|
||||
let result: std::result::Result<(), String> = async {
|
||||
$eval
|
||||
}.await;
|
||||
result
|
||||
};
|
||||
let $name = tokio::spawn($name);
|
||||
)*
|
||||
$(
|
||||
let $name = $name.await.expect("Failed to spawn.");
|
||||
)*
|
||||
$(
|
||||
print!("test {} ... ", stringify!($name));
|
||||
match $name {
|
||||
Ok(_) => {
|
||||
println!("{}", "ok".green());
|
||||
oks += 1;
|
||||
}
|
||||
Err(msg) => {
|
||||
println!("{}", "error".bright_red());
|
||||
println!("{}", msg);
|
||||
errs += 1;
|
||||
}
|
||||
}
|
||||
)*
|
||||
println!();
|
||||
print!("test result: {}. ", if errs > 0 { "error".bright_red() } else { "ok".green() });
|
||||
println!("{} passed; {} failed; 0 ignored; 0 measured; 0 filtered out", oks, errs);
|
||||
println!();
|
||||
(oks, errs)
|
||||
});
|
||||
// Just returns #errs as exit code.
|
||||
errs as i32
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! rassert {
|
||||
( $x:expr ) => {
|
||||
{
|
||||
if $x { Ok(()) } else { Err(stringify!($x)) }?
|
||||
}
|
||||
};
|
||||
( $x:expr, $format:expr $(, $arg:expr)* ) => {
|
||||
{
|
||||
if $x { Ok(()) } else { Err( format!($format $(, $arg )* ) ) }?
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! rassert_eq {
|
||||
( $a:expr, $b:expr ) => { rassert!($a == $b) };
|
||||
( $a:expr, $b:expr, $format:expr $(, $arg:expr)* ) => {
|
||||
rassert!($a == $b, $format $(, $arg )* )
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! rassert_ne {
|
||||
( $a:expr, $b:expr ) => { rassert!($a != $b) };
|
||||
( $a:expr, $b:expr, $format:expr $(, $arg:expr)* ) => {
|
||||
rassert!($a != $b, $format $(, $arg )* )
|
||||
};
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use testutils::*;
|
||||
|
||||
const ROUTE: PlatformRoute = PlatformRoute::RU;
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
summoner_leagues: async {
|
||||
let sum = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "d3atomiz3d");
|
||||
let sum = sum.await
|
||||
.map_err(|e| format!("Error getting summoner: {}", e))?
|
||||
.ok_or_else(|| "Failed to find summoner".to_owned())?;
|
||||
|
||||
let p = RIOT_API.league_v4().get_league_entries_for_summoner(ROUTE, &sum.id);
|
||||
let s = p.await.map_err(|e| format!("Error getting league entries: {}", e))?;
|
||||
let _ = s;
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use riven::models::tournament_stub_v5::*;
|
||||
use testutils::*;
|
||||
|
||||
const ROUTE: RegionalRoute = RegionalRoute::AMERICAS;
|
||||
|
||||
static MATCHES: &[&str] = &[
|
||||
// New games with `match-v5.ParticipantDto.challenges` field.
|
||||
"NA1_4209556127",
|
||||
"NA1_4212715433",
|
||||
"NA1_4265913704", // `match-v5.ParticipantDto.challenges.mejaisFullStackInTime`
|
||||
];
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
// Account-v1
|
||||
account_v1_getbyriotid_getbypuuid: async {
|
||||
// Game name is case and whitespace insensitive.
|
||||
// But tag cannot have spaces. (Is it case sensitive?).
|
||||
let account_tag = RIOT_API.account_v1().get_by_riot_id(ROUTE, "Lug nuts K", "000")
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get account by riot ID: {}", e))?
|
||||
.ok_or("Riot account not found!".to_owned())?;
|
||||
|
||||
let account_puuid = RIOT_API.account_v1().get_by_puuid(ROUTE, &account_tag.puuid)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get account by PUUID: {}", e))?;
|
||||
|
||||
let _ = account_puuid;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Tournament stub test.
|
||||
tournamentstub: async {
|
||||
let ts = RIOT_API.tournament_stub_v5();
|
||||
let provider_id = ts.register_provider_data(ROUTE, &ProviderRegistrationParametersV5 {
|
||||
region: PlatformRoute::NA1.as_region_str().to_owned(),
|
||||
url: "https://github.com/MingweiSamuel/Riven".to_owned(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
println!("provider_id: {}", provider_id);
|
||||
|
||||
let tournament_id = ts.register_tournament(ROUTE, &TournamentRegistrationParametersV5 {
|
||||
name: Some("Riven Tourney :)".to_owned()),
|
||||
provider_id,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
println!("tournament_id: {}", tournament_id);
|
||||
|
||||
let codes_result = ts.create_tournament_code(ROUTE, &TournamentCodeParametersV5 {
|
||||
map_type: "SUMMONERS_RIFT".to_owned(),
|
||||
metadata: Some("eW91IGZvdW5kIHRoZSBzZWNyZXQgbWVzc2FnZQ==".to_owned()),
|
||||
pick_type: "TOURNAMENT_DRAFT".to_owned(),
|
||||
spectator_type: "ALL".to_owned(),
|
||||
team_size: 5,
|
||||
allowed_participants: None,
|
||||
enough_players: false,
|
||||
}, tournament_id as i64, Some(300))
|
||||
.await;
|
||||
|
||||
match codes_result {
|
||||
Ok(codes) => {
|
||||
rassert_eq!(300, codes.len());
|
||||
println!("codes: {}", codes.join(", "));
|
||||
Ok(())
|
||||
}
|
||||
Err(mut e) => {
|
||||
if let Some(response) = e.take_response() {
|
||||
eprintln!("{:?}", response.text().await);
|
||||
}
|
||||
Err(e.to_string())
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
match_v5_get: async {
|
||||
match_v5_get(ROUTE, MATCHES).await
|
||||
},
|
||||
match_v5_get_timeline: async {
|
||||
match_v5_get_timeline(ROUTE, MATCHES).await
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use testutils::*;
|
||||
|
||||
const ROUTE: PlatformRoute = PlatformRoute::JP1;
|
||||
|
||||
static MATCHES: &[&str] = &[
|
||||
// Only has participant IDs for blue team.
|
||||
"JP1_391732436",
|
||||
// New field `ParticipantChallenges` `twoWardsOneSweeperCount`
|
||||
"JP1_397348569",
|
||||
// New fields:
|
||||
// `match-v5.ParticipantDto.playerAugment[1234],playerSubteamId,subteamPlacement`
|
||||
"JP1_400700181",
|
||||
// New field: `match-v5.ParticipantDto.placement`
|
||||
"JP1_405073638",
|
||||
// New ARENA 2v2v2v2 game mode, broken `subteamPlacement`
|
||||
"KR_6604607115",
|
||||
// New field: `match-v5.ParticipantDto.missions`
|
||||
"JP1_417935351",
|
||||
// New field: `match-v5.ParticipantDto.riotIdGameName`
|
||||
"JP1_419115017",
|
||||
];
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
// Summoner tests.
|
||||
summoner_get_kanjikana: async {
|
||||
let p = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "私の 頭が かたい");
|
||||
let s = p.await.map_err(|e| e.to_string())?.ok_or_else(|| "Failed to get myheadhard".to_owned())?;
|
||||
rassert_eq!("私の頭がかたい", s.name);
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Failure cases.
|
||||
// // Make sure get_raw_response(...) with invalid path fails as expected.
|
||||
// raw_response_invalid: async {
|
||||
// let p = RIOT_API.get_raw_response("summoner-v4.getBySummonerName", Region::JP.into(), "INVALID/PATH".to_owned(), None);
|
||||
// let r = p.await;
|
||||
// rassert!(r.is_err());
|
||||
// Ok(())
|
||||
// },
|
||||
// summoner_v4().get_by_summoner_name(...) normally returns an option.
|
||||
// If we use `get` (instead of `get_optional`) make sure it errors.
|
||||
get_nonoptional_invalid: async {
|
||||
let path_string = format!("/lol/summoner/v4/summoners/by-name/{}", "SUMMONER THAT DOES NOT EXIST");
|
||||
let request = RIOT_API.request(reqwest::Method::GET, ROUTE.into(), &path_string);
|
||||
let p = RIOT_API.execute_val::<riven::models::summoner_v4::Summoner>(
|
||||
"summoner-v4.getBySummonerName", ROUTE.into(), request);
|
||||
let r = p.await;
|
||||
rassert!(r.is_err());
|
||||
Ok(())
|
||||
},
|
||||
// Make sure 403 is handled as expected.
|
||||
tournament_forbidden: async {
|
||||
let p = RIOT_API.tournament_v5().get_tournament_code(ROUTE.to_regional(), "INVALID_CODE");
|
||||
let r = p.await;
|
||||
rassert!(r.is_err());
|
||||
rassert_eq!(Some(reqwest::StatusCode::FORBIDDEN), r.unwrap_err().status_code());
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// Disabled: Caihonbbt no longer ranked.
|
||||
// // tft-league-v1.getLeagueEntriesForSummoner
|
||||
// // https://github.com/MingweiSamuel/Riven/issues/25
|
||||
// tft_league_getleagueentriesforsummoner: async {
|
||||
// let sp = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "Caihonbbt");
|
||||
// let sr = sp.await.map_err(|e| e.to_string())?.ok_or_else(|| "Failed to get \"Caihonbbt\"".to_owned())?;
|
||||
// let lp = RIOT_API.tft_league_v1().get_league_entries_for_summoner(ROUTE, &sr.id);
|
||||
// let lr = lp.await.map_err(|e| e.to_string())?;
|
||||
// rassert!(!lr.is_empty());
|
||||
// Ok(())
|
||||
// },
|
||||
// tft-league-v1.getTopRatedLadder
|
||||
// https://github.com/MingweiSamuel/Riven/issues/24
|
||||
tft_league_gettopratedladder: async {
|
||||
let lp = RIOT_API.tft_league_v1().get_top_rated_ladder(ROUTE, QueueType::RANKED_TFT_TURBO);
|
||||
let lr = lp.await.map_err(|e| e.to_string())?;
|
||||
rassert!(!lr.is_empty());
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// ASIA regional tests
|
||||
league_v4_match_v5_latest_combo: async {
|
||||
league_v4_match_v5_latest_combo(ROUTE).await
|
||||
},
|
||||
match_v5_get: async {
|
||||
match_v5_get(ROUTE.to_regional(), MATCHES).await
|
||||
},
|
||||
match_v5_get_timeline: async {
|
||||
match_v5_get_timeline(ROUTE.to_regional(), MATCHES).await
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use testutils::*;
|
||||
|
||||
const ROUTE: RegionalRoute = RegionalRoute::EUROPE;
|
||||
|
||||
// Archived 2023-08-17
|
||||
// // Illegal big `championId`s. https://github.com/RiotGames/developer-relations/issues/553
|
||||
// "EUW1_5097684633",
|
||||
// "EUW1_5097963383",
|
||||
// "EUW1_5102203800", // https://github.com/MingweiSamuel/Riven/issues/36
|
||||
// "EUW1_5765650307", // https://gist.github.com/MingweiSamuel/d5f9dc40cc5a80a9255e488f27705c56?permalink_comment_id=4088256#gistcomment-4088256
|
||||
|
||||
static MATCHES: &[&str] = &[
|
||||
// New ARENA 2v2v2v2 game mode
|
||||
"EUW1_6511808246", // https://github.com/MingweiSamuel/Camille/issues/99
|
||||
// Added 2023-08-27
|
||||
"EUW1_6569580003",
|
||||
"EUW1_6569417645",
|
||||
"EUW1_6568707352",
|
||||
"EUW1_6568635198",
|
||||
"EUW1_6568537080",
|
||||
];
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
match_v5_get: async {
|
||||
match_v5_get(ROUTE, MATCHES).await
|
||||
},
|
||||
match_v5_get_timeline: async {
|
||||
match_v5_get_timeline(ROUTE, MATCHES).await
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use testutils::*;
|
||||
|
||||
const ROUTE: PlatformRoute = PlatformRoute::EUW1;
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
// Champion Mastery tests.
|
||||
// SUMMONER ID ENDPOINT BROKEN: https://github.com/RiotGames/developer-relations/issues/830
|
||||
// championmastery_getscore_ma5tery: async {
|
||||
// let sum = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "ma5tery");
|
||||
// let sum = sum.await
|
||||
// .map_err(|e| format!("Error getting summoner: {}", e))?
|
||||
// .ok_or_else(|| "Failed to find summoner".to_owned())?;
|
||||
|
||||
// let p = RIOT_API.champion_mastery_v4().get_champion_mastery_score(ROUTE, &sum.id);
|
||||
// let s = p.await.map_err(|e| format!("Error getting champion mastery score: {}", e))?;
|
||||
// rassert!((969..=1000).contains(&s), "Unexpected ma5tery score: {}.", s);
|
||||
// Ok(())
|
||||
// },
|
||||
championmastery_getscore_ma5tery: async {
|
||||
let sum = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "ma5tery");
|
||||
let sum = sum.await
|
||||
.map_err(|e| format!("Error getting summoner: {}", e))?
|
||||
.ok_or_else(|| "Failed to find summoner".to_owned())?;
|
||||
|
||||
let p = RIOT_API.champion_mastery_v4().get_champion_mastery_score_by_puuid(ROUTE, &sum.puuid);
|
||||
let s = p.await.map_err(|e| format!("Error getting champion mastery score: {}", e))?;
|
||||
rassert!((969..=1000).contains(&s), "Unexpected ma5tery score: {}.", s);
|
||||
Ok(())
|
||||
},
|
||||
championmastery_getall_ma5tery: async {
|
||||
let sum = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "ma5tery");
|
||||
let sum = sum.await
|
||||
.map_err(|e| format!("Error getting summoner: {}", e))?
|
||||
.ok_or_else(|| "Failed to find summoner".to_owned())?;
|
||||
|
||||
let p = RIOT_API.champion_mastery_v4().get_all_champion_masteries_by_puuid(ROUTE, &sum.puuid);
|
||||
let s = p.await.map_err(|e| format!("Error getting all champion masteries: {}", e))?;
|
||||
rassert!(s.len() >= 142, "Expected masteries: {}.", s.len());
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// https://github.com/RiotGames/developer-relations/issues/602
|
||||
spectator_combo: async {
|
||||
let featured_p = RIOT_API.spectator_v4().get_featured_games(ROUTE);
|
||||
let featured = featured_p.await.map_err(|e| e.to_string())?;
|
||||
|
||||
rassert!(!featured.game_list.is_empty());
|
||||
|
||||
// let summoner_name = &featured.game_list[0].participants[0].summoner_name;
|
||||
// let summoner_p = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, summoner_name);
|
||||
// let summoner = summoner_p.await.map_err(|e| e.to_string())?.ok_or_else(|| "Failed to find summoner".to_owned())?;
|
||||
|
||||
let featured_game = &featured.game_list[0];
|
||||
let participant = &featured_game.participants[0];
|
||||
let summoner_id = participant.summoner_id.as_ref()
|
||||
.ok_or_else(|| format!("Summoner in spectator featured game missing summoner ID: {}", &participant.summoner_name))?;
|
||||
|
||||
let livegame_p = RIOT_API.spectator_v4().get_current_game_info_by_summoner(ROUTE, &summoner_id);
|
||||
let livegame_o = livegame_p.await.map_err(|e| e.to_string())?;
|
||||
if let Some(livegame) = livegame_o {
|
||||
let participant_match = livegame.participants.iter().find(|p| p.summoner_name == participant.summoner_name);
|
||||
rassert!(participant_match.is_some(), "Failed to find summoner in match: {}.", &participant.summoner_name);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use testutils::*;
|
||||
|
||||
const ROUTE: PlatformRoute = PlatformRoute::EUW1;
|
||||
|
||||
static TFT_MATCHES: &[&str] = &[
|
||||
"EUW1_6307427444", // https://github.com/MingweiSamuel/Riven/issues/50
|
||||
"EUW1_6307262798",
|
||||
// https://github.com/MingweiSamuel/Riven/pull/62
|
||||
// https://github.com/MingweiSamuel/riotapi-schema/pull/43
|
||||
"EUW1_6786745342",
|
||||
];
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
tftmatchv1_get_list: async {
|
||||
tft_match_v1_get(ROUTE.to_regional(), TFT_MATCHES).await
|
||||
},
|
||||
|
||||
// // Don't have acecess to tft-status-v1.
|
||||
// tftstatusv1_getplatformdata: async {
|
||||
// let p = RIOT_API.tft_status_v1().get_platform_data(ROUTE);
|
||||
// let _s = p.await.map_err(|e| e.to_string())?;
|
||||
// Ok(())
|
||||
// },
|
||||
tftleaguev1_gettopratedladder: async {
|
||||
let p = RIOT_API.tft_league_v1().get_top_rated_ladder(ROUTE, QueueType::RANKED_TFT_TURBO);
|
||||
let l = p.await.map_err(|e| e.to_string())?;
|
||||
rassert!(l.len() > 10, "Expected a few ranked players, got: {}.", l.len());
|
||||
Ok(())
|
||||
},
|
||||
tftmatchv1_getmatch: async {
|
||||
let p = RIOT_API.tft_match_v1().get_match(ROUTE.to_regional(), "EUW1_6455483163");
|
||||
let _m = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get TFT match.".to_owned())?;
|
||||
Ok(())
|
||||
},
|
||||
tftsummonerv1_getbyname: async {
|
||||
let p = RIOT_API.tft_summoner_v1().get_by_summoner_name(ROUTE, "相当猥琐");
|
||||
let _s = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get TFT summoner.".to_owned())?;
|
||||
Ok(())
|
||||
},
|
||||
tftsummonerv1_getbyname_none: async {
|
||||
let p = RIOT_API.tft_summoner_v1().get_by_summoner_name(ROUTE, "this summoner does not exist");
|
||||
rassert!(p.await.map_err(|e| e.to_string())?.is_none());
|
||||
Ok(())
|
||||
},
|
||||
// Get top rated player, get some of their matches.
|
||||
tft_combo: async {
|
||||
let top_players = RIOT_API.tft_league_v1().get_top_rated_ladder(ROUTE, QueueType::RANKED_TFT_TURBO);
|
||||
let top_players = top_players.await.map_err(|e| e.to_string())?;
|
||||
rassert!(!top_players.is_empty());
|
||||
let top_player_entry = &top_players[0];
|
||||
let top_player = RIOT_API.tft_summoner_v1().get_by_summoner_id(ROUTE, &top_player_entry.summoner_id);
|
||||
let top_player = top_player.await.map_err(|e| e.to_string())?;
|
||||
println!("Top player is {} with `puuid` {}.", top_player.name, top_player.puuid);
|
||||
let match_ids = RIOT_API.tft_match_v1().get_match_ids_by_puuid(
|
||||
ROUTE.to_regional(), &top_player.puuid, Some(10), None, None, None);
|
||||
let match_ids = match_ids.await.map_err(|e| e.to_string())?;
|
||||
tft_match_v1_get(ROUTE.to_regional(), &*match_ids).await?;
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,60 +1,60 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![feature(async_closure)]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use testutils::*;
|
||||
|
||||
use futures_util::future;
|
||||
use colored::*;
|
||||
|
||||
use riven::consts::*;
|
||||
use riven::models::summoner_v4::Summoner;
|
||||
|
||||
const REGION: Region = Region::KR;
|
||||
|
||||
|
||||
async_tests!{
|
||||
my_runner {
|
||||
league_summoner_bulk_test: async {
|
||||
|
||||
let leagues = (1..10)
|
||||
.map(async move |i| {
|
||||
let leaguelist = RIOT_API.league_v4().get_league_entries(REGION,
|
||||
QueueType::RANKED_SOLO_5x5, Tier::GOLD, Division::III, Some(i));
|
||||
let leaguelist = leaguelist.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.ok_or("Failed to get challenger league".to_owned())?;
|
||||
|
||||
println!("League list {}: {} items.", i, leaguelist.len());
|
||||
|
||||
let summoners = leaguelist
|
||||
.iter()
|
||||
.map(async move |leagueentry| {
|
||||
let summonerfuture = RIOT_API.summoner_v4().get_by_summoner_id(
|
||||
REGION, &leagueentry.summoner_id);
|
||||
summonerfuture.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.ok_or(format!("Failed to get summoner_id {}.",
|
||||
leagueentry.summoner_id))
|
||||
});
|
||||
future::join_all(summoners).await
|
||||
.into_iter()
|
||||
// I'm not sure where this result goes.
|
||||
.collect::<Result<Vec<Summoner>, String>>()
|
||||
});
|
||||
|
||||
let all_summoners = future::join_all(leagues).await
|
||||
.into_iter()
|
||||
.flat_map(|league| league)
|
||||
.flat_map(|summoner| summoner);
|
||||
|
||||
for (i, summoner) in all_summoners.enumerate() {
|
||||
println!("{}: {}", i + 1, summoner.name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![feature(async_closure)]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use testutils::*;
|
||||
|
||||
use futures_util::future;
|
||||
use colored::*;
|
||||
|
||||
use riven::consts::*;
|
||||
use riven::models::summoner_v4::Summoner;
|
||||
|
||||
const REGION: Region = Region::KR;
|
||||
|
||||
|
||||
async_tests!{
|
||||
my_runner {
|
||||
league_summoner_bulk_test: async {
|
||||
|
||||
let leagues = (1..10)
|
||||
.map(async move |i| {
|
||||
let leaguelist = RIOT_API.league_v4().get_league_entries(REGION,
|
||||
QueueType::RANKED_SOLO_5x5, Tier::GOLD, Division::III, Some(i));
|
||||
let leaguelist = leaguelist.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.ok_or("Failed to get challenger league".to_owned())?;
|
||||
|
||||
println!("League list {}: {} items.", i, leaguelist.len());
|
||||
|
||||
let summoners = leaguelist
|
||||
.iter()
|
||||
.map(async move |leagueentry| {
|
||||
let summonerfuture = RIOT_API.summoner_v4().get_by_summoner_id(
|
||||
REGION, &leagueentry.summoner_id);
|
||||
summonerfuture.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.ok_or(format!("Failed to find summoner_id {}.",
|
||||
leagueentry.summoner_id))
|
||||
});
|
||||
future::join_all(summoners).await
|
||||
.into_iter()
|
||||
// I'm not sure where this result goes.
|
||||
.collect::<Result<Vec<Summoner>, String>>()
|
||||
});
|
||||
|
||||
let all_summoners = future::join_all(leagues).await
|
||||
.into_iter()
|
||||
.flat_map(|league| league)
|
||||
.flat_map(|summoner| summoner);
|
||||
|
||||
for (i, summoner) in all_summoners.enumerate() {
|
||||
println!("{}: {}", i + 1, summoner.name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use testutils::RIOT_API;
|
||||
|
||||
const ROUTE: PlatformRoute = PlatformRoute::LA1;
|
||||
|
||||
/// en_US description: "As a laner, get kills before 10 minutes outside your lane (anyone but your lane opponent)"
|
||||
const CHALLENGE_ID__ARAM_1K_DPM: i64 = 101101;
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
// /lol/challenges/v1/challenges/{challengeId}/leaderboards/by-level/{level}
|
||||
// /lol/challenges/v1/player-data/{puuid}
|
||||
lol_challenges_v1_leaderboards_playerdata: async {
|
||||
let challenge_id = CHALLENGE_ID__ARAM_1K_DPM;
|
||||
let leaderboard = RIOT_API.lol_challenges_v1()
|
||||
.get_challenge_leaderboards(ROUTE, challenge_id, Tier::GRANDMASTER, None)
|
||||
.await.map_err(|e| e.to_string())?
|
||||
.ok_or_else(|| format!("Challenge leaderboard with id {} returned 404", challenge_id))?;
|
||||
|
||||
{
|
||||
rassert!(!leaderboard.is_empty());
|
||||
let start = leaderboard[0].position;
|
||||
// Commented out: leaderboard is not monotonic for some reason.
|
||||
// let mut val = leaderboard[0].value;
|
||||
for (n, entry) in leaderboard.iter().enumerate() {
|
||||
rassert_eq!(start + (n as i32), entry.position);
|
||||
// rassert!(entry.val <= val);
|
||||
// val = etnry.val;
|
||||
}
|
||||
}
|
||||
|
||||
// Spot check 10% for `player-data`.
|
||||
for entry in leaderboard.iter().step_by(10)
|
||||
{
|
||||
let _player_data = RIOT_API.lol_challenges_v1().get_player_data(ROUTE, &entry.puuid)
|
||||
.await.map_err(|e| format!("Failed to get player data PUUID {}: {}", entry.puuid, e))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// /lol/challenges/v1/challenges/config
|
||||
// /lol/challenges/v1/challenges/{challengeId}/config
|
||||
lol_challenges_v1_check_configs: async {
|
||||
let challenges = RIOT_API.lol_challenges_v1().get_all_challenge_configs(ROUTE)
|
||||
.await.map_err(|e| e.to_string())?;
|
||||
rassert!(!challenges.is_empty());
|
||||
|
||||
for challenge in challenges.iter() {
|
||||
rassert!(!challenge.localized_names.is_empty());
|
||||
rassert!(!challenge.thresholds.is_empty());
|
||||
}
|
||||
|
||||
// Spot-check 10% of the challenge IDs.
|
||||
for challenge in challenges.iter().step_by(10) {
|
||||
RIOT_API.lol_challenges_v1().get_challenge_configs(ROUTE, challenge.id)
|
||||
.await.map_err(|e| format!("Failed to get challenge config with id {}\n{}", challenge.id, e))?
|
||||
.ok_or_else(|| format!("Challenge config with id {} returned 404", challenge.id))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// /lol/challenges/v1/challenges/percentiles
|
||||
// /lol/challenges/v1/challenges/{challengeId}/percentiles
|
||||
lol_challenges_v1_check_percentiles: async {
|
||||
// Check all percentiles.
|
||||
let percentiles = RIOT_API.lol_challenges_v1().get_all_challenge_percentiles(ROUTE)
|
||||
.await.map_err(|e| e.to_string())?;
|
||||
rassert!(!percentiles.is_empty());
|
||||
|
||||
// Spot-check 10% of the challenge IDs.
|
||||
for &challenge_id in percentiles.keys().step_by(10) {
|
||||
RIOT_API.lol_challenges_v1().get_challenge_percentiles(ROUTE, challenge_id)
|
||||
.await.map_err(|e| format!("Failed to get challenge percentile with id {}\n{}", challenge_id, e))?
|
||||
.ok_or_else(|| format!("Challenge percentile with id {} returned 404", challenge_id))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use riven::models::summoner_v4::*;
|
||||
use testutils::*;
|
||||
|
||||
fn validate_summoners(s1: Summoner, s2: Summoner) -> Result<(), String> {
|
||||
rassert_eq!(s1.name, s2.name, "Names didn't match {}.", "");
|
||||
rassert_eq!(s1.id, s2.id, "SummonerId didn't match {}.", "");
|
||||
rassert_eq!(
|
||||
s1.account_id,
|
||||
s2.account_id,
|
||||
"AccountId didn't match {}.",
|
||||
""
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const ROUTE: PlatformRoute = PlatformRoute::NA1;
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
// Summoner tests.
|
||||
summoner_double: async {
|
||||
let l1p = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "lug nuts k");
|
||||
let l2p = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "lugnuts k");
|
||||
let l1 = l1p.await.map_err(|e| e.to_string())?.ok_or_else(|| "'lug nuts k' not found!".to_owned())?;
|
||||
let l2 = l2p.await.map_err(|e| e.to_string())?.ok_or_else(|| "'lugnuts k' not found!".to_owned())?;
|
||||
validate_summoners(l1, l2)?;
|
||||
Ok(())
|
||||
},
|
||||
champion_getrotation: async {
|
||||
let p = RIOT_API.champion_v3().get_champion_info(ROUTE);
|
||||
let d = p.await.map_err(|e| e.to_string())?;
|
||||
let new_len = d.free_champion_ids_for_new_players.len();
|
||||
let free_len = d.free_champion_ids.len();
|
||||
let level = d.max_new_player_level;
|
||||
rassert!(new_len >= 10, "New len: {}", new_len);
|
||||
rassert!(free_len >= 15, "Free len: {}", free_len);
|
||||
rassert_eq!(10, level, "New player level: {}", level);
|
||||
Ok(())
|
||||
},
|
||||
leagueexp_get: async {
|
||||
let p = RIOT_API.league_exp_v4().get_league_entries(ROUTE, QueueType::RANKED_SOLO_5x5, Tier::CHALLENGER, Division::I, None);
|
||||
let d = p.await.map_err(|e| e.to_string())?;
|
||||
if d.is_empty() {
|
||||
eprintln!("Off-season, challenger league is empty.");
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
champion_mastery_v4: async {
|
||||
let summoner = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "LugnutsK");
|
||||
let summoner = summoner.await.map_err(|e| e.to_string())?.ok_or_else(|| "'LugnutsK' not found!".to_owned())?;
|
||||
let masteries = RIOT_API.champion_mastery_v4().get_all_champion_masteries_by_puuid(ROUTE, &summoner.puuid);
|
||||
let masteries = masteries.await.map_err(|e| e.to_string())?;
|
||||
rassert!(74 <= masteries.len());
|
||||
Ok(())
|
||||
},
|
||||
|
||||
// TODO: MATCH-V4 REMOVED.
|
||||
// matchlist_get: async {
|
||||
// let sp = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "haha yes");
|
||||
// let s = sp.await.map_err(|e| e.to_string())?.ok_or("Failed to get \"haha yes\"".to_owned())?;
|
||||
// let mp = RIOT_API.match_v4().get_matchlist(ROUTE, &s.account_id, None, Some(2500), None, None, Some(2600), None, None);
|
||||
// let m = mp.await.map_err(|e| e.to_string())?.ok_or("Failed to get matchlist".to_owned())?;
|
||||
// rassert!(m.matches.len() > 0, "Matchlist should not be empty");
|
||||
// Ok(())
|
||||
// },
|
||||
// matchlist_get2: async {
|
||||
// let sp = RIOT_API.summoner_v4().get_by_summoner_name(ROUTE, "haha yes");
|
||||
// let s = sp.await.map_err(|e| e.to_string())?.ok_or("Failed to get \"haha yes\"".to_owned())?;
|
||||
// let mp = RIOT_API.match_v4().get_matchlist(ROUTE, &s.account_id, None, None, Some(&[ Champion::SION, Champion::SIVIR, Champion::CASSIOPEIA ]), None, None, None, None);
|
||||
// let m = mp.await.map_err(|e| e.to_string())?.ok_or("Failed to get matchlist".to_owned())?;
|
||||
// rassert!(m.matches.len() > 0, "Matchlist should not be empty");
|
||||
// Ok(())
|
||||
// },
|
||||
|
||||
// match_get: async {
|
||||
// let p = RIOT_API.match_v4().get_match(ROUTE, 3190191338);
|
||||
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Match not found.".to_owned())?;
|
||||
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||
// Ok(())
|
||||
// },
|
||||
// match_get_bots: async {
|
||||
// let p = RIOT_API.match_v4().get_match(ROUTE, 3251803350);
|
||||
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Match not found.".to_owned())?;
|
||||
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||
// Ok(())
|
||||
// },
|
||||
// match_get_odyssey: async {
|
||||
// let p = RIOT_API.match_v4().get_match(ROUTE, 2881976826);
|
||||
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Match not found.".to_owned())?;
|
||||
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||
// Ok(())
|
||||
// },
|
||||
// match_get_aram: async {
|
||||
// let p = RIOT_API.match_v4().get_match(ROUTE, 2961635718);
|
||||
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get match.".to_owned())?;
|
||||
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||
// Ok(())
|
||||
// },
|
||||
// match_get_aram2: async {
|
||||
// let p = RIOT_API.match_v4().get_match(ROUTE, 3596184782);
|
||||
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Match not found.".to_owned())?;
|
||||
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||
// Ok(())
|
||||
// },
|
||||
// match_get_urf900: async {
|
||||
// let p = RIOT_API.match_v4().get_match(ROUTE, 2963663381);
|
||||
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get match.".to_owned())?;
|
||||
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||
// Ok(())
|
||||
// },
|
||||
// match_get_tutorial1: async {
|
||||
// let p = RIOT_API.match_v4().get_match(ROUTE, 3432145099);
|
||||
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get match.".to_owned())?;
|
||||
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||
// Ok(())
|
||||
// },
|
||||
// match_get_tutorial2: async {
|
||||
// let p = RIOT_API.match_v4().get_match(ROUTE, 3432116214);
|
||||
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get match.".to_owned())?;
|
||||
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||
// Ok(())
|
||||
// },
|
||||
// match_get_tutorial3: async {
|
||||
// let p = RIOT_API.match_v4().get_match(ROUTE, 3432156790);
|
||||
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Failed to get match.".to_owned())?;
|
||||
// rassert!(!m.participants.is_empty(), "Match should have participants.");
|
||||
// Ok(())
|
||||
// },
|
||||
|
||||
// match_gettimeline: async {
|
||||
// let p = RIOT_API.match_v4().get_match_timeline(ROUTE, 3190191338);
|
||||
// let m = p.await.map_err(|e| e.to_string())?.ok_or("Match timeline not found.".to_owned())?;
|
||||
// rassert!(!m.frames.is_empty(), "Match timeline should have frames.");
|
||||
// Ok(())
|
||||
// },
|
||||
|
||||
// Commented out, requires special API key.
|
||||
// // LOR
|
||||
// lor_ranked_get_leaderboards: async {
|
||||
// let future = RIOT_API.lor_ranked_v1().get_leaderboards(Region::AMERICAS);
|
||||
// let _leaderboard = future.await.map_err(|e| e.to_string())?;
|
||||
// Ok(())
|
||||
// },
|
||||
// CLASH
|
||||
clash_get_tournaments: async {
|
||||
let p = RIOT_API.clash_v1().get_tournaments(ROUTE);
|
||||
let tours = p.await.map_err(|e| e.to_string())?;
|
||||
if let Some(tour0) = tours.first() {
|
||||
let p = RIOT_API.clash_v1().get_tournament_by_id(ROUTE, tour0.id);
|
||||
let tour1 = p.await.map_err(|e| e.to_string())?;
|
||||
assert_eq!(Some(tour0.id), tour1.map(|t| t.id));
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
clash_get_team_by_id: async {
|
||||
let p = RIOT_API.clash_v1().get_team_by_id(ROUTE, "00000000-0000-0000-0000-000000000000");
|
||||
let team = p.await.map_err(|e| e.to_string())?;
|
||||
assert!(team.is_none());
|
||||
Ok(())
|
||||
},
|
||||
|
||||
status: async {
|
||||
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||
let status = p.await.map_err(|e| e.to_string())?;
|
||||
println!("{:?}", status);
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use testutils::*;
|
||||
|
||||
const ROUTE: PlatformRoute = PlatformRoute::PH2;
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
status: async {
|
||||
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||
let status = p.await.map_err(|e| e.to_string())?;
|
||||
println!("{:?}", status);
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use testutils::*;
|
||||
|
||||
const ROUTE: PlatformRoute = PlatformRoute::SG2;
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
status: async {
|
||||
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||
let status = p.await.map_err(|e| e.to_string())?;
|
||||
println!("{:?}", status);
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use testutils::*;
|
||||
|
||||
const ROUTE: PlatformRoute = PlatformRoute::TH2;
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
status: async {
|
||||
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||
let status = p.await.map_err(|e| e.to_string())?;
|
||||
println!("{:?}", status);
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,40 +1,37 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use testutils::RIOT_API;
|
||||
|
||||
use colored::*;
|
||||
|
||||
use riven::consts::*;
|
||||
use riven::models::summoner_v4::Summoner;
|
||||
|
||||
const REGION: Region = Region::TR;
|
||||
|
||||
|
||||
async_tests!{
|
||||
my_runner {
|
||||
league_summoner_bulk_test: async {
|
||||
let p = RIOT_API.league_v4().get_challenger_league(REGION, QueueType::RANKED_SOLO_5x5);
|
||||
// let p = future_start(p);
|
||||
let ll = p.await.map_err(|e| e.to_string())?;
|
||||
|
||||
println!("{:?} Challenger {} entries.", REGION, ll.entries.len());
|
||||
|
||||
let sl = ll.entries[..50].iter()
|
||||
.map(|entry| RIOT_API.summoner_v4().get_by_summoner_id(REGION, &entry.summoner_id))
|
||||
.map(tokio::spawn)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (i, s) in sl.into_iter().enumerate() {
|
||||
let summoner: Summoner = s.await
|
||||
.expect("tokio::spawn join error")
|
||||
.map_err(|e| e.to_string())?;
|
||||
println!("{}: {}", i + 1, summoner.name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use riven::models::summoner_v4::Summoner;
|
||||
use testutils::RIOT_API;
|
||||
|
||||
const ROUTE: PlatformRoute = PlatformRoute::TR1;
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
league_summoner_bulk_test: async {
|
||||
let p = RIOT_API.league_v4().get_challenger_league(ROUTE, QueueType::RANKED_SOLO_5x5);
|
||||
// let p = future_start(p);
|
||||
let ll = p.await.map_err(|e| e.to_string())?;
|
||||
|
||||
println!("{:?} Challenger {} entries.", ROUTE, ll.entries.len());
|
||||
|
||||
let sl = ll.entries.iter().take(50)
|
||||
.map(|entry| RIOT_API.summoner_v4().get_by_summoner_id(ROUTE, &entry.summoner_id))
|
||||
.map(tokio::spawn)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (i, s) in sl.into_iter().enumerate() {
|
||||
let summoner: Summoner = s.await
|
||||
.expect("tokio::spawn join error")
|
||||
.map_err(|e| e.to_string())?;
|
||||
println!("{}: {}", i + 1, summoner.name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use testutils::*;
|
||||
|
||||
use colored::*;
|
||||
|
||||
use riven::consts::*;
|
||||
|
||||
const ROUTE: PlatformRoute = PlatformRoute::TW2;
|
||||
|
||||
async_tests!{
|
||||
my_runner {
|
||||
// TODO: for some reason status is not available on TW2...
|
||||
// https://developer.riotgames.com/apis#lol-status-v4/GET_getPlatformData
|
||||
status: async {
|
||||
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||
let status = p.await.map_err(|e| e.to_string())?;
|
||||
println!("{:?}", status);
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use testutils::RIOT_API;
|
||||
|
||||
const ROUTE: ValPlatformRoute = ValPlatformRoute::LATAM;
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
val_content_ranked_test: async {
|
||||
let p = RIOT_API.val_content_v1().get_content(ROUTE, Some("zh-CN"));
|
||||
let contents = p.await.map_err(|e| format!("Failed to get content: {}", e))?;
|
||||
|
||||
// Find the LAST active act, via `.rev().find(...)`.
|
||||
// Added filter when parent id is 0000... as there are multiple that are active, the last active seems to be episode 5
|
||||
// Not sure if this a bandaid fix
|
||||
let act = contents.acts.iter().rev().find(|act| act.is_active && act.parent_id != Some("00000000-0000-0000-0000-000000000000".to_string()))
|
||||
.ok_or(format!("No active acts of {} found.", contents.acts.len()))?;
|
||||
|
||||
let p = RIOT_API.val_ranked_v1().get_leaderboard(ROUTE, &act.id, None, None);
|
||||
let leaderboard = p.await.map_err(|e| e.to_string())?
|
||||
.ok_or(format!("Failed to get act leaderboard {} {}.", act.id, act.name))?;
|
||||
|
||||
rassert_eq!(act.id, leaderboard.act_id);
|
||||
|
||||
for (i, p) in leaderboard.players.iter().take(10).enumerate() {
|
||||
rassert_eq!(i + 1, p.leaderboard_rank as usize);
|
||||
println!("{:>2}: {:>4} {:<22} ({} wins)",
|
||||
p.leaderboard_rank,
|
||||
p.ranked_rating,
|
||||
format!("{}#{}",
|
||||
p.game_name.as_deref().unwrap_or("<NONE>"),
|
||||
p.tag_line.as_deref().unwrap_or("<NONE>")),
|
||||
p.number_of_wins);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
|
||||
#![cfg_attr(feature = "nightly", test_runner(my_runner))]
|
||||
|
||||
mod async_tests;
|
||||
mod testutils;
|
||||
use colored::*;
|
||||
use riven::consts::*;
|
||||
use testutils::*;
|
||||
|
||||
const ROUTE: PlatformRoute = PlatformRoute::VN2;
|
||||
|
||||
async_tests! {
|
||||
my_runner {
|
||||
status: async {
|
||||
let p = RIOT_API.lol_status_v4().get_platform_data(ROUTE);
|
||||
let status = p.await.map_err(|e| e.to_string())?;
|
||||
println!("{:?}", status);
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use std::future::Future;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use riven::consts::{PlatformRoute, QueueType, RegionalRoute};
|
||||
use riven::{RiotApi, RiotApiConfig};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref RIOT_API: RiotApi = {
|
||||
let api_key = std::env::var("RGAPI_KEY")
|
||||
.ok()
|
||||
.or_else(|| std::fs::read_to_string("apikey.txt").ok())
|
||||
.expect("Failed to find RGAPI_KEY env var or apikey.txt.");
|
||||
RiotApi::new(RiotApiConfig::with_key(api_key.trim()).preconfig_burst())
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn league_v4_match_v5_latest_combo(route: PlatformRoute) -> Result<(), String> {
|
||||
const NUM_MATCHES: usize = 10;
|
||||
|
||||
let challenger_future = RIOT_API
|
||||
.league_v4()
|
||||
.get_challenger_league(route, QueueType::RANKED_SOLO_5x5);
|
||||
let challenger_league = challenger_future
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get challenger league: {}", e))?;
|
||||
|
||||
let Some(queue) = challenger_league.queue else {
|
||||
assert!(challenger_league.entries.is_empty());
|
||||
eprintln!("Off-season, challenger league is empty.");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if QueueType::RANKED_SOLO_5x5 != queue {
|
||||
return Err(format!("Unexpected `queue`: {:?}", queue));
|
||||
}
|
||||
if challenger_league.entries.is_empty() {
|
||||
return Err("Challenger league is unexpectedly empty!".to_owned());
|
||||
}
|
||||
|
||||
let match_ids_futures = challenger_league
|
||||
.entries
|
||||
.iter()
|
||||
.take(5)
|
||||
.map(|entry| async move {
|
||||
let summoner_future = RIOT_API
|
||||
.summoner_v4()
|
||||
.get_by_summoner_id(route, &entry.summoner_id);
|
||||
let summoner_info = summoner_future
|
||||
.await
|
||||
.map_err(|e| format!("Failed to find summoner info: {}", e))?;
|
||||
|
||||
let match_ids_future = RIOT_API.match_v5().get_match_ids_by_puuid(
|
||||
route.to_regional(),
|
||||
&summoner_info.puuid,
|
||||
Some(5),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let match_ids = match_ids_future
|
||||
.await
|
||||
.map_err(|e| format!("Failed to find summoner match IDs: {}", e))?;
|
||||
Ok(match_ids) as Result<_, String>
|
||||
});
|
||||
|
||||
let match_ids = futures::future::try_join_all(match_ids_futures).await?;
|
||||
|
||||
let mut match_ids: Vec<String> = match_ids.into_iter().flatten().collect();
|
||||
match_ids.sort_unstable_by(|a, b| a.cmp(b).reverse()); // Sort descending, so latest are first.
|
||||
|
||||
let _ = tokio::try_join!(
|
||||
match_v5_get(route.to_regional(), match_ids.iter().take(NUM_MATCHES)),
|
||||
match_v5_get_timeline(route.to_regional(), match_ids.iter().take(NUM_MATCHES)),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn tft_match_v1_get(
|
||||
route: RegionalRoute,
|
||||
matches: impl IntoIterator<Item = impl AsRef<str>>,
|
||||
) -> Result<(), String> {
|
||||
let futures = matches.into_iter().map(|matche| async move {
|
||||
let matche = matche.as_ref();
|
||||
let p = RIOT_API.tft_match_v1().get_match(route, matche);
|
||||
let m = p
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get match {}: {:?}", matche, e))?
|
||||
.ok_or(format!("Match {} not found.", matche))?;
|
||||
|
||||
if matche != &*m.metadata.match_id {
|
||||
return Err(format!(
|
||||
"Bad match id? Sent {}, received {}.",
|
||||
matche, m.metadata.match_id
|
||||
));
|
||||
}
|
||||
if m.metadata.participants.is_empty() {
|
||||
return Err(format!(
|
||||
"Match {} should have participants (metadata).",
|
||||
matche
|
||||
));
|
||||
}
|
||||
if m.metadata.participants.len() != m.info.participants.len() {
|
||||
return Err(format!(
|
||||
"Match {} participants do not line up with participant UUIDs.",
|
||||
matche
|
||||
));
|
||||
}
|
||||
if m.info.participants.is_empty() {
|
||||
return Err(format!("Match {} should have participants (info).", matche));
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
futures::future::try_join_all(futures).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn match_v5_get(
|
||||
route: RegionalRoute,
|
||||
matches: impl IntoIterator<Item = impl AsRef<str>>,
|
||||
) -> Result<(), String> {
|
||||
let futures = matches.into_iter().map(|matche| async move {
|
||||
let matche = matche.as_ref();
|
||||
let p = RIOT_API.match_v5().get_match(route, matche);
|
||||
let m = p
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get match {}: {:?}", matche, e))?
|
||||
.ok_or(format!("Match {} not found.", matche))?;
|
||||
|
||||
if matche != &*m.metadata.match_id {
|
||||
return Err(format!(
|
||||
"Bad match id? Sent {}, received {}.",
|
||||
matche, m.metadata.match_id
|
||||
));
|
||||
}
|
||||
if m.metadata.participants.is_empty() {
|
||||
return Err(format!("Match {} should have participants.", matche));
|
||||
}
|
||||
if m.metadata.participants.len() != m.info.participants.len() {
|
||||
// Sometimes only returns match IDs for one team? JP1_391732436
|
||||
// Do not return error.
|
||||
eprintln!(
|
||||
"Match {} participants do not line up with participant UUIDs.",
|
||||
matche
|
||||
);
|
||||
}
|
||||
for participant in &m.info.participants {
|
||||
participant
|
||||
.champion()
|
||||
.map_err(|e| format!("Failed to determine match {} champion: {}", matche, e))?;
|
||||
}
|
||||
if m.info.teams.is_empty() {
|
||||
return Err(format!("Match {} should have teams.", matche));
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
join_all_future_errs(futures).await
|
||||
}
|
||||
|
||||
pub async fn match_v5_get_timeline(
|
||||
route: RegionalRoute,
|
||||
matches: impl IntoIterator<Item = impl AsRef<str>>,
|
||||
) -> Result<(), String> {
|
||||
let futures = matches.into_iter().map(|matche| async move {
|
||||
let matche = matche.as_ref();
|
||||
let p = RIOT_API.match_v5().get_timeline(route, matche);
|
||||
let m = p
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get match {}: {:?}", matche, e))?
|
||||
.ok_or(format!("Match {} not found.", matche))?;
|
||||
if matche != &*m.metadata.match_id {
|
||||
return Err(format!(
|
||||
"Bad match id? Sent {}, received {}.",
|
||||
matche, m.metadata.match_id
|
||||
));
|
||||
}
|
||||
if m.metadata.participants.is_empty() {
|
||||
return Err(format!("Match {} should have participants.", matche));
|
||||
}
|
||||
if let Some(game_id) = m.info.game_id {
|
||||
if matche[(matche.find('_').unwrap() + 1)..] != game_id.to_string() {
|
||||
return Err(format!("Match {} number ID should match.", matche));
|
||||
}
|
||||
}
|
||||
if m.info.frames.is_empty() {
|
||||
return Err(format!("Match {} timleine should have frames.", matche));
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
join_all_future_errs(futures).await
|
||||
}
|
||||
|
||||
/// Joins all futures and keeps ALL error messages, separated by newlines.
|
||||
async fn join_all_future_errs<T>(
|
||||
result_tasks: impl Iterator<Item = impl Future<Output = Result<T, String>>>,
|
||||
) -> Result<(), String> {
|
||||
futures::future::join_all(result_tasks)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(Result::err)
|
||||
.reduce(|a, b| a + "\n" + &b)
|
||||
.map(Err)
|
||||
.unwrap_or(Ok(()))
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
|
@ -0,0 +1,10 @@
|
|||
format_code_in_doc_comments = true
|
||||
format_macro_matchers = true
|
||||
group_imports = "StdExternalCrate"
|
||||
hex_literal_case = "Lower"
|
||||
imports_granularity = "Module"
|
||||
newline_style = "Unix"
|
||||
normalize_comments = true
|
||||
normalize_doc_attributes = true
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
167
src/config.rs
167
src/config.rs
|
@ -1,167 +0,0 @@
|
|||
//! Configuration of RiotApi.
|
||||
use std::time::Duration;
|
||||
|
||||
use reqwest::ClientBuilder;
|
||||
|
||||
/// Configuration for instantiating RiotApi.
|
||||
///
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct RiotApiConfig {
|
||||
pub(crate) api_key: String,
|
||||
pub(crate) retries: u8,
|
||||
pub(crate) burst_pct: f32,
|
||||
pub(crate) duration_overhead: Duration,
|
||||
pub(crate) client_builder: Option<ClientBuilder>,
|
||||
}
|
||||
|
||||
impl RiotApiConfig {
|
||||
/// `0.99`
|
||||
///
|
||||
/// `burst_pct` used by `preconfig_burst` (and default `with_key`).
|
||||
pub const PRECONFIG_BURST_BURST_PCT: f32 = 0.99;
|
||||
/// `989` ms
|
||||
///
|
||||
/// `duration_overhead` used by `preconfig_burst` (and default `with_key`).
|
||||
pub const PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS: u64 = 989;
|
||||
|
||||
/// `0.47`
|
||||
///
|
||||
/// `burst_pct` used by `preconfig_throughput`.
|
||||
pub const PRECONFIG_THROUGHPUT_BURST_PCT: f32 = 0.47;
|
||||
/// `10` ms.
|
||||
///
|
||||
/// `duration_overhead` used by `preconfig_throughput`.
|
||||
pub const PRECONFIG_THROUGHPUT_DURATION_OVERHEAD_MILLIS: u64 = 10;
|
||||
|
||||
/// Creates a new `RiotApiConfig` with the given `api_key` with the following
|
||||
/// configuration:
|
||||
///
|
||||
/// * `retries = 3`.
|
||||
/// * `purst_pct = 0.99` (`preconfig_burst`).
|
||||
/// * `duration_overhead = 989 ms` (`preconfig_burst`).
|
||||
///
|
||||
/// `api_key` should be a Riot Games API key from
|
||||
/// [https://developer.riotgames.com/](https://developer.riotgames.com/),
|
||||
/// and should look like `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`.
|
||||
pub fn with_key<T: Into<String>>(api_key: T) -> Self {
|
||||
Self {
|
||||
api_key: api_key.into(),
|
||||
retries: 3,
|
||||
burst_pct: Self::PRECONFIG_BURST_BURST_PCT,
|
||||
duration_overhead: Duration::from_millis(Self::PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS),
|
||||
client_builder: Some(ClientBuilder::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets rate limiting settings to preconfigured values optimized for burst,
|
||||
/// low latency:
|
||||
///
|
||||
/// * `burst_pct = 0.99` (`PRECONFIG_BURST_BURST_PCT`).
|
||||
/// * `duration_overhead = 989 ms` (`PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS`).
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn preconfig_burst(mut self) -> Self {
|
||||
self.burst_pct = Self::PRECONFIG_BURST_BURST_PCT;
|
||||
self.duration_overhead = Duration::from_millis(Self::PRECONFIG_BURST_DURATION_OVERHEAD_MILLIS);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the rate limiting settings to preconfigured values optimized for
|
||||
/// high throughput:
|
||||
///
|
||||
/// * `burst_pct = 0.47` (`PRECONFIG_THROUGHPUT_BURST_PCT`).
|
||||
/// * `duration_overhead = 10 ms` (`PRECONFIG_THROUGHPUT_DURATION_OVERHEAD_MILLIS`).
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn preconfig_throughput(mut self) -> Self {
|
||||
self.burst_pct = Self::PRECONFIG_THROUGHPUT_BURST_PCT;
|
||||
self.duration_overhead = Duration::from_millis(Self::PRECONFIG_THROUGHPUT_DURATION_OVERHEAD_MILLIS);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set number of times to retry requests. Naturally, only retryable requests
|
||||
/// will be retried: responses with status codes 5xx or 429 (after waiting
|
||||
/// for retry-after headers). A value of `0` means one request will be sent
|
||||
/// and it will not be retried if it fails.
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn set_retries(mut self, retries: u8) -> Self {
|
||||
self.retries = retries;
|
||||
self
|
||||
}
|
||||
|
||||
/// Burst percentage controls how many burst requests are allowed and
|
||||
/// therefore how requests are spread out. Higher equals more burst,
|
||||
/// less spread. Lower equals less burst, more spread.
|
||||
///
|
||||
/// The value must be in the range (0, 1];
|
||||
/// Between 0, exclusive, and 1, inclusive. However values should generally
|
||||
/// be larger than 0.25.
|
||||
///
|
||||
/// Burst percentage behaves as follows:<br>
|
||||
/// A burst percentage of x% means, for each token bucket, "x% of the
|
||||
/// tokens can be used in x% of the bucket duration." So, for example, if x
|
||||
/// is 90%, a bucket would allow 90% of the requests to be made without
|
||||
/// any delay. Then, after waiting 90% of the bucket's duration, the
|
||||
/// remaining 10% of requests could be made.
|
||||
///
|
||||
/// A burst percentage of 100% results in no request spreading, which would
|
||||
/// allow for the largest bursts and lowest latency, but could result in
|
||||
/// 429s as bucket boundaries occur.
|
||||
///
|
||||
/// A burst percentage of near 0% results in high spreading causing
|
||||
/// temporally equidistant requests. This prevents 429s but has the highest
|
||||
/// latency. Additionally, if the number of tokens is high, this may lower
|
||||
/// the overall throughput due to the rate at which requests can be
|
||||
/// scheduled.
|
||||
///
|
||||
/// Therefore, for interactive applications like summoner & match history
|
||||
/// lookup, a higher percentage may be better. For data-collection apps
|
||||
/// like champion winrate aggregation, a medium-low percentage may be
|
||||
/// better.
|
||||
///
|
||||
/// # Panics
|
||||
/// If `burst_pct` is not in range (0, 1].
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn set_burst_pct(mut self, burst_pct: f32) -> Self {
|
||||
// Use inverted check to handle NaN.
|
||||
if 0.0 < burst_pct && burst_pct < 1.0 {
|
||||
self.burst_pct = burst_pct;
|
||||
return self;
|
||||
}
|
||||
panic!("burst_pct \"{}\" not in range (0, 1].", burst_pct);
|
||||
}
|
||||
|
||||
/// Sets the additional bucket duration to consider when rate limiting.
|
||||
/// Increasing this value will decrease the chances of 429s, but will lower
|
||||
/// the overall throughput.
|
||||
///
|
||||
/// In a sense, the `duration_overhead` is how much to "widen" the temporal
|
||||
/// width of buckets.
|
||||
///
|
||||
/// Given a particular Riot Game API rate limit bucket that allows N requests
|
||||
/// per D duration, when counting requests this library will consider requests
|
||||
/// sent in the past `D + duration_overhead` duration.
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn set_duration_overhead(mut self, duration_overhead: Duration) -> Self {
|
||||
self.duration_overhead = duration_overhead;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the reqwest `ClientBuilder`.
|
||||
///
|
||||
/// # Returns
|
||||
/// `self`, for chaining.
|
||||
pub fn set_client_builder(mut self, client_builder: ClientBuilder) -> Self {
|
||||
self.client_builder = Some(client_builder);
|
||||
self
|
||||
}
|
||||
}
|
|
@ -1,505 +0,0 @@
|
|||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
||||
use strum_macros::{ EnumString, EnumIter, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// League of Legend's champions.
|
||||
///
|
||||
/// The documentation of each variant specifies:<br>
|
||||
/// NAME (`IDENTIFIER`, ID).
|
||||
///
|
||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator).
|
||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[derive(Serialize_repr, Deserialize_repr)]
|
||||
#[derive(EnumString, EnumIter, Display, AsRefStr, IntoStaticStr)]
|
||||
#[repr(i16)]
|
||||
pub enum Champion {
|
||||
/// A champion that doesn't exist. Used in TeamBans when no champion was banned.
|
||||
///
|
||||
/// None (`NONE`, -1).
|
||||
None = -1,
|
||||
|
||||
/// Aatrox (`Aatrox`, 266).
|
||||
#[strum(to_string="Aatrox", serialize="Aatrox")] Aatrox = 266,
|
||||
/// Ahri (`Ahri`, 103).
|
||||
#[strum(to_string="Ahri", serialize="Ahri")] Ahri = 103,
|
||||
/// Akali (`Akali`, 84).
|
||||
#[strum(to_string="Akali", serialize="Akali")] Akali = 84,
|
||||
/// Alistar (`Alistar`, 12).
|
||||
#[strum(to_string="Alistar", serialize="Alistar")] Alistar = 12,
|
||||
/// Amumu (`Amumu`, 32).
|
||||
#[strum(to_string="Amumu", serialize="Amumu")] Amumu = 32,
|
||||
/// Anivia (`Anivia`, 34).
|
||||
#[strum(to_string="Anivia", serialize="Anivia")] Anivia = 34,
|
||||
/// Annie (`Annie`, 1).
|
||||
#[strum(to_string="Annie", serialize="Annie")] Annie = 1,
|
||||
/// Aphelios (`Aphelios`, 523).
|
||||
#[strum(to_string="Aphelios", serialize="Aphelios")] Aphelios = 523,
|
||||
/// Ashe (`Ashe`, 22).
|
||||
#[strum(to_string="Ashe", serialize="Ashe")] Ashe = 22,
|
||||
/// Aurelion Sol (`AurelionSol`, 136).
|
||||
#[strum(to_string="Aurelion Sol", serialize="AurelionSol")] AurelionSol = 136,
|
||||
/// Azir (`Azir`, 268).
|
||||
#[strum(to_string="Azir", serialize="Azir")] Azir = 268,
|
||||
/// Bard (`Bard`, 432).
|
||||
#[strum(to_string="Bard", serialize="Bard")] Bard = 432,
|
||||
/// Blitzcrank (`Blitzcrank`, 53).
|
||||
#[strum(to_string="Blitzcrank", serialize="Blitzcrank")] Blitzcrank = 53,
|
||||
/// Brand (`Brand`, 63).
|
||||
#[strum(to_string="Brand", serialize="Brand")] Brand = 63,
|
||||
/// Braum (`Braum`, 201).
|
||||
#[strum(to_string="Braum", serialize="Braum")] Braum = 201,
|
||||
/// Caitlyn (`Caitlyn`, 51).
|
||||
#[strum(to_string="Caitlyn", serialize="Caitlyn")] Caitlyn = 51,
|
||||
/// Camille (`Camille`, 164).
|
||||
#[strum(to_string="Camille", serialize="Camille")] Camille = 164,
|
||||
/// Cassiopeia (`Cassiopeia`, 69).
|
||||
#[strum(to_string="Cassiopeia", serialize="Cassiopeia")] Cassiopeia = 69,
|
||||
/// Cho'Gath (`Chogath`, 31).
|
||||
#[strum(to_string="Cho'Gath", serialize="Chogath")] ChoGath = 31,
|
||||
/// Corki (`Corki`, 42).
|
||||
#[strum(to_string="Corki", serialize="Corki")] Corki = 42,
|
||||
/// Darius (`Darius`, 122).
|
||||
#[strum(to_string="Darius", serialize="Darius")] Darius = 122,
|
||||
/// Diana (`Diana`, 131).
|
||||
#[strum(to_string="Diana", serialize="Diana")] Diana = 131,
|
||||
/// Dr. Mundo (`DrMundo`, 36).
|
||||
#[strum(to_string="Dr. Mundo", serialize="DrMundo")] DrMundo = 36,
|
||||
/// Draven (`Draven`, 119).
|
||||
#[strum(to_string="Draven", serialize="Draven")] Draven = 119,
|
||||
/// Ekko (`Ekko`, 245).
|
||||
#[strum(to_string="Ekko", serialize="Ekko")] Ekko = 245,
|
||||
/// Elise (`Elise`, 60).
|
||||
#[strum(to_string="Elise", serialize="Elise")] Elise = 60,
|
||||
/// Evelynn (`Evelynn`, 28).
|
||||
#[strum(to_string="Evelynn", serialize="Evelynn")] Evelynn = 28,
|
||||
/// Ezreal (`Ezreal`, 81).
|
||||
#[strum(to_string="Ezreal", serialize="Ezreal")] Ezreal = 81,
|
||||
/// Fiddlesticks (`FiddleSticks`, 9).
|
||||
#[strum(to_string="Fiddlesticks", serialize="FiddleSticks")] Fiddlesticks = 9,
|
||||
/// Fiora (`Fiora`, 114).
|
||||
#[strum(to_string="Fiora", serialize="Fiora")] Fiora = 114,
|
||||
/// Fizz (`Fizz`, 105).
|
||||
#[strum(to_string="Fizz", serialize="Fizz")] Fizz = 105,
|
||||
/// Galio (`Galio`, 3).
|
||||
#[strum(to_string="Galio", serialize="Galio")] Galio = 3,
|
||||
/// Gangplank (`Gangplank`, 41).
|
||||
#[strum(to_string="Gangplank", serialize="Gangplank")] Gangplank = 41,
|
||||
/// Garen (`Garen`, 86).
|
||||
#[strum(to_string="Garen", serialize="Garen")] Garen = 86,
|
||||
/// Gnar (`Gnar`, 150).
|
||||
#[strum(to_string="Gnar", serialize="Gnar")] Gnar = 150,
|
||||
/// Gragas (`Gragas`, 79).
|
||||
#[strum(to_string="Gragas", serialize="Gragas")] Gragas = 79,
|
||||
/// Graves (`Graves`, 104).
|
||||
#[strum(to_string="Graves", serialize="Graves")] Graves = 104,
|
||||
/// Hecarim (`Hecarim`, 120).
|
||||
#[strum(to_string="Hecarim", serialize="Hecarim")] Hecarim = 120,
|
||||
/// Heimerdinger (`Heimerdinger`, 74).
|
||||
#[strum(to_string="Heimerdinger", serialize="Heimerdinger")] Heimerdinger = 74,
|
||||
/// Illaoi (`Illaoi`, 420).
|
||||
#[strum(to_string="Illaoi", serialize="Illaoi")] Illaoi = 420,
|
||||
/// Irelia (`Irelia`, 39).
|
||||
#[strum(to_string="Irelia", serialize="Irelia")] Irelia = 39,
|
||||
/// Ivern (`Ivern`, 427).
|
||||
#[strum(to_string="Ivern", serialize="Ivern")] Ivern = 427,
|
||||
/// Janna (`Janna`, 40).
|
||||
#[strum(to_string="Janna", serialize="Janna")] Janna = 40,
|
||||
/// Jarvan IV (`JarvanIV`, 59).
|
||||
#[strum(to_string="Jarvan IV", serialize="JarvanIV")] JarvanIV = 59,
|
||||
/// Jax (`Jax`, 24).
|
||||
#[strum(to_string="Jax", serialize="Jax")] Jax = 24,
|
||||
/// Jayce (`Jayce`, 126).
|
||||
#[strum(to_string="Jayce", serialize="Jayce")] Jayce = 126,
|
||||
/// Jhin (`Jhin`, 202).
|
||||
#[strum(to_string="Jhin", serialize="Jhin")] Jhin = 202,
|
||||
/// Jinx (`Jinx`, 222).
|
||||
#[strum(to_string="Jinx", serialize="Jinx")] Jinx = 222,
|
||||
/// Kai'Sa (`Kaisa`, 145).
|
||||
#[strum(to_string="Kai'Sa", serialize="Kaisa")] KaiSa = 145,
|
||||
/// Kalista (`Kalista`, 429).
|
||||
#[strum(to_string="Kalista", serialize="Kalista")] Kalista = 429,
|
||||
/// Karma (`Karma`, 43).
|
||||
#[strum(to_string="Karma", serialize="Karma")] Karma = 43,
|
||||
/// Karthus (`Karthus`, 30).
|
||||
#[strum(to_string="Karthus", serialize="Karthus")] Karthus = 30,
|
||||
/// Kassadin (`Kassadin`, 38).
|
||||
#[strum(to_string="Kassadin", serialize="Kassadin")] Kassadin = 38,
|
||||
/// Katarina (`Katarina`, 55).
|
||||
#[strum(to_string="Katarina", serialize="Katarina")] Katarina = 55,
|
||||
/// Kayle (`Kayle`, 10).
|
||||
#[strum(to_string="Kayle", serialize="Kayle")] Kayle = 10,
|
||||
/// Kayn (`Kayn`, 141).
|
||||
#[strum(to_string="Kayn", serialize="Kayn")] Kayn = 141,
|
||||
/// Kennen (`Kennen`, 85).
|
||||
#[strum(to_string="Kennen", serialize="Kennen")] Kennen = 85,
|
||||
/// Kha'Zix (`Khazix`, 121).
|
||||
#[strum(to_string="Kha'Zix", serialize="Khazix")] KhaZix = 121,
|
||||
/// Kindred (`Kindred`, 203).
|
||||
#[strum(to_string="Kindred", serialize="Kindred")] Kindred = 203,
|
||||
/// Kled (`Kled`, 240).
|
||||
#[strum(to_string="Kled", serialize="Kled")] Kled = 240,
|
||||
/// Kog'Maw (`KogMaw`, 96).
|
||||
#[strum(to_string="Kog'Maw", serialize="KogMaw")] KogMaw = 96,
|
||||
/// LeBlanc (`Leblanc`, 7).
|
||||
#[strum(to_string="LeBlanc", serialize="Leblanc")] LeBlanc = 7,
|
||||
/// Lee Sin (`LeeSin`, 64).
|
||||
#[strum(to_string="Lee Sin", serialize="LeeSin")] LeeSin = 64,
|
||||
/// Leona (`Leona`, 89).
|
||||
#[strum(to_string="Leona", serialize="Leona")] Leona = 89,
|
||||
/// Lissandra (`Lissandra`, 127).
|
||||
#[strum(to_string="Lissandra", serialize="Lissandra")] Lissandra = 127,
|
||||
/// Lucian (`Lucian`, 236).
|
||||
#[strum(to_string="Lucian", serialize="Lucian")] Lucian = 236,
|
||||
/// Lulu (`Lulu`, 117).
|
||||
#[strum(to_string="Lulu", serialize="Lulu")] Lulu = 117,
|
||||
/// Lux (`Lux`, 99).
|
||||
#[strum(to_string="Lux", serialize="Lux")] Lux = 99,
|
||||
/// Malphite (`Malphite`, 54).
|
||||
#[strum(to_string="Malphite", serialize="Malphite")] Malphite = 54,
|
||||
/// Malzahar (`Malzahar`, 90).
|
||||
#[strum(to_string="Malzahar", serialize="Malzahar")] Malzahar = 90,
|
||||
/// Maokai (`Maokai`, 57).
|
||||
#[strum(to_string="Maokai", serialize="Maokai")] Maokai = 57,
|
||||
/// Master Yi (`MasterYi`, 11).
|
||||
#[strum(to_string="Master Yi", serialize="MasterYi")] MasterYi = 11,
|
||||
/// Miss Fortune (`MissFortune`, 21).
|
||||
#[strum(to_string="Miss Fortune", serialize="MissFortune")] MissFortune = 21,
|
||||
/// Mordekaiser (`Mordekaiser`, 82).
|
||||
#[strum(to_string="Mordekaiser", serialize="Mordekaiser")] Mordekaiser = 82,
|
||||
/// Morgana (`Morgana`, 25).
|
||||
#[strum(to_string="Morgana", serialize="Morgana")] Morgana = 25,
|
||||
/// Nami (`Nami`, 267).
|
||||
#[strum(to_string="Nami", serialize="Nami")] Nami = 267,
|
||||
/// Nasus (`Nasus`, 75).
|
||||
#[strum(to_string="Nasus", serialize="Nasus")] Nasus = 75,
|
||||
/// Nautilus (`Nautilus`, 111).
|
||||
#[strum(to_string="Nautilus", serialize="Nautilus")] Nautilus = 111,
|
||||
/// Neeko (`Neeko`, 518).
|
||||
#[strum(to_string="Neeko", serialize="Neeko")] Neeko = 518,
|
||||
/// Nidalee (`Nidalee`, 76).
|
||||
#[strum(to_string="Nidalee", serialize="Nidalee")] Nidalee = 76,
|
||||
/// Nocturne (`Nocturne`, 56).
|
||||
#[strum(to_string="Nocturne", serialize="Nocturne")] Nocturne = 56,
|
||||
/// Nunu & Willump (`Nunu`, 20).
|
||||
#[strum(to_string="Nunu & Willump", serialize="Nunu")] NunuWillump = 20,
|
||||
/// Olaf (`Olaf`, 2).
|
||||
#[strum(to_string="Olaf", serialize="Olaf")] Olaf = 2,
|
||||
/// Orianna (`Orianna`, 61).
|
||||
#[strum(to_string="Orianna", serialize="Orianna")] Orianna = 61,
|
||||
/// Ornn (`Ornn`, 516).
|
||||
#[strum(to_string="Ornn", serialize="Ornn")] Ornn = 516,
|
||||
/// Pantheon (`Pantheon`, 80).
|
||||
#[strum(to_string="Pantheon", serialize="Pantheon")] Pantheon = 80,
|
||||
/// Poppy (`Poppy`, 78).
|
||||
#[strum(to_string="Poppy", serialize="Poppy")] Poppy = 78,
|
||||
/// Pyke (`Pyke`, 555).
|
||||
#[strum(to_string="Pyke", serialize="Pyke")] Pyke = 555,
|
||||
/// Qiyana (`Qiyana`, 246).
|
||||
#[strum(to_string="Qiyana", serialize="Qiyana")] Qiyana = 246,
|
||||
/// Quinn (`Quinn`, 133).
|
||||
#[strum(to_string="Quinn", serialize="Quinn")] Quinn = 133,
|
||||
/// Rakan (`Rakan`, 497).
|
||||
#[strum(to_string="Rakan", serialize="Rakan")] Rakan = 497,
|
||||
/// Rammus (`Rammus`, 33).
|
||||
#[strum(to_string="Rammus", serialize="Rammus")] Rammus = 33,
|
||||
/// Rek'Sai (`RekSai`, 421).
|
||||
#[strum(to_string="Rek'Sai", serialize="RekSai")] RekSai = 421,
|
||||
/// Renekton (`Renekton`, 58).
|
||||
#[strum(to_string="Renekton", serialize="Renekton")] Renekton = 58,
|
||||
/// Rengar (`Rengar`, 107).
|
||||
#[strum(to_string="Rengar", serialize="Rengar")] Rengar = 107,
|
||||
/// Riven (`Riven`, 92).
|
||||
#[strum(to_string="Riven", serialize="Riven")] Riven = 92,
|
||||
/// Rumble (`Rumble`, 68).
|
||||
#[strum(to_string="Rumble", serialize="Rumble")] Rumble = 68,
|
||||
/// Ryze (`Ryze`, 13).
|
||||
#[strum(to_string="Ryze", serialize="Ryze")] Ryze = 13,
|
||||
/// Sejuani (`Sejuani`, 113).
|
||||
#[strum(to_string="Sejuani", serialize="Sejuani")] Sejuani = 113,
|
||||
/// Senna (`Senna`, 235).
|
||||
#[strum(to_string="Senna", serialize="Senna")] Senna = 235,
|
||||
/// Sett (`Sett`, 875).
|
||||
#[strum(to_string="Sett", serialize="Sett")] Sett = 875,
|
||||
/// Shaco (`Shaco`, 35).
|
||||
#[strum(to_string="Shaco", serialize="Shaco")] Shaco = 35,
|
||||
/// Shen (`Shen`, 98).
|
||||
#[strum(to_string="Shen", serialize="Shen")] Shen = 98,
|
||||
/// Shyvana (`Shyvana`, 102).
|
||||
#[strum(to_string="Shyvana", serialize="Shyvana")] Shyvana = 102,
|
||||
/// Singed (`Singed`, 27).
|
||||
#[strum(to_string="Singed", serialize="Singed")] Singed = 27,
|
||||
/// Sion (`Sion`, 14).
|
||||
#[strum(to_string="Sion", serialize="Sion")] Sion = 14,
|
||||
/// Sivir (`Sivir`, 15).
|
||||
#[strum(to_string="Sivir", serialize="Sivir")] Sivir = 15,
|
||||
/// Skarner (`Skarner`, 72).
|
||||
#[strum(to_string="Skarner", serialize="Skarner")] Skarner = 72,
|
||||
/// Sona (`Sona`, 37).
|
||||
#[strum(to_string="Sona", serialize="Sona")] Sona = 37,
|
||||
/// Soraka (`Soraka`, 16).
|
||||
#[strum(to_string="Soraka", serialize="Soraka")] Soraka = 16,
|
||||
/// Swain (`Swain`, 50).
|
||||
#[strum(to_string="Swain", serialize="Swain")] Swain = 50,
|
||||
/// Sylas (`Sylas`, 517).
|
||||
#[strum(to_string="Sylas", serialize="Sylas")] Sylas = 517,
|
||||
/// Syndra (`Syndra`, 134).
|
||||
#[strum(to_string="Syndra", serialize="Syndra")] Syndra = 134,
|
||||
/// Tahm Kench (`TahmKench`, 223).
|
||||
#[strum(to_string="Tahm Kench", serialize="TahmKench")] TahmKench = 223,
|
||||
/// Taliyah (`Taliyah`, 163).
|
||||
#[strum(to_string="Taliyah", serialize="Taliyah")] Taliyah = 163,
|
||||
/// Talon (`Talon`, 91).
|
||||
#[strum(to_string="Talon", serialize="Talon")] Talon = 91,
|
||||
/// Taric (`Taric`, 44).
|
||||
#[strum(to_string="Taric", serialize="Taric")] Taric = 44,
|
||||
/// Teemo (`Teemo`, 17).
|
||||
#[strum(to_string="Teemo", serialize="Teemo")] Teemo = 17,
|
||||
/// Thresh (`Thresh`, 412).
|
||||
#[strum(to_string="Thresh", serialize="Thresh")] Thresh = 412,
|
||||
/// Tristana (`Tristana`, 18).
|
||||
#[strum(to_string="Tristana", serialize="Tristana")] Tristana = 18,
|
||||
/// Trundle (`Trundle`, 48).
|
||||
#[strum(to_string="Trundle", serialize="Trundle")] Trundle = 48,
|
||||
/// Tryndamere (`Tryndamere`, 23).
|
||||
#[strum(to_string="Tryndamere", serialize="Tryndamere")] Tryndamere = 23,
|
||||
/// Twisted Fate (`TwistedFate`, 4).
|
||||
#[strum(to_string="Twisted Fate", serialize="TwistedFate")] TwistedFate = 4,
|
||||
/// Twitch (`Twitch`, 29).
|
||||
#[strum(to_string="Twitch", serialize="Twitch")] Twitch = 29,
|
||||
/// Udyr (`Udyr`, 77).
|
||||
#[strum(to_string="Udyr", serialize="Udyr")] Udyr = 77,
|
||||
/// Urgot (`Urgot`, 6).
|
||||
#[strum(to_string="Urgot", serialize="Urgot")] Urgot = 6,
|
||||
/// Varus (`Varus`, 110).
|
||||
#[strum(to_string="Varus", serialize="Varus")] Varus = 110,
|
||||
/// Vayne (`Vayne`, 67).
|
||||
#[strum(to_string="Vayne", serialize="Vayne")] Vayne = 67,
|
||||
/// Veigar (`Veigar`, 45).
|
||||
#[strum(to_string="Veigar", serialize="Veigar")] Veigar = 45,
|
||||
/// Vel'Koz (`Velkoz`, 161).
|
||||
#[strum(to_string="Vel'Koz", serialize="Velkoz")] VelKoz = 161,
|
||||
/// Vi (`Vi`, 254).
|
||||
#[strum(to_string="Vi", serialize="Vi")] Vi = 254,
|
||||
/// Viktor (`Viktor`, 112).
|
||||
#[strum(to_string="Viktor", serialize="Viktor")] Viktor = 112,
|
||||
/// Vladimir (`Vladimir`, 8).
|
||||
#[strum(to_string="Vladimir", serialize="Vladimir")] Vladimir = 8,
|
||||
/// Volibear (`Volibear`, 106).
|
||||
#[strum(to_string="Volibear", serialize="Volibear")] Volibear = 106,
|
||||
/// Warwick (`Warwick`, 19).
|
||||
#[strum(to_string="Warwick", serialize="Warwick")] Warwick = 19,
|
||||
/// Wukong (`MonkeyKing`, 62).
|
||||
#[strum(to_string="Wukong", serialize="MonkeyKing")] Wukong = 62,
|
||||
/// Xayah (`Xayah`, 498).
|
||||
#[strum(to_string="Xayah", serialize="Xayah")] Xayah = 498,
|
||||
/// Xerath (`Xerath`, 101).
|
||||
#[strum(to_string="Xerath", serialize="Xerath")] Xerath = 101,
|
||||
/// Xin Zhao (`XinZhao`, 5).
|
||||
#[strum(to_string="Xin Zhao", serialize="XinZhao")] XinZhao = 5,
|
||||
/// Yasuo (`Yasuo`, 157).
|
||||
#[strum(to_string="Yasuo", serialize="Yasuo")] Yasuo = 157,
|
||||
/// Yorick (`Yorick`, 83).
|
||||
#[strum(to_string="Yorick", serialize="Yorick")] Yorick = 83,
|
||||
/// Yuumi (`Yuumi`, 350).
|
||||
#[strum(to_string="Yuumi", serialize="Yuumi")] Yuumi = 350,
|
||||
/// Zac (`Zac`, 154).
|
||||
#[strum(to_string="Zac", serialize="Zac")] Zac = 154,
|
||||
/// Zed (`Zed`, 238).
|
||||
#[strum(to_string="Zed", serialize="Zed")] Zed = 238,
|
||||
/// Ziggs (`Ziggs`, 115).
|
||||
#[strum(to_string="Ziggs", serialize="Ziggs")] Ziggs = 115,
|
||||
/// Zilean (`Zilean`, 26).
|
||||
#[strum(to_string="Zilean", serialize="Zilean")] Zilean = 26,
|
||||
/// Zoe (`Zoe`, 142).
|
||||
#[strum(to_string="Zoe", serialize="Zoe")] Zoe = 142,
|
||||
/// Zyra (`Zyra`, 143).
|
||||
#[strum(to_string="Zyra", serialize="Zyra")] Zyra = 143,
|
||||
}
|
||||
|
||||
impl Champion {
|
||||
/// The champion's name (localized `en_US`), or `"NONE"` for the None variant.
|
||||
pub fn name(self) -> &'static str {
|
||||
self.into()
|
||||
}
|
||||
|
||||
/// The champion's identifier key. Somtimes called "key", "identifier", or "alias".
|
||||
/// This is mainly used in DDragon paths.
|
||||
///
|
||||
/// This is generally the `en_US` name with spaces and punctuation removed,
|
||||
/// but there are the following exceptions:
|
||||
///
|
||||
/// Variant | Name | Identifier
|
||||
/// --------|------|-----------
|
||||
/// `None` | "NONE" | "NONE"
|
||||
/// `ChoGath` | "Cho'Gath" | "Chogath"
|
||||
/// `Fiddlesticks` | "Fiddlesticks" | "FiddleSticks"
|
||||
/// `KaiSa` | "Kai'Sa" | "Kaisa"
|
||||
/// `KhaZix` | "Kha'Zix" | "Khazix"
|
||||
/// `LeBlanc` | "LeBlanc" | "Leblanc"
|
||||
/// `NunuWillump` | "Nunu & Willump" | "Nunu"
|
||||
/// `VelKoz` | "Vel'Koz" | "Velkoz"
|
||||
/// `Wukong` | "Wukong" | "MonkeyKing"
|
||||
pub fn identifier(self) -> &'static str {
|
||||
match self {
|
||||
Self::None => "NONE",
|
||||
Self::Aatrox => "Aatrox",
|
||||
Self::Ahri => "Ahri",
|
||||
Self::Akali => "Akali",
|
||||
Self::Alistar => "Alistar",
|
||||
Self::Amumu => "Amumu",
|
||||
Self::Anivia => "Anivia",
|
||||
Self::Annie => "Annie",
|
||||
Self::Aphelios => "Aphelios",
|
||||
Self::Ashe => "Ashe",
|
||||
Self::AurelionSol => "AurelionSol",
|
||||
Self::Azir => "Azir",
|
||||
Self::Bard => "Bard",
|
||||
Self::Blitzcrank => "Blitzcrank",
|
||||
Self::Brand => "Brand",
|
||||
Self::Braum => "Braum",
|
||||
Self::Caitlyn => "Caitlyn",
|
||||
Self::Camille => "Camille",
|
||||
Self::Cassiopeia => "Cassiopeia",
|
||||
Self::ChoGath => "Chogath",
|
||||
Self::Corki => "Corki",
|
||||
Self::Darius => "Darius",
|
||||
Self::Diana => "Diana",
|
||||
Self::DrMundo => "DrMundo",
|
||||
Self::Draven => "Draven",
|
||||
Self::Ekko => "Ekko",
|
||||
Self::Elise => "Elise",
|
||||
Self::Evelynn => "Evelynn",
|
||||
Self::Ezreal => "Ezreal",
|
||||
Self::Fiddlesticks => "FiddleSticks",
|
||||
Self::Fiora => "Fiora",
|
||||
Self::Fizz => "Fizz",
|
||||
Self::Galio => "Galio",
|
||||
Self::Gangplank => "Gangplank",
|
||||
Self::Garen => "Garen",
|
||||
Self::Gnar => "Gnar",
|
||||
Self::Gragas => "Gragas",
|
||||
Self::Graves => "Graves",
|
||||
Self::Hecarim => "Hecarim",
|
||||
Self::Heimerdinger => "Heimerdinger",
|
||||
Self::Illaoi => "Illaoi",
|
||||
Self::Irelia => "Irelia",
|
||||
Self::Ivern => "Ivern",
|
||||
Self::Janna => "Janna",
|
||||
Self::JarvanIV => "JarvanIV",
|
||||
Self::Jax => "Jax",
|
||||
Self::Jayce => "Jayce",
|
||||
Self::Jhin => "Jhin",
|
||||
Self::Jinx => "Jinx",
|
||||
Self::KaiSa => "Kaisa",
|
||||
Self::Kalista => "Kalista",
|
||||
Self::Karma => "Karma",
|
||||
Self::Karthus => "Karthus",
|
||||
Self::Kassadin => "Kassadin",
|
||||
Self::Katarina => "Katarina",
|
||||
Self::Kayle => "Kayle",
|
||||
Self::Kayn => "Kayn",
|
||||
Self::Kennen => "Kennen",
|
||||
Self::KhaZix => "Khazix",
|
||||
Self::Kindred => "Kindred",
|
||||
Self::Kled => "Kled",
|
||||
Self::KogMaw => "KogMaw",
|
||||
Self::LeBlanc => "Leblanc",
|
||||
Self::LeeSin => "LeeSin",
|
||||
Self::Leona => "Leona",
|
||||
Self::Lissandra => "Lissandra",
|
||||
Self::Lucian => "Lucian",
|
||||
Self::Lulu => "Lulu",
|
||||
Self::Lux => "Lux",
|
||||
Self::Malphite => "Malphite",
|
||||
Self::Malzahar => "Malzahar",
|
||||
Self::Maokai => "Maokai",
|
||||
Self::MasterYi => "MasterYi",
|
||||
Self::MissFortune => "MissFortune",
|
||||
Self::Mordekaiser => "Mordekaiser",
|
||||
Self::Morgana => "Morgana",
|
||||
Self::Nami => "Nami",
|
||||
Self::Nasus => "Nasus",
|
||||
Self::Nautilus => "Nautilus",
|
||||
Self::Neeko => "Neeko",
|
||||
Self::Nidalee => "Nidalee",
|
||||
Self::Nocturne => "Nocturne",
|
||||
Self::NunuWillump => "Nunu",
|
||||
Self::Olaf => "Olaf",
|
||||
Self::Orianna => "Orianna",
|
||||
Self::Ornn => "Ornn",
|
||||
Self::Pantheon => "Pantheon",
|
||||
Self::Poppy => "Poppy",
|
||||
Self::Pyke => "Pyke",
|
||||
Self::Qiyana => "Qiyana",
|
||||
Self::Quinn => "Quinn",
|
||||
Self::Rakan => "Rakan",
|
||||
Self::Rammus => "Rammus",
|
||||
Self::RekSai => "RekSai",
|
||||
Self::Renekton => "Renekton",
|
||||
Self::Rengar => "Rengar",
|
||||
Self::Riven => "Riven",
|
||||
Self::Rumble => "Rumble",
|
||||
Self::Ryze => "Ryze",
|
||||
Self::Sejuani => "Sejuani",
|
||||
Self::Senna => "Senna",
|
||||
Self::Sett => "Sett",
|
||||
Self::Shaco => "Shaco",
|
||||
Self::Shen => "Shen",
|
||||
Self::Shyvana => "Shyvana",
|
||||
Self::Singed => "Singed",
|
||||
Self::Sion => "Sion",
|
||||
Self::Sivir => "Sivir",
|
||||
Self::Skarner => "Skarner",
|
||||
Self::Sona => "Sona",
|
||||
Self::Soraka => "Soraka",
|
||||
Self::Swain => "Swain",
|
||||
Self::Sylas => "Sylas",
|
||||
Self::Syndra => "Syndra",
|
||||
Self::TahmKench => "TahmKench",
|
||||
Self::Taliyah => "Taliyah",
|
||||
Self::Talon => "Talon",
|
||||
Self::Taric => "Taric",
|
||||
Self::Teemo => "Teemo",
|
||||
Self::Thresh => "Thresh",
|
||||
Self::Tristana => "Tristana",
|
||||
Self::Trundle => "Trundle",
|
||||
Self::Tryndamere => "Tryndamere",
|
||||
Self::TwistedFate => "TwistedFate",
|
||||
Self::Twitch => "Twitch",
|
||||
Self::Udyr => "Udyr",
|
||||
Self::Urgot => "Urgot",
|
||||
Self::Varus => "Varus",
|
||||
Self::Vayne => "Vayne",
|
||||
Self::Veigar => "Veigar",
|
||||
Self::VelKoz => "Velkoz",
|
||||
Self::Vi => "Vi",
|
||||
Self::Viktor => "Viktor",
|
||||
Self::Vladimir => "Vladimir",
|
||||
Self::Volibear => "Volibear",
|
||||
Self::Warwick => "Warwick",
|
||||
Self::Wukong => "MonkeyKing",
|
||||
Self::Xayah => "Xayah",
|
||||
Self::Xerath => "Xerath",
|
||||
Self::XinZhao => "XinZhao",
|
||||
Self::Yasuo => "Yasuo",
|
||||
Self::Yorick => "Yorick",
|
||||
Self::Yuumi => "Yuumi",
|
||||
Self::Zac => "Zac",
|
||||
Self::Zed => "Zed",
|
||||
Self::Ziggs => "Ziggs",
|
||||
Self::Zilean => "Zilean",
|
||||
Self::Zoe => "Zoe",
|
||||
Self::Zyra => "Zyra",
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
#![macro_use]
|
||||
macro_rules! serde_string {
|
||||
( $x:ty ) => {
|
||||
impl<'de> serde::de::Deserialize<'de> for $x
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
s.parse().map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
impl serde::ser::Serialize for $x {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::ser::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_ref())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||
|
||||
/// League of Legends maps.
|
||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||
#[derive(Serialize_repr, Deserialize_repr)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum Map {
|
||||
|
||||
/// Summoner's Rift
|
||||
/// <br>Original Summer variant
|
||||
SUMMONERS_RIFT_ORIGINAL_SUMMER_VARIANT = 1,
|
||||
/// Summoner's Rift
|
||||
/// <br>Original Autumn variant
|
||||
SUMMONERS_RIFT_ORIGINAL_AUTUMN_VARIANT = 2,
|
||||
/// Summoner's Rift
|
||||
/// <br>Current Version
|
||||
SUMMONERS_RIFT = 11,
|
||||
|
||||
/// The Proving Grounds
|
||||
/// <br>Tutorial Map
|
||||
THE_PROVING_GROUNDS = 3,
|
||||
|
||||
/// Twisted Treeline
|
||||
/// <br>Original Version
|
||||
TWISTED_TREELINE_ORIGINAL_VERSION = 4,
|
||||
/// Twisted Treeline
|
||||
/// <br>Last TT map
|
||||
TWISTED_TREELINE = 10,
|
||||
|
||||
/// The Crystal Scar
|
||||
/// <br>Dominion map
|
||||
THE_CRYSTAL_SCAR = 8,
|
||||
|
||||
/// Howling Abyss
|
||||
/// <br>ARAM map
|
||||
HOWLING_ABYSS = 12,
|
||||
|
||||
/// Butcher's Bridge
|
||||
/// <br>Alternate ARAM map
|
||||
BUTCHERS_BRIDGE = 14,
|
||||
|
||||
/// Cosmic Ruins
|
||||
/// <br>Dark Star: Singularity map
|
||||
COSMIC_RUINS = 16,
|
||||
|
||||
/// Valoran City Park
|
||||
/// <br>Star Guardian Invasion map
|
||||
VALORAN_CITY_PARK = 18,
|
||||
|
||||
/// Substructure 43
|
||||
/// <br>PROJECT: Hunters map
|
||||
SUBSTRUCTURE43 = 19,
|
||||
|
||||
/// Crash Site
|
||||
/// <br>Odyssey: Extraction map
|
||||
CRASH_SITE = 20,
|
||||
|
||||
/// Nexus Blitz
|
||||
/// <br>Nexus Blitz map
|
||||
NEXUS_BLITZ = 21,
|
||||
}
|
|
@ -1,314 +0,0 @@
|
|||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||
|
||||
/// League of Legends matchmaking queue.
|
||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq)]
|
||||
#[derive(Serialize_repr, Deserialize_repr)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[repr(u16)]
|
||||
pub enum Queue {
|
||||
|
||||
/// Custom games.
|
||||
CUSTOM_GAMES_ = 0,
|
||||
|
||||
/// 5v5 Blind Pick games games on Summoner's Rift.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 430
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 430")]
|
||||
SUMMONERS_RIFT_5V5_BLIND_PICK_GAMES_DEPRECATED_2 = 2,
|
||||
/// 5v5 Blind Pick games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_5V5_BLIND_PICK_GAMES = 430,
|
||||
|
||||
/// 5v5 Ranked Solo games games on Summoner's Rift.
|
||||
/// <br>Deprecated in favor of queueId 420
|
||||
#[deprecated(note="Deprecated in favor of queueId 420")]
|
||||
SUMMONERS_RIFT_5V5_RANKED_SOLO_GAMES_DEPRECATED_4 = 4,
|
||||
/// 5v5 Ranked Solo games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_5V5_RANKED_SOLO_GAMES = 420,
|
||||
|
||||
/// 5v5 Ranked Premade games games on Summoner's Rift.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
SUMMONERS_RIFT_5V5_RANKED_PREMADE_GAMES = 6,
|
||||
|
||||
/// Co-op vs AI games games on Summoner's Rift.
|
||||
/// <br>Deprecated in favor of queueId 32 and 33
|
||||
#[deprecated(note="Deprecated in favor of queueId 32 and 33")]
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_GAMES = 7,
|
||||
|
||||
/// 3v3 Normal games games on Twisted Treeline.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 460
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 460")]
|
||||
TWISTED_TREELINE_3V3_NORMAL_GAMES = 8,
|
||||
|
||||
/// 3v3 Ranked Flex games games on Twisted Treeline.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 470
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 470")]
|
||||
TWISTED_TREELINE_3V3_RANKED_FLEX_GAMES_DEPRECATED_9 = 9,
|
||||
/// 3v3 Ranked Flex games games on Twisted Treeline.
|
||||
/// <br>Deprecated in patch 9.23
|
||||
#[deprecated(note="Deprecated in patch 9.23")]
|
||||
TWISTED_TREELINE_3V3_RANKED_FLEX_GAMES_DEPRECATED_470 = 470,
|
||||
|
||||
/// 5v5 Draft Pick games games on Summoner's Rift.
|
||||
/// <br>Deprecated in favor of queueId 400
|
||||
#[deprecated(note="Deprecated in favor of queueId 400")]
|
||||
SUMMONERS_RIFT_5V5_DRAFT_PICK_GAMES_DEPRECATED_14 = 14,
|
||||
/// 5v5 Draft Pick games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_5V5_DRAFT_PICK_GAMES = 400,
|
||||
|
||||
/// 5v5 Dominion Blind Pick games games on Crystal Scar.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
CRYSTAL_SCAR_5V5_DOMINION_BLIND_PICK_GAMES = 16,
|
||||
|
||||
/// 5v5 Dominion Draft Pick games games on Crystal Scar.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
CRYSTAL_SCAR_5V5_DOMINION_DRAFT_PICK_GAMES = 17,
|
||||
|
||||
/// Dominion Co-op vs AI games games on Crystal Scar.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
CRYSTAL_SCAR_DOMINION_CO_OP_VS_AI_GAMES = 25,
|
||||
|
||||
/// Co-op vs AI Intro Bot games games on Summoner's Rift.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 830
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 830")]
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTRO_BOT_GAMES_DEPRECATED_31 = 31,
|
||||
/// Co-op vs. AI Intro Bot games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTRO_BOT_GAMES = 830,
|
||||
|
||||
/// Co-op vs AI Beginner Bot games games on Summoner's Rift.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 840
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 840")]
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_BEGINNER_BOT_GAMES_DEPRECATED_32 = 32,
|
||||
/// Co-op vs. AI Beginner Bot games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_BEGINNER_BOT_GAMES = 840,
|
||||
|
||||
/// Co-op vs AI Intermediate Bot games games on Summoner's Rift.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 850
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 850")]
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTERMEDIATE_BOT_GAMES_DEPRECATED_33 = 33,
|
||||
/// Co-op vs. AI Intermediate Bot games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_INTERMEDIATE_BOT_GAMES = 850,
|
||||
|
||||
/// 3v3 Ranked Team games games on Twisted Treeline.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
TWISTED_TREELINE_3V3_RANKED_TEAM_GAMES = 41,
|
||||
|
||||
/// 5v5 Ranked Team games games on Summoner's Rift.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
SUMMONERS_RIFT_5V5_RANKED_TEAM_GAMES = 42,
|
||||
|
||||
/// Co-op vs AI games games on Twisted Treeline.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 800
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 800")]
|
||||
TWISTED_TREELINE_CO_OP_VS_AI_GAMES = 52,
|
||||
|
||||
/// 5v5 Team Builder games games on Summoner's Rift.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
SUMMONERS_RIFT_5V5_TEAM_BUILDER_GAMES = 61,
|
||||
|
||||
/// 5v5 ARAM games games on Howling Abyss.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 450
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 450")]
|
||||
HOWLING_ABYSS_5V5_ARAM_GAMES_DEPRECATED_65 = 65,
|
||||
/// 5v5 ARAM games games on Howling Abyss.
|
||||
HOWLING_ABYSS_5V5_ARAM_GAMES = 450,
|
||||
|
||||
/// ARAM Co-op vs AI games games on Howling Abyss.
|
||||
/// <br>Game mode deprecated
|
||||
#[deprecated(note="Game mode deprecated")]
|
||||
HOWLING_ABYSS_ARAM_CO_OP_VS_AI_GAMES = 67,
|
||||
|
||||
/// One for All games games on Summoner's Rift.
|
||||
/// <br>Deprecated in patch 8.6 in favor of queueId 1020
|
||||
#[deprecated(note="Deprecated in patch 8.6 in favor of queueId 1020")]
|
||||
SUMMONERS_RIFT_ONE_FOR_ALL_GAMES_DEPRECATED_70 = 70,
|
||||
/// One for All games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_ONE_FOR_ALL_GAMES = 1020,
|
||||
|
||||
/// 1v1 Snowdown Showdown games games on Howling Abyss.
|
||||
HOWLING_ABYSS_1V1_SNOWDOWN_SHOWDOWN_GAMES = 72,
|
||||
|
||||
/// 2v2 Snowdown Showdown games games on Howling Abyss.
|
||||
HOWLING_ABYSS_2V2_SNOWDOWN_SHOWDOWN_GAMES = 73,
|
||||
|
||||
/// 6v6 Hexakill games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_6V6_HEXAKILL_GAMES = 75,
|
||||
|
||||
/// Ultra Rapid Fire games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_ULTRA_RAPID_FIRE_GAMES = 76,
|
||||
|
||||
/// One For All: Mirror Mode games games on Howling Abyss.
|
||||
HOWLING_ABYSS_ONE_FOR_ALL_MIRROR_MODE_GAMES = 78,
|
||||
|
||||
/// Co-op vs AI Ultra Rapid Fire games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_CO_OP_VS_AI_ULTRA_RAPID_FIRE_GAMES = 83,
|
||||
|
||||
/// Doom Bots Rank 1 games games on Summoner's Rift.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 950
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
||||
SUMMONERS_RIFT_DOOM_BOTS_RANK1_GAMES = 91,
|
||||
|
||||
/// Doom Bots Rank 2 games games on Summoner's Rift.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 950
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
||||
SUMMONERS_RIFT_DOOM_BOTS_RANK2_GAMES = 92,
|
||||
|
||||
/// Doom Bots Rank 5 games games on Summoner's Rift.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 950
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 950")]
|
||||
SUMMONERS_RIFT_DOOM_BOTS_RANK5_GAMES = 93,
|
||||
|
||||
/// Ascension games games on Crystal Scar.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 910
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 910")]
|
||||
CRYSTAL_SCAR_ASCENSION_GAMES_DEPRECATED_96 = 96,
|
||||
/// Ascension games games on Crystal Scar.
|
||||
CRYSTAL_SCAR_ASCENSION_GAMES = 910,
|
||||
|
||||
/// 6v6 Hexakill games games on Twisted Treeline.
|
||||
TWISTED_TREELINE_6V6_HEXAKILL_GAMES = 98,
|
||||
|
||||
/// 5v5 ARAM games games on Butcher's Bridge.
|
||||
BUTCHERS_BRIDGE_5V5_ARAM_GAMES = 100,
|
||||
|
||||
/// Legend of the Poro King games games on Howling Abyss.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 920
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 920")]
|
||||
HOWLING_ABYSS_LEGEND_OF_THE_PORO_KING_GAMES_DEPRECATED_300 = 300,
|
||||
/// Legend of the Poro King games games on Howling Abyss.
|
||||
HOWLING_ABYSS_LEGEND_OF_THE_PORO_KING_GAMES = 920,
|
||||
|
||||
/// Nemesis games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_NEMESIS_GAMES = 310,
|
||||
|
||||
/// Black Market Brawlers games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_BLACK_MARKET_BRAWLERS_GAMES = 313,
|
||||
|
||||
/// Nexus Siege games games on Summoner's Rift.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 940
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 940")]
|
||||
SUMMONERS_RIFT_NEXUS_SIEGE_GAMES_DEPRECATED_315 = 315,
|
||||
/// Nexus Siege games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_NEXUS_SIEGE_GAMES = 940,
|
||||
|
||||
/// Definitely Not Dominion games games on Crystal Scar.
|
||||
CRYSTAL_SCAR_DEFINITELY_NOT_DOMINION_GAMES = 317,
|
||||
|
||||
/// ARURF games games on Summoner's Rift.
|
||||
/// <br>Deprecated in patch 7.19 in favor of queueId 900
|
||||
#[deprecated(note="Deprecated in patch 7.19 in favor of queueId 900")]
|
||||
SUMMONERS_RIFT_ARURF_GAMES = 318,
|
||||
|
||||
/// All Random games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_ALL_RANDOM_GAMES = 325,
|
||||
|
||||
/// 5v5 Ranked Dynamic games games on Summoner's Rift.
|
||||
/// <br>Game mode deprecated in patch 6.22
|
||||
#[deprecated(note="Game mode deprecated in patch 6.22")]
|
||||
SUMMONERS_RIFT_5V5_RANKED_DYNAMIC_GAMES = 410,
|
||||
|
||||
/// 5v5 Ranked Flex games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_5V5_RANKED_FLEX_GAMES = 440,
|
||||
|
||||
/// 3v3 Blind Pick games games on Twisted Treeline.
|
||||
/// <br>Deprecated in patch 9.23
|
||||
#[deprecated(note="Deprecated in patch 9.23")]
|
||||
TWISTED_TREELINE_3V3_BLIND_PICK_GAMES = 460,
|
||||
|
||||
/// Blood Hunt Assassin games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_BLOOD_HUNT_ASSASSIN_GAMES = 600,
|
||||
|
||||
/// Dark Star: Singularity games games on Cosmic Ruins.
|
||||
COSMIC_RUINS_DARK_STAR_SINGULARITY_GAMES = 610,
|
||||
|
||||
/// Clash games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_CLASH_GAMES = 700,
|
||||
|
||||
/// Co-op vs. AI Intermediate Bot games games on Twisted Treeline.
|
||||
/// <br>Deprecated in patch 9.23
|
||||
#[deprecated(note="Deprecated in patch 9.23")]
|
||||
TWISTED_TREELINE_CO_OP_VS_AI_INTERMEDIATE_BOT_GAMES = 800,
|
||||
|
||||
/// Co-op vs. AI Intro Bot games games on Twisted Treeline.
|
||||
/// <br>Deprecated in patch 9.23
|
||||
#[deprecated(note="Deprecated in patch 9.23")]
|
||||
TWISTED_TREELINE_CO_OP_VS_AI_INTRO_BOT_GAMES = 810,
|
||||
|
||||
/// Co-op vs. AI Beginner Bot games games on Twisted Treeline.
|
||||
TWISTED_TREELINE_CO_OP_VS_AI_BEGINNER_BOT_GAMES = 820,
|
||||
|
||||
/// URF games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_URF_GAMES = 900,
|
||||
|
||||
/// Doom Bots Voting games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_DOOM_BOTS_VOTING_GAMES = 950,
|
||||
|
||||
/// Doom Bots Standard games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_DOOM_BOTS_STANDARD_GAMES = 960,
|
||||
|
||||
/// Star Guardian Invasion: Normal games games on Valoran City Park.
|
||||
VALORAN_CITY_PARK_STAR_GUARDIAN_INVASION_NORMAL_GAMES = 980,
|
||||
|
||||
/// Star Guardian Invasion: Onslaught games games on Valoran City Park.
|
||||
VALORAN_CITY_PARK_STAR_GUARDIAN_INVASION_ONSLAUGHT_GAMES = 990,
|
||||
|
||||
/// PROJECT: Hunters games games on Overcharge.
|
||||
OVERCHARGE_PROJECT_HUNTERS_GAMES = 1000,
|
||||
|
||||
/// Snow ARURF games games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_SNOW_ARURF_GAMES = 1010,
|
||||
|
||||
/// Odyssey Extraction: Intro games games on Crash Site.
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_INTRO_GAMES = 1030,
|
||||
|
||||
/// Odyssey Extraction: Cadet games games on Crash Site.
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_CADET_GAMES = 1040,
|
||||
|
||||
/// Odyssey Extraction: Crewmember games games on Crash Site.
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_CREWMEMBER_GAMES = 1050,
|
||||
|
||||
/// Odyssey Extraction: Captain games games on Crash Site.
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_CAPTAIN_GAMES = 1060,
|
||||
|
||||
/// Odyssey Extraction: Onslaught games games on Crash Site.
|
||||
CRASH_SITE_ODYSSEY_EXTRACTION_ONSLAUGHT_GAMES = 1070,
|
||||
|
||||
/// Teamfight Tactics games games on Convergence.
|
||||
CONVERGENCE_TEAMFIGHT_TACTICS_GAMES = 1090,
|
||||
|
||||
/// Ranked Teamfight Tactics games games on Convergence.
|
||||
CONVERGENCE_RANKED_TEAMFIGHT_TACTICS_GAMES = 1100,
|
||||
|
||||
/// Teamfight Tactics Tutorial games games on Convergence.
|
||||
CONVERGENCE_TEAMFIGHT_TACTICS_TUTORIAL_GAMES = 1110,
|
||||
|
||||
/// Nexus Blitz games games on Nexus Blitz.
|
||||
/// <br>Deprecated in patch 9.2
|
||||
#[deprecated(note="Deprecated in patch 9.2")]
|
||||
NEXUS_BLITZ_NEXUS_BLITZ_GAMES = 1200,
|
||||
|
||||
/// Tutorial 1 games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_TUTORIAL1 = 2000,
|
||||
|
||||
/// Tutorial 2 games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_TUTORIAL2 = 2010,
|
||||
|
||||
/// Tutorial 3 games on Summoner's Rift.
|
||||
SUMMONERS_RIFT_TUTORIAL3 = 2020,
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// LoL or TFT ranked queue types.
|
||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||
pub enum QueueType {
|
||||
// League of Legends, Summoner's Rift (5v5), Ranked Solo Queue.
|
||||
RANKED_SOLO_5x5,
|
||||
// League of Legends, Summoner's Rift (5v5), Flex Queue.
|
||||
RANKED_FLEX_SR,
|
||||
// League of Legends, Twisted Treeline (3v3), Flex Queue.
|
||||
RANKED_FLEX_TT,
|
||||
// Ranked Teamfight Tactics.
|
||||
#[deprecated(note = "Teamfight Tactics ranks should be acquired using `TftLeagueV1::get_league_entries`.")]
|
||||
RANKED_TFT,
|
||||
}
|
||||
|
||||
serde_string!(QueueType);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_as_ref() {
|
||||
assert_eq!("RANKED_SOLO_5x5", QueueType::RANKED_SOLO_5x5.as_ref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_to_string() {
|
||||
assert_eq!("RANKED_SOLO_5x5", QueueType::RANKED_SOLO_5x5.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_from_string() {
|
||||
assert_eq!(Some(QueueType::RANKED_SOLO_5x5), "RANKED_SOLO_5x5".parse().ok());
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
use strum_macros::{ EnumString, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// A region served by a single game server.
|
||||
/// Each Riot Games API request is directed at a particular region,
|
||||
/// with tournament API requests directed at the AMERICAS "global" region.
|
||||
#[derive(Debug)]
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(EnumString, Display, AsRefStr, IntoStaticStr)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Region {
|
||||
#[strum(to_string="BR1", serialize="BR")]
|
||||
BR,
|
||||
#[strum(to_string="EUN1", serialize="EUNE")]
|
||||
EUNE,
|
||||
#[strum(to_string="EUW1", serialize="EUW")]
|
||||
EUW,
|
||||
#[strum(to_string="NA1", serialize="NA")]
|
||||
NA,
|
||||
#[strum(to_string="KR", serialize="KR")]
|
||||
KR,
|
||||
#[strum(to_string="LA1", serialize="LAN")]
|
||||
LAN,
|
||||
#[strum(to_string="LA2", serialize="LAS")]
|
||||
LAS,
|
||||
#[strum(to_string="OC1", serialize="OCE")]
|
||||
OCE,
|
||||
#[strum(to_string="RU", serialize="RU")]
|
||||
RU,
|
||||
#[strum(to_string="TR1", serialize="TR")]
|
||||
TR,
|
||||
#[strum(to_string="JP1", serialize="JP")]
|
||||
JP,
|
||||
#[strum(to_string="PBE1", serialize="PBE")]
|
||||
PBE,
|
||||
#[strum(to_string="AMERICAS", serialize="AMERICAS")]
|
||||
AMERICAS,
|
||||
#[strum(to_string="EUROPE", serialize="EUROPE")]
|
||||
EUROPE,
|
||||
#[strum(to_string="ASIA", serialize="ASIA")]
|
||||
ASIA,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
assert_eq!("BR1", Region::BR.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get() {
|
||||
assert_eq!(Ok(Region::JP), "JP".parse());
|
||||
assert_eq!(Ok(Region::NA), "NA1".parse());
|
||||
assert!("LA".parse::<Region>().is_err());
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
///////////////////////////////////////////////
|
||||
// //
|
||||
// ! //
|
||||
// This file is automatically generated! //
|
||||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||
|
||||
/// League of Legends matchmaking seasons.
|
||||
#[cfg_attr(feature = "nightly", non_exhaustive)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||
#[derive(Serialize_repr, Deserialize_repr)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum Season {
|
||||
PRESEASON_3 = 0,
|
||||
SEASON_3 = 1,
|
||||
PRESEASON_2014 = 2,
|
||||
SEASON_2014 = 3,
|
||||
PRESEASON_2015 = 4,
|
||||
SEASON_2015 = 5,
|
||||
PRESEASON_2016 = 6,
|
||||
SEASON_2016 = 7,
|
||||
PRESEASON_2017 = 8,
|
||||
SEASON_2017 = 9,
|
||||
PRESEASON_2018 = 10,
|
||||
SEASON_2018 = 11,
|
||||
PRESEASON_2019 = 12,
|
||||
SEASON_2019 = 13,
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
use serde_repr::{ Serialize_repr, Deserialize_repr };
|
||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||
|
||||
/// League of Legends team.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
#[derive(Serialize_repr, Deserialize_repr)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum Team {
|
||||
/// Blue team (bottom left on Summoner's Rift).
|
||||
BLUE = 100,
|
||||
/// Red team (top right on Summoner's Rift).
|
||||
RED = 200,
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||
use strum_macros::{ EnumString, EnumIter, Display, AsRefStr, IntoStaticStr };
|
||||
|
||||
/// LoL and TFT ranked tiers, such as gold, diamond, challenger, etc.
|
||||
///
|
||||
/// Sorts from lowest rank to highest rank.
|
||||
///
|
||||
/// Repr'd as arbitrary `u8` values.
|
||||
///
|
||||
/// Implements [IntoEnumIterator](super::IntoEnumIterator).
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[derive(EnumString, EnumIter, Display, AsRefStr, IntoStaticStr)]
|
||||
#[repr(u8)]
|
||||
pub enum Tier {
|
||||
/// Challenger, the highest tier, an apex tier. Repr: `220_u8`.
|
||||
CHALLENGER = 220,
|
||||
/// Grand Master, an apex tier. Repr: `200_u8`.
|
||||
GRANDMASTER = 200,
|
||||
/// Master, an apex tier. Repr: `180_u8`.
|
||||
MASTER = 180,
|
||||
/// Diamond, the higest non-apex tier. Repr: `140_u8`.
|
||||
DIAMOND = 140,
|
||||
/// Platinum. Repr: `120_u8`.
|
||||
PLATINUM = 120,
|
||||
/// Gold. Repr: `100_u8`.
|
||||
GOLD = 100,
|
||||
/// Silver. Repr: `80_u8`.
|
||||
SILVER = 80,
|
||||
/// Bronze. Repr: `60_u8`.
|
||||
BRONZE = 60,
|
||||
/// Iron, the lowest tier. Repr: `40_u8`.
|
||||
IRON = 40,
|
||||
}
|
||||
|
||||
serde_string!(Tier);
|
||||
|
||||
impl Tier {
|
||||
/// If this tier is an apex tier: master and above.
|
||||
///
|
||||
/// Inverse of is_standard().
|
||||
///
|
||||
/// These tiers are NOT queryable by LeagueV4Endpoints::get_league_entries(...).
|
||||
pub const fn is_apex(self) -> bool {
|
||||
(Self::MASTER as u8) <= (self as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sort() {
|
||||
assert!(Tier::GOLD < Tier::DIAMOND);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apex_check() {
|
||||
assert!( Tier::GRANDMASTER.is_apex());
|
||||
assert!(!Tier::DIAMOND.is_apex());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
assert_eq!("GRANDMASTER", Tier::GRANDMASTER.as_ref());
|
||||
assert_eq!("GRANDMASTER", Tier::GRANDMASTER.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_string() {
|
||||
assert_eq!(Ok(Tier::GRANDMASTER), "GRANDMASTER".parse());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter() {
|
||||
use strum::IntoEnumIterator;
|
||||
let mut iter = Tier::iter();
|
||||
assert_eq!(Some(Tier::CHALLENGER), iter.next());
|
||||
iter.next();
|
||||
iter.next();
|
||||
assert_eq!(Some(Tier::DIAMOND), iter.next());
|
||||
iter.next();
|
||||
iter.next();
|
||||
iter.next();
|
||||
iter.next();
|
||||
assert_eq!(Some(Tier::IRON), iter.next());
|
||||
assert_eq!(None, iter.next());
|
||||
}
|
||||
}
|
1062
src/endpoints.rs
1062
src/endpoints.rs
File diff suppressed because it is too large
Load Diff
26
src/lib.rs
26
src/lib.rs
|
@ -1,26 +0,0 @@
|
|||
#![cfg_attr(feature = "nightly", feature(non_exhaustive))]
|
||||
#![cfg_attr(feature = "nightly", feature(external_doc))]
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
#![cfg_attr(feature = "nightly", doc(include = "../README.md"))]
|
||||
#![cfg_attr(not(feature = "nightly"), doc = "See [README.md](https://github.com/MingweiSamuel/Riven#readme).")]
|
||||
|
||||
mod config;
|
||||
pub use config::RiotApiConfig;
|
||||
|
||||
pub mod consts;
|
||||
|
||||
pub mod endpoints;
|
||||
|
||||
mod error;
|
||||
pub use error::*;
|
||||
|
||||
pub mod models;
|
||||
|
||||
mod req;
|
||||
|
||||
mod riot_api;
|
||||
pub use riot_api::*;
|
||||
|
||||
mod util;
|
1777
src/models.rs
1777
src/models.rs
File diff suppressed because it is too large
Load Diff
|
@ -1,187 +0,0 @@
|
|||
use std::cmp;
|
||||
use std::time::{ Duration, Instant };
|
||||
|
||||
use log;
|
||||
use parking_lot::{ RwLock, RwLockUpgradableReadGuard };
|
||||
use reqwest::{ StatusCode, Response };
|
||||
use scan_fmt::scan_fmt;
|
||||
|
||||
use crate::RiotApiConfig;
|
||||
use super::{ TokenBucket, VectorTokenBucket };
|
||||
use super::RateLimitType;
|
||||
|
||||
pub struct RateLimit {
|
||||
rate_limit_type: RateLimitType,
|
||||
// Buckets for this rate limit (synchronized).
|
||||
// Almost always read, written only when rate limit rates are updated
|
||||
// from API response.
|
||||
buckets: RwLock<Vec<VectorTokenBucket>>,
|
||||
// Set to when we can retry if a retry-after header is received.
|
||||
retry_after: RwLock<Option<Instant>>,
|
||||
}
|
||||
|
||||
impl RateLimit {
|
||||
/// Header specifying which RateLimitType caused a 429.
|
||||
const HEADER_XRATELIMITTYPE: &'static str = "X-Rate-Limit-Type";
|
||||
/// Header specifying retry after time in seconds after a 429.
|
||||
const HEADER_RETRYAFTER: &'static str = "Retry-After";
|
||||
|
||||
pub fn new(rate_limit_type: RateLimitType) -> Self {
|
||||
let initial_bucket = VectorTokenBucket::new(
|
||||
Duration::from_secs(1), 1, Duration::new(0, 0), 1.0);
|
||||
RateLimit {
|
||||
rate_limit_type: rate_limit_type,
|
||||
// Rate limit before getting from response: 1/s.
|
||||
buckets: RwLock::new(vec![initial_bucket]),
|
||||
retry_after: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_both_or_delay(app_rate_limit: &Self, method_rate_limit: &Self) -> Option<Duration> {
|
||||
// Check retry after.
|
||||
{
|
||||
let retry_after_delay = app_rate_limit.get_retry_after_delay()
|
||||
.and_then(|a| method_rate_limit.get_retry_after_delay().map(|m| cmp::max(a, m)));
|
||||
if retry_after_delay.is_some() {
|
||||
return retry_after_delay
|
||||
}
|
||||
}
|
||||
// Check buckets.
|
||||
let app_buckets = app_rate_limit.buckets.read();
|
||||
let method_buckets = method_rate_limit.buckets.read();
|
||||
for bucket in app_buckets.iter().chain(method_buckets.iter()) {
|
||||
let delay = bucket.get_delay();
|
||||
if delay.is_some() {
|
||||
return delay;
|
||||
}
|
||||
}
|
||||
// Success.
|
||||
for bucket in app_buckets.iter().chain(method_buckets.iter()) {
|
||||
bucket.get_tokens(1);
|
||||
}
|
||||
|
||||
log::debug!("Tokens obtained, buckets: APP {:?} METHOD {:?}", app_buckets, method_buckets);
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_retry_after_delay(&self) -> Option<Duration> {
|
||||
self.retry_after.read().and_then(|i| Instant::now().checked_duration_since(i))
|
||||
}
|
||||
|
||||
pub fn on_response(&self, config: &RiotApiConfig, response: &Response) {
|
||||
self.on_response_retry_after(response);
|
||||
self.on_response_rate_limits(config, response);
|
||||
}
|
||||
|
||||
/// `on_response` helper for retry after check.
|
||||
#[inline]
|
||||
fn on_response_retry_after(&self, response: &Response) {
|
||||
if let Some(retry_after) = || -> Option<Instant> {
|
||||
// Only care about 429s.
|
||||
if StatusCode::TOO_MANY_REQUESTS != response.status() {
|
||||
return None;
|
||||
}
|
||||
|
||||
{
|
||||
// Only care if the header that indicates the relevant RateLimit is present.
|
||||
let type_name_header = response.headers()
|
||||
.get(RateLimit::HEADER_XRATELIMITTYPE)?.to_str()
|
||||
.expect("Failed to read x-rate-limit-type header as string.");
|
||||
// Only care if that header's value matches us.
|
||||
if self.rate_limit_type.type_name() != type_name_header.to_lowercase() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// Get retry after header. Only care if it exists.
|
||||
let retry_after_header = response.headers()
|
||||
.get(RateLimit::HEADER_RETRYAFTER)?.to_str()
|
||||
.expect("Failed to read retry-after header as string.");
|
||||
|
||||
log::debug!("Hit 429, retry-after {} secs.", retry_after_header);
|
||||
|
||||
// Header currently only returns ints, but float is more general. Can be zero.
|
||||
let retry_after_secs: f32 = retry_after_header.parse()
|
||||
.expect("Failed to parse retry-after header as f32.");
|
||||
// Add 0.5 seconds to account for rounding, cases when response is zero.
|
||||
let delay = Duration::from_secs_f32(0.5 + retry_after_secs);
|
||||
return Some(Instant::now() + delay);
|
||||
}() {
|
||||
*self.retry_after.write() = Some(retry_after);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn on_response_rate_limits(&self, config: &RiotApiConfig, response: &Response) {
|
||||
// Check if rate limits changed.
|
||||
let headers = response.headers();
|
||||
let limit_header_opt = headers.get(self.rate_limit_type.limit_header())
|
||||
.map(|h| h.to_str().expect("Failed to read limit header as string."));
|
||||
let count_header_opt = headers.get(self.rate_limit_type.count_header())
|
||||
.map(|h| h.to_str().expect("Failed to read count header as string."));
|
||||
|
||||
// https://github.com/rust-lang/rust/issues/53667
|
||||
if let Some(limit_header) = limit_header_opt {
|
||||
if let Some(count_header) = count_header_opt {
|
||||
|
||||
let buckets = self.buckets.upgradable_read();
|
||||
if !buckets_require_updating(limit_header, &*buckets) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Buckets require updating. Upgrade to write lock.
|
||||
let mut buckets = RwLockUpgradableReadGuard::upgrade(buckets);
|
||||
*buckets = buckets_from_header(config, limit_header, count_header)
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
fn buckets_require_updating(limit_header: &str, buckets: &Vec<VectorTokenBucket>) -> bool {
|
||||
if buckets.len() != limit_header.split(",").count() {
|
||||
return true;
|
||||
}
|
||||
for (limit_header_entry, bucket) in limit_header.split(",").zip(&*buckets) {
|
||||
// limit_header_entry "100:60" means 100 req per 60 sec.
|
||||
let bucket_entry = format!("{}:{}", bucket.get_total_limit(), bucket.get_bucket_duration().as_secs());
|
||||
if limit_header_entry != bucket_entry {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn buckets_from_header(config: &RiotApiConfig, limit_header: &str, count_header: &str) -> Vec<VectorTokenBucket> {
|
||||
// Limits: "20000:10,1200000:600"
|
||||
// Counts: "7:10,58:600"
|
||||
let size = limit_header.split(",").count();
|
||||
debug_assert!(size == count_header.split(",").count());
|
||||
let mut out = Vec::with_capacity(size);
|
||||
|
||||
for (limit_entry, count_entry) in limit_header.split(",").zip(count_header.split(",")) {
|
||||
let (limit, limit_secs) = scan_fmt!(limit_entry, "{d}:{d}", usize, u64)
|
||||
.unwrap_or_else(|_| panic!("Failed to parse limit entry \"{}\".", limit_entry));
|
||||
let (count, count_secs) = scan_fmt!(count_entry, "{d}:{d}", usize, u64)
|
||||
.unwrap_or_else(|_| panic!("Failed to parse count entry \"{}\".", count_entry));
|
||||
debug_assert!(limit_secs == count_secs);
|
||||
|
||||
let limit_f32 = limit as f32;
|
||||
let scaled_burst_pct = config.burst_pct * limit_f32 / (limit_f32 + 1.0);
|
||||
|
||||
let bucket = VectorTokenBucket::new(Duration::from_secs(limit_secs), limit,
|
||||
config.duration_overhead, scaled_burst_pct);
|
||||
bucket.get_tokens(count);
|
||||
out.push(bucket);
|
||||
}
|
||||
log::debug!("Set buckets to {} limit, {} count.", limit_header, count_header);
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// use super::*;
|
||||
|
||||
// fn send_sync() {
|
||||
// fn is_send_sync<T: Send + Sync>() {}
|
||||
// is_send_sync::<RateLimit>();
|
||||
// }
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
use log;
|
||||
use reqwest::{ Client, StatusCode, Url };
|
||||
use tokio::time::delay_for;
|
||||
|
||||
use crate::Result;
|
||||
use crate::RiotApiError;
|
||||
use crate::RiotApiConfig;
|
||||
use crate::util::InsertOnlyCHashMap;
|
||||
|
||||
use super::RateLimit;
|
||||
use super::RateLimitType;
|
||||
|
||||
pub struct RegionalRequester {
|
||||
/// Represents the app rate limit.
|
||||
app_rate_limit: RateLimit,
|
||||
/// Represents method rate limits.
|
||||
method_rate_limits: InsertOnlyCHashMap<&'static str, RateLimit>,
|
||||
}
|
||||
|
||||
impl RegionalRequester {
|
||||
/// Request header name for the Riot API key.
|
||||
const RIOT_KEY_HEADER: &'static str = "X-Riot-Token";
|
||||
|
||||
/// HTTP status codes which are considered a success but will results in `None`.
|
||||
const NONE_STATUS_CODES: [StatusCode; 2] = [
|
||||
StatusCode::NO_CONTENT, // 204
|
||||
StatusCode::NOT_FOUND, // 404
|
||||
];
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
app_rate_limit: RateLimit::new(RateLimitType::Application),
|
||||
method_rate_limits: InsertOnlyCHashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_optional<'a, T: serde::de::DeserializeOwned>(self: Arc<Self>,
|
||||
config: &'a RiotApiConfig, client: &'a Client,
|
||||
method_id: &'static str, region_platform: &'a str, path: String, query: Option<String>)
|
||||
-> impl Future<Output = Result<Option<T>>> + 'a
|
||||
{
|
||||
async move {
|
||||
let response_result = self.get(config, client,
|
||||
method_id, region_platform, path, query).await;
|
||||
response_result.map(|value| Some(value))
|
||||
.or_else(|e| {
|
||||
if let Some(status) = e.status_code() {
|
||||
if Self::NONE_STATUS_CODES.contains(&status) {
|
||||
return Ok(None);
|
||||
}}
|
||||
Err(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get<'a, T: serde::de::DeserializeOwned>(self: Arc<Self>,
|
||||
config: &'a RiotApiConfig, client: &'a Client,
|
||||
method_id: &'static str, region_platform: &'a str, path: String, query: Option<String>)
|
||||
-> impl Future<Output = Result<T>> + 'a
|
||||
{
|
||||
async move {
|
||||
#[cfg(feature = "nightly")] let query = query.as_deref();
|
||||
#[cfg(not(feature = "nightly"))] let query = query.as_ref().map(|s| s.as_ref());
|
||||
|
||||
let mut retries: u8 = 0;
|
||||
loop {
|
||||
let method_rate_limit: Arc<RateLimit> = self.method_rate_limits
|
||||
.get_or_insert_with(method_id, || RateLimit::new(RateLimitType::Method));
|
||||
|
||||
// Rate limiting.
|
||||
while let Some(delay) = RateLimit::get_both_or_delay(&self.app_rate_limit, &*method_rate_limit) {
|
||||
delay_for(delay).await;
|
||||
}
|
||||
|
||||
// Send request.
|
||||
let url_base = format!("https://{}.api.riotgames.com", region_platform);
|
||||
let mut url = Url::parse(&*url_base)
|
||||
.unwrap_or_else(|_| panic!("Failed to parse url_base: \"{}\".", url_base));
|
||||
url.set_path(&*path);
|
||||
url.set_query(query);
|
||||
|
||||
let response = client.get(url)
|
||||
.header(Self::RIOT_KEY_HEADER, &*config.api_key)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| RiotApiError::new(e, retries, None, None))?;
|
||||
|
||||
// Maybe update rate limits (based on response headers).
|
||||
self.app_rate_limit.on_response(&config, &response);
|
||||
method_rate_limit.on_response(&config, &response);
|
||||
|
||||
let status = response.status();
|
||||
// Handle normal success / failure cases.
|
||||
match response.error_for_status_ref() {
|
||||
// Success.
|
||||
Ok(_response) => {
|
||||
log::trace!("Response {} (retried {} times), parsed result.", status, retries);
|
||||
let value = response.json::<T>().await;
|
||||
break value.map_err(|e| RiotApiError::new(e, retries, None, Some(status)));
|
||||
},
|
||||
// Failure, may or may not be retryable.
|
||||
Err(err) => {
|
||||
// Not-retryable: no more retries or 4xx or ? (3xx, redirects exceeded).
|
||||
// Retryable: retries remaining, and 429 or 5xx.
|
||||
if retries >= config.retries ||
|
||||
(StatusCode::TOO_MANY_REQUESTS != status
|
||||
&& !status.is_server_error())
|
||||
{
|
||||
log::debug!("Response {} (retried {} times), returning.", status, retries);
|
||||
break Err(RiotApiError::new(err, retries, Some(response), Some(status)));
|
||||
}
|
||||
log::debug!("Response {} (retried {} times), retrying.", status, retries);
|
||||
},
|
||||
};
|
||||
|
||||
retries += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// use super::*;
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
#![cfg(test)]
|
||||
|
||||
use fake_instant::FakeClock as Instant;
|
||||
|
||||
/// This is a hack to test token bucket, substituting FakeClock for Instant.
|
||||
mod token_bucket {
|
||||
include!("token_bucket.rs");
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref D00: Duration = Duration::new(0, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
Instant::set_time(50_000);
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 0.95);
|
||||
assert!(bucket.get_tokens(50), "Should have not violated limit.");
|
||||
assert_eq!(None, bucket.get_delay(), "Can get stuff.");
|
||||
assert!(!bucket.get_tokens(51), "Should have violated limit.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_internal_constructor() {
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 1.0);
|
||||
assert_eq!(100, bucket.burst_limit);
|
||||
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 1e-6);
|
||||
assert_eq!(1, bucket.burst_limit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saturated_100_burst() {
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 1.00);
|
||||
|
||||
Instant::set_time(50_000);
|
||||
assert!(bucket.get_tokens(100), "All tokens should be immediately available.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(1001); // Extra buffer for Duration(0).
|
||||
assert!(bucket.get_tokens(100), "All tokens should be available after a bucket duration.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saturated_95_burst() {
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 0.50);
|
||||
|
||||
Instant::set_time(50_000);
|
||||
assert!(bucket.get_tokens(95), "95 tokens should be immediately available.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(475); // Total 951.
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(476); // Extra buffer for Duration(0).
|
||||
assert!(bucket.get_tokens(5), "Last 5 tokens should be available.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(51);
|
||||
assert!(bucket.get_tokens(95), "95 tokens should be available.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(951);
|
||||
assert!(bucket.get_tokens(5), "Last 5 tokens should be available.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saturated_50_burst() {
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 0.5);
|
||||
|
||||
Instant::set_time(50_000);
|
||||
assert!(bucket.get_tokens(50), "Half the tokens should be immediately available.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(501); // Extra buffer for Duration(0).
|
||||
assert!(bucket.get_tokens(50), "Half the tokens should be available after a half bucket duration.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(501);
|
||||
assert!(bucket.get_tokens(50), "Half the tokens should be available after a full bucket duration.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
|
||||
Instant::advance_time(501);
|
||||
assert!(bucket.get_tokens(50));
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_many() {
|
||||
Instant::set_time(50_000);
|
||||
let bucket = VectorTokenBucket::new(Duration::from_millis(1000), 100, *D00, 0.95);
|
||||
assert!(bucket.get_tokens(50), "Should have not violated limit.");
|
||||
assert_eq!(None, bucket.get_delay(), "Should not be blocked.");
|
||||
for _ in 0..20_000 {
|
||||
Instant::advance_time(501);
|
||||
assert!(bucket.get_tokens(50), "Should have not violated limit.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
Instant::advance_time(501);
|
||||
assert!(bucket.get_tokens(50), "Should have not violated limit.");
|
||||
assert_ne!(None, bucket.get_delay(), "Bucket should have delay.");
|
||||
}
|
||||
assert!(bucket.timestamps.lock().len() < 110, "Check memory leak.");
|
||||
}
|
||||
}
|
||||
}
|
109
src/riot_api.rs
109
src/riot_api.rs
|
@ -1,109 +0,0 @@
|
|||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
use log;
|
||||
use reqwest::Client;
|
||||
|
||||
use crate::Result;
|
||||
use crate::RiotApiConfig;
|
||||
use crate::req::RegionalRequester;
|
||||
use crate::util::InsertOnlyCHashMap;
|
||||
|
||||
/// For retrieving data from the Riot Games API.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// Construct an instance using [`with_key(api_key)`](RiotApi::with_key) or
|
||||
/// [`with_config(config)`](RiotApi::with_config).
|
||||
///
|
||||
/// An instance provides access to "endpoint handles" which in turn provide access
|
||||
/// to individual API method calls. For example, getting a summoner by name:
|
||||
/// ```ignore
|
||||
/// riot_api.summoner_v4().get_by_summoner_name(Region::NA, "LugnutsK")
|
||||
/// ```
|
||||
///
|
||||
/// # Rate Limiting
|
||||
///
|
||||
/// The Riot Game API enforces _dynamic_ rate limiting, meaning that rate limits are
|
||||
/// specified in response headers and (theoretically) could change at any time.
|
||||
/// Riven keeps track of changing rate limits seamlessly, preventing you from
|
||||
/// getting blacklisted.
|
||||
///
|
||||
/// Riven's rate limiting is highly efficient, meaning that it can reach the limits
|
||||
/// of your rate limit without going over.
|
||||
///
|
||||
/// To adjust rate limiting, see [RiotApiConfig](crate::RiotApiConfig) and use
|
||||
/// [`with_config(config)`](RiotApi::with_config) to construct an instance.
|
||||
pub struct RiotApi {
|
||||
/// Configuration settings.
|
||||
config: RiotApiConfig,
|
||||
/// Client for making requests.
|
||||
client: Client,
|
||||
|
||||
/// Per-region requesters.
|
||||
regional_requesters: InsertOnlyCHashMap<&'static str, RegionalRequester>,
|
||||
}
|
||||
|
||||
impl RiotApi {
|
||||
/// Constructs a new instance from the given [RiotApiConfig](crate::RiotApiConfig), consuming it.
|
||||
pub fn with_config(mut config: RiotApiConfig) -> Self {
|
||||
let client_builder = config.client_builder.take()
|
||||
.expect("!NONE CLIENT_BUILDER IN CONFIG.");
|
||||
Self {
|
||||
config: config,
|
||||
client: client_builder.build().expect("Failed to create client from builder."),
|
||||
regional_requesters: InsertOnlyCHashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new instance from the given API key, using default configuration.
|
||||
///
|
||||
/// `api_key` should be a Riot Games API key from
|
||||
/// [https://developer.riotgames.com/](https://developer.riotgames.com/),
|
||||
/// and should look like `"RGAPI-01234567-89ab-cdef-0123-456789abcdef"`.
|
||||
pub fn with_key<T: Into<String>>(api_key: T) -> Self {
|
||||
Self::with_config(RiotApiConfig::with_key(api_key))
|
||||
}
|
||||
|
||||
/// This method is not meant to be used directly.
|
||||
///
|
||||
/// This sends a GET request based on the given parameters and returns an optional parsed result.
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
|
||||
/// * `region_platform` - The stringified platform, prepended to `.api.riotgames.com` to create the hostname.
|
||||
/// * `path` - The path relative to the hostname.
|
||||
/// * `query` - An optional query string.
|
||||
pub fn get_optional<'a, T: serde::de::DeserializeOwned + 'a>(&'a self,
|
||||
method_id: &'static str, region_platform: &'static str, path: String, query: Option<String>)
|
||||
-> impl Future<Output = Result<Option<T>>> + 'a
|
||||
{
|
||||
self.regional_requester(region_platform)
|
||||
.get_optional(&self.config, &self.client, method_id, region_platform, path, query)
|
||||
}
|
||||
|
||||
/// This method is not meant to be used directly.
|
||||
///
|
||||
/// This sends a GET request based on the given parameters and returns a parsed result.
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `method_id` - A unique string id representing the endpoint method for per-method rate limiting.
|
||||
/// * `region_platform` - The stringified platform, prepended to `.api.riotgames.com` to create the hostname.
|
||||
/// * `path` - The path relative to the hostname.
|
||||
/// * `query` - An optional query string.
|
||||
pub fn get<'a, T: serde::de::DeserializeOwned + 'a>(&'a self,
|
||||
method_id: &'static str, region_platform: &'static str, path: String, query: Option<String>)
|
||||
-> impl Future<Output = Result<T>> + 'a
|
||||
{
|
||||
self.regional_requester(region_platform)
|
||||
.get(&self.config, &self.client, method_id, region_platform, path, query)
|
||||
}
|
||||
|
||||
/// Get or create the RegionalRequester for the given region.
|
||||
fn regional_requester(&self, region_platform: &'static str) -> Arc<RegionalRequester> {
|
||||
self.regional_requesters.get_or_insert_with(region_platform, || {
|
||||
log::debug!("Creating requester for region platform {}.", region_platform);
|
||||
RegionalRequester::new()
|
||||
})
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue