use crate::official::request::pagination::{
GamesDelegate, GamesStream, ProjectFilesDelegate, ProjectFilesStream, ProjectSearchDelegate,
ProjectSearchStream,
};
use crate::official::request::params::{
several_body, CategoriesParams, FeaturedProjectsBody, GamesParams, ProjectFilesParams,
ProjectSearchParams,
};
use crate::official::request::{ApiDataResult, ApiPageResult, ApiResponse, DataResponse};
use crate::official::types::{
Category, FeaturedProjects, Game, GameVersionType, GameVersions, Project, ProjectFile,
};
use crate::Error;
pub static DEFAULT_API_BASE: &str = "https://api.curseforge.com/v1/";
pub const API_PAGINATION_RESULTS_LIMIT: usize = 10_000;
macro_rules! endpoint {
(
$client:ident $method:ident,
uri: $base:ident / $path:literal,
$(vars: [$($var:ident),+],)?
$(params: $params:expr,)?
$(body: $body:expr,)?
) => {{
use futures_lite::io::AsyncReadExt;
#[allow(unused_mut)]
let mut uri = endpoint!(@uri, $base, $path $(, [$($var),*])?);
$(uri.set_query(Some(&serde_qs::to_string($params).unwrap()));)?
let builder = isahc::Request::builder()
.method(endpoint!(@str $method))
.uri(uri.as_str());
let request = endpoint!(@build, builder $(, $body)?)?;
let response = $client.send_async(request).await?;
let status = response.status();
let mut bytes = Vec::new();
response.into_body().read_to_end(&mut bytes).await.unwrap();
if status != 200 {
return Err(Error::StatusNotOk { uri, status, bytes: Box::new(bytes) });
}
let deser = &mut serde_json::Deserializer::from_slice(bytes.as_slice());
let result = serde_path_to_error::deserialize(deser);
match result {
Ok(value) => Ok(ApiResponse { bytes, value }),
Err(error) => Err(Error::Deserialize { uri, error, bytes: Box::new(bytes) }),
}
}};
(@uri, $base:ident, $path:literal) => {
$base.join($path).unwrap()
};
(@uri, $base:ident, $path:literal, [$($var:ident),+]) => {
$base.join(&format!($path, $($var),*)).unwrap()
};
(@build, $builder:ident) => {
$builder.body(())
};
(@build, $builder:ident, $body:expr) => {
$builder.body(serde_json::to_string($body).unwrap())
};
(@str GET) => {
"GET"
};
(@str POST) => {
"POST"
};
}
pub async fn game(
client: &isahc::HttpClient,
base: &url::Url,
game_id: i32,
) -> ApiDataResult<Game> {
endpoint! {
client GET,
uri: base / "games/{}",
vars: [game_id],
}
}
pub async fn games(
client: &isahc::HttpClient,
base: &url::Url,
params: &GamesParams,
) -> ApiPageResult<Game> {
endpoint! {
client GET,
uri: base / "games",
params: params,
}
}
pub fn games_iter<'cu, 'f>(
client: &'cu isahc::HttpClient,
base: &'cu url::Url,
params: GamesParams,
) -> GamesStream<'cu, 'f> {
GamesDelegate::new(client, base, params).into()
}
pub async fn game_versions(
client: &isahc::HttpClient,
base: &url::Url,
game_id: i32,
) -> ApiDataResult<Vec<GameVersions>> {
endpoint! {
client GET,
uri: base / "games/{}/versions",
vars: [game_id],
}
}
pub async fn game_version_types(
client: &isahc::HttpClient,
base: &url::Url,
game_id: i32,
) -> ApiDataResult<Vec<GameVersionType>> {
endpoint! {
client GET,
uri: base / "games/{}/version-types",
vars: [game_id],
}
}
pub async fn categories(
client: &isahc::HttpClient,
base: &url::Url,
params: &CategoriesParams,
) -> ApiDataResult<Vec<Category>> {
endpoint! {
client GET,
uri: base / "categories",
params: params,
}
}
pub async fn search_projects(
client: &isahc::HttpClient,
base: &url::Url,
params: &ProjectSearchParams,
) -> ApiPageResult<Project> {
endpoint! {
client GET,
uri: base / "mods/search",
params: params,
}
}
pub fn search_projects_iter<'cu, 'f>(
client: &'cu isahc::HttpClient,
base: &'cu url::Url,
params: ProjectSearchParams,
) -> ProjectSearchStream<'cu, 'f> {
ProjectSearchDelegate::new(client, base, params).into()
}
pub async fn project(
client: &isahc::HttpClient,
base: &url::Url,
project_id: i32,
) -> ApiDataResult<Project> {
endpoint! {
client GET,
uri: base / "mods/{}",
vars: [project_id],
}
}
pub async fn projects<I>(
client: &isahc::HttpClient,
base: &url::Url,
project_ids: I,
) -> ApiDataResult<Vec<Project>>
where
I: IntoIterator<Item = i32>,
{
endpoint! {
client POST,
uri: base / "mods",
body: &several_body!("modIds", i32, project_ids.into_iter()),
}
}
pub async fn featured_projects(
client: &isahc::HttpClient,
base: &url::Url,
body: &FeaturedProjectsBody,
) -> ApiDataResult<FeaturedProjects> {
endpoint! {
client POST,
uri: base / "mods/featured",
body: body,
}
}
pub async fn project_description(
client: &isahc::HttpClient,
base: &url::Url,
project_id: i32,
) -> ApiDataResult<String> {
endpoint! {
client GET,
uri: base / "mods/{}/description",
vars: [project_id],
}
}
pub async fn project_file(
client: &isahc::HttpClient,
base: &url::Url,
project_id: i32,
file_id: i32,
) -> ApiDataResult<ProjectFile> {
endpoint! {
client GET,
uri: base / "mods/{}/files/{}",
vars: [project_id, file_id],
}
}
pub async fn project_file_by_id(
client: &isahc::HttpClient,
base: &url::Url,
file_id: i32,
) -> ApiDataResult<ProjectFile> {
project_files_by_ids(client, base, [file_id])
.await
.map(|mut r| ApiResponse {
bytes: r.bytes,
value: DataResponse {
data: r.value.data.pop().unwrap(),
#[cfg(feature = "allow-unknown-fields")]
other_fields: r.value.other_fields,
},
})
}
pub async fn project_files(
client: &isahc::HttpClient,
base: &url::Url,
project_id: i32,
params: &ProjectFilesParams,
) -> ApiPageResult<ProjectFile> {
endpoint! {
client GET,
uri: base / "mods/{}/files",
vars: [project_id],
params: params,
}
}
pub fn project_files_iter<'cu, 'f>(
client: &'cu isahc::HttpClient,
base: &'cu url::Url,
project_id: i32,
params: ProjectFilesParams,
) -> ProjectFilesStream<'cu, 'f> {
ProjectFilesDelegate::new(client, base, project_id, params).into()
}
pub async fn project_files_by_ids<I>(
client: &isahc::HttpClient,
base: &url::Url,
file_ids: I,
) -> ApiDataResult<Vec<ProjectFile>>
where
I: IntoIterator<Item = i32>,
{
endpoint! {
client POST,
uri: base / "mods/files",
body: &several_body!("fileIds", i32, file_ids.into_iter()),
}
}
pub async fn project_file_changelog(
client: &isahc::HttpClient,
base: &url::Url,
project_id: i32,
file_id: i32,
) -> ApiDataResult<String> {
endpoint! {
client GET,
uri: base / "mods/{}/files/{}/changelog",
vars: [project_id, file_id],
}
}
pub async fn project_file_download_url(
client: &isahc::HttpClient,
base: &url::Url,
project_id: i32,
file_id: i32,
) -> ApiDataResult<String> {
endpoint! {
client GET,
uri: base / "mods/{}/files/{}/download-url",
vars: [project_id, file_id],
}
}