platform: improve multiplatform resolution and CurseForge search

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ia32a968e1b048ed396ba0f61df30bb616a6a6964
This commit is contained in:
raf 2026-05-01 21:35:21 +03:00
commit 65ac620831
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
2 changed files with 60 additions and 22 deletions

View file

@ -231,8 +231,10 @@ impl CurseForgePlatform {
&self,
slug: &str,
) -> Result<CurseForgeProject> {
let url =
format!("{CURSEFORGE_API_BASE}/mods/search?gameId=432&slug={slug}");
let url = format!(
"{CURSEFORGE_API_BASE}/mods/search?gameId=432&pageSize=1&sortField=6&\
sortOrder=desc&slug={slug}"
);
let response = self
.client
@ -247,11 +249,20 @@ impl CurseForgePlatform {
let result: CurseForgeSearchResponse = response.json().await?;
result
log::debug!(
"CurseForge search for '{slug}' returned {} results (slugs: {:?})",
result.data.len(),
result.data.iter().map(|p| &p.slug).collect::<Vec<_>>()
);
let project = result
.data
.into_iter()
.iter()
.find(|p| p.slug == slug)
.ok_or_else(|| PakkerError::ProjectNotFound(slug.to_string()))
.cloned()
.or_else(|| result.data.first().cloned());
project.ok_or_else(|| PakkerError::ProjectNotFound(slug.to_string()))
}
}

View file

@ -38,7 +38,9 @@ impl MultiplatformPlatform {
Err(e) => {
let is_not_found = matches!(
e,
PakkerError::ProjectNotFound(_) | PakkerError::InvalidResponse(_)
PakkerError::ProjectNotFound(_)
| PakkerError::InvalidResponse(_)
| PakkerError::ConfigError(_)
);
if is_not_found { Ok(None) } else { Err(e) }
},
@ -60,23 +62,50 @@ impl PlatformClient for MultiplatformPlatform {
let (cf_result, mr_result) = tokio::join!(cf_future, mr_future);
// Handle results - extract Options, propagate first error if both fail
let (cf_project, mr_project) = match (cf_result, mr_result) {
(Ok(Some(cf)), Ok(Some(mr))) => (Some(cf), Some(mr)),
(Ok(None), Ok(None)) => (None, None),
(Ok(None), Ok(Some(mr))) => (None, Some(mr)),
(Ok(Some(cf)), Ok(None)) => (Some(cf), None),
(Err(_e), Ok(None)) | (Ok(None), Err(_e)) => (None, None),
(Err(e), Ok(Some(_))) | (Ok(Some(_)), Err(e)) => {
return Err(e);
// Use whichever platform succeeds. Only propagate errors when BOTH fail.
let mut cf_project = None;
let mut mr_project = None;
let mut first_error = None;
match cf_result {
Ok(Some(p)) => {
log::debug!("Multiplatform: found '{identifier}' on CurseForge");
cf_project = Some(p);
},
(Err(e), Err(_)) => return Err(e),
};
Ok(None) => {
log::debug!("Multiplatform: '{identifier}' not found on CurseForge");
},
Err(e) => {
log::debug!("Multiplatform: CurseForge error for '{identifier}': {e}");
first_error = Some(e);
},
}
match mr_result {
Ok(Some(p)) => {
log::debug!("Multiplatform: found '{identifier}' on Modrinth");
mr_project = Some(p);
},
Ok(None) => {
log::debug!("Multiplatform: '{identifier}' not found on Modrinth");
},
Err(e) => {
log::debug!("Multiplatform: Modrinth error for '{identifier}': {e}");
if first_error.is_none() {
first_error = Some(e);
}
},
}
if cf_project.is_none() && mr_project.is_none() {
if let Some(e) = first_error {
return Err(e);
}
return Err(PakkerError::ProjectNotFound(identifier.to_string()));
}
// Cross-reference: if project exists on only one platform, fetch from the
// other using the slug
let mut cf_project = cf_project;
let mut mr_project = mr_project;
// Cross-reference using each platform's own slug on the other platform.
// Modrinth projects store their slug under "modrinth"; CurseForge under
@ -103,9 +132,7 @@ impl PlatformClient for MultiplatformPlatform {
(Some(cf), Some(mr)) => cf.merged(mr)?,
(Some(cf), None) => cf,
(None, Some(mr)) => mr,
(None, None) => {
return Err(PakkerError::ProjectNotFound(identifier.to_string()));
},
(None, None) => unreachable!("handled above"),
};
Ok(combined)