use std::{collections::HashMap, sync::OnceLock, time::Duration};
use log::{trace, warn};
use reqwest::Client;
use serde::{de::DeserializeOwned, Serialize};
pub mod auth;
pub mod error;
pub use error::Error;
pub mod alliance;
pub mod character;
pub mod corporation;
pub mod market;
pub mod scope;
pub mod universe;
pub type EsiResult<T> = Result<T, error::Error>;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Paged<T> {
pub data: T,
pub page: i32,
pub total_pages: i32,
}
const ESI_URL: &str = "https://esi.evetech.net";
const ESI_DATASOURCE: &str = "tranquility";
const TIMEOUT: u64 = 10;
const CLIENT: OnceLock<Client> = OnceLock::new();
const USER_AGENT: OnceLock<String> = OnceLock::new();
pub fn client() -> Client {
CLIENT.get_or_init(|| Client::new()).clone()
}
pub fn user_agent() -> String {
USER_AGENT
.get_or_init(|| format!("{} ({})", "eversal", "contact@eversal.io"))
.clone()
}
pub fn initialize(application_name: String, application_email: String) {
match CLIENT.set(Client::new()) {
Ok(_) => (),
Err(_) => warn!("Client already initialized"),
};
match USER_AGENT.set(format!("{} ({})", application_name, application_email)) {
Ok(_) => (),
Err(_) => warn!("User agent already initialized"),
};
}
pub async fn get_public<T: DeserializeOwned>(
path: &str,
params: Option<HashMap<&str, String>>,
) -> Result<T, reqwest::Error> {
let url = build_url(path, params);
trace!("Requesting: {}", url);
let req = client()
.get(url)
.header(reqwest::header::USER_AGENT, user_agent())
.timeout(std::time::Duration::from_secs(TIMEOUT))
.send()
.await?;
req.error_for_status_ref()?;
let result: T = req.json().await?;
Ok(result)
}
pub async fn get_public_paged<T: DeserializeOwned>(
path: &str,
params: Option<HashMap<&str, String>>,
) -> Result<Paged<T>, reqwest::Error> {
let page = match ¶ms {
Some(params) => match params.get("page") {
Some(page) => match page.parse::<i32>() {
Ok(page) => page,
Err(_) => 1,
},
None => 1,
},
None => 1,
};
let url = build_url(path, params);
trace!("Requesting: {}", url);
let req = client()
.get(url)
.header(reqwest::header::USER_AGENT, user_agent())
.timeout(std::time::Duration::from_secs(TIMEOUT))
.send()
.await?;
req.error_for_status_ref()?;
let total_pages = match req.headers().get("X-Pages") {
Some(pages) => match pages.to_str() {
Ok(pages) => match pages.parse::<i32>() {
Ok(pages) => pages,
Err(_) => 1,
},
Err(_) => 1,
},
None => 1,
};
let result: T = req.json().await?;
Ok(Paged {
data: result,
page,
total_pages,
})
}
pub async fn get_authenticated<T: DeserializeOwned>(
access_token: &str,
path: &str,
params: Option<HashMap<&str, String>>,
) -> Result<T, reqwest::Error> {
let url = build_url(path, params);
trace!("Requesting: {}", url);
let req = client()
.get(url)
.header(reqwest::header::USER_AGENT, user_agent())
.header(
reqwest::header::AUTHORIZATION,
format!("Bearer {}", access_token),
)
.timeout(std::time::Duration::from_secs(TIMEOUT))
.send()
.await?;
req.error_for_status_ref()?;
let result: T = req.json().await?;
Ok(result)
}
pub async fn get_authenticated_paged<T: DeserializeOwned>(
access_token: &str,
path: &str,
params: Option<HashMap<&str, String>>,
) -> Result<Paged<T>, reqwest::Error> {
let page = match ¶ms {
Some(params) => match params.get("page") {
Some(page) => match page.parse::<i32>() {
Ok(page) => page,
Err(_) => 1,
},
None => 1,
},
None => 1,
};
let url = build_url(path, params);
trace!("Requesting: {}", url);
let req = client()
.get(url)
.header(reqwest::header::USER_AGENT, user_agent())
.header(
reqwest::header::AUTHORIZATION,
format!("Bearer {}", access_token),
)
.timeout(std::time::Duration::from_secs(TIMEOUT))
.send()
.await?;
req.error_for_status_ref()?;
let total_pages = match req.headers().get("X-Pages") {
Some(pages) => match pages.to_str() {
Ok(pages) => match pages.parse::<i32>() {
Ok(pages) => pages,
Err(_) => 1,
},
Err(_) => 1,
},
None => 1,
};
let result: T = req.json().await?;
Ok(Paged {
data: result,
page,
total_pages,
})
}
pub async fn post_public<T: DeserializeOwned, U: Serialize + ?Sized>(
path: &str,
params: Option<HashMap<&str, String>>,
data: &U,
) -> Result<T, reqwest::Error> {
let url = build_url(path, params);
trace!(
"Requesting: {} with data: {}",
url,
serde_json::to_string(data).unwrap()
);
let req = client()
.post(url)
.header(reqwest::header::USER_AGENT, user_agent())
.timeout(std::time::Duration::from_secs(TIMEOUT))
.json(data)
.send()
.await?;
req.error_for_status_ref()?;
let result: T = req.json().await?;
Ok(result)
}
pub async fn post_authenticated<T: DeserializeOwned, U: Serialize + ?Sized>(
access_token: &str,
path: &str,
params: Option<HashMap<&str, String>>,
data: &U,
) -> Result<T, reqwest::Error> {
let url = build_url(path, params);
trace!(
"Requesting: {} with data: {}",
url,
serde_json::to_string(data).unwrap()
);
let req = client()
.post(url)
.header(reqwest::header::USER_AGENT, user_agent())
.header(
reqwest::header::AUTHORIZATION,
format!("Bearer {}", access_token),
)
.timeout(Duration::from_secs(TIMEOUT))
.json(data)
.send()
.await?;
req.error_for_status_ref()?;
let result: T = req.json().await?;
Ok(result)
}
fn build_url(path: &str, params: Option<HashMap<&str, String>>) -> String {
let mut url = format!("{}/latest/{}?datasource={}", ESI_URL, path, ESI_DATASOURCE);
if let Some(params) = params {
for (key, value) in params {
url.push_str(&format!("&{}={}", key, value));
}
}
url
}