libcnb-package 0.30.4

Library for cross-compiling and packaging buildpacks written with libcnb.rs
Documentation
use crate::util::absolutize_path;
use libcnb_data::buildpack::{BuildpackId, BuildpackIdError};
use libcnb_data::package_descriptor::{
    PackageDescriptor, PackageDescriptorDependency, PackageDescriptorDependencyError,
};
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

pub(crate) fn normalize_package_descriptor(
    descriptor: &PackageDescriptor,
    descriptor_path: &Path,
    buildpack_paths: &BTreeMap<BuildpackId, PathBuf>,
) -> Result<PackageDescriptor, NormalizePackageDescriptorError> {
    replace_libcnb_uris(descriptor, buildpack_paths)
        .map_err(NormalizePackageDescriptorError::ReplaceLibcnbUriError)
        .and_then(|package_descriptor| {
            absolutize_dependency_paths(&package_descriptor, descriptor_path)
                .map_err(NormalizePackageDescriptorError::PackageDescriptorDependencyError)
        })
}

#[derive(thiserror::Error, Debug)]
pub enum NormalizePackageDescriptorError {
    #[error(transparent)]
    ReplaceLibcnbUriError(ReplaceLibcnbUriError),
    #[error(transparent)]
    PackageDescriptorDependencyError(PackageDescriptorDependencyError),
}

fn replace_libcnb_uris(
    descriptor: &PackageDescriptor,
    buildpack_paths: &BTreeMap<BuildpackId, PathBuf>,
) -> Result<PackageDescriptor, ReplaceLibcnbUriError> {
    descriptor
        .dependencies
        .iter()
        .map(|dependency| replace_libcnb_uri(dependency, buildpack_paths))
        .collect::<Result<Vec<_>, _>>()
        .map(|dependencies| PackageDescriptor {
            dependencies,
            ..descriptor.clone()
        })
}

fn replace_libcnb_uri(
    dependency: &PackageDescriptorDependency,
    buildpack_paths: &BTreeMap<BuildpackId, PathBuf>,
) -> Result<PackageDescriptorDependency, ReplaceLibcnbUriError> {
    buildpack_id_from_libcnb_dependency(dependency)
        .map_err(ReplaceLibcnbUriError::BuildpackIdError)
        .and_then(|maybe_buildpack_id| {
            maybe_buildpack_id.map_or(Ok(dependency.clone()), |buildpack_id| {
                buildpack_paths
                    .get(&buildpack_id)
                    .ok_or(ReplaceLibcnbUriError::MissingBuildpackPath(buildpack_id))
                    .cloned()
                    .and_then(|buildpack_path| {
                        PackageDescriptorDependency::try_from(buildpack_path)
                            .map_err(ReplaceLibcnbUriError::PackageDescriptorDependencyError)
                    })
            })
        })
}

#[derive(thiserror::Error, Debug)]
pub enum ReplaceLibcnbUriError {
    #[error("Buildpack reference uses an invalid buildpack id: {0}")]
    BuildpackIdError(BuildpackIdError),
    #[error("Invalid package descriptor dependency: {0}")]
    PackageDescriptorDependencyError(PackageDescriptorDependencyError),
    #[error("Missing path for buildpack with id {0}")]
    MissingBuildpackPath(BuildpackId),
}

fn absolutize_dependency_paths(
    descriptor: &PackageDescriptor,
    descriptor_path: &Path,
) -> Result<PackageDescriptor, PackageDescriptorDependencyError> {
    let descriptor_parent_path = descriptor_path
        .parent()
        .map(PathBuf::from)
        .unwrap_or_default();

    descriptor
        .dependencies
        .iter()
        .map(|dependency| {
            let scheme = dependency
                .uri
                .scheme()
                .map(uriparse::scheme::Scheme::as_str);

            match scheme {
                None => PackageDescriptorDependency::try_from(absolutize_path(
                    &PathBuf::from(dependency.uri.path().to_string()),
                    &descriptor_parent_path,
                )),
                _ => Ok(dependency.clone()),
            }
        })
        .collect::<Result<Vec<_>, _>>()
        .map(|dependencies| PackageDescriptor {
            dependencies,
            ..descriptor.clone()
        })
}

pub(crate) fn buildpack_id_from_libcnb_dependency(
    dependency: &PackageDescriptorDependency,
) -> Result<Option<BuildpackId>, BuildpackIdError> {
    Some(&dependency.uri)
        .filter(|uri| {
            uri.scheme()
                .is_some_and(|scheme| scheme.as_str() == "libcnb")
        })
        .map(|uri| uri.path().to_string().parse())
        .transpose()
}