diff --git a/Cargo.lock b/Cargo.lock index 675881fa..55f01b9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,20 +9,51 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee67c11feeac938fae061b232e38e0b6d94f97a9df10e6271319325ac4c56a86" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "getrandom" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "libc" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" + [[package]] name = "libcnb" version = "0.1.0" dependencies = [ + "anyhow", "lazy_static", "regex", "semver", "serde", + "tempfile", "thiserror", "toml", ] @@ -42,6 +73,12 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + [[package]] name = "proc-macro2" version = "1.0.24" @@ -60,6 +97,53 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + [[package]] name = "regex" version = "1.4.2" @@ -78,6 +162,15 @@ version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "semver" version = "0.11.0" @@ -128,6 +221,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +dependencies = [ + "cfg-if", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "thiserror" version = "1.0.22" @@ -177,3 +284,31 @@ name = "unicode-xid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index b7958aed..f1d5e2ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,7 @@ semver = { version = "0.11", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" toml = "0.5.8" + +[dev-dependencies] +anyhow = "1" +tempfile = "3" diff --git a/src/data/layer.rs b/src/data/layer.rs index 1a4ae74e..c8cafa44 100644 --- a/src/data/layer.rs +++ b/src/data/layer.rs @@ -12,3 +12,14 @@ pub struct Layer { pub cache: bool, pub metadata: Table, } + +impl Layer { + pub fn new() -> Self { + Layer { + launch: false, + build: false, + cache: false, + metadata: Table::new(), + } + } +} diff --git a/src/error.rs b/src/error.rs index db93267c..01f5c6d9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -#[derive(thiserror::Error, Debug, PartialEq)] +#[derive(thiserror::Error, Debug)] pub enum Error { #[error("Found `{0}` but value MUST be in form . or , where is equivalent to .0.")] InvalidBuildpackApi(String), @@ -12,4 +12,10 @@ pub enum Error { "Found `{0}` but value MUST only contain numbers, letters, and the characters ., /, and -." )] InvalidStackId(String), + #[error("could not serialize into TOML")] + TomlSerError(#[from] toml::ser::Error), + #[error("could not deserialize from TOML")] + TomlDeError(#[from] toml::de::Error), + #[error("I/O Error: {0}")] + IoError(#[from] std::io::Error), } diff --git a/src/layer.rs b/src/layer.rs new file mode 100644 index 00000000..379803d9 --- /dev/null +++ b/src/layer.rs @@ -0,0 +1,123 @@ +use crate::{data::layer::Layer as ContentMetadata, Error}; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +/// CNB Layer +pub struct Layer { + pub name: String, + path: PathBuf, + content_metadata_path: PathBuf, + content_metadata: ContentMetadata, +} + +impl Layer { + /// Layer Constructor that makes a ready to go layer: + /// * create `// if it doesn't exist + /// * `//.toml` will be read and parsed from disk if found. If not found an + /// empty [`crate::data::layer::Layer`] will be constructed. + /// + /// # Errors + /// This function will return an error when: + /// * if it can not create the layer dir + /// * if it can not deserialize Layer Content Metadata to [`crate::data::layer::Layer`] + /// + /// # Examples + /// ``` + /// # use tempfile::tempdir; + /// use libcnb::layer::Layer; + /// + /// # fn main() -> Result<(), libcnb::Error> { + /// # let layers_dir = tempdir().unwrap().path().to_owned(); + /// let layer = Layer::new("foo", layers_dir)?; + /// + /// assert!(layer.as_path().exists()); + /// assert_eq!(layer.content_metadata().launch, false); + /// assert_eq!(layer.content_metadata().build, false); + /// assert_eq!(layer.content_metadata().cache, false); + /// assert!(layer.content_metadata().metadata.is_empty()); + /// # Ok(()) + /// # } + /// ``` + pub fn new(name: impl Into, layers_dir: impl AsRef) -> Result { + let name = name.into(); + let layers_dir = layers_dir.as_ref(); + let path = layers_dir.join(&name); + + fs::create_dir_all(&path)?; + + let content_metadata_path = layers_dir.join(format!("{}.toml", &name)); + let content_metadata = if let Ok(contents) = fs::read_to_string(&content_metadata_path) { + toml::from_str(&contents)? + } else { + ContentMetadata::new() + }; + + Ok(Layer { + name, + path, + content_metadata, + content_metadata_path, + }) + } + + /// Returns the path to the layer contents `///`. + pub fn as_path(&self) -> &Path { + self.path.as_path() + } + + /// Returns a reference to the [`crate::data::layer::Layer`] + pub fn content_metadata(&self) -> &ContentMetadata { + &self.content_metadata + } + + /// Returns a mutable reference to the [`crate::data::layer::Layer`] + pub fn mut_content_metadata(&mut self) -> &mut ContentMetadata { + &mut self.content_metadata + } + + /// Write [`crate::data::layer::Layer`] to `.toml` + pub fn write_content_metadata(&self) -> Result<(), crate::Error> { + fs::write( + &self.content_metadata_path, + toml::to_string(&self.content_metadata)?, + )?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use tempfile::tempdir; + + #[test] + fn new_reads_layer_toml_metadata() -> Result<(), anyhow::Error> { + let layers_dir = tempdir()?.path().to_owned(); + fs::create_dir_all(&layers_dir)?; + fs::write( + layers_dir.join("foo.toml"), + r#" +[metadata] +bar = "baz" +"#, + )?; + + let layer = Layer::new("foo", &layers_dir)?; + assert_eq!( + layer + .content_metadata() + .metadata + .get::("bar") + .unwrap() + .as_str() + .unwrap(), + "baz" + ); + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index a41f83f4..160a77d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,5 +3,6 @@ pub mod data; mod error; pub use error::Error; pub mod detect; +pub mod layer; pub mod platform; pub mod shared;