use std::collections::HashMap;
use reqwest::{
Client, ClientBuilder, Method, RequestBuilder, StatusCode,
header::{HeaderMap, HeaderValue},
};
use crate::{
VERSION,
err::{self, delete, get, post, validate},
nexus_joiner,
request::{
CategoryName, Changelog, Endorsements, GameId, GameMod, ModFile, ModFiles, ModId,
ModUpdated, RateLimiting, TimePeriod, TrackedModsRaw, Validate,
},
};
pub struct Api {
key: String,
client: Client,
}
impl Api {
pub fn new<S: Into<String>>(key: S) -> Self {
let key = key.into();
let client = ClientBuilder::new().default_headers({
let mut h = HeaderMap::new();
h.insert("apikey", key.parse().unwrap());
h.insert("accept", HeaderValue::from_static("application/json"));
h
});
Self {
key,
client: client.build().expect("oops"),
}
}
pub(crate) fn key(&self) -> &str {
&self.key
}
fn build(
&self,
method: Method,
ver: &str,
slugs: &[&str],
params: &[(&'static str, &str)],
) -> RequestBuilder {
self.client
.request(method, nexus_joiner!(ver, slugs))
.query(params)
}
}
impl Api {
pub async fn validate(&self) -> Result<Validate, validate::ValidateError> {
let response = self
.build(Method::GET, VERSION, &["users", "validate"], &[])
.send()
.await?;
match response.status() {
StatusCode::OK => response
.json()
.await
.map_err(validate::ValidateError::Reqwest),
StatusCode::UNAUTHORIZED => Err(validate::ValidateError::InvalidAPIKey(
response.json().await?,
)),
StatusCode::UNPROCESSABLE_ENTITY => {
unimplemented!(
"I have not yet encountered this return code but it is listed as a valid return code"
);
}
_ => unreachable!("The only three documented return codes are 200, 404 (401), and 422"),
}
}
pub async fn tracked_mods(&self) -> Result<TrackedModsRaw, validate::ValidateError> {
let response = self
.build(Method::GET, VERSION, &["user", "tracked_mods"], &[])
.send()
.await?;
match response.status() {
StatusCode::OK => response
.json()
.await
.map_err(validate::ValidateError::Reqwest),
StatusCode::UNAUTHORIZED => Err(validate::ValidateError::InvalidAPIKey(
response.json().await?,
)),
StatusCode::UNPROCESSABLE_ENTITY => {
unimplemented!(
"I have not yet encountered this return code but it is listed as a valid return code"
);
}
_ => unreachable!("The only three documented return codes are 200, 404 (401), and 422"),
}
}
pub async fn track_mod<T: Into<u64>>(
&self,
game: &str,
id: T,
) -> Result<post::PostModStatus, post::TrackModError> {
let id = id.into();
let response = self
.build(Method::POST, VERSION, &["user", "tracked_mods"], &[])
.query(&[("domain_name", game)])
.form(&HashMap::from([("mod_id", id)]))
.send()
.await?;
match response.status() {
StatusCode::OK => Ok(post::PostModStatus::AlreadyTracking(ModId::from_u64(id))),
StatusCode::CREATED => Ok(post::PostModStatus::SuccessfullyTracked(ModId::from_u64(
id,
))),
StatusCode::UNAUTHORIZED => {
Err(response.json::<err::InvalidAPIKeyError>().await?.into())
}
StatusCode::NOT_FOUND => Err(response.json::<err::ModNotFoundError>().await?.into()),
_ => unreachable!("The only four documented return codes are 200, 201, 404, and 401"),
}
}
pub async fn untrack_mod<T: Into<ModId>>(
&self,
game: &str,
id: T,
) -> Result<(), delete::DeleteModError> {
let id = id.into();
let response = self
.build(Method::DELETE, VERSION, &["user", "tracked_mods"], &[])
.query(&[("domain_name", game)])
.form(&HashMap::from([("mod_id", id)]))
.send()
.await?;
match response.status() {
StatusCode::OK => Ok(()),
StatusCode::NOT_FOUND => {
Err(response.json::<err::UntrackedOrInvalidMod>().await?.into())
}
_ => unreachable!("The only two documented return codes are 200 and 404"),
}
}
pub async fn endorsements(&self) -> Result<Endorsements, validate::ValidateError> {
let response = self
.build(Method::GET, VERSION, &["user", "endorsements"], &[])
.send()
.await?;
match response.status() {
StatusCode::OK => response
.json()
.await
.map_err(validate::ValidateError::Reqwest),
StatusCode::UNAUTHORIZED => Err(validate::ValidateError::InvalidAPIKey(
response.json().await?,
)),
StatusCode::UNPROCESSABLE_ENTITY => {
unimplemented!(
"I have not yet encountered this return code but it is listed as a valid return code"
);
}
_ => unreachable!("The only three documented return codes are 200, 404 (401), and 422"),
}
}
}
impl Api {
pub async fn updated_during(
&self,
game: &str,
time: TimePeriod,
) -> Result<Vec<ModUpdated>, get::GameModError> {
let response = self
.build(
Method::GET,
VERSION,
&["games", game, "mods", "updated"],
&[("period", time.as_str())],
)
.send()
.await?;
match response.status() {
StatusCode::OK => response.json().await.map_err(get::GameModError::Reqwest),
StatusCode::NOT_FOUND => Err(response.json::<err::InvalidAPIKeyError>().await?.into()),
StatusCode::UNPROCESSABLE_ENTITY => {
unimplemented!(
"I have not yet encountered this return code but it is listed as a valid return code"
);
}
_ => unreachable!("The only three documented return codes are 200, 404, and 422"),
}
}
pub async fn changelogs<T: Into<ModId>>(
&self,
game: &str,
id: T,
) -> Result<Changelog, get::GameModError> {
let id = id.into();
let response = self
.build(
Method::GET,
VERSION,
&["games", game, "mods", id.to_string().as_str(), "changelogs"],
&[],
)
.send()
.await?;
match response.status() {
StatusCode::OK => response.json().await.map_err(get::GameModError::Reqwest),
StatusCode::NOT_FOUND => Err(response.json::<err::InvalidAPIKeyError>().await?.into()),
StatusCode::UNPROCESSABLE_ENTITY => {
unimplemented!(
"I have not yet encountered this return code but it is listed as a valid return code"
);
}
_ => unreachable!("The only three documented return codes are 200, 404, and 422"),
}
}
pub async fn mod_info<T: Into<ModId>>(
&self,
game: &str,
id: T,
) -> Result<GameMod, get::GameModError> {
let id = id.into();
let response = self
.build(
Method::GET,
VERSION,
&["games", game, "mods", id.to_string().as_str()],
&[],
)
.send()
.await?;
match response.status() {
StatusCode::OK => response.json().await.map_err(get::GameModError::Reqwest),
StatusCode::NOT_FOUND => Err(response.json::<err::InvalidAPIKeyError>().await?.into()),
StatusCode::UNPROCESSABLE_ENTITY => {
unimplemented!(
"I have not yet encountered this return code but it is listed as a valid return code"
);
}
_ => unreachable!("The only three documented return codes are 200, 404, and 422"),
}
}
}
impl Api {
pub async fn games(&self) -> Result<Vec<GameId>, get::GameModError> {
let response = self
.build(Method::GET, VERSION, &["games"], &[])
.send()
.await?;
match response.status() {
StatusCode::OK => response.json().await.map_err(get::GameModError::Reqwest),
StatusCode::NOT_FOUND => Err(response.json::<err::InvalidAPIKeyError>().await?.into()),
StatusCode::UNPROCESSABLE_ENTITY => {
unimplemented!(
"I have not yet encountered this return code but it is listed as a valid return code"
);
}
_ => unreachable!("The only three documented return codes are 200, 404, and 422"),
}
}
pub async fn game(&self, game: &str) -> Result<GameId, get::GameModError> {
let response = self
.build(Method::GET, VERSION, &["games", game], &[])
.send()
.await?;
match response.status() {
StatusCode::OK => response.json().await.map_err(get::GameModError::Reqwest),
StatusCode::NOT_FOUND => Err(response.json::<err::InvalidAPIKeyError>().await?.into()),
StatusCode::UNPROCESSABLE_ENTITY => {
unimplemented!(
"I have not yet encountered this return code but it is listed as a valid return code"
);
}
_ => unreachable!("The only three documented return codes are 200, 404, and 422"),
}
}
}
impl Api {
pub async fn mod_files<S: Into<ModId>>(
&self,
game: &str,
mod_id: S,
category: Option<CategoryName>,
) -> Result<ModFiles, get::GameModError> {
let mod_id = mod_id.into();
let response = self
.build(
Method::GET,
VERSION,
&["games", game, "mods", mod_id.to_string().as_str(), "files"],
&category
.iter()
.map(|c| ("category", c.to_header_str()))
.collect::<Vec<_>>(),
)
.send()
.await?;
match response.status() {
StatusCode::OK => response.json().await.map_err(get::GameModError::Reqwest),
StatusCode::NOT_FOUND => Err(response.json::<err::InvalidAPIKeyError>().await?.into()),
StatusCode::UNPROCESSABLE_ENTITY => {
unimplemented!(
"I have not yet encountered this return code but it is listed as a valid return code"
);
}
_ => unreachable!("The only three documented return codes are 200, 404, and 422"),
}
}
pub async fn mod_file<S: Into<ModId>>(
&self,
game: &str,
mod_id: S,
file_id: u64,
) -> Result<ModFile, get::GameModError> {
let mod_id = mod_id.into();
let response = self
.build(
Method::GET,
VERSION,
&[
"games",
game,
"mods",
mod_id.to_string().as_str(),
"files",
file_id.to_string().as_str(),
],
&[],
)
.send()
.await?;
match response.status() {
StatusCode::OK => response.json().await.map_err(get::GameModError::Reqwest),
StatusCode::NOT_FOUND => Err(response.json::<err::InvalidAPIKeyError>().await?.into()),
StatusCode::UNPROCESSABLE_ENTITY => {
unimplemented!(
"I have not yet encountered this return code but it is listed as a valid return code"
);
}
_ => unreachable!("The only three documented return codes are 200, 404, and 422"),
}
}
}