#![warn(
clippy::complexity,
clippy::correctness,
clippy::perf,
clippy::style,
clippy::missing_const_for_fn,
clippy::undocumented_unsafe_blocks,
missing_docs,
rust_2018_idioms
)]
use std::fmt;
use reqwest::{header::AUTHORIZATION, RequestBuilder, Response, StatusCode};
pub mod category;
pub mod game;
pub mod race;
pub mod run;
pub mod runner;
mod schema;
mod wrapper;
pub use schema::*;
pub use uuid;
pub struct Client {
client: reqwest::Client,
access_token: Option<String>,
}
impl Default for Client {
fn default() -> Self {
#[allow(unused_mut)]
let mut builder = reqwest::Client::builder();
#[cfg(not(target_family = "wasm"))]
{
builder = builder.http2_prior_knowledge();
#[cfg(feature = "rustls")]
{
builder = builder.use_rustls_tls();
}
}
Client {
client: builder.build().unwrap(),
access_token: None,
}
}
}
impl Client {
pub fn new() -> Self {
Self::default()
}
pub fn set_access_token(&mut self, access_token: &str) {
let buf = self.access_token.get_or_insert_with(String::new);
buf.clear();
buf.push_str("Bearer ");
buf.push_str(access_token);
}
}
#[derive(Debug)]
pub enum Error {
Status {
status: StatusCode,
},
Api {
status: StatusCode,
message: Box<str>,
},
Download {
source: reqwest::Error,
},
UnidentifiableResource,
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Status { status } => {
write!(
fmt,
"HTTP Status: {}",
status.canonical_reason().unwrap_or_else(|| status.as_str()),
)
}
Error::Api { message, .. } => fmt::Display::fmt(message, fmt),
Error::Download { .. } => {
fmt::Display::fmt("Failed downloading the response.", fmt)
}
Error::UnidentifiableResource => {
fmt::Display::fmt(
"The resource can not be sufficiently identified for finding resources attached to it.",
fmt,
)
}
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Status { .. } => None,
Error::Api { .. } => None,
Error::Download { source, .. } => Some(source),
Error::UnidentifiableResource => None,
}
}
}
async fn get_response_unchecked(
client: &Client,
mut request: RequestBuilder,
) -> Result<Response, Error> {
if let Some(access_token) = &client.access_token {
request = request.header(AUTHORIZATION, access_token);
}
request
.send()
.await
.map_err(|source| Error::Download { source })
}
async fn get_response(client: &Client, request: RequestBuilder) -> Result<Response, Error> {
let response = get_response_unchecked(client, request).await?;
let status = response.status();
if !status.is_success() {
if let Ok(error) = response.json::<ApiError>().await {
return Err(Error::Api {
status,
message: error.error,
});
}
return Err(Error::Status { status });
}
Ok(response)
}
async fn get_json<T: serde::de::DeserializeOwned>(
client: &Client,
request: RequestBuilder,
) -> Result<T, Error> {
let response = get_response(client, request).await?;
response
.json()
.await
.map_err(|source| Error::Download { source })
}
#[derive(serde_derive::Deserialize)]
struct ApiError {
#[serde(alias = "message")]
error: Box<str>,
}