diff --git a/src/image/mod.rs b/src/image/mod.rs index 3a2ef2b973..73a49fec10 100644 --- a/src/image/mod.rs +++ b/src/image/mod.rs @@ -6,6 +6,7 @@ mod config; mod descriptor; mod index; mod manifest; +mod oci_layout; mod version; use std::fmt::Display; @@ -18,6 +19,7 @@ pub use config::*; pub use descriptor::*; pub use index::*; pub use manifest::*; +pub use oci_layout::*; pub use version::*; /// Media types used by OCI image format spec. Values MUST comply with RFC 6838, diff --git a/src/image/oci_layout.rs b/src/image/oci_layout.rs new file mode 100644 index 0000000000..441126cc36 --- /dev/null +++ b/src/image/oci_layout.rs @@ -0,0 +1,247 @@ +use crate::{ + error::{OciSpecError, Result}, + from_file, from_reader, to_file, to_string, to_writer, +}; +use derive_builder::Builder; +use getset::{Getters, Setters}; +use serde::{Deserialize, Serialize}; +use std::{ + io::{Read, Write}, + path::Path, +}; + +#[derive(Builder, Clone, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +#[builder( + pattern = "owned", + setter(into, strip_option), + build_fn(error = "OciSpecError") +)] + +/// The oci layout JSON object serves as a marker for the base of an Open Container Image Layout +/// and to provide the version of the image-layout in use. The imageLayoutVersion value will align +/// with the OCI Image Specification version at the time changes to the layout are made, and will +/// pin a given version until changes to the image layout are required. +pub struct OciLayout { + /// This REQUIRED property specifies the image layout version. + #[getset(get = "pub", set = "pub")] + image_layout_version: String, +} + +impl OciLayout { + /// Attempts to load an oci layout from a file. + /// # Errors + /// This function will return an [OciSpecError::Io](crate::OciSpecError::Io) + /// if the file does not exist or an + /// [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the oci layout + /// cannot be deserialized. + /// # Example + /// ``` no_run + /// use oci_spec::image::OciLayout; + /// + /// let oci_layout = OciLayout::from_file("oci-layout").unwrap(); + /// ``` + pub fn from_file>(path: P) -> Result { + from_file(path) + } + + /// Attempts to load an oci layout from a stream. + /// # Errors + /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) + /// if the oci layout cannot be deserialized. + /// # Example + /// ``` no_run + /// use oci_spec::image::OciLayout; + /// use std::fs::File; + /// + /// let reader = File::open("oci-layout").unwrap(); + /// let oci_layout = OciLayout::from_reader(reader).unwrap(); + /// ``` + pub fn from_reader(reader: R) -> Result { + from_reader(reader) + } + + /// Attempts to write an oci layout to a file as JSON. If the file already exists, it + /// will be overwritten. + /// # Errors + /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if + /// the oci layout cannot be serialized. + /// # Example + /// ``` no_run + /// use oci_spec::image::OciLayout; + /// + /// let oci_layout = OciLayout::from_file("oci-layout").unwrap(); + /// oci_layout.to_file("oci-layout").unwrap(); + /// ``` + pub fn to_file>(&self, path: P) -> Result<()> { + to_file(&self, path, false) + } + + /// Attempts to write an oci layout to a file as pretty printed JSON. If the file + /// already exists, it will be overwritten. + /// # Errors + /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if + /// the oci layout cannot be serialized. + /// # Example + /// ``` no_run + /// use oci_spec::image::OciLayout; + /// + /// let oci_layout = OciLayout::from_file("oci-layout").unwrap(); + /// oci_layout.to_file_pretty("my-oci-layout").unwrap(); + /// ``` + pub fn to_file_pretty>(&self, path: P) -> Result<()> { + to_file(&self, path, true) + } + + /// Attempts to write an oci layout to a stream as JSON. + /// # Errors + /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if + /// the oci layout cannot be serialized. + /// # Example + /// ``` no_run + /// use oci_spec::image::OciLayout; + /// + /// let oci_layout = OciLayout::from_file("oci-layout").unwrap(); + /// let mut writer = Vec::new(); + /// oci_layout.to_writer(&mut writer); + /// ``` + pub fn to_writer(&self, writer: &mut W) -> Result<()> { + to_writer(&self, writer, false) + } + + /// Attempts to write an oci layout to a stream as pretty printed JSON. + /// # Errors + /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if + /// the oci layout cannot be serialized. + /// # Example + /// ``` no_run + /// use oci_spec::image::OciLayout; + /// + /// let oci_layout = OciLayout::from_file("oci-layout").unwrap(); + /// let mut writer = Vec::new(); + /// oci_layout.to_writer_pretty(&mut writer); + /// ``` + pub fn to_writer_pretty(&self, writer: &mut W) -> Result<()> { + to_writer(&self, writer, true) + } + + /// Attempts to write an oci layout to a string as JSON. + /// # Errors + /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if + /// the oci layout configuration cannot be serialized. + /// # Example + /// ``` no_run + /// use oci_spec::image::OciLayout; + /// + /// let oci_layout = OciLayout::from_file("oci-layout").unwrap(); + /// let json_str = oci_layout.to_string().unwrap(); + /// ``` + pub fn to_string(&self) -> Result { + to_string(&self, false) + } + + /// Attempts to write an oci layout to a string as pretty printed JSON. + /// # Errors + /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if + /// the oci layout configuration cannot be serialized. + /// # Example + /// ``` no_run + /// use oci_spec::image::OciLayout; + /// + /// let oci_layout = OciLayout::from_file("oci-layout").unwrap(); + /// let json_str = oci_layout.to_string_pretty().unwrap(); + /// ``` + pub fn to_string_pretty(&self) -> Result { + to_string(&self, true) + } +} + +#[cfg(test)] +mod tests { + use std::{fs, path::PathBuf}; + + use super::*; + + fn create_oci_layout() -> OciLayout { + OciLayoutBuilder::default() + .image_layout_version("lorem ipsum") + .build() + .expect("build oci layout") + } + + fn get_oci_layout_path() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test/data/oci-layout") + } + + #[test] + fn load_oci_layout_from_file() { + // arrange + let oci_layout_path = get_oci_layout_path(); + + // act + let actual = OciLayout::from_file(oci_layout_path).expect("from file"); + + // assert + let expected = create_oci_layout(); + assert_eq!(actual, expected); + } + + #[test] + fn load_oci_layout_from_reader() { + // arrange + let reader = fs::read(get_oci_layout_path()).expect("read oci-layout"); + + // act + let actual = OciLayout::from_reader(&*reader).expect("from reader"); + + // assert + let expected = create_oci_layout(); + assert_eq!(actual, expected); + } + + #[test] + fn save_oci_layout_to_file() { + // arrange + let tmp = std::env::temp_dir().join("save_oci_layout_to_file"); + fs::create_dir_all(&tmp).expect("create test directory"); + let oci_layout = create_oci_layout(); + let oci_layout_path = tmp.join("oci-layout"); + + // act + oci_layout + .to_file_pretty(&oci_layout_path) + .expect("write oci-layout to file"); + + // assert + let actual = fs::read_to_string(oci_layout_path).expect("read actual"); + let expected = fs::read_to_string(get_oci_layout_path()).expect("read expected"); + assert_eq!(actual, expected); + } + + #[test] + fn save_oci_layout_to_writer() { + // arrange + let mut actual = Vec::new(); + let oci_layout = create_oci_layout(); + + // act + oci_layout.to_writer_pretty(&mut actual).expect("to writer"); + + // assert + let expected = fs::read(get_oci_layout_path()).expect("read expected"); + assert_eq!(actual, expected); + } + + #[test] + fn save_oci_layout_to_string() { + // arrange + let oci_layout = create_oci_layout(); + + // act + let actual = oci_layout.to_string_pretty().expect("to string"); + + // assert + let expected = fs::read_to_string(get_oci_layout_path()).expect("read expected"); + assert_eq!(actual, expected); + } +} diff --git a/test/data/oci-layout b/test/data/oci-layout new file mode 100644 index 0000000000..31cf4a0e62 --- /dev/null +++ b/test/data/oci-layout @@ -0,0 +1,3 @@ +{ + "imageLayoutVersion": "lorem ipsum" +} \ No newline at end of file