use crate::{prelude::*, spec::Spec};
use reqwest::{header, Client};
use std::time::Duration;
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct EsiBuilder {
pub(crate) compatibility_date: Option<String>,
pub(crate) client_id: Option<String>,
pub(crate) client_secret: Option<String>,
pub(crate) application_auth: Option<bool>,
pub(crate) callback_url: Option<String>,
pub(crate) base_api_url: Option<String>,
pub(crate) authorize_url: Option<String>,
pub(crate) token_url: Option<String>,
pub(crate) spec_url: Option<String>,
pub(crate) scope: Option<String>,
pub(crate) access_token: Option<String>,
pub(crate) access_expiration: Option<i64>,
pub(crate) refresh_token: Option<String>,
pub(crate) user_agent: Option<String>,
pub(crate) http_timeout: Option<u64>,
pub(crate) spec: Option<Spec>,
}
impl EsiBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn compatibility_date(mut self, val: &str) -> Self {
self.compatibility_date = Some(val.to_owned());
self
}
pub fn client_id(mut self, val: &str) -> Self {
self.client_id = Some(val.to_owned());
self
}
pub fn client_secret(mut self, val: &str) -> Self {
self.client_secret = Some(val.to_owned());
self
}
pub fn enable_application_authentication(mut self, val: bool) -> Self {
self.application_auth = Some(val);
self
}
pub fn callback_url(mut self, val: &str) -> Self {
self.callback_url = Some(val.to_owned());
self
}
pub fn base_api_url(mut self, val: &str) -> Self {
self.base_api_url = Some(val.to_owned());
self
}
pub fn authorize_url(mut self, val: &str) -> Self {
self.authorize_url = Some(val.to_owned());
self
}
pub fn token_url(mut self, val: &str) -> Self {
self.token_url = Some(val.to_owned());
self
}
pub fn spec_url(mut self, val: &str) -> Self {
self.spec_url = Some(val.to_owned());
self
}
pub fn scope(mut self, val: &str) -> Self {
self.scope = Some(val.to_owned().replace(' ', "%20"));
self
}
pub fn access_token(mut self, val: Option<&str>) -> Self {
self.access_token = val.map(|v| v.to_owned());
self
}
pub fn access_expiration(mut self, val: Option<i64>) -> Self {
self.access_expiration = val;
self
}
pub fn refresh_token(mut self, val: Option<&str>) -> Self {
self.refresh_token = val.map(|v| v.to_owned());
self
}
pub fn user_agent(mut self, val: &str) -> Self {
self.user_agent = Some(val.to_owned());
self
}
pub fn http_timeout(mut self, val: Option<u64>) -> Self {
self.http_timeout = val;
self
}
pub fn spec(mut self, spec: Option<Spec>) -> Self {
self.spec = spec;
self
}
pub(crate) fn construct_client(&self) -> EsiResult<Client> {
let http_timeout = self
.http_timeout
.map(Duration::from_millis)
.unwrap_or_else(|| Duration::from_secs(60));
let headers = {
let mut map = header::HeaderMap::new();
let user_agent = &self
.user_agent
.as_ref()
.ok_or_else(|| EsiError::EmptyClientValue("user_agent".to_owned()))?
.to_owned();
map.insert(
header::USER_AGENT,
header::HeaderValue::from_str(user_agent)?,
);
map.insert(
header::ACCEPT,
header::HeaderValue::from_static("application/json"),
);
map
};
#[cfg(not(feature = "rustls-tls"))]
let client = Client::builder()
.timeout(http_timeout)
.default_headers(headers)
.build()?;
#[cfg(feature = "rustls-tls")]
let client = Client::builder()
.timeout(http_timeout)
.default_headers(headers)
.use_rustls_tls()
.build()?;
Ok(client)
}
pub fn build(self) -> EsiResult<Esi> {
Esi::from_builder(self)
}
}
#[cfg(test)]
mod tests {
use super::EsiBuilder;
use crate::spec::Spec;
#[test]
fn test_builder_valid() {
let b = EsiBuilder::new()
.client_id("a")
.client_secret("b")
.callback_url("c")
.user_agent("d")
.build()
.unwrap();
assert_eq!(b.client_id, Some(String::from("a")));
assert_eq!(b.client_secret, Some(String::from("b")));
assert_eq!(b.callback_url, Some(String::from("c")));
assert_eq!(b.compatibility_date, "2025-08-26");
assert_eq!(b.access_token, None);
assert_eq!(b.spec, None);
}
#[test]
fn test_builder_no_client() {
let b = EsiBuilder::new().user_agent("d").build().unwrap();
assert_eq!(b.client_id, None);
assert_eq!(b.client_secret, None);
assert_eq!(b.callback_url, None);
assert_eq!(b.base_api_url, "https://esi.evetech.net/");
assert_eq!(
b.authorize_url,
"https://login.eveonline.com/v2/oauth/authorize"
);
assert_eq!(b.token_url, "https://login.eveonline.com/v2/oauth/token");
assert_eq!(b.spec_url, "https://esi.evetech.net/latest/swagger.json");
assert_eq!(b.compatibility_date, "2025-08-26");
assert_eq!(b.access_token, None);
assert_eq!(b.spec, None);
}
#[test]
fn test_builder_change_urls() {
let b = EsiBuilder::new()
.user_agent("d")
.base_api_url("http://eve-api/")
.authorize_url("http://authorize-url/")
.token_url("http://token-url")
.spec_url("http://spec-url/")
.build()
.unwrap();
assert_eq!(b.base_api_url, "http://eve-api/");
assert_eq!(b.authorize_url, "http://authorize-url/");
assert_eq!(b.token_url, "http://token-url");
assert_eq!(b.spec_url, "http://spec-url/");
}
#[test]
fn test_builder_missing_value() {
let res = EsiBuilder::new().build();
assert!(res.is_err());
let s = format!("{}", res.unwrap_err());
assert_eq!(s, "Missing required builder struct value 'user_agent'");
}
#[test]
fn test_builder_with_spec() {
let spec: Spec = serde_json::from_str(r#"{"paths": {}}"#).unwrap();
let b = EsiBuilder::new()
.user_agent("d")
.spec(Some(spec.clone()))
.build()
.unwrap();
assert_eq!(spec, b.spec.unwrap());
}
#[test]
fn test_builder_to_json_empty() {
let json = r#"{"compatibility_date":null,"client_id":null,"client_secret":null,"application_auth":null,"callback_url":null,"base_api_url":null,"authorize_url":null,"token_url":null,"spec_url":null,"scope":null,"access_token":null,"access_expiration":null,"refresh_token":null,"user_agent":null,"http_timeout":null,"spec":null}"#;
assert_eq!(json, serde_json::to_string(&EsiBuilder::new()).unwrap());
}
#[test]
fn test_builder_from_json_filled() {
let json = r#"{
"compatibility_date": "2025-08-26",
"client_id": "a",
"client_secret": "b",
"callback_url": "c",
"scope": "d",
"access_token": "e",
"access_expiration": 1,
"refresh_token": "f",
"user_agent": "g",
"http_timeout": 60000,
"spec": null
}"#;
let actual: EsiBuilder = serde_json::from_str(json).unwrap();
let expected = EsiBuilder::new()
.compatibility_date("2025-08-26")
.client_id("a")
.client_secret("b")
.callback_url("c")
.scope("d")
.access_token(Some("e"))
.access_expiration(Some(1))
.refresh_token(Some("f"))
.user_agent("g")
.http_timeout(Some(60_000))
.spec(None);
assert_eq!(actual, expected);
}
}