diff --git a/file_lib/Cargo.toml b/file_lib/Cargo.toml index ecfcac21..1df66271 100644 --- a/file_lib/Cargo.toml +++ b/file_lib/Cargo.toml @@ -9,5 +9,10 @@ edition = "2021" crossterm = "0.27.0" digest = "0.10.7" hex-string = "0.1.0" +schemars = "0.8.15" +serde = "1.0.188" +serde_json = "1.0.107" sha1 = "0.10.6" sha2 = "0.10.8" +strum = "0.25.0" +strum_macros = "0.25.2" diff --git a/file_lib/cspell.yaml b/file_lib/cspell.yaml index ca7c4012..2f3733e0 100644 --- a/file_lib/cspell.yaml +++ b/file_lib/cspell.yaml @@ -1,3 +1,4 @@ words: - crossterm - hasher + - schemars diff --git a/file_lib/src/checksum.rs b/file_lib/src/checksum.rs index a8bc7e3e..c3b4b0bd 100644 --- a/file_lib/src/checksum.rs +++ b/file_lib/src/checksum.rs @@ -1,12 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use hex_string::HexString; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; use sha1::Sha1; use sha2::{Sha256, Sha512}; -use hex_string::HexString; +use strum_macros::Display; /// The supported checksum hash algorithms. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, Display)] pub enum Algorithm { /// The SHA-1 algorithm. (Considered insecure.) Sha1, diff --git a/file_lib/src/configuration.rs b/file_lib/src/configuration.rs new file mode 100644 index 00000000..96887c20 --- /dev/null +++ b/file_lib/src/configuration.rs @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::Algorithm; + +/// Definition for a file resource configuration, including the path, hash, and content. +/// +/// * `path` - The path to the file. +/// * `hash` - The hash of the file to either compare or compute. +/// * `content` - The content to use when asserting or setting the desired state. +/// * `exist` - The well-known flag indicating whether or not the file exists or should exist. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] +#[schemars(title = "DSC.FileConfiguration", description = "File resource configuration.")] +pub struct File { + pub path: String, + #[serde(rename = "hash", skip_serializing_if = "Option::is_none")] + pub hash: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option, + #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] + pub exist: Option, +} + +impl File { + /// Serialize the file configuration to a JSON string. + /// + /// # Return value + /// + /// The file configuration instance as a JSON string. + /// + /// # Examples + /// + /// ``` + /// # use file_lib::configuration::*; + /// # use file_lib::checksum::Algorithm; + /// # let EXPECTED_PATH = "path/to/file"; + /// # let EXPECTED_ALGORITHM = Algorithm::Sha512; + /// # let EXPECTED_CHECKSUM = "checksum-of-file"; + /// # let EXPECTED_CONTENT = "content-of-file"; + /// # let file = File { + /// # path: EXPECTED_PATH.to_string(), + /// # hash: Some(Hash { + /// # algorithm: EXPECTED_ALGORITHM, + /// # checksum: Some(EXPECTED_CHECKSUM.to_string()), + /// # }), + /// # content: Some(EXPECTED_CONTENT.to_string()), + /// # exist: None, + /// # }; + /// let json = file.to_json(); + /// assert!(json.contains(EXPECTED_PATH)); + /// assert!(json.contains(EXPECTED_ALGORITHM.to_string().as_str())); + /// assert!(json.contains(EXPECTED_CHECKSUM)); + /// assert!(json.contains(EXPECTED_CONTENT)); + /// ``` + #[must_use] + pub fn to_json(&self) -> String { + match serde_json::to_string(self) { + Ok(json) => json, + Err(e) => { + eprintln!("Failed to serialize to JSON: {e}"); + String::new() + } + } + } + + /// Deserialize a file configuration from a JSON string. + /// + /// * `json` - The JSON string to deserialize. + /// + /// # Return value + /// On success, the deserialized file configuration; otherwise, `None`. + /// + /// # Examples + /// + /// ``` + /// # use file_lib::configuration::File; + /// # use file_lib::checksum::Algorithm; + /// # let EXPECTED_PATH = "path/to/file"; + /// # let EXPECTED_ALGORITHM = Algorithm::Sha512; + /// # let EXPECTED_CHECKSUM = "checksum-of-file"; + /// # let EXPECTED_CONTENT = "content-of-file"; + /// # let JSON = format!( + /// # r#"{{"path":"{path}","hash":{{"algorithm":"{algorithm}","checksum":"{checksum}"}},"content":"{content}"}}"#, + /// # path = EXPECTED_PATH, + /// # algorithm = EXPECTED_ALGORITHM, + /// # checksum = EXPECTED_CHECKSUM, + /// # content = EXPECTED_CONTENT); + /// let file = File::from_json(&JSON).unwrap(); + /// assert_eq!(file.path, EXPECTED_PATH); + /// assert_eq!(&file.content.unwrap(), EXPECTED_CONTENT); + /// + /// let hash = file.hash.unwrap(); + /// assert_eq!(hash.algorithm, EXPECTED_ALGORITHM); + /// assert_eq!(hash.checksum.unwrap(), EXPECTED_CHECKSUM); + /// ``` + #[must_use] + pub fn from_json(json: &str) -> Option { + match serde_json::from_str(json) { + Ok(file) => Some(file), + Err(e) => { + eprintln!("Failed to deserialize from JSON: {e}"); + None + } + } + } + + /// Get the JSON schema for the file resource configuration. + /// + /// * `pretty` - Flag indicating whether or not to pretty print the schema. + /// + /// # Return value + /// + /// The JSON schema as a string. + /// + /// # Examples + /// + /// ``` + /// # use file_lib::configuration::File; + /// let schema = File::get_schema(false); + /// assert!(schema.unwrap().contains(r#""$schema":"http://json-schema.org/draft-07/schema#""#)); + /// ``` + /// + /// # Errors + /// + /// Serialization fails if the schema cannot be generated. + pub fn get_schema(pretty: bool) -> Result { + let schema = schemars::schema_for!(File); + if pretty { + serde_json::to_string_pretty(&schema) + } else { + serde_json::to_string(&schema) + } + } +} + +impl Default for File { + /// Create an empty file configuration. + fn default() -> Self { + Self { + path: String::new(), + hash: None, + content: None, + exist: None, + } + } +} + +/// Definition for a hash using a given algorithm, and an optional checksum. +/// +/// * `algorithm` - The algorithm to use when comparing or computing the checksum. +/// * `checksum` - The checksum to compare against or the computed result. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] +#[schemars(title = "DSC.HashConfiguration", description = "Hash configuration.")] +pub struct Hash { + pub algorithm: Algorithm, + #[serde(skip_serializing_if = "Option::is_none")] + pub checksum: Option, +} + +impl Default for Hash { + /// Create a default hash configuration with no checksum. + fn default() -> Self { + Self { + algorithm: Algorithm::Sha512, + checksum: None, + } + } +} diff --git a/file_lib/src/lib.rs b/file_lib/src/lib.rs index 421cb071..4c2ece67 100644 --- a/file_lib/src/lib.rs +++ b/file_lib/src/lib.rs @@ -4,6 +4,9 @@ pub use self::checksum::Algorithm; pub use self::checksum::compute; pub use self::debug::check_debugger_prompt; +pub use self::configuration::File; +pub use self::configuration::Hash; pub mod checksum; pub mod debug; +pub mod configuration;