libcnb_package/
package_descriptor.rs

1use crate::util::absolutize_path;
2use libcnb_data::buildpack::{BuildpackId, BuildpackIdError};
3use libcnb_data::package_descriptor::{
4    PackageDescriptor, PackageDescriptorDependency, PackageDescriptorDependencyError,
5};
6use std::collections::BTreeMap;
7use std::path::{Path, PathBuf};
8
9pub(crate) fn normalize_package_descriptor(
10    descriptor: &PackageDescriptor,
11    descriptor_path: &Path,
12    buildpack_paths: &BTreeMap<BuildpackId, PathBuf>,
13) -> Result<PackageDescriptor, NormalizePackageDescriptorError> {
14    replace_libcnb_uris(descriptor, buildpack_paths)
15        .map_err(NormalizePackageDescriptorError::ReplaceLibcnbUriError)
16        .and_then(|package_descriptor| {
17            absolutize_dependency_paths(&package_descriptor, descriptor_path)
18                .map_err(NormalizePackageDescriptorError::PackageDescriptorDependencyError)
19        })
20}
21
22#[derive(thiserror::Error, Debug)]
23pub enum NormalizePackageDescriptorError {
24    #[error(transparent)]
25    ReplaceLibcnbUriError(ReplaceLibcnbUriError),
26    #[error(transparent)]
27    PackageDescriptorDependencyError(PackageDescriptorDependencyError),
28}
29
30fn replace_libcnb_uris(
31    descriptor: &PackageDescriptor,
32    buildpack_paths: &BTreeMap<BuildpackId, PathBuf>,
33) -> Result<PackageDescriptor, ReplaceLibcnbUriError> {
34    descriptor
35        .dependencies
36        .iter()
37        .map(|dependency| replace_libcnb_uri(dependency, buildpack_paths))
38        .collect::<Result<Vec<_>, _>>()
39        .map(|dependencies| PackageDescriptor {
40            dependencies,
41            ..descriptor.clone()
42        })
43}
44
45fn replace_libcnb_uri(
46    dependency: &PackageDescriptorDependency,
47    buildpack_paths: &BTreeMap<BuildpackId, PathBuf>,
48) -> Result<PackageDescriptorDependency, ReplaceLibcnbUriError> {
49    buildpack_id_from_libcnb_dependency(dependency)
50        .map_err(ReplaceLibcnbUriError::BuildpackIdError)
51        .and_then(|maybe_buildpack_id| {
52            maybe_buildpack_id.map_or(Ok(dependency.clone()), |buildpack_id| {
53                buildpack_paths
54                    .get(&buildpack_id)
55                    .ok_or(ReplaceLibcnbUriError::MissingBuildpackPath(buildpack_id))
56                    .cloned()
57                    .and_then(|buildpack_path| {
58                        PackageDescriptorDependency::try_from(buildpack_path)
59                            .map_err(ReplaceLibcnbUriError::PackageDescriptorDependencyError)
60                    })
61            })
62        })
63}
64
65#[derive(thiserror::Error, Debug)]
66pub enum ReplaceLibcnbUriError {
67    #[error("Buildpack reference uses an invalid buildpack id: {0}")]
68    BuildpackIdError(BuildpackIdError),
69    #[error("Invalid package descriptor dependency: {0}")]
70    PackageDescriptorDependencyError(PackageDescriptorDependencyError),
71    #[error("Missing path for buildpack with id {0}")]
72    MissingBuildpackPath(BuildpackId),
73}
74
75fn absolutize_dependency_paths(
76    descriptor: &PackageDescriptor,
77    descriptor_path: &Path,
78) -> Result<PackageDescriptor, PackageDescriptorDependencyError> {
79    let descriptor_parent_path = descriptor_path
80        .parent()
81        .map(PathBuf::from)
82        .unwrap_or_default();
83
84    descriptor
85        .dependencies
86        .iter()
87        .map(|dependency| {
88            let scheme = dependency
89                .uri
90                .scheme()
91                .map(uriparse::scheme::Scheme::as_str);
92
93            match scheme {
94                None => PackageDescriptorDependency::try_from(absolutize_path(
95                    &PathBuf::from(dependency.uri.path().to_string()),
96                    &descriptor_parent_path,
97                )),
98                _ => Ok(dependency.clone()),
99            }
100        })
101        .collect::<Result<Vec<_>, _>>()
102        .map(|dependencies| PackageDescriptor {
103            dependencies,
104            ..descriptor.clone()
105        })
106}
107
108pub(crate) fn buildpack_id_from_libcnb_dependency(
109    dependency: &PackageDescriptorDependency,
110) -> Result<Option<BuildpackId>, BuildpackIdError> {
111    Some(&dependency.uri)
112        .filter(|uri| {
113            uri.scheme()
114                .is_some_and(|scheme| scheme.as_str() == "libcnb")
115        })
116        .map(|uri| uri.path().to_string().parse())
117        .transpose()
118}