libcnb_data/
package_descriptor.rs1use crate::package_descriptor::PlatformOs::Linux;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::path::PathBuf;
4use uriparse::{URIReference, URIReferenceError};
5
6#[derive(Debug, Deserialize, Serialize, Clone)]
35#[serde(deny_unknown_fields)]
36pub struct PackageDescriptor {
37 pub buildpack: PackageDescriptorBuildpackReference,
39
40 #[serde(default)]
44 pub dependencies: Vec<PackageDescriptorDependency>,
45
46 #[serde(default)]
48 pub platform: Platform,
49}
50
51impl Default for PackageDescriptor {
52 fn default() -> Self {
53 PackageDescriptor {
54 buildpack: PackageDescriptorBuildpackReference::try_from(".")
55 .expect("a package.toml with buildpack.uri=\".\" should be valid"),
56 dependencies: Vec::new(),
57 platform: Platform::default(),
58 }
59 }
60}
61
62#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)]
64#[serde(deny_unknown_fields)]
65pub struct PackageDescriptorBuildpackReference {
66 #[serde(deserialize_with = "deserialize_uri_reference")]
70 #[serde(serialize_with = "serialize_uri_reference")]
71 pub uri: URIReference<'static>,
72}
73
74#[derive(Debug)]
75pub enum PackageDescriptorBuildpackError {
76 InvalidUri(String),
77}
78
79impl TryFrom<&str> for PackageDescriptorBuildpackReference {
80 type Error = PackageDescriptorBuildpackError;
81
82 fn try_from(value: &str) -> Result<Self, Self::Error> {
83 try_uri_from_str(value)
84 .map(|uri| PackageDescriptorBuildpackReference { uri })
85 .map_err(|_| PackageDescriptorBuildpackError::InvalidUri(value.to_string()))
86 }
87}
88
89#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)]
91#[serde(deny_unknown_fields)]
92pub struct PackageDescriptorDependency {
93 #[serde(deserialize_with = "deserialize_uri_reference")]
96 #[serde(serialize_with = "serialize_uri_reference")]
97 pub uri: URIReference<'static>,
98}
99
100#[derive(thiserror::Error, Debug)]
101pub enum PackageDescriptorDependencyError {
102 #[error("Invalid URI: {0}")]
103 InvalidUri(String),
104}
105
106impl TryFrom<PathBuf> for PackageDescriptorDependency {
107 type Error = PackageDescriptorDependencyError;
108
109 fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
110 Self::try_from(value.to_string_lossy().to_string().as_str())
111 }
112}
113
114impl TryFrom<&str> for PackageDescriptorDependency {
115 type Error = PackageDescriptorDependencyError;
116
117 fn try_from(value: &str) -> Result<Self, Self::Error> {
118 try_uri_from_str(value)
119 .map(|uri| PackageDescriptorDependency { uri })
120 .map_err(|_| PackageDescriptorDependencyError::InvalidUri(value.to_string()))
121 }
122}
123
124fn try_uri_from_str(value: &str) -> Result<URIReference<'static>, URIReferenceError> {
125 URIReference::try_from(value).map(URIReference::into_owned)
126}
127
128#[derive(Debug, Deserialize, Serialize, Clone)]
130#[serde(deny_unknown_fields)]
131pub struct Platform {
132 pub os: PlatformOs,
135}
136
137impl Default for Platform {
138 fn default() -> Self {
139 Self { os: Linux }
140 }
141}
142
143#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)]
144#[serde(rename_all = "lowercase")]
145pub enum PlatformOs {
146 Linux,
147 Windows,
148}
149
150fn deserialize_uri_reference<'de, D>(deserializer: D) -> Result<URIReference<'static>, D::Error>
155where
156 D: Deserializer<'de>,
157{
158 let value = String::deserialize(deserializer)?;
159 let uri = URIReference::try_from(value.as_str()).map_err(serde::de::Error::custom)?;
160 Ok(uri.into_owned())
161}
162
163fn serialize_uri_reference<S>(uri: &URIReference, serializer: S) -> Result<S::Ok, S::Error>
167where
168 S: Serializer,
169{
170 let value = uri.to_string();
171 serializer.serialize_str(value.as_str())
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use crate::package_descriptor::PlatformOs::Windows;
178
179 #[test]
180 fn it_parses_minimal() {
181 let toml_str = r#"
182[buildpack]
183uri = "."
184"#;
185
186 let package_descriptor = toml::from_str::<PackageDescriptor>(toml_str).unwrap();
187 assert_eq!(
188 package_descriptor.buildpack,
189 PackageDescriptorBuildpackReference::try_from(".").unwrap()
190 );
191 assert_eq!(package_descriptor.platform.os, Linux);
192 }
193
194 #[test]
195 fn it_parses_with_dependencies_and_platform() {
196 let toml_str = r#"
197[buildpack]
198uri = "."
199
200[[dependencies]]
201uri = "libcnb:buildpack-id"
202
203[[dependencies]]
204uri = "../relative/path"
205
206[[dependencies]]
207uri = "/absolute/path"
208
209[[dependencies]]
210uri = "docker://docker.io/heroku/example:1.2.3"
211
212[platform]
213os = "windows"
214"#;
215
216 let package_descriptor = toml::from_str::<PackageDescriptor>(toml_str).unwrap();
217 assert_eq!(
218 package_descriptor.buildpack,
219 PackageDescriptorBuildpackReference::try_from(".").unwrap()
220 );
221 assert_eq!(package_descriptor.platform.os, Windows);
222 assert_eq!(
223 package_descriptor.dependencies,
224 [
225 PackageDescriptorDependency::try_from("libcnb:buildpack-id").unwrap(),
226 PackageDescriptorDependency::try_from("../relative/path").unwrap(),
227 PackageDescriptorDependency::try_from("/absolute/path").unwrap(),
228 PackageDescriptorDependency::try_from("docker://docker.io/heroku/example:1.2.3")
229 .unwrap()
230 ]
231 );
232 }
233
234 #[test]
235 fn it_serializes() {
236 let package_descriptor = PackageDescriptor {
237 buildpack: PackageDescriptorBuildpackReference::try_from(".").unwrap(),
238 dependencies: vec![
239 PackageDescriptorDependency::try_from("libcnb:buildpack-id").unwrap(),
240 PackageDescriptorDependency::try_from("../relative/path").unwrap(),
241 PackageDescriptorDependency::try_from("/absolute/path").unwrap(),
242 PackageDescriptorDependency::try_from("docker://docker.io/heroku/example:1.2.3")
243 .unwrap(),
244 ],
245 platform: Platform::default(),
246 };
247
248 let package_descriptor_contents = toml::to_string(&package_descriptor).unwrap();
249 assert_eq!(
250 package_descriptor_contents,
251 r#"
252[buildpack]
253uri = "."
254
255[[dependencies]]
256uri = "libcnb:buildpack-id"
257
258[[dependencies]]
259uri = "../relative/path"
260
261[[dependencies]]
262uri = "/absolute/path"
263
264[[dependencies]]
265uri = "docker://docker.io/heroku/example:1.2.3"
266
267[platform]
268os = "linux"
269"#
270 .trim_start()
271 );
272 }
273}