mirror of
https://github.com/MingweiSamuel/Riven.git
synced 2025-04-11 08:23:16 -07:00
feat: eserde
feature for better maintenance (#80)
Using [`eserde`](https://github.com/mainmatter/eserde) at least for testing makes it much, much easier to find all the schema changes Riot makes every other Wednesday, and therefore makes this and https://github.com/MingweiSamuel/riotapi-schema easier to maintain. Example of error output: ```rust Failed to get match JP1_498473890: Riot API request failed after 0 retries with status code Some(200). Deserialization error: Something went wrong during deserialization: - info.participants[0].PlayerScore2: invalid type: floating point `732.5479125976562`, expected i32 at line 1 column 1795 - info.participants[0].PlayerScore3: invalid type: floating point `0.3532763719558716`, expected i32 at line 1 column 1829 - info.participants[0].PlayerScore4: invalid type: floating point `-0.8999999761581421`, expected i32 at line 1 column 1864 - info.participants[0].PlayerScore5: invalid type: floating point `1428.712158203125`, expected i32 at line 1 column 1897 - info.participants[0].PlayerScore6: invalid type: floating point `3546.9150390625`, expected i32 at line 1 column 1928 - info.participants[0].PlayerScore7: invalid type: floating point `848.3316650390625`, expected i32 at line 1 column 1961 - info.participants[0].PlayerScore8: invalid type: floating point `23.0013484954834`, expected i32 at line 1 column 1993 - info.participants[0].missions.playerScore2: invalid type: floating point `732.5479125976562`, expected i32 at line 1 column 6427 - info.participants[0].missions.playerScore3: invalid type: floating point `0.3532763719558716`, expected i32 at line 1 column 6461 - info.participants[0].missions.playerScore4: invalid type: floating point `-0.8999999761581421`, expected i32 at line 1 column 6496 - info.participants[0].missions.playerScore5: invalid type: floating point `1428.712158203125`, expected i32 at line 1 column 6529 - info.participants[0].missions.playerScore6: invalid type: floating point `3546.9150390625`, expected i32 at line 1 column 6560 - info.participants[0].missions.playerScore7: invalid type: floating point `848.3316650390625`, expected i32 at line 1 column 6593 - info.participants[0].missions.playerScore8: invalid type: floating point `23.0013484954834`, expected i32 at line 1 column 6625 ```
This commit is contained in:
parent
a8c457fad7
commit
ab80137e6f
28 changed files with 1150 additions and 955 deletions
.github/workflows
.vscode
Cargo.lockriven
Cargo.toml
test-full.bashtest-wasm.bashtest.bashexamples/proxy
src
srcgen
tests
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -74,12 +74,12 @@ jobs:
|
|||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all-targets --features nightly,deny-unknown,__proxy
|
||||
args: --all-targets --features nightly,deny-unknown,eserde,__proxy
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --no-fail-fast --features nightly,deny-unknown,__proxy
|
||||
args: --no-fail-fast --features nightly,deny-unknown,eserde,__proxy
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
RUST_LOG: riven=debug
|
||||
|
@ -89,7 +89,7 @@ jobs:
|
|||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
- working-directory: riven
|
||||
run: wasm-pack test --node -- --features nightly,deny-unknown
|
||||
run: wasm-pack test --node -- --features nightly,deny-unknown,eserde
|
||||
env:
|
||||
RGAPI_KEY: ${{ secrets.RGAPI_KEY }}
|
||||
|
||||
|
|
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
|
@ -11,6 +11,9 @@
|
|||
"rust-analyzer.cargo.features": [
|
||||
"nightly",
|
||||
"tracing",
|
||||
"deny-unknown"
|
||||
]
|
||||
}
|
||||
"deny-unknown",
|
||||
"eserde",
|
||||
],
|
||||
"evenBetterToml.formatter.compactArrays": false,
|
||||
"evenBetterToml.formatter.columnWidth": 100
|
||||
}
|
25
Cargo.lock
generated
25
Cargo.lock
generated
|
@ -277,6 +277,30 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eserde"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b00bbe58b41137cb1f1421385eae8ae360ce02722ab24dcc91a45be30daeeea"
|
||||
dependencies = [
|
||||
"eserde_derive",
|
||||
"itoa",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eserde_derive"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92f146f29032c31be76a0b0a0275a5d11b5835711db47689fe4edd3017993615"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake_instant"
|
||||
version = "0.5.0"
|
||||
|
@ -1146,6 +1170,7 @@ dependencies = [
|
|||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"env_logger",
|
||||
"eserde",
|
||||
"fake_instant",
|
||||
"futures",
|
||||
"gloo-timers",
|
||||
|
|
|
@ -1,78 +1,77 @@
|
|||
[package]
|
||||
name = "riven"
|
||||
version = "2.64.0"
|
||||
authors = ["Mingwei Samuel <mingwei.samuel@gmail.com>"]
|
||||
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"
|
||||
rust-version = "1.71.1"
|
||||
include = ["src/**", "../README.md", "/examples"]
|
||||
keywords = ["riot-games", "riot", "league", "league-of-legends"]
|
||||
categories = ["api-bindings", "web-programming::http-client", "wasm"]
|
||||
include = [ "src/**", "../README.md", "/examples" ]
|
||||
keywords = [ "riot-games", "riot", "league", "league-of-legends" ]
|
||||
categories = [ "api-bindings", "web-programming::http-client", "wasm" ]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
crate-type = [ "cdylib", "rlib" ]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["nightly"]
|
||||
features = [ "nightly" ]
|
||||
|
||||
[features]
|
||||
default = ["default-tls"]
|
||||
default = [ "default-tls" ]
|
||||
|
||||
nightly = ["parking_lot/nightly"]
|
||||
nightly = [ "parking_lot/nightly" ]
|
||||
|
||||
default-tls = ["reqwest/default-tls"]
|
||||
native-tls = ["reqwest/native-tls"]
|
||||
rustls-tls = ["reqwest/rustls-tls"]
|
||||
default-tls = [ "reqwest/default-tls" ]
|
||||
native-tls = [ "reqwest/native-tls" ]
|
||||
rustls-tls = [ "reqwest/rustls-tls" ]
|
||||
|
||||
metrics = ["dep:metrics"]
|
||||
metrics = [ "dep:metrics" ]
|
||||
eserde = [ "dep:eserde" ]
|
||||
|
||||
deny-unknown = ["deny-unknown-fields", "deny-unknown-enum-variants"]
|
||||
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 = []
|
||||
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 = []
|
||||
deny-unknown-enum-variants-strings = [ ]
|
||||
deny-unknown-enum-variants-integers = [ ]
|
||||
|
||||
__proxy = []
|
||||
__proxy = [ ]
|
||||
|
||||
[[example]]
|
||||
name = "proxy"
|
||||
required-features = ["__proxy"]
|
||||
required-features = [ "__proxy" ]
|
||||
|
||||
[dependencies]
|
||||
eserde = { optional = true, version = "0.1.6", features = [ "json" ] }
|
||||
futures = "0.3.0"
|
||||
log = "0.4.8"
|
||||
memo-map = "0.3.0"
|
||||
metrics = { version = "0.24.0", optional = true }
|
||||
metrics = { optional = true, version = "0.24.0" }
|
||||
num_enum = "0.5.0"
|
||||
parking_lot = "0.12.0"
|
||||
reqwest = { version = "0.11.2", default-features = false, features = [
|
||||
"gzip",
|
||||
"json",
|
||||
] }
|
||||
serde = { version = "1.0.85", features = ["derive"] }
|
||||
reqwest = { version = "0.11.2", default-features = false, features = [ "gzip" ] }
|
||||
serde = { version = "1.0.85", features = [ "derive" ] }
|
||||
serde_derive = "1.0.85"
|
||||
serde_json = "1.0.1"
|
||||
serde_repr = "0.1.0"
|
||||
slab = "0.4.4"
|
||||
strum = "0.20.0"
|
||||
strum_macros = "0.20.0"
|
||||
tracing = { version = "0.1.22", optional = true }
|
||||
tracing = { optional = true, version = "0.1.22" }
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
tokio = { version = "1.20.0", default-features = false, features = ["time"] }
|
||||
tokio = { version = "1.20.0", default-features = false, features = [ "time" ] }
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
gloo-timers = { version = "0.3", features = ["futures"] }
|
||||
gloo-timers = { version = "0.3", features = [ "futures" ] }
|
||||
web-time = "1.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -82,8 +81,8 @@ tracing = "0.1.22"
|
|||
tracing-subscriber = "0.3.17"
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
|
||||
hyper = { version = "0.14.5", features = ["server"] }
|
||||
tokio = { version = "1.20.0", features = ["macros", "rt-multi-thread"] }
|
||||
hyper = { version = "0.14.5", features = [ "server" ] }
|
||||
tokio = { version = "1.20.0", features = [ "macros", "rt-multi-thread" ] }
|
||||
tokio-shared-rt = "0.1.0"
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dev-dependencies]
|
||||
|
|
|
@ -84,7 +84,7 @@ async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible
|
|||
.await;
|
||||
let resp_info = match resp_result {
|
||||
Err(err) => {
|
||||
log::info!("Riot API error: {:#?}", err.source_reqwest_error());
|
||||
log::info!("Riot API error: {}", err);
|
||||
return Ok(create_json_response(
|
||||
r#"{"error":"Riot API request failed."}"#,
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||
|
||||
|
@ -25,8 +26,8 @@ use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
|||
IntoStaticStr,
|
||||
IntoPrimitive,
|
||||
TryFromPrimitive,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
serde::Serialize,
|
||||
crate::de::Deserialize,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum Division {
|
||||
|
|
|
@ -7,14 +7,13 @@
|
|||
// //
|
||||
///////////////////////////////////////////////
|
||||
|
||||
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)]
|
||||
#[derive(serde::Serialize, crate::de::Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum GameType {
|
||||
/// Custom games
|
||||
|
|
|
@ -65,6 +65,7 @@ macro_rules! serde_strum_unknown {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl_edeserialize!($name);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -172,6 +173,7 @@ macro_rules! newtype_enum {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl_edeserialize!($name);
|
||||
|
||||
impl std::fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
|
@ -185,3 +187,21 @@ macro_rules! newtype_enum {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_edeserialize {
|
||||
($($t:ty),* $(,)?) => {
|
||||
$(
|
||||
#[cfg(feature = "eserde")]
|
||||
impl<'de> eserde::EDeserialize<'de> for $t {
|
||||
fn deserialize_for_errors<D>(deserializer: D) -> Result<(), ()>
|
||||
where
|
||||
D: serde::Deserializer<'de>
|
||||
{
|
||||
<Self as serde::de::Deserialize<'de>>::deserialize(deserializer).map(|_| ()).map_err(|e| {
|
||||
eserde::reporter::ErrorReporter::report(e);
|
||||
})
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// Do not directly edit! //
|
||||
// //
|
||||
///////////////////////////////////////////////
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||
use strum_macros::{ EnumString, EnumIter, Display, IntoStaticStr };
|
||||
|
@ -332,7 +333,7 @@ pub enum ValPlatformRoute {
|
|||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(serde::Serialize, crate::de::Deserialize)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
|
|
|
@ -29,3 +29,5 @@ pub enum Team {
|
|||
/// "killerTeamId" when Baron Nashor spawns and kills Rift Herald.
|
||||
OTHER = 300,
|
||||
}
|
||||
|
||||
impl_edeserialize!(Team);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
||||
|
||||
|
@ -25,8 +26,8 @@ use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
|
|||
Display,
|
||||
AsRefStr,
|
||||
IntoStaticStr,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
serde::Serialize,
|
||||
crate::de::Deserialize,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum Tier {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
///////////////////////////////////////////////
|
||||
|
||||
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
||||
// Version 1b37b11075bdad584e6a46a1bdeffa7ad35c4303
|
||||
// Version 0ec1ee73a0d4f3138f9cbbe51b2787d41c3d8892
|
||||
|
||||
//! Automatically generated endpoint handles.
|
||||
#![allow(clippy::let_and_return, clippy::too_many_arguments)]
|
||||
|
@ -1183,7 +1183,9 @@ impl<'a> LorDeckV1<'a> {
|
|||
let request = self.base.request(Method::POST, route_str, "/lor/deck/v1/decks/me");
|
||||
let mut request = request.bearer_auth(access_token);
|
||||
if let Some(clear) = self.base.get_rso_clear_header() { request = request.header(clear, "") }
|
||||
let request = request.body(serde_json::ser::to_vec(body).unwrap());
|
||||
let request = request
|
||||
.body(serde_json::ser::to_vec(body).unwrap())
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json");
|
||||
let future = self.base.execute_val::<String>("lor-deck-v1.createDeck", route_str, request);
|
||||
#[cfg(feature = "tracing")]
|
||||
let future = future.instrument(tracing::info_span!("lor-deck-v1.createDeck", route = route_str));
|
||||
|
@ -2053,7 +2055,9 @@ impl<'a> TournamentStubV5<'a> {
|
|||
let request = self.base.request(Method::POST, route_str, "/lol/tournament-stub/v5/codes");
|
||||
let request = request.query(&[ ("tournamentId", tournament_id) ]);
|
||||
let request = if let Some(count) = count { request.query(&[ ("count", count) ]) } else { request };
|
||||
let request = request.body(serde_json::ser::to_vec(body).unwrap());
|
||||
let request = request
|
||||
.body(serde_json::ser::to_vec(body).unwrap())
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json");
|
||||
let future = self.base.execute_val::<Vec<String>>("tournament-stub-v5.createTournamentCode", route_str, request);
|
||||
#[cfg(feature = "tracing")]
|
||||
let future = future.instrument(tracing::info_span!("tournament-stub-v5.createTournamentCode", route = route_str));
|
||||
|
@ -2118,7 +2122,9 @@ impl<'a> TournamentStubV5<'a> {
|
|||
{
|
||||
let route_str = route.into();
|
||||
let request = self.base.request(Method::POST, route_str, "/lol/tournament-stub/v5/providers");
|
||||
let request = request.body(serde_json::ser::to_vec(body).unwrap());
|
||||
let request = request
|
||||
.body(serde_json::ser::to_vec(body).unwrap())
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json");
|
||||
let future = self.base.execute_val::<i32>("tournament-stub-v5.registerProviderData", route_str, request);
|
||||
#[cfg(feature = "tracing")]
|
||||
let future = future.instrument(tracing::info_span!("tournament-stub-v5.registerProviderData", route = route_str));
|
||||
|
@ -2139,7 +2145,9 @@ impl<'a> TournamentStubV5<'a> {
|
|||
{
|
||||
let route_str = route.into();
|
||||
let request = self.base.request(Method::POST, route_str, "/lol/tournament-stub/v5/tournaments");
|
||||
let request = request.body(serde_json::ser::to_vec(body).unwrap());
|
||||
let request = request
|
||||
.body(serde_json::ser::to_vec(body).unwrap())
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json");
|
||||
let future = self.base.execute_val::<i32>("tournament-stub-v5.registerTournament", route_str, request);
|
||||
#[cfg(feature = "tracing")]
|
||||
let future = future.instrument(tracing::info_span!("tournament-stub-v5.registerTournament", route = route_str));
|
||||
|
@ -2176,7 +2184,9 @@ impl<'a> TournamentV5<'a> {
|
|||
let request = self.base.request(Method::POST, route_str, "/lol/tournament/v5/codes");
|
||||
let request = request.query(&[ ("tournamentId", tournament_id) ]);
|
||||
let request = if let Some(count) = count { request.query(&[ ("count", count) ]) } else { request };
|
||||
let request = request.body(serde_json::ser::to_vec(body).unwrap());
|
||||
let request = request
|
||||
.body(serde_json::ser::to_vec(body).unwrap())
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json");
|
||||
let future = self.base.execute_val::<Vec<String>>("tournament-v5.createTournamentCode", route_str, request);
|
||||
#[cfg(feature = "tracing")]
|
||||
let future = future.instrument(tracing::info_span!("tournament-v5.createTournamentCode", route = route_str));
|
||||
|
@ -2219,7 +2229,9 @@ impl<'a> TournamentV5<'a> {
|
|||
{
|
||||
let route_str = route.into();
|
||||
let request = self.base.request(Method::PUT, route_str, &format!("/lol/tournament/v5/codes/{}", tournament_code));
|
||||
let request = request.body(serde_json::ser::to_vec(body).unwrap());
|
||||
let request = request
|
||||
.body(serde_json::ser::to_vec(body).unwrap())
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json");
|
||||
let future = self.base.execute("tournament-v5.updateCode", route_str, request);
|
||||
#[cfg(feature = "tracing")]
|
||||
let future = future.instrument(tracing::info_span!("tournament-v5.updateCode", route = route_str));
|
||||
|
@ -2290,7 +2302,9 @@ impl<'a> TournamentV5<'a> {
|
|||
{
|
||||
let route_str = route.into();
|
||||
let request = self.base.request(Method::POST, route_str, "/lol/tournament/v5/providers");
|
||||
let request = request.body(serde_json::ser::to_vec(body).unwrap());
|
||||
let request = request
|
||||
.body(serde_json::ser::to_vec(body).unwrap())
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json");
|
||||
let future = self.base.execute_val::<i32>("tournament-v5.registerProviderData", route_str, request);
|
||||
#[cfg(feature = "tracing")]
|
||||
let future = future.instrument(tracing::info_span!("tournament-v5.registerProviderData", route = route_str));
|
||||
|
@ -2311,7 +2325,9 @@ impl<'a> TournamentV5<'a> {
|
|||
{
|
||||
let route_str = route.into();
|
||||
let request = self.base.request(Method::POST, route_str, "/lol/tournament/v5/tournaments");
|
||||
let request = request.body(serde_json::ser::to_vec(body).unwrap());
|
||||
let request = request
|
||||
.body(serde_json::ser::to_vec(body).unwrap())
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json");
|
||||
let future = self.base.execute_val::<i32>("tournament-v5.registerTournament", route_str, request);
|
||||
#[cfg(feature = "tracing")]
|
||||
let future = future.instrument(tracing::info_span!("tournament-v5.registerTournament", route = route_str));
|
||||
|
|
|
@ -1,55 +1,73 @@
|
|||
use std::fmt;
|
||||
|
||||
use reqwest::{Error, Response, StatusCode};
|
||||
use reqwest::{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,
|
||||
reqwest_errors: Vec<reqwest::Error>,
|
||||
de_error: Option<crate::de::Error>,
|
||||
retries: u8,
|
||||
response: Option<Response>,
|
||||
status_code: Option<StatusCode>,
|
||||
}
|
||||
impl RiotApiError {
|
||||
pub(crate) fn new(
|
||||
reqwest_error: Error,
|
||||
reqwest_errors: Vec<reqwest::Error>,
|
||||
serde_error: Option<crate::de::Error>,
|
||||
retries: u8,
|
||||
response: Option<Response>,
|
||||
status_code: Option<StatusCode>,
|
||||
) -> Self {
|
||||
Self {
|
||||
reqwest_error,
|
||||
reqwest_errors,
|
||||
de_error: serde_error,
|
||||
retries,
|
||||
response,
|
||||
status_code,
|
||||
}
|
||||
}
|
||||
/// The reqwest::Error for the final failed request.
|
||||
pub fn source_reqwest_error(&self) -> &Error {
|
||||
&self.reqwest_error
|
||||
|
||||
/// Returns the final `reqwest::Error`, for the final failed request, or panics if this was a deserialization error.
|
||||
#[deprecated = "Use `reqwest_errors()` or `de_error()` instead."]
|
||||
pub fn source_reqwest_error(&self) -> &reqwest::Error {
|
||||
self.reqwest_errors.last().unwrap()
|
||||
}
|
||||
|
||||
/// Returns all `reqwest::Error`s across all retries, in the chronological order they occurred.
|
||||
///
|
||||
/// May be empty if there was a deserialization error.
|
||||
pub fn reqwest_errors(&self) -> &[reqwest::Error] {
|
||||
&self.reqwest_errors
|
||||
}
|
||||
|
||||
/// Returns the final deserialization error if any occured.
|
||||
pub fn de_error(&self) -> Option<&crate::de::Error> {
|
||||
self.de_error.as_ref()
|
||||
}
|
||||
|
||||
/// The number of retires attempted. Zero means exactly one request, zero retries.
|
||||
pub fn retries(&self) -> u8 {
|
||||
self.retries
|
||||
}
|
||||
/// The failed response.
|
||||
|
||||
/// The failed, unparsed 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.
|
||||
|
@ -59,11 +77,31 @@ impl RiotApiError {
|
|||
}
|
||||
impl fmt::Display for RiotApiError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:#?}", self)
|
||||
writeln!(
|
||||
f,
|
||||
"Riot API request failed after {} retries with status code {:?}{}:",
|
||||
self.retries(),
|
||||
self.status_code(),
|
||||
if self.response().is_some() {
|
||||
" (response not parsed)"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
)?;
|
||||
for (i, reqwest_error) in self.reqwest_errors().iter().enumerate() {
|
||||
writeln!(f, "- Reqwest error {}: {}", i + 1, reqwest_error)?;
|
||||
}
|
||||
if let Some(serde_error) = self.de_error() {
|
||||
writeln!(f, "- Deserialization error: {}", serde_error)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl std::error::Error for RiotApiError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
Some(&self.reqwest_error)
|
||||
self.reqwest_errors()
|
||||
.last()
|
||||
.map(|e| e as _)
|
||||
.or_else(|| self.de_error().map(|e| e as _))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -190,8 +190,12 @@
|
|||
//! To run the srcgen use `node riven/srcgen` from the repository root.
|
||||
//!
|
||||
|
||||
// Re-exported reqwest types.
|
||||
// Re-exported crates.
|
||||
pub use reqwest;
|
||||
pub use serde;
|
||||
#[cfg(feature = "eserde")]
|
||||
pub use eserde;
|
||||
|
||||
|
||||
mod config;
|
||||
pub use config::RiotApiConfig;
|
||||
|
@ -236,3 +240,17 @@ pub mod time {
|
|||
#[cfg(target_family = "wasm")]
|
||||
pub use gloo_timers::future::sleep;
|
||||
}
|
||||
|
||||
/// Deserialization utilities from either `serde_json` or `eserde`.
|
||||
#[rustfmt::skip]
|
||||
pub mod de {
|
||||
#[cfg(not(feature = "eserde"))]
|
||||
pub use serde::Deserialize;
|
||||
#[cfg(not(feature = "eserde"))]
|
||||
pub use serde_json::{Error, from_str, from_slice};
|
||||
|
||||
#[cfg(feature = "eserde")]
|
||||
pub use eserde::{Deserialize, EDeserialize as Deserialize, DeserializationErrors as Error};
|
||||
#[cfg(feature = "eserde")]
|
||||
pub use eserde::json::{from_str, from_slice};
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
///////////////////////////////////////////////
|
||||
|
||||
// http://www.mingweisamuel.com/riotapi-schema/tool/
|
||||
// Version 1b37b11075bdad584e6a46a1bdeffa7ad35c4303
|
||||
// Version 0ec1ee73a0d4f3138f9cbbe51b2787d41c3d8892
|
||||
|
||||
//! Metadata about the Riot API and Riven.
|
||||
//!
|
||||
|
|
1700
riven/src/models.rs
1700
riven/src/models.rs
File diff suppressed because it is too large
Load diff
|
@ -35,6 +35,7 @@ impl RegionalRequester {
|
|||
request: RequestBuilder,
|
||||
) -> Result<ResponseInfo> {
|
||||
let mut retries: u8 = 0;
|
||||
let mut reqwest_errors = Vec::new();
|
||||
loop {
|
||||
let method_rate_limit = self
|
||||
.method_rate_limits
|
||||
|
@ -58,15 +59,19 @@ impl RegionalRequester {
|
|||
Ok(response) => response,
|
||||
// Check for lower level errors, like connection errors.
|
||||
Err(e) => {
|
||||
reqwest_errors.push(e);
|
||||
if retries >= config.retries {
|
||||
log::debug!(
|
||||
"Request failed (retried {} times), failure, returning error.",
|
||||
retries
|
||||
);
|
||||
break Err(RiotApiError::new(e, retries, None, None));
|
||||
break Err(RiotApiError::new(reqwest_errors, None, retries, None, None));
|
||||
}
|
||||
let delay = Duration::from_secs(2_u64.pow(retries as u32));
|
||||
log::debug!("Request failed with cause \"{}\", (retried {} times), using exponential backoff, retrying after {:?}.", e.to_string(), retries, delay);
|
||||
log::debug!(
|
||||
"Request failed with cause \"{}\", (retried {} times), using exponential backoff, retrying after {:?}.",
|
||||
reqwest_errors.last().unwrap().to_string(), retries, delay,
|
||||
);
|
||||
let backoff = sleep(delay);
|
||||
#[cfg(feature = "tracing")]
|
||||
let backoff = backoff.instrument(tracing::info_span!("backoff"));
|
||||
|
@ -95,14 +100,16 @@ impl RegionalRequester {
|
|||
response,
|
||||
retries,
|
||||
status_none,
|
||||
reqwest_errors,
|
||||
});
|
||||
}
|
||||
let err = response.error_for_status_ref().err().unwrap_or_else(|| {
|
||||
reqwest_errors.push(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.
|
||||
|
@ -115,7 +122,8 @@ impl RegionalRequester {
|
|||
retries
|
||||
);
|
||||
break Err(RiotApiError::new(
|
||||
err,
|
||||
reqwest_errors,
|
||||
None,
|
||||
retries,
|
||||
Some(response),
|
||||
Some(status),
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use reqwest::Response;
|
||||
|
||||
use crate::{Result, RiotApiError};
|
||||
|
||||
/// A "raw" unparsed successful response from the Riot API, for internal or advanced use cases.
|
||||
#[non_exhaustive]
|
||||
pub struct ResponseInfo {
|
||||
/// The reqwest response.
|
||||
pub response: Response,
|
||||
|
@ -8,4 +11,45 @@ pub struct ResponseInfo {
|
|||
pub retries: u8,
|
||||
/// If the response has an HTTP status code indicating a `None` response (i.e. 204, 404).
|
||||
pub status_none: bool,
|
||||
/// Any reqwest errors that occurred. A response may still be successful even if this is non-empty due to retrying.
|
||||
pub reqwest_errors: Vec<reqwest::Error>,
|
||||
}
|
||||
|
||||
impl ResponseInfo {
|
||||
/// Helper to deserialize the JSON-encoded value from a `ResponseInfo`, properly handling errors.
|
||||
pub(crate) async fn json<T: for<'de> crate::de::Deserialize<'de>>(self) -> Result<T> {
|
||||
let Self {
|
||||
response,
|
||||
retries,
|
||||
status_none: _,
|
||||
mut reqwest_errors,
|
||||
} = self;
|
||||
let status = response.status();
|
||||
let bytes = match response.bytes().await {
|
||||
Ok(bytes) => bytes,
|
||||
Err(err) => {
|
||||
reqwest_errors.push(err);
|
||||
return Err(RiotApiError::new(
|
||||
reqwest_errors,
|
||||
None,
|
||||
retries,
|
||||
None, // `.bytes()` consumes the response.
|
||||
Some(status),
|
||||
));
|
||||
}
|
||||
};
|
||||
let value = match crate::de::from_slice::<T>(&bytes) {
|
||||
Ok(value) => value,
|
||||
Err(serde_err) => {
|
||||
return Err(RiotApiError::new(
|
||||
reqwest_errors,
|
||||
Some(serde_err),
|
||||
retries,
|
||||
None, // `.bytes()` consumes the response.
|
||||
Some(status),
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ impl RiotApi {
|
|||
///
|
||||
/// # 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>(
|
||||
pub async fn execute_val<'a, T: for<'de> crate::de::Deserialize<'de> + 'a>(
|
||||
&'a self,
|
||||
method_id: &'static str,
|
||||
region_platform: &'static str,
|
||||
|
@ -99,10 +99,7 @@ impl RiotApi {
|
|||
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)))
|
||||
rinfo.json::<T>().await
|
||||
}
|
||||
|
||||
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||
|
@ -116,7 +113,7 @@ impl RiotApi {
|
|||
///
|
||||
/// # 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>(
|
||||
pub async fn execute_opt<'a, T: for<'de> crate::de::Deserialize<'de> + 'a>(
|
||||
&'a self,
|
||||
method_id: &'static str,
|
||||
region_platform: &'static str,
|
||||
|
@ -128,10 +125,7 @@ impl RiotApi {
|
|||
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)))
|
||||
rinfo.json::<Option<T>>().await
|
||||
}
|
||||
|
||||
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||
|
@ -156,11 +150,17 @@ impl RiotApi {
|
|||
.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)))
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
Err(RiotApiError::new(
|
||||
rinfo.reqwest_errors,
|
||||
None,
|
||||
retries,
|
||||
Some(rinfo.response),
|
||||
Some(status),
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This method should generally not be used directly. Consider using endpoint wrappers instead.
|
||||
|
|
|
@ -3,14 +3,13 @@
|
|||
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)]
|
||||
#[derive(serde::Serialize, crate::de::Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum GameType {
|
||||
{{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
const dotUtils = require('./dotUtils.js');
|
||||
const routesTable = require('./.routesTable.json');
|
||||
}}{{= dotUtils.preamble() }}
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
use num_enum::{ IntoPrimitive, TryFromPrimitive };
|
||||
use strum_macros::{ EnumString, EnumIter, Display, IntoStaticStr };
|
||||
|
@ -171,7 +172,7 @@ pub enum ValPlatformRoute {
|
|||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(IntoPrimitive, TryFromPrimitive)]
|
||||
#[derive(EnumString, EnumIter, Display, IntoStaticStr)]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(serde::Serialize, crate::de::Deserialize)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
|
|
|
@ -199,7 +199,9 @@ impl<'a> {{= endpoint }}<'a> {
|
|||
{{= dotUtils.formatAddHeaderParam(headerParam) }}
|
||||
{{~}}
|
||||
{{? bodyType }}
|
||||
let request = request.body(serde_json::ser::to_vec(body).unwrap());
|
||||
let request = request
|
||||
.body(serde_json::ser::to_vec(body).unwrap())
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json");
|
||||
{{?}}
|
||||
let future = self.base.execute{{= hasReturn ? (returnOptional ? '_opt' : '_val') : '' }}{{= returnTypeTurbofish }}("{{= operationId }}", route_str, request);
|
||||
#[cfg(feature = "tracing")]
|
||||
|
|
|
@ -10,8 +10,12 @@
|
|||
//!{{= line ? (' ' + line) : '' }}
|
||||
{{~}}
|
||||
|
||||
// Re-exported reqwest types.
|
||||
// Re-exported crates.
|
||||
pub use reqwest;
|
||||
pub use serde;
|
||||
#[cfg(feature = "eserde")]
|
||||
pub use eserde;
|
||||
|
||||
|
||||
mod config;
|
||||
pub use config::RiotApiConfig;
|
||||
|
@ -56,3 +60,17 @@ pub mod time {
|
|||
#[cfg(target_family = "wasm")]
|
||||
pub use gloo_timers::future::sleep;
|
||||
}
|
||||
|
||||
/// Deserialization utilities from either `serde_json` or `eserde`.
|
||||
#[rustfmt::skip]
|
||||
pub mod de {
|
||||
#[cfg(not(feature = "eserde"))]
|
||||
pub use serde::Deserialize;
|
||||
#[cfg(not(feature = "eserde"))]
|
||||
pub use serde_json::{Error, from_str, from_slice};
|
||||
|
||||
#[cfg(feature = "eserde")]
|
||||
pub use eserde::{Deserialize, EDeserialize as Deserialize, DeserializationErrors as Error};
|
||||
#[cfg(feature = "eserde")]
|
||||
pub use eserde::json::{from_str, from_slice};
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
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) }} {
|
||||
|
@ -37,7 +37,7 @@ pub mod {{= dotUtils.changeCase.snakeCase(endpoint) }} {
|
|||
const props = schema.properties;
|
||||
const requiredSet = new Set(schema.required);
|
||||
}}
|
||||
/// {{= schemaName }} data object.
|
||||
/// `{{= endpoint }}.{{= rawSchemaName }}` data object.
|
||||
{{? schema.description }}
|
||||
/// # Description
|
||||
/// {{= schema.description.split('\n').map(x => x.trim()).join('<br>\r\n /// ') }}
|
||||
|
@ -45,7 +45,7 @@ pub mod {{= dotUtils.changeCase.snakeCase(endpoint) }} {
|
|||
/// Note: This struct is automatically generated
|
||||
{{?}}
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(serde::Serialize, crate::de::Deserialize)]
|
||||
#[cfg_attr(feature = "deny-unknown-fields", serde(deny_unknown_fields))]
|
||||
pub struct {{= schemaName }} {
|
||||
{{
|
||||
|
@ -65,7 +65,7 @@ pub mod {{= dotUtils.changeCase.snakeCase(endpoint) }} {
|
|||
{{?}}
|
||||
{{= dotUtils.formatJsonProperty(propKey, prop) }}
|
||||
{{? optional }}
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
{{?}}
|
||||
{{? 'championId' === propKey && (prop.description || '').includes('this field returned invalid championIds') }}
|
||||
///
|
||||
|
|
|
@ -178,7 +178,7 @@ pub async fn tft_match_v1_get(
|
|||
let p = riot_api().tft_match_v1().get_match(route, matche);
|
||||
let m = p
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get tft match {}: {:?}", matche, e))?
|
||||
.map_err(|e| format!("Failed to get tft match {}: {}", matche, e))?
|
||||
.ok_or(format!("Match {} not found.", matche))?;
|
||||
|
||||
if matche != &*m.metadata.match_id {
|
||||
|
@ -219,7 +219,7 @@ pub async fn match_v5_get(
|
|||
let p = riot_api().match_v5().get_match(route, matche);
|
||||
let m = p
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get match {}: {:?}", matche, e))?
|
||||
.map_err(|e| format!("Failed to get match {}: {}", matche, e))?
|
||||
.ok_or(format!("Match {} not found.", matche))?;
|
||||
|
||||
if matche != &*m.metadata.match_id {
|
||||
|
@ -279,7 +279,7 @@ pub async fn match_v5_get_timeline(
|
|||
let p = riot_api().match_v5().get_timeline(route, matche);
|
||||
let m = p
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get match timeline {}: {:?}", matche, e))?
|
||||
.map_err(|e| format!("Failed to get match timeline {}: {}", matche, e))?
|
||||
.ok_or(format!("Match {} not found.", matche))?;
|
||||
if matche != &*m.metadata.match_id {
|
||||
return Err(format!(
|
||||
|
@ -474,7 +474,7 @@ pub async fn val_match_v1_get(
|
|||
let p = riot_api().val_match_v1().get_match(route, matche);
|
||||
let m = p
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get val match {}: {:?}", matche, e))?
|
||||
.map_err(|e| format!("Failed to get val match {}: {}", matche, e))?
|
||||
.ok_or(format!("Match {} not found.", matche))?;
|
||||
|
||||
// TODO(mingwei): Check the match a bit.
|
||||
|
|
|
@ -7,7 +7,7 @@ cargo +stable check --all-targets --features metrics,tracing,__proxy
|
|||
|
||||
# Ensure nightly builds.
|
||||
cargo check --all-targets --features nightly,metrics,tracing,__proxy
|
||||
cargo build --all-targets --features nightly,deny-unknown,__proxy
|
||||
cargo build --all-targets --features nightly,deny-unknown,eserde,__proxy
|
||||
|
||||
# Run nightly tests.
|
||||
bash test.bash
|
||||
|
|
|
@ -9,4 +9,4 @@ cd riven
|
|||
wasm-pack build -- --features nightly,tracing,metrics
|
||||
|
||||
# Run tests.
|
||||
wasm-pack test --node -- --features nightly,deny-unknown
|
||||
wasm-pack test --node -- --features nightly,deny-unknown,eserde
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
set -euxo pipefail
|
||||
|
||||
RGAPI_KEY="$(cat apikey.txt)" RUST_BACKTRACE=full RUST_LOG=riven=debug cargo test --no-fail-fast --features nightly,tracing,deny-unknown -- --nocapture
|
||||
RGAPI_KEY="$(cat apikey.txt)" RUST_BACKTRACE=full RUST_LOG=riven=debug cargo test --no-fail-fast --features nightly,tracing,eserde,deny-unknown,eserde -- --nocapture
|
||||
|
|
Loading…
Reference in a new issue