157 lines
3.9 KiB
Rust
157 lines
3.9 KiB
Rust
use http::{HeaderMap, HeaderValue, header::AUTHORIZATION};
|
|
use serde::Deserialize;
|
|
|
|
use crate::{
|
|
request::{ApiResponse, IntoRequest},
|
|
scopes::{FactionScope, ForumScope, MarketScope, RacingScope, TornScope, UserScope},
|
|
};
|
|
|
|
pub trait Executor {
|
|
type Error: From<serde_json::Error> + From<crate::ApiError> + Send;
|
|
|
|
fn execute<R>(
|
|
&self,
|
|
request: R,
|
|
) -> impl Future<Output = Result<ApiResponse<R::Discriminant>, Self::Error>> + Send
|
|
where
|
|
R: IntoRequest;
|
|
|
|
fn fetch<R>(&self, request: R) -> impl Future<Output = Result<R::Response, Self::Error>> + Send
|
|
where
|
|
R: IntoRequest,
|
|
{
|
|
// HACK: workaround for not using `async` in trait declaration.
|
|
// The future is `Send` but `&self` might not be.
|
|
let fut = self.execute(request);
|
|
async {
|
|
let resp = fut.await?;
|
|
|
|
let bytes = resp.body.unwrap();
|
|
|
|
if bytes.starts_with(br#"{"error":{"#) {
|
|
#[derive(Deserialize)]
|
|
struct ErrorBody<'a> {
|
|
code: u16,
|
|
error: &'a str,
|
|
}
|
|
#[derive(Deserialize)]
|
|
struct ErrorContainer<'a> {
|
|
#[serde(borrow)]
|
|
error: ErrorBody<'a>,
|
|
}
|
|
|
|
let error: ErrorContainer = serde_json::from_slice(&bytes)?;
|
|
return Err(crate::ApiError::new(error.error.code, error.error.error).into());
|
|
}
|
|
|
|
let resp = serde_json::from_slice(&bytes)?;
|
|
|
|
Ok(resp)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct ReqwestClient(reqwest::Client);
|
|
|
|
impl ReqwestClient {
|
|
pub fn new(api_key: &str) -> Self {
|
|
let mut headers = HeaderMap::with_capacity(1);
|
|
headers.insert(
|
|
AUTHORIZATION,
|
|
HeaderValue::from_str(&format!("ApiKey {api_key}")).unwrap(),
|
|
);
|
|
|
|
let client = reqwest::Client::builder()
|
|
.default_headers(headers)
|
|
.brotli(true)
|
|
.build()
|
|
.unwrap();
|
|
|
|
Self(client)
|
|
}
|
|
}
|
|
|
|
pub trait ExecutorExt: Executor + Sized {
|
|
fn user(&self) -> UserScope<'_, Self>;
|
|
|
|
fn faction(&self) -> FactionScope<'_, Self>;
|
|
|
|
fn torn(&self) -> TornScope<'_, Self>;
|
|
|
|
fn market(&self) -> MarketScope<'_, Self>;
|
|
|
|
fn racing(&self) -> RacingScope<'_, Self>;
|
|
|
|
fn forum(&self) -> ForumScope<'_, Self>;
|
|
}
|
|
|
|
impl<T> ExecutorExt for T
|
|
where
|
|
T: Executor + Sized,
|
|
{
|
|
fn user(&self) -> UserScope<'_, Self> {
|
|
UserScope::new(self)
|
|
}
|
|
|
|
fn faction(&self) -> FactionScope<'_, Self> {
|
|
FactionScope::new(self)
|
|
}
|
|
|
|
fn torn(&self) -> TornScope<'_, Self> {
|
|
TornScope::new(self)
|
|
}
|
|
|
|
fn market(&self) -> MarketScope<'_, Self> {
|
|
MarketScope::new(self)
|
|
}
|
|
|
|
fn racing(&self) -> RacingScope<'_, Self> {
|
|
RacingScope::new(self)
|
|
}
|
|
|
|
fn forum(&self) -> ForumScope<'_, Self> {
|
|
ForumScope::new(self)
|
|
}
|
|
}
|
|
|
|
impl Executor for ReqwestClient {
|
|
type Error = crate::Error;
|
|
|
|
async fn execute<R>(&self, request: R) -> Result<ApiResponse<R::Discriminant>, Self::Error>
|
|
where
|
|
R: IntoRequest,
|
|
{
|
|
let request = request.into_request();
|
|
let url = request.url();
|
|
|
|
let response = self.0.get(url).send().await?;
|
|
let status = response.status();
|
|
let body = response.bytes().await.ok();
|
|
|
|
Ok(ApiResponse {
|
|
discriminant: request.disriminant,
|
|
status,
|
|
body,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use crate::{ApiError, Error, scopes::test::test_client};
|
|
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn api_error() {
|
|
let client = test_client().await;
|
|
|
|
let resp = client.faction().basic_for_id((-1).into(), |b| b).await;
|
|
|
|
match resp {
|
|
Err(Error::Api(ApiError::IncorrectIdEntityRelation)) => (),
|
|
other => panic!("Expected incorrect id entity relation error, got {other:?}"),
|
|
}
|
|
}
|
|
}
|