diff --git a/crates/binstalk-fetchers/src/dist_manifest_fetcher.rs b/crates/binstalk-fetchers/src/dist_manifest_fetcher.rs index 25b7350a..102e3370 100644 --- a/crates/binstalk-fetchers/src/dist_manifest_fetcher.rs +++ b/crates/binstalk-fetchers/src/dist_manifest_fetcher.rs @@ -6,15 +6,9 @@ use compact_str::CompactString; use crate::FetchError; -#[derive(Clone, Debug)] -struct Binary { - /// Key: target, value: artifact - artifacts: BTreeMap, -} - /// An tarball/zip artifact. #[derive(Clone, Debug)] -struct Artifact { +struct BinaryArtifact { /// Filename of artifact on release artifacts, /// need to infer the format. filename: CompactString, @@ -25,11 +19,121 @@ struct Artifact { checksum_filename: Option, } +impl BinaryArtifact { + /// Return `Self` and target if the artifact contains binary, or `None`. + fn new<'artifacts>( + artifact_id: &str, + artifacts: &'artifacts BTreeMap, + app_name: &str, + ) -> Result)>, FetchError> { + let get_artifact = |artifact_id| { + artifacts.get(artifact_id).ok_or_else(|| { + FetchError::InvalidDistManifest(format!("Missing artifact_id {artifact_id}").into()) + }) + }; + let binary_artifact = get_artifact(artifact_id)?; + + if !matches!( + binary_artifact.kind, + cargo_dist_schema::ArtifactKind::ExecutableZip + ) { + return Ok(None); + } + let Some(filename) = binary_artifact.name.as_deref() else { + return Ok(None); + }; + + let path_to_exe = binary_artifact + .assets + .iter() + .find_map(|asset| match (&asset.kind, asset.path.as_deref()) { + (&cargo_dist_schema::AssetKind::Executable(_), Some(path)) + if path.ends_with(app_name) => + { + Some(path) + } + + _ => None, + }) + .ok_or_else(|| { + FetchError::InvalidDistManifest( + format!( + "Cannot find `{app_name}` in artifact `{filename}` with id `{artifact_id}`" + ) + .into(), + ) + })?; + + let checksum_filename = if let Some(checksum_artifact_id) = &binary_artifact.checksum { + let checksum_artifact = get_artifact(checksum_artifact_id)?; + + let Some(checksum_filename) = checksum_artifact.name.as_deref() else { + return Err(FetchError::InvalidDistManifest( + format!("Checksum artifact id {checksum_artifact_id} does not have a filename") + .into(), + )); + }; + + if !matches!( + binary_artifact.kind, + cargo_dist_schema::ArtifactKind::Checksum + ) { + return Err(FetchError::InvalidDistManifest( + format!( + "Checksum artifact {checksum_filename} with id {checksum_artifact_id} does not have kind Checksum" + ) + .into(), + )); + } + + Some(checksum_filename.into()) + } else { + None + }; + + Ok(Some(( + Self { + filename: filename.into(), + path_to_exe: path_to_exe.into(), + checksum_filename, + }, + binary_artifact.target_triples.iter(), + ))) + } +} + +#[derive(Clone, Debug)] +struct Binary { + /// Key: target, value: artifact + binary_artifacts: BTreeMap, +} + +impl Binary { + fn new( + artifact_ids: Vec, + artifacts: &BTreeMap, + app_name: &str, + ) -> Result { + let mut binary_artifacts = BTreeMap::new(); + + for artifact_id in artifact_ids { + if let Some((binary_artifact, targets)) = + BinaryArtifact::new(&artifact_id, artifacts, app_name)? + { + for target in targets { + binary_artifacts.insert(target.into(), binary_artifact.clone()); + } + } + } + Ok(Self { binary_artifacts }) + } +} + #[derive(Clone, Debug)] enum DistManifest { NotSupported(Format), - /// Key: name of the binary - Binaries(BTreeMap), + /// Key: name and version of the binary + Binaries(BTreeMap<(CompactString, CompactString), Binary>), } impl DistManifest { @@ -37,11 +141,28 @@ impl DistManifest { let manifest: cargo_dist_schema::DistManifest = response.json().await?; let format = manifest.format(); - if format.unsupported() { + if format < cargo_dist_schema::Format::Epoch3 { return Ok(Self::NotSupported(format)); } - todo!() + let cargo_dist_schema::DistManifest { + releases, + artifacts, + .. + } = manifest; + + Ok(Self::Binaries( + releases + .into_iter() + .map(|release| { + let binary = Binary::new(release.artifacts, &artifacts, &release.app_name)?; + Ok::<_, FetchError>(( + (release.app_name.into(), release.app_version.into()), + binary, + )) + }) + .collect::>()?, + )) } } diff --git a/crates/binstalk-fetchers/src/lib.rs b/crates/binstalk-fetchers/src/lib.rs index c71f942a..3144d46b 100644 --- a/crates/binstalk-fetchers/src/lib.rs +++ b/crates/binstalk-fetchers/src/lib.rs @@ -1,6 +1,6 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] -use std::{path::Path, sync::Arc}; +use std::{borrow::Cow, path::Path, sync::Arc}; use binstalk_downloader::{ download::DownloadError, gh_api_client::GhApiError, remote::Error as RemoteError, @@ -75,6 +75,10 @@ pub enum FetchError { #[error("Failed to verify signature")] InvalidSignature, + + #[cfg(feature = "dist-manifest")] + #[error("Invalid dist manifest: {0}")] + InvalidDistManifest(Cow<'static, str>), } impl From for FetchError {