diff --git a/build.ps1 b/build.ps1 index b19abbbf9..991614945 100755 --- a/build.ps1 +++ b/build.ps1 @@ -8,6 +8,7 @@ param( [ValidateSet('current','aarch64-pc-windows-msvc','x86_64-pc-windows-msvc','aarch64-apple-darwin','x86_64-apple-darwin','aarch64-unknown-linux-gnu','aarch64-unknown-linux-musl','x86_64-unknown-linux-gnu','x86_64-unknown-linux-musl')] $architecture = 'current', [switch]$Clippy, + [switch]$FmtFix, [switch]$SkipBuild, [ValidateSet('msix','msix-private','msixbundle','tgz','zip')] $packageType, @@ -252,6 +253,19 @@ if ($null -ne $packageType) { if ($LASTEXITCODE -ne 0) { throw "Failed to install clippy" } + + Write-Verbose -Verbose "Installing rustfmt..." + if ($UseCFS) { + cargo install rustfmt --config .cargo/config.toml + } else { + if ($architecture -ne 'current') { + write-verbose -verbose "Installing rustfmt for $architecture" + rustup component add rustfmt --target $architecture + } else { + write-verbose -verbose "Installing rustfmt for current architecture" + rustup component add rustfmt + } + } } ## Test if Node is installed @@ -433,6 +447,22 @@ if (!$SkipBuild) { Write-Verbose -Verbose "Running clippy with pedantic for $project" cargo clippy @flags --% -- -Dwarnings -Dclippy::pedantic } + + if ($LASTEXITCODE -ne 0) { + throw "Clippy failed for $project" + } + + if ($FmtFix) { + Write-Verbose -Verbose "Running rustfmt for $project" + cargo fmt --all + } + else { + Write-Verbose -Verbose "Running rustfmt check for $project" + cargo fmt --all -- --check + } + if ($LASTEXITCODE -ne 0) { + throw "Rustfmt failed for $project" + } } else { if ($UpdateLockFile) { diff --git a/dsc/src/args.rs b/dsc/src/args.rs index 298136071..43fc7d848 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -170,7 +170,7 @@ pub enum ConfigSubCommand { file: Option, #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, - } + }, } #[derive(Debug, PartialEq, Eq, Subcommand)] @@ -226,7 +226,11 @@ pub enum ResourceSubCommand { #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, - #[clap(name = "set", about = "Invoke the set operation to a resource", arg_required_else_help = true)] + #[clap( + name = "set", + about = "Invoke the set operation to a resource", + arg_required_else_help = true + )] Set { #[clap(short, long, help = t!("args.resource").to_string())] resource: String, @@ -239,7 +243,11 @@ pub enum ResourceSubCommand { #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, - #[clap(name = "test", about = "Invoke the test operation to a resource", arg_required_else_help = true)] + #[clap( + name = "test", + about = "Invoke the test operation to a resource", + arg_required_else_help = true + )] Test { #[clap(short, long, help = t!("args.resource").to_string())] resource: String, @@ -252,7 +260,11 @@ pub enum ResourceSubCommand { #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, - #[clap(name = "delete", about = "Invoke the delete operation to a resource", arg_required_else_help = true)] + #[clap( + name = "delete", + about = "Invoke the delete operation to a resource", + arg_required_else_help = true + )] Delete { #[clap(short, long, help = t!("args.resource").to_string())] resource: String, @@ -263,7 +275,11 @@ pub enum ResourceSubCommand { #[clap(short = 'f', long, help = t!("args.file").to_string(), conflicts_with = "input")] file: Option, }, - #[clap(name = "schema", about = "Get the JSON schema for a resource", arg_required_else_help = true)] + #[clap( + name = "schema", + about = "Get the JSON schema for a resource", + arg_required_else_help = true + )] Schema { #[clap(short, long, help = t!("args.resource").to_string())] resource: String, @@ -272,7 +288,11 @@ pub enum ResourceSubCommand { #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, - #[clap(name = "export", about = "Retrieve all resource instances", arg_required_else_help = true)] + #[clap( + name = "export", + about = "Retrieve all resource instances", + arg_required_else_help = true + )] Export { #[clap(short, long, help = t!("args.resource").to_string())] resource: String, @@ -304,5 +324,5 @@ pub enum SchemaType { ExtensionManifest, ExtensionDiscoverResult, FunctionDefinition, - RestartRequired + RestartRequired, } diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 7a1a7f876..79193806f 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -4,12 +4,12 @@ use args::{Args, SubCommand}; use clap::{CommandFactory, Parser}; use clap_complete::generate; +use dsc_lib::progress::ProgressFormat; use mcp::start_mcp_server; use rust_i18n::{i18n, t}; use std::{io, io::Read, process::exit}; -use sysinfo::{Process, RefreshKind, System, get_current_pid, ProcessRefreshKind}; -use tracing::{error, info, warn, debug}; -use dsc_lib::progress::ProgressFormat; +use sysinfo::{get_current_pid, Process, ProcessRefreshKind, RefreshKind, System}; +use tracing::{debug, error, info, warn}; use crate::util::EXIT_INVALID_INPUT; @@ -28,6 +28,7 @@ pub mod util; i18n!("locales", fallback = "en-us"); +#[allow(clippy::too_many_lines)] fn main() { #[cfg(debug_assertions)] check_debug(); @@ -45,29 +46,33 @@ fn main() { debug!("{}: {}", t!("main.usingDscVersion"), env!("CARGO_PKG_VERSION")); - let progress_format = args.progress_format.unwrap_or( ProgressFormat::Default ); + let progress_format = args.progress_format.unwrap_or(ProgressFormat::Default); match args.subcommand { SubCommand::Completer { shell } => { info!("{} {:?}", t!("main.generatingCompleter"), shell); let mut cmd = Args::command(); generate(shell, &mut cmd, "dsc", &mut io::stdout()); - }, - SubCommand::Config { subcommand, parameters, parameters_file, system_root, as_group, as_assert, as_include } => { + } + SubCommand::Config { + subcommand, + parameters, + parameters_file, + system_root, + as_group, + as_assert, + as_include, + } => { if let Some(file_name) = parameters_file { if file_name == "-" { info!("{}", t!("main.readingParametersFromStdin")); let mut stdin = Vec::::new(); let parameters = match io::stdin().read_to_end(&mut stdin) { - Ok(_) => { - match String::from_utf8(stdin) { - Ok(input) => { - input - }, - Err(err) => { - error!("{}: {err}", t!("util.invalidUtf8")); - exit(EXIT_INVALID_INPUT); - } + Ok(_) => match String::from_utf8(stdin) { + Ok(input) => input, + Err(err) => { + error!("{}: {err}", t!("util.invalidUtf8")); + exit(EXIT_INVALID_INPUT); } }, Err(err) => { @@ -75,28 +80,54 @@ fn main() { exit(EXIT_INVALID_INPUT); } }; - subcommand::config(&subcommand, &Some(parameters), true, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format); + subcommand::config( + &subcommand, + &Some(parameters), + true, + system_root.as_ref(), + &as_group, + &as_assert, + &as_include, + progress_format, + ); return; } info!("{}: {file_name}", t!("main.readingParametersFile")); match std::fs::read_to_string(&file_name) { - Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), false, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format), + Ok(parameters) => subcommand::config( + &subcommand, + &Some(parameters), + false, + system_root.as_ref(), + &as_group, + &as_assert, + &as_include, + progress_format, + ), Err(err) => { error!("{} '{file_name}': {err}", t!("main.failedReadingParametersFile")); exit(util::EXIT_INVALID_INPUT); } } + } else { + subcommand::config( + &subcommand, + ¶meters, + false, + system_root.as_ref(), + &as_group, + &as_assert, + &as_include, + progress_format, + ); } - else { - subcommand::config(&subcommand, ¶meters, false, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format); - } - }, + } SubCommand::Extension { subcommand } => { subcommand::extension(&subcommand, progress_format); - }, + } SubCommand::Function { subcommand } => { subcommand::function(&subcommand); - }, + } SubCommand::Mcp => { if let Err(err) = start_mcp_server() { error!("{}", t!("main.failedToStartMcpServer", error = err)); @@ -106,8 +137,11 @@ fn main() { } SubCommand::Resource { subcommand } => { subcommand::resource(&subcommand, progress_format); - }, - SubCommand::Schema { dsc_type , output_format } => { + } + SubCommand::Schema { + dsc_type, + output_format, + } => { let schema = util::get_schema(dsc_type); let json = match serde_json::to_string(&schema) { Ok(json) => json, @@ -117,7 +151,7 @@ fn main() { } }; util::write_object(&json, output_format.as_ref(), false); - }, + } } exit(util::EXIT_SUCCESS); @@ -144,21 +178,43 @@ fn ctrlc_handler() { } fn terminate_subprocesses(sys: &System, process: &Process) { - info!("{}: {:?} {}", t!("main.terminatingSubprocess"), process.name(), process.pid()); - for subprocess in sys.processes().values().filter(|p| p.parent().is_some_and(|parent| parent == process.pid())) { + info!( + "{}: {:?} {}", + t!("main.terminatingSubprocess"), + process.name(), + process.pid() + ); + for subprocess in sys + .processes() + .values() + .filter(|p| p.parent().is_some_and(|parent| parent == process.pid())) + { terminate_subprocesses(sys, subprocess); } - info!("{}: {:?} {}", t!("main.terminatingProcess"), process.name(), process.pid()); + info!( + "{}: {:?} {}", + t!("main.terminatingProcess"), + process.name(), + process.pid() + ); if !process.kill() { - error!("{}: {:?} {}", t!("main.failedTerminatingProcess"), process.name(), process.pid()); + error!( + "{}: {:?} {}", + t!("main.failedTerminatingProcess"), + process.name(), + process.pid() + ); } } #[cfg(debug_assertions)] fn check_debug() { if env::var("DEBUG_DSC").is_ok() { - eprintln!("attach debugger to pid {} and press a key to continue", std::process::id()); + eprintln!( + "attach debugger to pid {} and press a key to continue", + std::process::id() + ); loop { let event = match event::read() { Ok(event) => event, @@ -200,7 +256,9 @@ fn check_store() { }; // MS Store runs app using `sihost.exe` - if parent_process.name().eq_ignore_ascii_case("sihost.exe") || parent_process.name().eq_ignore_ascii_case("explorer.exe") { + if parent_process.name().eq_ignore_ascii_case("sihost.exe") + || parent_process.name().eq_ignore_ascii_case("explorer.exe") + { eprintln!("{}", t!("main.storeMessage")); // wait for keypress let _ = io::stdin().read(&mut [0u8]).unwrap(); diff --git a/dsc/src/mcp/invoke_dsc_resource.rs b/dsc/src/mcp/invoke_dsc_resource.rs index e060e4ca7..f005056fc 100644 --- a/dsc/src/mcp/invoke_dsc_resource.rs +++ b/dsc/src/mcp/invoke_dsc_resource.rs @@ -6,16 +6,11 @@ use dsc_lib::{ configure::config_doc::ExecutionKind, dscresources::{ dscresource::Invoke, - invoke_result::{ - ExportResult, - GetResult, - SetResult, - TestResult, - }, + invoke_result::{ExportResult, GetResult, SetResult, TestResult}, }, DscManager, }; -use rmcp::{ErrorData as McpError, Json, tool, tool_router, handler::server::wrapper::Parameters}; +use rmcp::{handler::server::wrapper::Parameters, tool, tool_router, ErrorData as McpError, Json}; use rust_i18n::t; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -50,7 +45,9 @@ pub struct InvokeDscResourceRequest { pub operation: DscOperation, #[schemars(description = "The type name of the DSC resource to invoke")] pub resource_type: String, - #[schemars(description = "The properties to pass to the DSC resource as JSON. Must match the resource JSON schema from `show_dsc_resource` tool.")] + #[schemars( + description = "The properties to pass to the DSC resource as JSON. Must match the resource JSON schema from `show_dsc_resource` tool." + )] pub properties_json: String, } @@ -66,11 +63,21 @@ impl McpServer { open_world_hint = true, ) )] - pub async fn invoke_dsc_resource(&self, Parameters(InvokeDscResourceRequest { operation, resource_type, properties_json }): Parameters) -> Result, McpError> { + pub async fn invoke_dsc_resource( + &self, + Parameters(InvokeDscResourceRequest { + operation, + resource_type, + properties_json, + }): Parameters, + ) -> Result, McpError> { let result = task::spawn_blocking(move || { let mut dsc = DscManager::new(); let Some(resource) = dsc.find_resource(&resource_type, None) else { - return Err(McpError::invalid_request(t!("mcp.invoke_dsc_resource.resourceNotFound", resource = resource_type), None)); + return Err(McpError::invalid_request( + t!("mcp.invoke_dsc_resource.resourceNotFound", resource = resource_type), + None, + )); }; match operation { DscOperation::Get => { @@ -79,21 +86,21 @@ impl McpServer { Err(e) => return Err(McpError::internal_error(e.to_string(), None)), }; Ok(ResourceOperationResult::GetResult(result)) - }, + } DscOperation::Set => { let result = match resource.set(&properties_json, false, &ExecutionKind::Actual) { Ok(res) => res, Err(e) => return Err(McpError::internal_error(e.to_string(), None)), }; Ok(ResourceOperationResult::SetResult(result)) - }, + } DscOperation::Test => { let result = match resource.test(&properties_json) { Ok(res) => res, Err(e) => return Err(McpError::internal_error(e.to_string(), None)), }; Ok(ResourceOperationResult::TestResult(result)) - }, + } DscOperation::Export => { let result = match resource.export(&properties_json) { Ok(res) => res, @@ -102,7 +109,9 @@ impl McpServer { Ok(ResourceOperationResult::ExportResult(result)) } } - }).await.map_err(|e| McpError::internal_error(e.to_string(), None))??; + }) + .await + .map_err(|e| McpError::internal_error(e.to_string(), None))??; Ok(Json(InvokeDscResourceResponse { result })) } diff --git a/dsc/src/mcp/list_dsc_functions.rs b/dsc/src/mcp/list_dsc_functions.rs index 52fe87d38..dcb90d512 100644 --- a/dsc/src/mcp/list_dsc_functions.rs +++ b/dsc/src/mcp/list_dsc_functions.rs @@ -2,13 +2,13 @@ // Licensed under the MIT License. use crate::mcp::mcp_server::McpServer; -use dsc_lib::functions::{FunctionDispatcher, FunctionDefinition}; +use dsc_lib::functions::{FunctionDefinition, FunctionDispatcher}; use dsc_lib::util::convert_wildcard_to_regex; -use rmcp::{ErrorData as McpError, Json, tool, tool_router, handler::server::wrapper::Parameters}; +use regex::RegexBuilder; +use rmcp::{handler::server::wrapper::Parameters, tool, tool_router, ErrorData as McpError, Json}; use rust_i18n::t; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use regex::RegexBuilder; use tokio::task; #[derive(Serialize, JsonSchema)] @@ -34,28 +34,34 @@ impl McpServer { open_world_hint = true, ) )] - pub async fn list_dsc_functions(&self, Parameters(ListFunctionsRequest { function_filter }): Parameters) -> Result, McpError> { + pub async fn list_dsc_functions( + &self, + Parameters(ListFunctionsRequest { function_filter }): Parameters, + ) -> Result, McpError> { let result = task::spawn_blocking(move || { let function_dispatcher = FunctionDispatcher::new(); let mut functions = function_dispatcher.list(); - + // apply filtering if function_filter is provided if let Some(name_pattern) = function_filter { let regex_str = convert_wildcard_to_regex(&name_pattern); let mut regex_builder = RegexBuilder::new(®ex_str); regex_builder.case_insensitive(true); - - let regex = regex_builder.build() - .map_err(|_| McpError::invalid_params( - t!("mcp.list_dsc_functions.invalidNamePattern", pattern = name_pattern), - None - ))?; - + + let regex = regex_builder.build().map_err(|_| { + McpError::invalid_params( + t!("mcp.list_dsc_functions.invalidNamePattern", pattern = name_pattern), + None, + ) + })?; + functions.retain(|func| regex.is_match(&func.name)); } - + Ok(FunctionListResult { functions }) - }).await.map_err(|e| McpError::internal_error(e.to_string(), None))??; + }) + .await + .map_err(|e| McpError::internal_error(e.to_string(), None))??; Ok(Json(result)) } diff --git a/dsc/src/mcp/list_dsc_resources.rs b/dsc/src/mcp/list_dsc_resources.rs index 2f347fb87..d493cf350 100644 --- a/dsc/src/mcp/list_dsc_resources.rs +++ b/dsc/src/mcp/list_dsc_resources.rs @@ -3,12 +3,12 @@ use crate::mcp::mcp_server::McpServer; use dsc_lib::{ - DscManager, discovery::{ - command_discovery::ImportedManifest::Resource, - discovery_trait::DiscoveryKind, - }, dscresources::resource_manifest::Kind, progress::ProgressFormat + discovery::{command_discovery::ImportedManifest::Resource, discovery_trait::DiscoveryKind}, + dscresources::resource_manifest::Kind, + progress::ProgressFormat, + DscManager, }; -use rmcp::{ErrorData as McpError, Json, tool, tool_router, handler::server::wrapper::Parameters}; +use rmcp::{handler::server::wrapper::Parameters, tool, tool_router, ErrorData as McpError, Json}; use rust_i18n::t; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -31,7 +31,9 @@ pub struct ResourceSummary { #[derive(Deserialize, JsonSchema)] pub struct ListResourcesRequest { - #[schemars(description = "Filter adapted resources to only those requiring the specified adapter type. If not specified, all non-adapted resources are returned.")] + #[schemars( + description = "Filter adapted resources to only those requiring the specified adapter type. If not specified, all non-adapted resources are returned." + )] pub adapter: Option, } @@ -47,20 +49,29 @@ impl McpServer { open_world_hint = true, ) )] - pub async fn list_dsc_resources(&self, Parameters(ListResourcesRequest { adapter }): Parameters) -> Result, McpError> { + pub async fn list_dsc_resources( + &self, + Parameters(ListResourcesRequest { adapter }): Parameters, + ) -> Result, McpError> { let result = task::spawn_blocking(move || { let mut dsc = DscManager::new(); let adapter_filter = match adapter { Some(adapter) => { if let Some(resource) = dsc.find_resource(&adapter, None) { if resource.kind != Kind::Adapter { - return Err(McpError::invalid_params(t!("mcp.list_dsc_resources.resourceNotAdapter", adapter = adapter), None)); + return Err(McpError::invalid_params( + t!("mcp.list_dsc_resources.resourceNotAdapter", adapter = adapter), + None, + )); } adapter } else { - return Err(McpError::invalid_params(t!("mcp.list_dsc_resources.adapterNotFound", adapter = adapter), None)); + return Err(McpError::invalid_params( + t!("mcp.list_dsc_resources.adapterNotFound", adapter = adapter), + None, + )); } - }, + } None => String::new(), }; let mut resources = BTreeMap::::new(); @@ -75,8 +86,12 @@ impl McpServer { resources.insert(resource.type_name.to_lowercase(), summary); } } - Ok(ResourceListResult { resources: resources.into_values().collect() }) - }).await.map_err(|e| McpError::internal_error(e.to_string(), None))??; + Ok(ResourceListResult { + resources: resources.into_values().collect(), + }) + }) + .await + .map_err(|e| McpError::internal_error(e.to_string(), None))??; Ok(Json(result)) } diff --git a/dsc/src/mcp/mcp_server.rs b/dsc/src/mcp/mcp_server.rs index 6f75af3a8..b10fefebc 100644 --- a/dsc/src/mcp/mcp_server.rs +++ b/dsc/src/mcp/mcp_server.rs @@ -2,29 +2,26 @@ // Licensed under the MIT License. use rmcp::{ - ErrorData as McpError, handler::server::tool::ToolRouter, - model::{InitializeResult, InitializeRequestParam, ServerCapabilities, ServerInfo}, + model::{InitializeRequestParam, InitializeResult, ServerCapabilities, ServerInfo}, service::{RequestContext, RoleServer}, - ServerHandler, - tool_handler, + tool_handler, ErrorData as McpError, ServerHandler, }; use rust_i18n::t; #[derive(Debug, Clone)] pub struct McpServer { - tool_router: ToolRouter + tool_router: ToolRouter, } impl McpServer { #[must_use] pub fn new() -> Self { Self { - tool_router: - Self::invoke_dsc_resource_router() + tool_router: Self::invoke_dsc_resource_router() + Self::list_dsc_functions_router() + Self::list_dsc_resources_router() - + Self::show_dsc_resource_router() + + Self::show_dsc_resource_router(), } } } @@ -39,15 +36,17 @@ impl Default for McpServer { impl ServerHandler for McpServer { fn get_info(&self) -> ServerInfo { ServerInfo { - capabilities: ServerCapabilities::builder() - .enable_tools() - .build(), + capabilities: ServerCapabilities::builder().enable_tools().build(), instructions: Some(t!("mcp.mod.instructions").to_string()), ..Default::default() } } - async fn initialize(&self, _request: InitializeRequestParam, _context: RequestContext) -> Result { + async fn initialize( + &self, + _request: InitializeRequestParam, + _context: RequestContext, + ) -> Result { Ok(self.get_info()) } } diff --git a/dsc/src/mcp/mod.rs b/dsc/src/mcp/mod.rs index 3cf1e3a8e..e132d322c 100644 --- a/dsc/src/mcp/mod.rs +++ b/dsc/src/mcp/mod.rs @@ -2,11 +2,7 @@ // Licensed under the MIT License. use crate::mcp::mcp_server::McpServer; -use rmcp::{ - ErrorData as McpError, - ServiceExt, - transport::stdio, -}; +use rmcp::{transport::stdio, ErrorData as McpError, ServiceExt}; use rust_i18n::t; pub mod invoke_dsc_resource; @@ -25,11 +21,15 @@ pub async fn start_mcp_server_async() -> Result<(), McpError> { let server = McpServer::new(); // Try to create the service with proper error handling - let service = server.serve(stdio()).await - .map_err(|err| McpError::internal_error(t!("mcp.mod.failedToInitialize", error = err.to_string()), None))?; + let service = server + .serve(stdio()) + .await + .map_err(|err| McpError::internal_error(t!("mcp.mod.failedToInitialize", error = err.to_string()), None))?; // Wait for the service to complete with proper error handling - service.waiting().await + service + .waiting() + .await .map_err(|err| McpError::internal_error(t!("mcp.mod.serverWaitFailed", error = err.to_string()), None))?; tracing::info!("{}", t!("mcp.mod.serverStopped")); diff --git a/dsc/src/mcp/show_dsc_resource.rs b/dsc/src/mcp/show_dsc_resource.rs index 660fbf312..a9a80eaf6 100644 --- a/dsc/src/mcp/show_dsc_resource.rs +++ b/dsc/src/mcp/show_dsc_resource.rs @@ -3,13 +3,13 @@ use crate::mcp::mcp_server::McpServer; use dsc_lib::{ - DscManager, dscresources::{ dscresource::{Capability, Invoke}, - resource_manifest::Kind + resource_manifest::Kind, }, + DscManager, }; -use rmcp::{ErrorData as McpError, Json, tool, tool_router, handler::server::wrapper::Parameters}; +use rmcp::{handler::server::wrapper::Parameters, tool, tool_router, ErrorData as McpError, Json}; use rust_i18n::t; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -19,7 +19,7 @@ use tokio::task; #[derive(Serialize, JsonSchema)] pub struct DscResource { /// The namespaced name of the resource. - #[serde(rename="type")] + #[serde(rename = "type")] pub type_name: String, /// The kind of resource. pub kind: Kind, @@ -55,11 +55,17 @@ impl McpServer { open_world_hint = true, ) )] - pub async fn show_dsc_resource(&self, Parameters(ShowResourceRequest { r#type }): Parameters) -> Result, McpError> { + pub async fn show_dsc_resource( + &self, + Parameters(ShowResourceRequest { r#type }): Parameters, + ) -> Result, McpError> { let result = task::spawn_blocking(move || { let mut dsc = DscManager::new(); let Some(resource) = dsc.find_resource(&r#type, None) else { - return Err(McpError::invalid_params(t!("mcp.show_dsc_resource.resourceNotFound", type_name = r#type), None)) + return Err(McpError::invalid_params( + t!("mcp.show_dsc_resource.resourceNotFound", type_name = r#type), + None, + )); }; let schema = match resource.schema() { Ok(schema_str) => serde_json::from_str(&schema_str).ok(), @@ -74,7 +80,9 @@ impl McpServer { author: resource.author.clone(), schema, }) - }).await.map_err(|e| McpError::internal_error(e.to_string(), None))??; + }) + .await + .map_err(|e| McpError::internal_error(e.to_string(), None))??; Ok(Json(result)) } diff --git a/dsc/src/resolve.rs b/dsc/src/resolve.rs index 11ff535f9..539f9b4b5 100644 --- a/dsc/src/resolve.rs +++ b/dsc/src/resolve.rs @@ -5,8 +5,8 @@ use dsc_lib::util::parse_input_to_json; use rust_i18n::t; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::io::Read; use std::fs::File; +use std::io::Read; use std::path::{Path, PathBuf}; use tracing::{debug, info}; @@ -55,6 +55,7 @@ pub struct Include { /// This function will return an error if the Include input is not valid JSON, if the file /// specified in the Include input cannot be read, or if the content of the file cannot be /// deserialized as YAML or JSON. +#[allow(clippy::too_many_lines)] pub fn get_contents(input: &str) -> Result<(Option, String), String> { debug!("{}", t!("resolve.processingInclude")); @@ -73,41 +74,57 @@ pub fn get_contents(input: &str) -> Result<(Option, String), String> { // read the file specified in the Include input let mut buffer: Vec = Vec::new(); match File::open(&include_path) { - Ok(mut file) => { - match file.read_to_end(&mut buffer) { - Ok(_) => (), - Err(err) => { - return Err(t!("resolve.failedToReadFile", path = include_path.to_string_lossy(), error = err.to_string()).to_string()); - } + Ok(mut file) => match file.read_to_end(&mut buffer) { + Ok(_) => (), + Err(err) => { + return Err(t!( + "resolve.failedToReadFile", + path = include_path.to_string_lossy(), + error = err.to_string() + ) + .to_string()); } }, Err(err) => { - return Err(t!("resolve.failedToOpenFile", path = include_path.to_string_lossy(), error = err.to_string()).to_string()); + return Err(t!( + "resolve.failedToOpenFile", + path = include_path.to_string_lossy(), + error = err.to_string() + ) + .to_string()); } } // convert the buffer to a string let include_content = match String::from_utf8(buffer) { Ok(input) => input, Err(err) => { - return Err(t!("resolve.invalidFileContent", path = include_path.to_string_lossy(), error = err.to_string()).to_string()); + return Err(t!( + "resolve.invalidFileContent", + path = include_path.to_string_lossy(), + error = err.to_string() + ) + .to_string()); } }; match parse_input_to_json(&include_content) { Ok(json) => json, Err(err) => { - return Err(t!("resolve.invalidFile", path = include_path.to_string_lossy(), error = err.to_string()).to_string()); - } - } - }, - IncludeKind::ConfigurationContent(text) => { - match parse_input_to_json(&text) { - Ok(json) => json, - Err(err) => { - return Err(t!("resolve.invalidContent", error = err.to_string()).to_string()); + return Err(t!( + "resolve.invalidFile", + path = include_path.to_string_lossy(), + error = err.to_string() + ) + .to_string()); } } } + IncludeKind::ConfigurationContent(text) => match parse_input_to_json(&text) { + Ok(json) => json, + Err(err) => { + return Err(t!("resolve.invalidContent", error = err.to_string()).to_string()); + } + }, }; let parameters = match include.parameters { @@ -120,16 +137,26 @@ pub fn get_contents(input: &str) -> Result<(Option, String), String> { let parameters_json = match parse_input_to_json(¶meters) { Ok(json) => json, Err(err) => { - return Err(t!("resolve.failedParseParametersFile", path = parameters_file.to_string_lossy(), error = err.to_string()).to_string()); + return Err(t!( + "resolve.failedParseParametersFile", + path = parameters_file.to_string_lossy(), + error = err.to_string() + ) + .to_string()); } }; Some(parameters_json) - }, + } Err(err) => { - return Err(t!("resolve.couldNotReadParametersFile", path = parameters_file.to_string_lossy(), error = err.to_string()).to_string()); + return Err(t!( + "resolve.couldNotReadParametersFile", + path = parameters_file.to_string_lossy(), + error = err.to_string() + ) + .to_string()); } } - }, + } Some(IncludeParametersKind::ParametersContent(text)) => { let parameters_json = match parse_input_to_json(&text) { Ok(json) => json, @@ -138,7 +165,7 @@ pub fn get_contents(input: &str) -> Result<(Option, String), String> { } }; Some(parameters_json) - }, + } None => { debug!("{}", t!("resolve.noParameters")); None diff --git a/dsc/src/resource_command.rs b/dsc/src/resource_command.rs index 22b6c7a74..4824232e1 100644 --- a/dsc/src/resource_command.rs +++ b/dsc/src/resource_command.rs @@ -2,29 +2,50 @@ // Licensed under the MIT License. use crate::args::{GetOutputFormat, OutputFormat}; -use crate::util::{EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_JSON_ERROR, EXIT_DSC_RESOURCE_NOT_FOUND, write_object}; -use dsc_lib::configure::config_doc::{Configuration, ExecutionKind}; +use crate::util::{write_object, EXIT_DSC_ERROR, EXIT_DSC_RESOURCE_NOT_FOUND, EXIT_INVALID_ARGS, EXIT_JSON_ERROR}; use dsc_lib::configure::add_resource_export_results_to_configuration; -use dsc_lib::dscresources::{resource_manifest::Kind, invoke_result::{GetResult, ResourceGetResponse}}; +use dsc_lib::configure::config_doc::{Configuration, ExecutionKind}; use dsc_lib::dscerror::DscError; +use dsc_lib::dscresources::{ + invoke_result::{GetResult, ResourceGetResponse}, + resource_manifest::Kind, +}; use rust_i18n::t; -use tracing::{error, debug}; +use tracing::{debug, error}; use dsc_lib::{ - dscresources::dscresource::{Invoke, DscResource}, - DscManager + dscresources::dscresource::{DscResource, Invoke}, + DscManager, }; use std::process::exit; -pub fn get(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, input: &str, format: Option<&GetOutputFormat>) { +pub fn get( + dsc: &mut DscManager, + resource_type: &str, + version: Option<&str>, + input: &str, + format: Option<&GetOutputFormat>, +) { let Some(resource) = get_resource(dsc, resource_type, version) else { - error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string()); + error!( + "{}", + DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string() + ); exit(EXIT_DSC_RESOURCE_NOT_FOUND); }; - debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); + debug!( + "{} {} {:?}", + resource.type_name, + t!("resource_command.implementedAs"), + resource.implemented_as + ); if resource.kind == Kind::Adapter { - error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); + error!( + "{}: {}", + t!("resource_command.invalidOperationOnAdapter"), + resource.type_name + ); exit(EXIT_DSC_ERROR); } @@ -70,18 +91,30 @@ pub fn get(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, inp pub fn get_all(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, format: Option<&GetOutputFormat>) { let input = String::new(); let Some(resource) = get_resource(dsc, resource_type, version) else { - error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string()); + error!( + "{}", + DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string() + ); exit(EXIT_DSC_RESOURCE_NOT_FOUND); }; - debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); + debug!( + "{} {} {:?}", + resource.type_name, + t!("resource_command.implementedAs"), + resource.implemented_as + ); if resource.kind == Kind::Adapter { - error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); + error!( + "{}: {}", + t!("resource_command.invalidOperationOnAdapter"), + resource.type_name + ); exit(EXIT_DSC_ERROR); } let export_result = match resource.export(&input) { - Ok(export) => { export } + Ok(export) => export, Err(err) => { error!("{err}"); exit(EXIT_DSC_ERROR); @@ -101,8 +134,7 @@ pub fn get_all(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, } let mut include_separator = false; - for instance in export_result.actual_state - { + for instance in export_result.actual_state { let get_result = GetResult::Resource(ResourceGetResponse { actual_state: instance.clone(), }); @@ -125,20 +157,38 @@ pub fn get_all(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, } } -pub fn set(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, input: &str, format: Option<&OutputFormat>) { +pub fn set( + dsc: &mut DscManager, + resource_type: &str, + version: Option<&str>, + input: &str, + format: Option<&OutputFormat>, +) { if input.is_empty() { error!("{}", t!("resource_command.setInputEmpty")); exit(EXIT_INVALID_ARGS); } let Some(resource) = get_resource(dsc, resource_type, version) else { - error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string()); + error!( + "{}", + DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string() + ); exit(EXIT_DSC_RESOURCE_NOT_FOUND); }; - debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); + debug!( + "{} {} {:?}", + resource.type_name, + t!("resource_command.implementedAs"), + resource.implemented_as + ); if resource.kind == Kind::Adapter { - error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); + error!( + "{}: {}", + t!("resource_command.invalidOperationOnAdapter"), + resource.type_name + ); exit(EXIT_DSC_ERROR); } @@ -161,20 +211,38 @@ pub fn set(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, inp } } -pub fn test(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, input: &str, format: Option<&OutputFormat>) { +pub fn test( + dsc: &mut DscManager, + resource_type: &str, + version: Option<&str>, + input: &str, + format: Option<&OutputFormat>, +) { if input.is_empty() { error!("{}", t!("resource_command.testInputEmpty")); exit(EXIT_INVALID_ARGS); } let Some(resource) = get_resource(dsc, resource_type, version) else { - error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string()); + error!( + "{}", + DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string() + ); exit(EXIT_DSC_RESOURCE_NOT_FOUND); }; - debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); + debug!( + "{} {} {:?}", + resource.type_name, + t!("resource_command.implementedAs"), + resource.implemented_as + ); if resource.kind == Kind::Adapter { - error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); + error!( + "{}: {}", + t!("resource_command.invalidOperationOnAdapter"), + resource.type_name + ); exit(EXIT_DSC_ERROR); } @@ -199,13 +267,25 @@ pub fn test(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, in pub fn delete(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, input: &str) { let Some(resource) = get_resource(dsc, resource_type, version) else { - error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string()); + error!( + "{}", + DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string() + ); exit(EXIT_DSC_RESOURCE_NOT_FOUND); }; - debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); + debug!( + "{} {} {:?}", + resource.type_name, + t!("resource_command.implementedAs"), + resource.implemented_as + ); if resource.kind == Kind::Adapter { - error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); + error!( + "{}: {}", + t!("resource_command.invalidOperationOnAdapter"), + resource.type_name + ); exit(EXIT_DSC_ERROR); } @@ -220,11 +300,18 @@ pub fn delete(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, pub fn schema(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, format: Option<&OutputFormat>) { let Some(resource) = get_resource(dsc, resource_type, version) else { - error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string()); + error!( + "{}", + DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string() + ); exit(EXIT_DSC_RESOURCE_NOT_FOUND); }; if resource.kind == Kind::Adapter { - error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); + error!( + "{}: {}", + t!("resource_command.invalidOperationOnAdapter"), + resource.type_name + ); exit(EXIT_DSC_ERROR); } @@ -247,14 +334,27 @@ pub fn schema(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, } } -pub fn export(dsc: &mut DscManager, resource_type: &str, version: Option<&str>, input: &str, format: Option<&OutputFormat>) { +pub fn export( + dsc: &mut DscManager, + resource_type: &str, + version: Option<&str>, + input: &str, + format: Option<&OutputFormat>, +) { let Some(dsc_resource) = get_resource(dsc, resource_type, version) else { - error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string()); + error!( + "{}", + DscError::ResourceNotFound(resource_type.to_string(), version.unwrap_or("").to_string()).to_string() + ); exit(EXIT_DSC_RESOURCE_NOT_FOUND); }; if dsc_resource.kind == Kind::Adapter { - error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), dsc_resource.type_name); + error!( + "{}: {}", + t!("resource_command.invalidOperationOnAdapter"), + dsc_resource.type_name + ); exit(EXIT_DSC_ERROR); } diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index 57d85cdc0..235e839e9 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -1,51 +1,48 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::args::{ConfigSubCommand, SchemaType, ExtensionSubCommand, FunctionSubCommand, GetOutputFormat, ListOutputFormat, OutputFormat, ResourceSubCommand}; +use crate::args::{ + ConfigSubCommand, ExtensionSubCommand, FunctionSubCommand, GetOutputFormat, ListOutputFormat, OutputFormat, + ResourceSubCommand, SchemaType, +}; use crate::resolve::{get_contents, Include}; -use crate::resource_command::{get_resource, self}; +use crate::resource_command::{self, get_resource}; use crate::tablewriter::Table; -use crate::util::{get_input, get_schema, in_desired_state, set_dscconfigroot, write_object, DSC_CONFIG_ROOT, EXIT_DSC_ASSERTION_FAILED, EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_INVALID_INPUT, EXIT_JSON_ERROR}; +use crate::util::{ + get_input, get_schema, in_desired_state, set_dscconfigroot, write_object, DSC_CONFIG_ROOT, + EXIT_DSC_ASSERTION_FAILED, EXIT_DSC_ERROR, EXIT_INVALID_ARGS, EXIT_INVALID_INPUT, EXIT_JSON_ERROR, +}; +use core::convert::AsRef; use dsc_lib::functions::FunctionArgKind; use dsc_lib::{ configure::{ - config_doc::{ - Configuration, - ExecutionKind, - Resource, - }, + config_doc::{Configuration, ExecutionKind, Resource}, config_result::ResourceGetResult, Configurator, }, - discovery::discovery_trait::{DiscoveryFilter, DiscoveryKind}, discovery::command_discovery::ImportedManifest, + discovery::discovery_trait::{DiscoveryFilter, DiscoveryKind}, dscerror::DscError, - DscManager, - dscresources::invoke_result::{ - ResolveResult, - TestResult, - ValidateResult, - }, - dscresources::dscresource::{Capability, ImplementedAs, validate_json, validate_properties}, + dscresources::dscresource::{validate_json, validate_properties, Capability, ImplementedAs}, + dscresources::invoke_result::{ResolveResult, TestResult, ValidateResult}, dscresources::resource_manifest::import_manifest, extensions::dscextension::Capability as ExtensionCapability, functions::FunctionDispatcher, progress::ProgressFormat, util::convert_wildcard_to_regex, + DscManager, }; use regex::RegexBuilder; use rust_i18n::t; -use core::convert::AsRef; use std::{ collections::HashMap, io::{self, IsTerminal}, path::Path, - process::exit + process::exit, }; use tracing::{debug, error, trace}; -pub fn config_get(configurator: &mut Configurator, format: Option<&OutputFormat>, as_group: &bool) -{ +pub fn config_get(configurator: &mut Configurator, format: Option<&OutputFormat>, as_group: &bool) { match configurator.invoke_get() { Ok(result) => { if *as_group { @@ -57,8 +54,7 @@ pub fn config_get(configurator: &mut Configurator, format: Option<&OutputFormat> } }; write_object(&json, format, false); - } - else { + } else { let json = match serde_json::to_string(&result) { Ok(json) => json, Err(err) => { @@ -71,7 +67,7 @@ pub fn config_get(configurator: &mut Configurator, format: Option<&OutputFormat> exit(EXIT_DSC_ERROR); } } - }, + } Err(err) => { error!("{err}"); exit(EXIT_DSC_ERROR); @@ -79,8 +75,7 @@ pub fn config_get(configurator: &mut Configurator, format: Option<&OutputFormat> } } -pub fn config_set(configurator: &mut Configurator, format: Option<&OutputFormat>, as_group: &bool) -{ +pub fn config_set(configurator: &mut Configurator, format: Option<&OutputFormat>, as_group: &bool) { match configurator.invoke_set(false) { Ok(result) => { if *as_group { @@ -92,8 +87,7 @@ pub fn config_set(configurator: &mut Configurator, format: Option<&OutputFormat> } }; write_object(&json, format, false); - } - else { + } else { let json = match serde_json::to_string(&result) { Ok(json) => json, Err(err) => { @@ -106,7 +100,7 @@ pub fn config_set(configurator: &mut Configurator, format: Option<&OutputFormat> exit(EXIT_DSC_ERROR); } } - }, + } Err(err) => { error!("Error: {err}"); exit(EXIT_DSC_ERROR); @@ -114,8 +108,15 @@ pub fn config_set(configurator: &mut Configurator, format: Option<&OutputFormat> } } -pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat>, as_group: &bool, as_get: &bool, as_config: &bool, as_assert: &bool) -{ +#[allow(clippy::too_many_lines)] +pub fn config_test( + configurator: &mut Configurator, + format: Option<&OutputFormat>, + as_group: &bool, + as_get: &bool, + as_config: &bool, + as_assert: &bool, +) { match configurator.invoke_test() { Ok(result) => { if *as_group { @@ -124,7 +125,10 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat result_configuration.resources = Vec::new(); for test_result in result.results { if *as_assert && !in_desired_state(&test_result) { - error!("{}", t!("subcommand.assertionFailed", resource_type = test_result.resource_type)); + error!( + "{}", + t!("subcommand.assertionFailed", resource_type = test_result.resource_type) + ); exit(EXIT_DSC_ASSERTION_FAILED); } let properties = match test_result.result { @@ -135,7 +139,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat debug!("{}", t!("subcommand.actualStateNotObject")); None } - }, + } TestResult::Group(_) => { // not expected debug!("{}", t!("subcommand.unexpectedTestResult")); @@ -157,12 +161,14 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat exit(EXIT_JSON_ERROR); } } - } - else if *as_get { + } else if *as_get { let mut group_result = Vec::::new(); for test_result in result.results { if *as_assert && !in_desired_state(&test_result) { - error!("{}", t!("subcommand.assertionFailed", resource_type = test_result.resource_type)); + error!( + "{}", + t!("subcommand.assertionFailed", resource_type = test_result.resource_type) + ); exit(EXIT_DSC_ASSERTION_FAILED); } group_result.push(test_result.into()); @@ -174,12 +180,14 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat exit(EXIT_JSON_ERROR); } } - } - else { + } else { if *as_assert { for test_result in &result.results { if !in_desired_state(test_result) { - error!("{}", t!("subcommand.assertionFailed", resource_type = test_result.resource_type)); + error!( + "{}", + t!("subcommand.assertionFailed", resource_type = test_result.resource_type) + ); exit(EXIT_DSC_ASSERTION_FAILED); } } @@ -193,8 +201,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat } }; write_object(&json, format, false); - } - else { + } else { let json = match serde_json::to_string(&result) { Ok(json) => json, Err(err) => { @@ -207,7 +214,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat exit(EXIT_DSC_ERROR); } } - }, + } Err(err) => { error!("{err}"); exit(EXIT_DSC_ERROR); @@ -215,8 +222,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat } } -pub fn config_export(configurator: &mut Configurator, format: Option<&OutputFormat>) -{ +pub fn config_export(configurator: &mut Configurator, format: Option<&OutputFormat>) { match configurator.invoke_export() { Ok(result) => { let json = match serde_json::to_string(&result.result) { @@ -228,15 +234,13 @@ pub fn config_export(configurator: &mut Configurator, format: Option<&OutputForm }; write_object(&json, format, false); if result.had_errors { - - for msg in result.messages - { + for msg in result.messages { error!("{:?} {} {}", msg.level, t!("subcommand.message"), msg.message); - }; + } exit(EXIT_DSC_ERROR); } - }, + } Err(err) => { error!("{err}"); exit(EXIT_DSC_ERROR); @@ -262,7 +266,10 @@ fn initialize_config_root(path: Option<&String>) -> Option { debug!("DSC_CONFIG_ROOT = {config_root}"); } else { let current_directory = std::env::current_dir().unwrap_or_default(); - debug!("DSC_CONFIG_ROOT = {} '{current_directory:?}'", t!("subcommand.currentDirectory")); + debug!( + "DSC_CONFIG_ROOT = {} '{current_directory:?}'", + t!("subcommand.currentDirectory") + ); set_dscconfigroot(¤t_directory.to_string_lossy()); } @@ -276,13 +283,22 @@ fn initialize_config_root(path: Option<&String>) -> Option { #[allow(clippy::too_many_lines)] #[allow(clippy::too_many_arguments)] -pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, parameters_from_stdin: bool, mounted_path: Option<&String>, as_group: &bool, as_assert: &bool, as_include: &bool, progress_format: ProgressFormat) { +pub fn config( + subcommand: &ConfigSubCommand, + parameters: &Option, + parameters_from_stdin: bool, + mounted_path: Option<&String>, + as_group: &bool, + as_assert: &bool, + as_include: &bool, + progress_format: ProgressFormat, +) { let (new_parameters, json_string) = match subcommand { - ConfigSubCommand::Get { input, file, .. } | - ConfigSubCommand::Set { input, file, .. } | - ConfigSubCommand::Test { input, file, .. } | - ConfigSubCommand::Validate { input, file, .. } | - ConfigSubCommand::Export { input, file, .. } => { + ConfigSubCommand::Get { input, file, .. } + | ConfigSubCommand::Set { input, file, .. } + | ConfigSubCommand::Test { input, file, .. } + | ConfigSubCommand::Validate { input, file, .. } + | ConfigSubCommand::Export { input, file, .. } => { let new_path = initialize_config_root(file.as_ref()); let document = get_input(input.as_ref(), new_path.as_ref(), parameters_from_stdin); if *as_include { @@ -297,7 +313,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, parame } else { (None, document) } - }, + } ConfigSubCommand::Resolve { input, file, .. } => { let new_path = initialize_config_root(file.as_ref()); let document = get_input(input.as_ref(), new_path.as_ref(), parameters_from_stdin); @@ -322,7 +338,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, parame configurator.context.dsc_version = Some(env!("CARGO_PKG_VERSION").to_string()); - if let ConfigSubCommand::Set { what_if , .. } = subcommand { + if let ConfigSubCommand::Set { what_if, .. } = subcommand { if *what_if { configurator.context.execution_type = ExecutionKind::WhatIf; } @@ -336,28 +352,24 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, parame None => { debug!("{}", t!("subcommand.noParameters")); None - }, + } Some(parameters) => { debug!("{}", t!("subcommand.parameters")); match serde_json::from_str(parameters) { Ok(json) => Some(json), - Err(_) => { - match serde_yaml::from_str::(parameters) { - Ok(yaml) => { - match serde_json::to_value(yaml) { - Ok(json) => Some(json), - Err(err) => { - error!("{}: {err}", t!("subcommand.failedConvertJson")); - exit(EXIT_DSC_ERROR); - } - } - }, + Err(_) => match serde_yaml::from_str::(parameters) { + Ok(yaml) => match serde_json::to_value(yaml) { + Ok(json) => Some(json), Err(err) => { - error!("{}: {err}", t!("subcommand.invalidParameters")); - exit(EXIT_INVALID_INPUT); + error!("{}: {err}", t!("subcommand.failedConvertJson")); + exit(EXIT_DSC_ERROR); } + }, + Err(err) => { + error!("{}: {err}", t!("subcommand.invalidParameters")); + exit(EXIT_INVALID_INPUT); } - } + }, } } }; @@ -384,14 +396,30 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, parame match subcommand { ConfigSubCommand::Get { output_format, .. } => { config_get(&mut configurator, output_format.as_ref(), as_group); - }, + } ConfigSubCommand::Set { output_format, .. } => { config_set(&mut configurator, output_format.as_ref(), as_group); - }, - ConfigSubCommand::Test { output_format, as_get, as_config, .. } => { - config_test(&mut configurator, output_format.as_ref(), as_group, as_get, as_config, as_assert); - }, - ConfigSubCommand::Validate { input, file, output_format} => { + } + ConfigSubCommand::Test { + output_format, + as_get, + as_config, + .. + } => { + config_test( + &mut configurator, + output_format.as_ref(), + as_group, + as_get, + as_config, + as_assert, + ); + } + ConfigSubCommand::Validate { + input, + file, + output_format, + } => { let mut result = ValidateResult { valid: true, reason: None, @@ -402,7 +430,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, parame match serde_json::from_str::(&input) { Ok(_) => { // valid, so do nothing - }, + } Err(err) => { error!("{}: {err}", t!("subcommand.invalidInclude")); result.valid = false; @@ -412,7 +440,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, parame match validate_config(configurator.get_config(), progress_format) { Ok(()) => { // valid, so do nothing - }, + } Err(err) => { error!("{err}"); result.valid = false; @@ -426,10 +454,10 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, parame }; write_object(&json, output_format.as_ref(), false); - }, + } ConfigSubCommand::Export { output_format, .. } => { config_export(&mut configurator, output_format.as_ref()); - }, + } ConfigSubCommand::Resolve { output_format, .. } => { let configuration = match serde_json::from_str(&json_string) { Ok(json) => json, @@ -460,7 +488,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, parame } }; write_object(&json_string, output_format.as_ref(), false); - }, + } } } @@ -494,22 +522,38 @@ pub fn validate_config(config: &Configuration, progress_format: ProgressFormat) let mut resource_types = Vec::::new(); for resource_block in resources { let Some(type_name) = resource_block["type"].as_str() else { - return Err(DscError::Validation(t!("subcommand.resourceTypeNotSpecified").to_string())); + return Err(DscError::Validation( + t!("subcommand.resourceTypeNotSpecified").to_string(), + )); }; - resource_types.push(DiscoveryFilter::new(type_name, resource_block["api_version"].as_str().map(std::string::ToString::to_string))); + resource_types.push(DiscoveryFilter::new( + type_name, + resource_block["api_version"] + .as_str() + .map(std::string::ToString::to_string), + )); } dsc.find_resources(&resource_types, progress_format); for resource_block in resources { let Some(type_name) = resource_block["type"].as_str() else { - return Err(DscError::Validation(t!("subcommand.resourceTypeNotSpecified").to_string())); + return Err(DscError::Validation( + t!("subcommand.resourceTypeNotSpecified").to_string(), + )); }; - trace!("{} '{}'", t!("subcommand.validatingResource"), resource_block["name"].as_str().unwrap_or_default()); + trace!( + "{} '{}'", + t!("subcommand.validatingResource"), + resource_block["name"].as_str().unwrap_or_default() + ); // get the actual resource let Some(resource) = get_resource(&mut dsc, type_name, resource_block["api_version"].as_str()) else { - return Err(DscError::Validation(format!("{}: '{type_name}'", t!("subcommand.resourceNotFound")))); + return Err(DscError::Validation(format!( + "{}: '{type_name}'", + t!("subcommand.resourceNotFound") + ))); }; // see if the resource is command based @@ -525,18 +569,29 @@ pub fn extension(subcommand: &ExtensionSubCommand, progress_format: ProgressForm let mut dsc = DscManager::new(); match subcommand { - ExtensionSubCommand::List{extension_name, output_format} => { - list_extensions(&mut dsc, extension_name.as_ref(), output_format.as_ref(), progress_format); - }, + ExtensionSubCommand::List { + extension_name, + output_format, + } => { + list_extensions( + &mut dsc, + extension_name.as_ref(), + output_format.as_ref(), + progress_format, + ); + } } } pub fn function(subcommand: &FunctionSubCommand) { let functions = FunctionDispatcher::new(); match subcommand { - FunctionSubCommand::List { function_name, output_format } => { + FunctionSubCommand::List { + function_name, + output_format, + } => { list_functions(&functions, function_name.as_ref(), output_format.as_ref()); - }, + } } } @@ -545,51 +600,127 @@ pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat let mut dsc = DscManager::new(); match subcommand { - ResourceSubCommand::List { resource_name, adapter_name, description, tags, output_format } => { - list_resources(&mut dsc, resource_name.as_ref(), adapter_name.as_ref(), description.as_ref(), tags.as_ref(), output_format.as_ref(), progress_format); - }, - ResourceSubCommand::Schema { resource , version, output_format } => { + ResourceSubCommand::List { + resource_name, + adapter_name, + description, + tags, + output_format, + } => { + list_resources( + &mut dsc, + resource_name.as_ref(), + adapter_name.as_ref(), + description.as_ref(), + tags.as_ref(), + output_format.as_ref(), + progress_format, + ); + } + ResourceSubCommand::Schema { + resource, + version, + output_format, + } => { dsc.find_resources(&[DiscoveryFilter::new(resource, version.clone())], progress_format); resource_command::schema(&mut dsc, resource, version.as_deref(), output_format.as_ref()); - }, - ResourceSubCommand::Export { resource, version, input, file, output_format } => { + } + ResourceSubCommand::Export { + resource, + version, + input, + file, + output_format, + } => { dsc.find_resources(&[DiscoveryFilter::new(resource, version.clone())], progress_format); let parsed_input = get_input(input.as_ref(), file.as_ref(), false); - resource_command::export(&mut dsc, resource, version.as_deref(), &parsed_input, output_format.as_ref()); - }, - ResourceSubCommand::Get { resource, version, input, file: path, all, output_format } => { + resource_command::export( + &mut dsc, + resource, + version.as_deref(), + &parsed_input, + output_format.as_ref(), + ); + } + ResourceSubCommand::Get { + resource, + version, + input, + file: path, + all, + output_format, + } => { dsc.find_resources(&[DiscoveryFilter::new(resource, version.clone())], progress_format); if *all { resource_command::get_all(&mut dsc, resource, version.as_deref(), output_format.as_ref()); - } - else { + } else { if *output_format == Some(GetOutputFormat::JsonArray) { error!("{}", t!("subcommand.jsonArrayNotSupported")); exit(EXIT_INVALID_ARGS); } let parsed_input = get_input(input.as_ref(), path.as_ref(), false); - resource_command::get(&mut dsc, resource, version.as_deref(), &parsed_input, output_format.as_ref()); + resource_command::get( + &mut dsc, + resource, + version.as_deref(), + &parsed_input, + output_format.as_ref(), + ); } - }, - ResourceSubCommand::Set { resource, version, input, file: path, output_format } => { + } + ResourceSubCommand::Set { + resource, + version, + input, + file: path, + output_format, + } => { dsc.find_resources(&[DiscoveryFilter::new(resource, version.clone())], progress_format); let parsed_input = get_input(input.as_ref(), path.as_ref(), false); - resource_command::set(&mut dsc, resource, version.as_deref(), &parsed_input, output_format.as_ref()); - }, - ResourceSubCommand::Test { resource, version, input, file: path, output_format } => { + resource_command::set( + &mut dsc, + resource, + version.as_deref(), + &parsed_input, + output_format.as_ref(), + ); + } + ResourceSubCommand::Test { + resource, + version, + input, + file: path, + output_format, + } => { dsc.find_resources(&[DiscoveryFilter::new(resource, version.clone())], progress_format); let parsed_input = get_input(input.as_ref(), path.as_ref(), false); - resource_command::test(&mut dsc, resource, version.as_deref(), &parsed_input, output_format.as_ref()); - }, - ResourceSubCommand::Delete { resource, version, input, file: path } => { + resource_command::test( + &mut dsc, + resource, + version.as_deref(), + &parsed_input, + output_format.as_ref(), + ); + } + ResourceSubCommand::Delete { + resource, + version, + input, + file: path, + } => { dsc.find_resources(&[DiscoveryFilter::new(resource, version.clone())], progress_format); let parsed_input = get_input(input.as_ref(), path.as_ref(), false); resource_command::delete(&mut dsc, resource, version.as_deref(), &parsed_input); - }, + } } } -fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format: Option<&ListOutputFormat>, progress_format: ProgressFormat) { +fn list_extensions( + dsc: &mut DscManager, + extension_name: Option<&String>, + format: Option<&ListOutputFormat>, + progress_format: ProgressFormat, +) { let mut write_table = false; let mut table = Table::new(&[ t!("subcommand.tableHeader_type").to_string().as_ref(), @@ -602,12 +733,14 @@ fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format write_table = true; } let mut include_separator = false; - for manifest_resource in dsc.list_available(&DiscoveryKind::Extension, extension_name.unwrap_or(&String::from("*")), "", progress_format) { + for manifest_resource in dsc.list_available( + &DiscoveryKind::Extension, + extension_name.unwrap_or(&String::from("*")), + "", + progress_format, + ) { if let ImportedManifest::Extension(extension) = manifest_resource { - let capability_types = [ - (ExtensionCapability::Discover, "d"), - (ExtensionCapability::Secret, "s"), - ]; + let capability_types = [(ExtensionCapability::Discover, "d"), (ExtensionCapability::Secret, "s")]; let mut capabilities = "-".repeat(capability_types.len()); for (i, (capability, letter)) in capability_types.iter().enumerate() { @@ -621,10 +754,9 @@ fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format extension.type_name, extension.version, capabilities, - extension.description.unwrap_or_default() + extension.description.unwrap_or_default(), ]); - } - else { + } else { // convert to json let json = match serde_json::to_string(&extension) { Ok(json) => json, @@ -642,7 +774,9 @@ fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format write_object(&json, format.as_ref(), include_separator); include_separator = true; // insert newline separating instances if writing to console - if io::stdout().is_terminal() { println!(); } + if io::stdout().is_terminal() { + println!(); + } } } } @@ -653,7 +787,11 @@ fn list_extensions(dsc: &mut DscManager, extension_name: Option<&String>, format } } -fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String>, output_format: Option<&ListOutputFormat>) { +fn list_functions( + functions: &FunctionDispatcher, + function_name: Option<&String>, + output_format: Option<&ListOutputFormat>, +) { let mut write_table = false; let mut table = Table::new(&[ t!("subcommand.tableHeader_functionCategory").to_string().as_ref(), @@ -668,7 +806,7 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> write_table = true; } let mut include_separator = false; - let returned_types= [ + let returned_types = [ (FunctionArgKind::Array, "a"), (FunctionArgKind::Boolean, "b"), (FunctionArgKind::Number, "n"), @@ -709,15 +847,19 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> }; table.add_row(vec![ - function.category.iter().map(std::string::ToString::to_string).collect::>().join(", "), + function + .category + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(", "), function.name, function.min_args.to_string(), max_args, arg_types, - function.description + function.description, ]); - } - else { + } else { let json = match serde_json::to_string(&function) { Ok(json) => json, Err(err) => { @@ -734,7 +876,9 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> write_object(&json, format.as_ref(), include_separator); include_separator = true; // insert newline separating instances if writing to console - if io::stdout().is_terminal() { println!(); } + if io::stdout().is_terminal() { + println!(); + } } } @@ -744,7 +888,16 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> } } -pub fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_name: Option<&String>, description: Option<&String>, tags: Option<&Vec>, format: Option<&ListOutputFormat>, progress_format: ProgressFormat) { +#[allow(clippy::too_many_lines)] +pub fn list_resources( + dsc: &mut DscManager, + resource_name: Option<&String>, + adapter_name: Option<&String>, + description: Option<&String>, + tags: Option<&Vec>, + format: Option<&ListOutputFormat>, + progress_format: ProgressFormat, +) { let mut write_table = false; let mut table = Table::new(&[ t!("subcommand.tableHeader_type").to_string().as_ref(), @@ -759,7 +912,12 @@ pub fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adap write_table = true; } let mut include_separator = false; - for manifest_resource in dsc.list_available(&DiscoveryKind::Resource, resource_name.unwrap_or(&String::from("*")), adapter_name.unwrap_or(&String::new()), progress_format) { + for manifest_resource in dsc.list_available( + &DiscoveryKind::Resource, + resource_name.unwrap_or(&String::from("*")), + adapter_name.unwrap_or(&String::new()), + progress_format, + ) { if let ImportedManifest::Resource(resource) = manifest_resource { let capability_types = [ (Capability::Get, "g"), @@ -790,14 +948,22 @@ pub fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adap }; // if description is specified, skip if resource description does not contain it - if description.is_some() && - (manifest.description.is_none() | !manifest.description.unwrap_or_default().to_lowercase().contains(&description.unwrap_or(&String::new()).to_lowercase())) { + if description.is_some() + && (manifest.description.is_none() + | !manifest + .description + .unwrap_or_default() + .to_lowercase() + .contains(&description.unwrap_or(&String::new()).to_lowercase())) + { continue; } // if tags is specified, skip if resource tags do not contain the tags if let Some(tags) = tags { - let Some(manifest_tags) = manifest.tags else { continue; }; + let Some(manifest_tags) = manifest.tags else { + continue; + }; let mut found = false; for tag_to_find in tags { @@ -808,7 +974,9 @@ pub fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adap } } } - if !found { continue; } + if !found { + continue; + } } } else { // resource does not have a manifest but filtering on description or tags was requested - skip such resource @@ -824,10 +992,9 @@ pub fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adap resource.version, capabilities, resource.require_adapter.unwrap_or_default(), - resource.description.unwrap_or_default() + resource.description.unwrap_or_default(), ]); - } - else { + } else { // convert to json let json = match serde_json::to_string(&resource) { Ok(json) => json, @@ -845,7 +1012,9 @@ pub fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adap write_object(&json, format.as_ref(), include_separator); include_separator = true; // insert newline separating instances if writing to console - if io::stdout().is_terminal() { println!(); } + if io::stdout().is_terminal() { + println!(); + } } } } diff --git a/dsc/src/util.rs b/dsc/src/util.rs index cc2c4d064..5fb89fb15 100644 --- a/dsc/src/util.rs +++ b/dsc/src/util.rs @@ -1,64 +1,43 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::args::{SchemaType, OutputFormat, TraceFormat}; +use crate::args::{OutputFormat, SchemaType, TraceFormat}; use crate::resolve::Include; use dsc_lib::{ configure::{ - config_doc::{ - Configuration, - Resource, - RestartRequired, - }, - config_result::{ - ConfigurationGetResult, - ConfigurationSetResult, - ConfigurationTestResult, - ResourceTestResult, - }, + config_doc::{Configuration, Resource, RestartRequired}, + config_result::{ConfigurationGetResult, ConfigurationSetResult, ConfigurationTestResult, ResourceTestResult}, }, discovery::Discovery, dscerror::DscError, dscresources::{ command_resource::TraceLevel, dscresource::DscResource, - invoke_result::{ - GetResult, - SetResult, - TestResult, - ResolveResult, - }, - resource_manifest::ResourceManifest - }, - extensions::{ - discover::DiscoverResult, - dscextension::Capability, - extension_manifest::ExtensionManifest, + invoke_result::{GetResult, ResolveResult, SetResult, TestResult}, + resource_manifest::ResourceManifest, }, + extensions::{discover::DiscoverResult, dscextension::Capability, extension_manifest::ExtensionManifest}, functions::FunctionDefinition, - util::{ - get_setting, - parse_input_to_json, - }, + util::{get_setting, parse_input_to_json}, }; use path_absolutize::Absolutize; use rust_i18n::t; -use schemars::{Schema, schema_for}; +use schemars::{schema_for, Schema}; use serde::Deserialize; use std::collections::HashMap; use std::env; -use std::io::{IsTerminal, Read, stdout, Write}; +use std::io::{stdout, IsTerminal, Read, Write}; use std::path::Path; use std::process::exit; use syntect::{ easy::HighlightLines, highlighting::ThemeSet, parsing::SyntaxSet, - util::{as_24_bit_terminal_escaped, LinesWithEndings} + util::{as_24_bit_terminal_escaped, LinesWithEndings}, }; -use tracing::{Level, debug, error, info, warn, trace}; -use tracing_subscriber::{filter::EnvFilter, layer::SubscriberExt, Layer}; +use tracing::{debug, error, info, trace, warn, Level}; use tracing_indicatif::IndicatifLayer; +use tracing_subscriber::{filter::EnvFilter, layer::SubscriberExt, Layer}; pub const EXIT_SUCCESS: i32 = 0; pub const EXIT_INVALID_ARGS: i32 = 1; @@ -77,12 +56,12 @@ pub const DSC_TRACE_LEVEL: &str = "DSC_TRACE_LEVEL"; #[derive(Deserialize)] pub struct TracingSetting { /// Trace level to use - see pub enum `TraceLevel` in `dsc_lib\src\dscresources\command_resource.rs` - level: TraceLevel, + level: TraceLevel, /// Trace format to use - see pub enum `TraceFormat` in `dsc\src\args.rs` format: TraceFormat, /// Whether the 'level' can be overrridden by `DSC_TRACE_LEVEL` environment variable #[serde(rename = "allowOverride")] - allow_override: bool + allow_override: bool, } impl Default for TracingSetting { @@ -105,8 +84,7 @@ impl Default for TracingSetting { /// /// * `String` - The JSON as a string #[must_use] -pub fn serde_json_value_to_string(json: &serde_json::Value) -> String -{ +pub fn serde_json_value_to_string(json: &serde_json::Value) -> String { match serde_json::to_string(&json) { Ok(json_string) => json_string, Err(err) => { @@ -131,8 +109,7 @@ pub fn serde_json_value_to_string(json: &serde_json::Value) -> String /// /// * `DscError` - The JSON is invalid #[allow(clippy::implicit_hasher)] -pub fn add_fields_to_json(json: &str, fields_to_add: &HashMap) -> Result -{ +pub fn add_fields_to_json(json: &str, fields_to_add: &HashMap) -> Result { let mut v = serde_json::from_str::(json)?; if let serde_json::Value::Object(ref mut map) = v { @@ -159,49 +136,49 @@ pub fn get_schema(schema: SchemaType) -> Schema { match schema { SchemaType::GetResult => { schema_for!(GetResult) - }, + } SchemaType::SetResult => { schema_for!(SetResult) - }, + } SchemaType::TestResult => { schema_for!(TestResult) - }, + } SchemaType::ResolveResult => { schema_for!(ResolveResult) } SchemaType::DscResource => { schema_for!(DscResource) - }, + } SchemaType::Resource => { schema_for!(Resource) - }, + } SchemaType::ResourceManifest => { schema_for!(ResourceManifest) - }, + } SchemaType::Include => { schema_for!(Include) - }, + } SchemaType::Configuration => { schema_for!(Configuration) - }, + } SchemaType::ConfigurationGetResult => { schema_for!(ConfigurationGetResult) - }, + } SchemaType::ConfigurationSetResult => { schema_for!(ConfigurationSetResult) - }, + } SchemaType::ConfigurationTestResult => { schema_for!(ConfigurationTestResult) - }, + } SchemaType::ExtensionManifest => { schema_for!(ExtensionManifest) - }, + } SchemaType::ExtensionDiscoverResult => { schema_for!(DiscoverResult) - }, + } SchemaType::FunctionDefinition => { schema_for!(FunctionDefinition) - }, + } SchemaType::RestartRequired => { schema_for!(RestartRequired) } @@ -224,8 +201,7 @@ pub fn write_object(json: &str, format: Option<&OutputFormat>, include_separator if output_format.is_none() { output_format = Some(&OutputFormat::Yaml); } - } - else if output_format.is_none() { + } else if output_format.is_none() { output_format = Some(&OutputFormat::Json); } @@ -246,7 +222,7 @@ pub fn write_object(json: &str, format: Option<&OutputFormat>, include_separator exit(EXIT_JSON_ERROR); } } - }, + } Some(OutputFormat::Yaml) | None => { is_json = false; if include_separator { @@ -291,8 +267,7 @@ pub fn write_object(json: &str, format: Option<&OutputFormat>, include_separator let escaped = as_24_bit_terminal_escaped(&ranges[..], false); print!("{escaped}"); } - } - else { + } else { let mut stdout_lock = stdout().lock(); if writeln!(stdout_lock, "{output}").is_err() { // likely caused by a broken pipe (e.g. 'head' command closed early) @@ -303,7 +278,6 @@ pub fn write_object(json: &str, format: Option<&OutputFormat>, include_separator #[allow(clippy::too_many_lines)] pub fn enable_tracing(trace_level_arg: Option<&TraceLevel>, trace_format_arg: Option<&TraceFormat>) { - let mut policy_is_used = false; let mut tracing_setting = TracingSetting::default(); @@ -312,12 +286,13 @@ pub fn enable_tracing(trace_level_arg: Option<&TraceLevel>, trace_format_arg: Op .unwrap_or_default() .add_directive(Level::WARN.into()); let default_indicatif_layer = IndicatifLayer::new(); - let default_layer = tracing_subscriber::fmt::Layer::default().with_writer(default_indicatif_layer.get_stderr_writer()); - let default_fmt = default_layer - .with_ansi(true) - .with_level(true) - .boxed(); - let default_subscriber = tracing_subscriber::Registry::default().with(default_fmt).with(default_filter).with(default_indicatif_layer); + let default_layer = + tracing_subscriber::fmt::Layer::default().with_writer(default_indicatif_layer.get_stderr_writer()); + let default_fmt = default_layer.with_ansi(true).with_level(true).boxed(); + let default_subscriber = tracing_subscriber::Registry::default() + .with(default_fmt) + .with(default_filter) + .with(default_indicatif_layer); let default_guard = tracing::subscriber::set_default(default_subscriber); // read setting/policy from files @@ -327,15 +302,19 @@ pub fn enable_tracing(trace_level_arg: Option<&TraceLevel>, trace_format_arg: Op Ok(v) => { tracing_setting = v; policy_is_used = true; - }, - Err(e) => { error!("{e}"); } + } + Err(e) => { + error!("{e}"); + } } } else if v.setting != serde_json::Value::Null { match serde_json::from_value::(v.setting) { Ok(v) => { tracing_setting = v; - }, - Err(e) => { error!("{e}"); } + } + Err(e) => { + error!("{e}"); + } } } } else { @@ -387,34 +366,31 @@ pub fn enable_tracing(trace_level_arg: Option<&TraceLevel>, trace_format_arg: Op let layer = tracing_subscriber::fmt::Layer::default().with_writer(indicatif_layer.get_stderr_writer()); let with_source = tracing_level == Level::DEBUG || tracing_level == Level::TRACE; let fmt = match tracing_setting.format { - TraceFormat::Default => { - layer - .with_ansi(true) - .with_level(true) - .with_target(with_source) - .with_line_number(with_source) - .boxed() - }, - TraceFormat::Plaintext => { - layer - .with_ansi(false) - .with_level(true) - .with_target(with_source) - .with_line_number(with_source) - .boxed() - }, - TraceFormat::Json | TraceFormat::PassThrough => { - layer - .with_ansi(false) - .with_level(true) - .with_target(with_source) - .with_line_number(with_source) - .json() - .boxed() - }, + TraceFormat::Default => layer + .with_ansi(true) + .with_level(true) + .with_target(with_source) + .with_line_number(with_source) + .boxed(), + TraceFormat::Plaintext => layer + .with_ansi(false) + .with_level(true) + .with_target(with_source) + .with_line_number(with_source) + .boxed(), + TraceFormat::Json | TraceFormat::PassThrough => layer + .with_ansi(false) + .with_level(true) + .with_target(with_source) + .with_line_number(with_source) + .json() + .boxed(), }; - let subscriber = tracing_subscriber::Registry::default().with(fmt).with(filter).with(indicatif_layer); + let subscriber = tracing_subscriber::Registry::default() + .with(fmt) + .with(filter) + .with(indicatif_layer); drop(default_guard); if tracing::subscriber::set_global_default(subscriber).is_err() { @@ -448,15 +424,11 @@ pub fn get_input(input: Option<&String>, file: Option<&String>, parameters_from_ } let mut stdin = Vec::::new(); match std::io::stdin().read_to_end(&mut stdin) { - Ok(_) => { - match String::from_utf8(stdin) { - Ok(input) => { - input - }, - Err(err) => { - error!("{}: {err}", t!("util.invalidUtf8")); - exit(EXIT_INVALID_INPUT); - } + Ok(_) => match String::from_utf8(stdin) { + Ok(input) => input, + Err(err) => { + error!("{}: {err}", t!("util.invalidUtf8")); + exit(EXIT_INVALID_INPUT); } }, Err(err) => { @@ -481,7 +453,7 @@ pub fn get_input(input: Option<&String>, file: Option<&String>, parameters_from_ } else { input } - }, + } Err(err) => { error!("{}: {err}", t!("util.failedToReadFile")); exit(EXIT_INVALID_INPUT); @@ -517,14 +489,13 @@ pub fn get_input(input: Option<&String>, file: Option<&String>, parameters_from_ /// /// Absolute full path to the config file. /// If a directory is provided, the path returned is the directory path. -pub fn set_dscconfigroot(config_path: &str) -> String -{ +pub fn set_dscconfigroot(config_path: &str) -> String { let path = Path::new(config_path); // make path absolute let Ok(full_path) = path.absolutize() else { - error!("{}", t!("util.failedToAbsolutizePath")); - exit(EXIT_DSC_ERROR); + error!("{}", t!("util.failedToAbsolutizePath")); + exit(EXIT_DSC_ERROR); }; let config_root_path = if full_path.is_file() { @@ -550,7 +521,6 @@ pub fn set_dscconfigroot(config_path: &str) -> String full_path.to_string_lossy().into_owned() } - /// Check if the test result is in the desired state. /// /// # Arguments @@ -563,9 +533,7 @@ pub fn set_dscconfigroot(config_path: &str) -> String #[must_use] pub fn in_desired_state(test_result: &ResourceTestResult) -> bool { match &test_result.result { - TestResult::Resource(result) => { - result.in_desired_state - }, + TestResult::Resource(result) => result.in_desired_state, TestResult::Group(results) => { for result in results { if !in_desired_state(result) { diff --git a/dsc_lib/src/configure/config_doc.rs b/dsc_lib/src/configure/config_doc.rs index 47ec09294..12e852abc 100644 --- a/dsc_lib/src/configure/config_doc.rs +++ b/dsc_lib/src/configure/config_doc.rs @@ -3,7 +3,7 @@ use chrono::{DateTime, Local}; use rust_i18n::t; -use schemars::{JsonSchema, json_schema}; +use schemars::{json_schema, JsonSchema}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; use std::{collections::HashMap, fmt::Display}; @@ -379,17 +379,13 @@ impl Default for Resource { #[cfg(test)] mod test { - use crate::{ - configure::config_doc::Configuration, - dscerror::DscError, - schemas::DscRepoSchema - }; + use crate::{configure::config_doc::Configuration, dscerror::DscError, schemas::DscRepoSchema}; #[test] fn test_validate_schema_uri_with_invalid_uri() { let invalid_uri = "https://invalid.schema.uri".to_string(); - let manifest = Configuration{ + let manifest = Configuration { schema: invalid_uri.clone(), ..Default::default() }; @@ -402,16 +398,19 @@ mod test { DscError::UnrecognizedSchemaUri(actual, recognized) => { assert_eq!(actual, &invalid_uri); assert_eq!(recognized, &Configuration::recognized_schema_uris()) - }, + } _ => { - panic!("Expected validate_schema_uri() to error on unrecognized schema uri, but was {:?}", result.as_ref().unwrap_err()) + panic!( + "Expected validate_schema_uri() to error on unrecognized schema uri, but was {:?}", + result.as_ref().unwrap_err() + ) } } } #[test] fn test_validate_schema_uri_with_valid_uri() { - let manifest = Configuration{ + let manifest = Configuration { schema: Configuration::default_schema_id_uri(), ..Default::default() }; diff --git a/dsc_lib/src/configure/config_result.rs b/dsc_lib/src/configure/config_result.rs index 6f2be74b2..d2bfd9e0c 100644 --- a/dsc_lib/src/configure/config_result.rs +++ b/dsc_lib/src/configure/config_result.rs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::configure::config_doc::{Configuration, Metadata}; +use crate::dscresources::invoke_result::{GetResult, SetResult, TestResult}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::dscresources::invoke_result::{GetResult, SetResult, TestResult}; -use crate::configure::config_doc::{Configuration, Metadata}; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] @@ -18,7 +18,7 @@ pub enum MessageLevel { #[serde(deny_unknown_fields)] pub struct ResourceMessage { pub name: String, - #[serde(rename="type")] + #[serde(rename = "type")] pub resource_type: String, pub message: String, pub level: MessageLevel, @@ -30,7 +30,7 @@ pub struct ResourceGetResult { #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, pub name: String, - #[serde(rename="type")] + #[serde(rename = "type")] pub resource_type: String, pub result: GetResult, } @@ -95,7 +95,7 @@ pub struct ResourceSetResult { #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, pub name: String, - #[serde(rename="type")] + #[serde(rename = "type")] pub resource_type: String, pub result: SetResult, } @@ -120,9 +120,7 @@ pub struct GroupResourceSetResult { impl GroupResourceSetResult { #[must_use] pub fn new() -> Self { - Self { - results: Vec::new(), - } + Self { results: Vec::new() } } } @@ -166,7 +164,7 @@ pub struct ResourceTestResult { #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, pub name: String, - #[serde(rename="type")] + #[serde(rename = "type")] pub resource_type: String, pub result: TestResult, } @@ -180,9 +178,7 @@ pub struct GroupResourceTestResult { impl GroupResourceTestResult { #[must_use] pub fn new() -> Self { - Self { - results: Vec::new(), - } + Self { results: Vec::new() } } } diff --git a/dsc_lib/src/configure/constraints.rs b/dsc_lib/src/configure/constraints.rs index dccd64840..a0413bff8 100644 --- a/dsc_lib/src/configure/constraints.rs +++ b/dsc_lib/src/configure/constraints.rs @@ -26,48 +26,88 @@ pub fn check_length(name: &str, value: &Value, constraint: &Parameter) -> Result if let Some(min_length) = constraint.min_length { if value.is_string() { let Some(value) = value.as_str() else { - return Err(DscError::Validation(t!("configure.constraints.minLengthIsNull", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.constraints.minLengthIsNull", name = name).to_string(), + )); }; if value.len() < usize::try_from(min_length)? { - return Err(DscError::Validation(t!("configure.constraints.notMinLength", name = name, min_length = min_length, length = value.len()).to_string())); + return Err(DscError::Validation( + t!( + "configure.constraints.notMinLength", + name = name, + min_length = min_length, + length = value.len() + ) + .to_string(), + )); } - } - else if value.is_array() { + } else if value.is_array() { let Some(value) = value.as_array() else { - return Err(DscError::Validation(t!("configure.constraints.minLengthIsNull", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.constraints.minLengthIsNull", name = name).to_string(), + )); }; if value.len() < usize::try_from(min_length)? { - return Err(DscError::Validation(t!("configure.constraints.notMinLength", name = name, min_length = min_length, length = value.len()).to_string())); + return Err(DscError::Validation( + t!( + "configure.constraints.notMinLength", + name = name, + min_length = min_length, + length = value.len() + ) + .to_string(), + )); } - } - else { - return Err(DscError::Validation(t!("configure.constraints.minLengthNotStringOrArray", name = name).to_string())); + } else { + return Err(DscError::Validation( + t!("configure.constraints.minLengthNotStringOrArray", name = name).to_string(), + )); } } if let Some(max_length) = constraint.max_length { if value.is_string() { let Some(value) = value.as_str() else { - return Err(DscError::Validation(t!("configure.constraints.maxLengthIsNull", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.constraints.maxLengthIsNull", name = name).to_string(), + )); }; if value.len() > usize::try_from(max_length)? { - return Err(DscError::Validation(t!("configure.constraints.maxLengthExceeded", name = name, max_length = max_length, length = value.len()).to_string())); + return Err(DscError::Validation( + t!( + "configure.constraints.maxLengthExceeded", + name = name, + max_length = max_length, + length = value.len() + ) + .to_string(), + )); } - } - else if value.is_array() { + } else if value.is_array() { let Some(value) = value.as_array() else { - return Err(DscError::Validation(t!("configure.constraints.maxLengthIsNull", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.constraints.maxLengthIsNull", name = name).to_string(), + )); }; if value.len() > usize::try_from(max_length)? { - return Err(DscError::Validation(t!("configure.constraints.maxLengthExceeded", name = name, max_length = max_length, length = value.len()).to_string())); + return Err(DscError::Validation( + t!( + "configure.constraints.maxLengthExceeded", + name = name, + max_length = max_length, + length = value.len() + ) + .to_string(), + )); } - } - else { - return Err(DscError::Validation(t!("configure.constraints.maxLengthNotStringOrArray", name = name).to_string())); + } else { + return Err(DscError::Validation( + t!("configure.constraints.maxLengthNotStringOrArray", name = name).to_string(), + )); } } @@ -94,30 +134,52 @@ pub fn check_number_limits(name: &str, value: &Value, constraint: &Parameter) -> if let Some(min_value) = constraint.min_value { if value.is_i64() && value.as_i64().is_some() { let Some(value) = value.as_i64() else { - return Err(DscError::Validation(t!("configure.constraints.minValueIsNull", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.constraints.minValueIsNull", name = name).to_string(), + )); }; if value < min_value { - return Err(DscError::Validation(t!("configure.constraints.notMinValue", name = name, min_value = min_value, value = value).to_string())); + return Err(DscError::Validation( + t!( + "configure.constraints.notMinValue", + name = name, + min_value = min_value, + value = value + ) + .to_string(), + )); } - } - else { - return Err(DscError::Validation(t!("configure.constraints.minValueNotInteger", name = name).to_string())); + } else { + return Err(DscError::Validation( + t!("configure.constraints.minValueNotInteger", name = name).to_string(), + )); } } if let Some(max_value) = constraint.max_value { if value.is_i64() && value.as_i64().is_some() { let Some(value) = value.as_i64() else { - return Err(DscError::Validation(t!("configure.constraints.maxValueIsNull", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.constraints.maxValueIsNull", name = name).to_string(), + )); }; if value > max_value { - return Err(DscError::Validation(t!("configure.constraints.notMaxValue", name = name, max_value = max_value, value = value).to_string())); + return Err(DscError::Validation( + t!( + "configure.constraints.notMaxValue", + name = name, + max_value = max_value, + value = value + ) + .to_string(), + )); } - } - else { - return Err(DscError::Validation(t!("configure.constraints.maxValueNotInteger", name = name).to_string())); + } else { + return Err(DscError::Validation( + t!("configure.constraints.maxValueNotInteger", name = name).to_string(), + )); } } @@ -142,26 +204,34 @@ pub fn check_number_limits(name: &str, value: &Value, constraint: &Parameter) -> /// * `DscError::Validation` if the value does not match the constraints. pub fn check_allowed_values(name: &str, value: &Value, constraint: &Parameter) -> Result<(), DscError> { if let Some(allowed_values) = &constraint.allowed_values { - if value.is_string() && value.as_str().is_some(){ + if value.is_string() && value.as_str().is_some() { let Some(value) = value.as_str() else { - return Err(DscError::Validation(t!("configure.constraints.allowedValuesIsNull", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.constraints.allowedValuesIsNull", name = name).to_string(), + )); }; if !allowed_values.contains(&Value::String(value.to_string())) { - return Err(DscError::Validation(t!("configure.constraints.notAllowedValue", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.constraints.notAllowedValue", name = name).to_string(), + )); } - } - else if value.is_i64() && value.as_i64().is_some() { + } else if value.is_i64() && value.as_i64().is_some() { let Some(value) = value.as_i64() else { - return Err(DscError::Validation(t!("configure.constraints.allowedValuesIsNull", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.constraints.allowedValuesIsNull", name = name).to_string(), + )); }; if !allowed_values.contains(&Value::Number(value.into())) { - return Err(DscError::Validation(t!("configure.constraints.notAllowedValue", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.constraints.notAllowedValue", name = name).to_string(), + )); } - } - else { - return Err(DscError::Validation(t!("configure.constraints.allowedValuesNotStringOrInteger", name = name).to_string())); + } else { + return Err(DscError::Validation( + t!("configure.constraints.allowedValuesNotStringOrInteger", name = name).to_string(), + )); } } diff --git a/dsc_lib/src/configure/context.rs b/dsc_lib/src/configure/context.rs index e9b4b7f23..df28266f1 100644 --- a/dsc_lib/src/configure/context.rs +++ b/dsc_lib/src/configure/context.rs @@ -1,8 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::{ + configure::config_doc::{ExecutionKind, UserFunctionDefinition}, + extensions::dscextension::DscExtension, +}; use chrono::{DateTime, Local}; -use crate::{configure::config_doc::{ExecutionKind, UserFunctionDefinition}, extensions::dscextension::DscExtension}; use security_context_lib::{get_security_context, SecurityContext}; use serde_json::{Map, Value}; use std::{collections::HashMap, path::PathBuf}; diff --git a/dsc_lib/src/configure/depends_on.rs b/dsc_lib/src/configure/depends_on.rs index 3e7a16a9f..a1936eb42 100644 --- a/dsc_lib/src/configure/depends_on.rs +++ b/dsc_lib/src/configure/depends_on.rs @@ -3,11 +3,11 @@ use crate::configure::config_doc::Resource; use crate::configure::Configuration; -use crate::DscError; use crate::parser::Statement; +use crate::DscError; -use rust_i18n::t; use super::context::Context; +use rust_i18n::t; use tracing::debug; /// Gets the invocation order of resources based on their dependencies @@ -23,13 +23,31 @@ use tracing::debug; /// # Errors /// /// * `DscError::Validation` - The configuration is invalid -pub fn get_resource_invocation_order(config: &Configuration, parser: &mut Statement, context: &Context) -> Result, DscError> { +#[allow(clippy::too_many_lines)] +pub fn get_resource_invocation_order( + config: &Configuration, + parser: &mut Statement, + context: &Context, +) -> Result, DscError> { debug!("Getting resource invocation order"); let mut order: Vec = Vec::new(); for resource in &config.resources { // validate that the resource isn't specified more than once in the config - if config.resources.iter().filter(|r| r.name == resource.name && r.resource_type == resource.resource_type).count() > 1 { - return Err(DscError::Validation(t!("configure.dependsOn.duplicateResource", name = resource.name, type_name = resource.resource_type).to_string())); + if config + .resources + .iter() + .filter(|r| r.name == resource.name && r.resource_type == resource.resource_type) + .count() + > 1 + { + return Err(DscError::Validation( + t!( + "configure.dependsOn.duplicateResource", + name = resource.name, + type_name = resource.resource_type + ) + .to_string(), + )); } let mut dependency_already_in_order = true; @@ -37,20 +55,40 @@ pub fn get_resource_invocation_order(config: &Configuration, parser: &mut Statem for dependency in depends_on { let statement = parser.parse_and_execute(&dependency, context)?; let Some(string_result) = statement.as_str() else { - return Err(DscError::Validation(t!("configure.dependsOn.syntaxIncorrect", dependency = dependency).to_string())); + return Err(DscError::Validation( + t!("configure.dependsOn.syntaxIncorrect", dependency = dependency).to_string(), + )); }; let (resource_type, resource_name) = get_type_and_name(string_result)?; // find the resource by name let Some(dependency_resource) = config.resources.iter().find(|r| r.name.eq(resource_name)) else { - return Err(DscError::Validation(t!("configure.dependsOn.dependencyNotFound", dependency_name = resource_name, resource_name = resource.name).to_string())); + return Err(DscError::Validation( + t!( + "configure.dependsOn.dependencyNotFound", + dependency_name = resource_name, + resource_name = resource.name + ) + .to_string(), + )); }; // validate the type matches if dependency_resource.resource_type != resource_type { - return Err(DscError::Validation(t!("configure.dependsOn.dependencyTypeMismatch", resource_type = resource_type, dependency_type = dependency_resource.resource_type, resource_name = resource.name).to_string())); + return Err(DscError::Validation( + t!( + "configure.dependsOn.dependencyTypeMismatch", + resource_type = resource_type, + dependency_type = dependency_resource.resource_type, + resource_name = resource.name + ) + .to_string(), + )); } // see if the dependency is already in the order - if order.iter().any(|r| r.name == resource_name && r.resource_type == resource_type) { + if order + .iter() + .any(|r| r.name == resource_name && r.resource_type == resource_type) + { continue; } // add the dependency to the order @@ -60,24 +98,41 @@ pub fn get_resource_invocation_order(config: &Configuration, parser: &mut Statem } // make sure the resource is not already in the order - if order.iter().any(|r| r.name == resource.name && r.resource_type == resource.resource_type) { + if order + .iter() + .any(|r| r.name == resource.name && r.resource_type == resource.resource_type) + { // if dependencies were already in the order, then this might be a circular dependency if dependency_already_in_order { let Some(ref depends_on) = resource.depends_on else { - continue; + continue; }; // check if the order has resource before its dependencies - let resource_index = order.iter().position(|r| r.name == resource.name && r.resource_type == resource.resource_type).ok_or(DscError::Validation(t!("configure.dependsOn.resourceNotInOrder").to_string()))?; + let resource_index = order + .iter() + .position(|r| r.name == resource.name && r.resource_type == resource.resource_type) + .ok_or(DscError::Validation( + t!("configure.dependsOn.resourceNotInOrder").to_string(), + ))?; for dependency in depends_on { - let statement = parser.parse_and_execute(dependency, context)?; - let Some(string_result) = statement.as_str() else { - return Err(DscError::Validation(t!("configure.dependsOn.syntaxIncorrect", dependency = dependency).to_string())); - }; - let (resource_type, resource_name) = get_type_and_name(string_result)?; - let dependency_index = order.iter().position(|r| r.name == resource_name && r.resource_type == resource_type).ok_or(DscError::Validation(t!("configure.dependsOn.dependencyNotInOrder").to_string()))?; - if resource_index < dependency_index { - return Err(DscError::Validation(t!("configure.dependsOn.circularDependency", resource_name = resource.name).to_string())); - } + let statement = parser.parse_and_execute(dependency, context)?; + let Some(string_result) = statement.as_str() else { + return Err(DscError::Validation( + t!("configure.dependsOn.syntaxIncorrect", dependency = dependency).to_string(), + )); + }; + let (resource_type, resource_name) = get_type_and_name(string_result)?; + let dependency_index = order + .iter() + .position(|r| r.name == resource_name && r.resource_type == resource_type) + .ok_or(DscError::Validation( + t!("configure.dependsOn.dependencyNotInOrder").to_string(), + ))?; + if resource_index < dependency_index { + return Err(DscError::Validation( + t!("configure.dependsOn.circularDependency", resource_name = resource.name).to_string(), + )); + } } } @@ -94,7 +149,9 @@ pub fn get_resource_invocation_order(config: &Configuration, parser: &mut Statem fn get_type_and_name(statement: &str) -> Result<(&str, &str), DscError> { let parts: Vec<&str> = statement.split(':').collect(); if parts.len() != 2 { - return Err(DscError::Validation(t!("configure.dependsOn.syntaxIncorrect", dependency = statement).to_string())); + return Err(DscError::Validation( + t!("configure.dependsOn.syntaxIncorrect", dependency = statement).to_string(), + )); } Ok((parts[0], parts[1])) } diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index 109da31cc..c94ad2721 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -1,34 +1,38 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::configure::config_doc::{ExecutionKind, Metadata, Resource, Parameter}; +use self::config_doc::{Configuration, DataType, MicrosoftDscMetadata, Operation, SecurityContextKind}; +use self::config_result::{ + ConfigurationExportResult, ConfigurationGetResult, ConfigurationSetResult, ConfigurationTestResult, +}; +use self::constraints::{check_allowed_values, check_length, check_number_limits}; +use self::depends_on::get_resource_invocation_order; +use crate::configure::config_doc::{ExecutionKind, Metadata, Parameter, Resource}; use crate::configure::context::{Context, ProcessMode}; use crate::configure::{config_doc::RestartRequired, parameters::Input}; use crate::discovery::discovery_trait::DiscoveryFilter; +use crate::discovery::Discovery; use crate::dscerror::DscError; use crate::dscresources::{ - {dscresource::{Capability, Invoke, get_diff, validate_properties, get_adapter_input_kind}, - invoke_result::{GetResult, SetResult, TestResult, ExportResult, ResourceSetResponse}}, resource_manifest::{AdapterInputKind, Kind}, + { + dscresource::{get_adapter_input_kind, get_diff, validate_properties, Capability, Invoke}, + invoke_result::{ExportResult, GetResult, ResourceSetResponse, SetResult, TestResult}, + }, }; -use crate::DscResource; -use crate::discovery::Discovery; use crate::parser::Statement; use crate::progress::{Failure, ProgressBar, ProgressFormat}; -use self::config_doc::{Configuration, DataType, MicrosoftDscMetadata, Operation, SecurityContextKind}; -use self::depends_on::get_resource_invocation_order; -use self::config_result::{ConfigurationExportResult, ConfigurationGetResult, ConfigurationSetResult, ConfigurationTestResult}; -use self::constraints::{check_length, check_number_limits, check_allowed_values}; +use crate::DscResource; use rust_i18n::t; -use security_context_lib::{SecurityContext, get_security_context}; +use security_context_lib::{get_security_context, SecurityContext}; use serde_json::{Map, Value}; -use std::path::PathBuf; use std::collections::HashMap; +use std::path::PathBuf; use tracing::{debug, info, trace, warn}; -pub mod context; pub mod config_doc; pub mod config_result; pub mod constraints; +pub mod context; pub mod depends_on; pub mod parameters; @@ -55,8 +59,11 @@ pub struct Configurator { /// # Errors /// /// This function will return an error if the underlying resource fails. -pub fn add_resource_export_results_to_configuration(resource: &DscResource, conf: &mut Configuration, input: &str) -> Result { - +pub fn add_resource_export_results_to_configuration( + resource: &DscResource, + conf: &mut Configuration, + input: &str, +) -> Result { let export_result = resource.export(input)?; if resource.kind == Kind::Exporter { @@ -71,14 +78,16 @@ pub fn add_resource_export_results_to_configuration(resource: &DscResource, conf let mut props: Map = serde_json::from_value(instance.clone())?; if let Some(kind) = props.remove("_kind") { if !kind.is_string() { - return Err(DscError::Parser(t!("configure.mod.propertyNotString", name = "_kind", value = kind).to_string())); + return Err(DscError::Parser( + t!("configure.mod.propertyNotString", name = "_kind", value = kind).to_string(), + )); } r.kind = kind.as_str().map(std::string::ToString::to_string); } r.name = if let Some(name) = props.remove("_name") { - name.as_str() - .map(std::string::ToString::to_string) - .ok_or_else(|| DscError::Parser(t!("configure.mod.propertyNotString", name = "_name", value = name).to_string()))? + name.as_str().map(std::string::ToString::to_string).ok_or_else(|| { + DscError::Parser(t!("configure.mod.propertyNotString", name = "_name", value = name).to_string()) + })? } else { let resource_type_short = if let Some(pos) = resource.type_name.find('/') { &resource.type_name[pos + 1..] @@ -93,12 +102,10 @@ pub fn add_resource_export_results_to_configuration(resource: &DscResource, conf }; if let Some(security_context) = props.remove("_securityContext") { let security_context: SecurityContextKind = serde_json::from_value(security_context)?; - metadata.microsoft = Some( - MicrosoftDscMetadata { - security_context: Some(security_context), - ..Default::default() - } - ); + metadata.microsoft = Some(MicrosoftDscMetadata { + security_context: Some(security_context), + ..Default::default() + }); } r.properties = escape_property_values(&props)?; let mut properties = serde_json::to_value(&r.properties)?; @@ -126,7 +133,7 @@ fn escape_property_values(properties: &Map) -> Result { let value = escape_property_values(&object.clone())?; result.insert(name.clone(), serde_json::to_value(value)?); - }, + } Value::Array(array) => { let mut result_array: Vec = Vec::new(); for element in array { @@ -134,14 +141,18 @@ fn escape_property_values(properties: &Map) -> Result { let value = escape_property_values(&object.clone())?; result_array.push(serde_json::to_value(value)?); - }, + } Value::Array(_) => { - return Err(DscError::Parser(t!("configure.mod.nestedArraysNotSupported").to_string())); - }, + return Err(DscError::Parser( + t!("configure.mod.nestedArraysNotSupported").to_string(), + )); + } Value::String(_) => { // use as_str() so that the enclosing quotes are not included for strings let Some(statement) = element.as_str() else { - return Err(DscError::Parser(t!("configure.mod.arrayElementCouldNotTransformAsString").to_string())); + return Err(DscError::Parser( + t!("configure.mod.arrayElementCouldNotTransformAsString").to_string(), + )); }; if statement.starts_with('[') && statement.ends_with(']') { result_array.push(Value::String(format!("[{statement}"))); @@ -155,27 +166,33 @@ fn escape_property_values(properties: &Map) -> Result { // use as_str() so that the enclosing quotes are not included for strings let Some(statement) = value.as_str() else { - return Err(DscError::Parser(t!("configure.mod.valueCouldNotBeTransformedAsString", value = value).to_string())); + return Err(DscError::Parser( + t!("configure.mod.valueCouldNotBeTransformedAsString", value = value).to_string(), + )); }; if statement.starts_with('[') && statement.ends_with(']') { result.insert(name.clone(), Value::String(format!("[{statement}"))); } else { result.insert(name.clone(), value.clone()); } - }, + } _ => { result.insert(name.clone(), value.clone()); - }, + } } } Ok(Some(result)) } -fn add_metadata(dsc_resource: &DscResource, mut properties: Option>, resource_metadata: Option ) -> Result { +fn add_metadata( + dsc_resource: &DscResource, + mut properties: Option>, + resource_metadata: Option, +) -> Result { if dsc_resource.kind == Kind::Adapter && get_adapter_input_kind(dsc_resource)? == AdapterInputKind::Full { // add metadata to the properties so the adapter knows this is a config let mut metadata: Map = Map::new(); @@ -204,7 +221,8 @@ fn add_metadata(dsc_resource: &DscResource, mut properties: Option { - Ok(serde_json::to_string(&properties)?) - }, - _ => { - Ok(String::new()) - } + Some(properties) => Ok(serde_json::to_string(&properties)?), + _ => Ok(String::new()), } } @@ -232,17 +246,21 @@ fn check_security_context(metadata: Option<&Metadata>) -> Result<(), DscError> { match required_security_context { SecurityContextKind::Current => { // no check needed - }, + } SecurityContextKind::Elevated => { if get_security_context() != SecurityContext::Admin { - return Err(DscError::SecurityContext(t!("configure.mod.elevationRequired").to_string())); + return Err(DscError::SecurityContext( + t!("configure.mod.elevationRequired").to_string(), + )); } - }, + } SecurityContextKind::Restricted => { if get_security_context() != SecurityContext::User { - return Err(DscError::SecurityContext(t!("configure.mod.restrictedRequired").to_string())); + return Err(DscError::SecurityContext( + t!("configure.mod.restrictedRequired").to_string(), + )); } - }, + } } } } @@ -251,7 +269,11 @@ fn check_security_context(metadata: Option<&Metadata>) -> Result<(), DscError> { Ok(()) } -fn get_metadata_from_result(mut context: Option<&mut Context>, result: &mut Value, metadata: &mut Metadata) -> Result<(), DscError> { +fn get_metadata_from_result( + mut context: Option<&mut Context>, + result: &mut Value, + metadata: &mut Metadata, +) -> Result<(), DscError> { if let Some(metadata_value) = result.get("_metadata") { if let Some(metadata_map) = metadata_value.as_object() { for (key, value) in metadata_map { @@ -262,7 +284,10 @@ fn get_metadata_from_result(mut context: Option<&mut Context>, result: &mut Valu if let Some(ref mut context) = context { if key == "_restartRequired" { if let Ok(restart_required) = serde_json::from_value::>(value.clone()) { - context.restart_required.get_or_insert_with(Vec::new).extend(restart_required); + context + .restart_required + .get_or_insert_with(Vec::new) + .extend(restart_required); } else { warn!("{}", t!("configure.mod.metadataRestartRequiredInvalid", value = value)); continue; @@ -272,7 +297,9 @@ fn get_metadata_from_result(mut context: Option<&mut Context>, result: &mut Valu metadata.other.insert(key.clone(), value.clone()); } } else { - return Err(DscError::Parser(t!("configure.mod.metadataNotObject", value = metadata_value).to_string())); + return Err(DscError::Parser( + t!("configure.mod.metadataNotObject", value = metadata_value).to_string(), + )); } if let Some(value_map) = result.as_object_mut() { value_map.remove("_metadata"); @@ -327,15 +354,17 @@ impl Configurator { &mut self.discovery } - fn get_properties(&mut self, resource: &Resource, resource_kind: &Kind) -> Result>, DscError> { + fn get_properties( + &mut self, + resource: &Resource, + resource_kind: &Kind, + ) -> Result>, DscError> { match resource_kind { Kind::Group => { // if Group resource, we leave it to the resource to handle expressions Ok(resource.properties.clone()) - }, - _ => { - Ok(self.invoke_property_expressions(resource.properties.as_ref())?) - }, + } + _ => Ok(self.invoke_property_expressions(resource.properties.as_ref())?), } } @@ -364,8 +393,12 @@ impl Configurator { progress.write_increment(1); continue; } - let Some(dsc_resource) = discovery.find_resource(&resource.resource_type, resource.api_version.as_deref()) else { - return Err(DscError::ResourceNotFound(resource.resource_type, resource.api_version.as_deref().unwrap_or("").to_string())); + let Some(dsc_resource) = discovery.find_resource(&resource.resource_type, resource.api_version.as_deref()) + else { + return Err(DscError::ResourceNotFound( + resource.resource_type, + resource.api_version.as_deref().unwrap_or("").to_string(), + )); }; let properties = self.get_properties(&resource, &dsc_resource.kind)?; let filter = add_metadata(dsc_resource, properties, resource.metadata.clone())?; @@ -376,28 +409,36 @@ impl Configurator { progress.set_failure(get_failure_from_error(&e)); progress.write_increment(1); return Err(e); - }, + } }; let end_datetime = chrono::Local::now(); let mut metadata = Metadata { - microsoft: Some( - MicrosoftDscMetadata::new_with_duration(&start_datetime, &end_datetime) - ), + microsoft: Some(MicrosoftDscMetadata::new_with_duration(&start_datetime, &end_datetime)), other: Map::new(), }; match &mut get_result { GetResult::Resource(ref mut resource_result) => { - self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), serde_json::to_value(&resource_result.actual_state)?); - get_metadata_from_result(Some(&mut self.context), &mut resource_result.actual_state, &mut metadata)?; - }, + self.context.references.insert( + format!("{}:{}", resource.resource_type, evaluated_name), + serde_json::to_value(&resource_result.actual_state)?, + ); + get_metadata_from_result( + Some(&mut self.context), + &mut resource_result.actual_state, + &mut metadata, + )?; + } GetResult::Group(group) => { let mut results = Vec::::new(); for result in group { results.push(serde_json::to_value(&result.result)?); } - self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), Value::Array(results.clone())); - }, + self.context.references.insert( + format!("{}:{}", resource.resource_type, evaluated_name), + Value::Array(results.clone()), + ); + } } let resource_result = config_result::ResourceGetResult { metadata: Some(metadata), @@ -410,9 +451,7 @@ impl Configurator { progress.write_increment(1); } - result.metadata = Some( - self.get_result_metadata(Operation::Get) - ); + result.metadata = Some(self.get_result_metadata(Operation::Get)); Ok(result) } @@ -446,8 +485,12 @@ impl Configurator { progress.write_increment(1); continue; } - let Some(dsc_resource) = discovery.find_resource(&resource.resource_type, resource.api_version.as_deref()) else { - return Err(DscError::ResourceNotFound(resource.resource_type, resource.api_version.as_deref().unwrap_or("").to_string())); + let Some(dsc_resource) = discovery.find_resource(&resource.resource_type, resource.api_version.as_deref()) + else { + return Err(DscError::ResourceNotFound( + resource.resource_type, + resource.api_version.as_deref().unwrap_or("").to_string(), + )); }; let properties = self.get_properties(&resource, &dsc_resource.kind)?; debug!("resource_type {}", &resource.resource_type); @@ -460,10 +503,8 @@ impl Configurator { } else { true } - }, - _ => { - true } + _ => true, }; let desired = add_metadata(dsc_resource, properties, resource.metadata.clone())?; @@ -481,7 +522,7 @@ impl Configurator { progress.set_failure(get_failure_from_error(&e)); progress.write_increment(1); return Err(e); - }, + } }; end_datetime = chrono::Local::now(); } else if dsc_resource.capabilities.contains(&Capability::Delete) { @@ -495,7 +536,7 @@ impl Configurator { progress.set_failure(get_failure_from_error(&e)); progress.write_increment(1); return Err(e); - }, + } }; end_datetime = chrono::Local::now(); } else { @@ -505,7 +546,7 @@ impl Configurator { progress.set_failure(get_failure_from_error(&e)); progress.write_increment(1); return Err(e); - }, + } }; start_datetime = chrono::Local::now(); if let Err(e) = dsc_resource.delete(&desired) { @@ -519,13 +560,15 @@ impl Configurator { progress.set_failure(get_failure_from_error(&e)); progress.write_increment(1); return Err(e); - }, + } }; // convert get result to set result set_result = match before_result { GetResult::Resource(before_response) => { let GetResult::Resource(after_result) = after_result else { - return Err(DscError::NotSupported(t!("configure.mod.groupNotSupportedForDelete").to_string())) + return Err(DscError::NotSupported( + t!("configure.mod.groupNotSupportedForDelete").to_string(), + )); }; let diff = get_diff(&before_response.actual_state, &after_result.actual_state); let mut before: Map = serde_json::from_value(before_response.actual_state)?; @@ -540,35 +583,43 @@ impl Configurator { after_state: after_result.actual_state, changed_properties: Some(diff), }) - }, + } GetResult::Group(_) => { - return Err(DscError::NotSupported(t!("configure.mod.groupNotSupportedForDelete").to_string())) - }, + return Err(DscError::NotSupported( + t!("configure.mod.groupNotSupportedForDelete").to_string(), + )) + } }; end_datetime = chrono::Local::now(); } } else { - return Err(DscError::NotImplemented(t!("configure.mod.deleteNotSupported", resource = resource.resource_type).to_string())); + return Err(DscError::NotImplemented( + t!("configure.mod.deleteNotSupported", resource = resource.resource_type).to_string(), + )); } let mut metadata = Metadata { - microsoft: Some( - MicrosoftDscMetadata::new_with_duration(&start_datetime, &end_datetime) - ), + microsoft: Some(MicrosoftDscMetadata::new_with_duration(&start_datetime, &end_datetime)), other: Map::new(), }; match &mut set_result { SetResult::Resource(resource_result) => { - self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), serde_json::to_value(&resource_result.after_state)?); + self.context.references.insert( + format!("{}:{}", resource.resource_type, evaluated_name), + serde_json::to_value(&resource_result.after_state)?, + ); get_metadata_from_result(Some(&mut self.context), &mut resource_result.after_state, &mut metadata)?; - }, + } SetResult::Group(group) => { let mut results = Vec::::new(); for result in group { results.push(serde_json::to_value(&result.result)?); } - self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), Value::Array(results.clone())); - }, + self.context.references.insert( + format!("{}:{}", resource.resource_type, evaluated_name), + Value::Array(results.clone()), + ); + } } let resource_result = config_result::ResourceSetResult { metadata: Some(metadata), @@ -581,9 +632,7 @@ impl Configurator { progress.write_increment(1); } - result.metadata = Some( - self.get_result_metadata(Operation::Set) - ); + result.metadata = Some(self.get_result_metadata(Operation::Set)); Ok(result) } @@ -612,8 +661,12 @@ impl Configurator { progress.write_increment(1); continue; } - let Some(dsc_resource) = discovery.find_resource(&resource.resource_type, resource.api_version.as_deref()) else { - return Err(DscError::ResourceNotFound(resource.resource_type, resource.api_version.as_deref().unwrap_or("").to_string())); + let Some(dsc_resource) = discovery.find_resource(&resource.resource_type, resource.api_version.as_deref()) + else { + return Err(DscError::ResourceNotFound( + resource.resource_type, + resource.api_version.as_deref().unwrap_or("").to_string(), + )); }; let properties = self.get_properties(&resource, &dsc_resource.kind)?; debug!("resource_type {}", &resource.resource_type); @@ -626,27 +679,35 @@ impl Configurator { progress.set_failure(get_failure_from_error(&e)); progress.write_increment(1); return Err(e); - }, + } }; let end_datetime = chrono::Local::now(); let mut metadata = Metadata { - microsoft: Some( - MicrosoftDscMetadata::new_with_duration(&start_datetime, &end_datetime) - ), + microsoft: Some(MicrosoftDscMetadata::new_with_duration(&start_datetime, &end_datetime)), other: Map::new(), }; match &mut test_result { TestResult::Resource(resource_test_result) => { - self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), serde_json::to_value(&resource_test_result.actual_state)?); - get_metadata_from_result(Some(&mut self.context), &mut resource_test_result.actual_state, &mut metadata)?; - }, + self.context.references.insert( + format!("{}:{}", resource.resource_type, evaluated_name), + serde_json::to_value(&resource_test_result.actual_state)?, + ); + get_metadata_from_result( + Some(&mut self.context), + &mut resource_test_result.actual_state, + &mut metadata, + )?; + } TestResult::Group(group) => { let mut results = Vec::::new(); for result in group { results.push(serde_json::to_value(&result.result)?); } - self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), Value::Array(results.clone())); - }, + self.context.references.insert( + format!("{}:{}", resource.resource_type, evaluated_name), + Value::Array(results.clone()), + ); + } } let resource_result = config_result::ResourceTestResult { metadata: Some(metadata), @@ -655,13 +716,11 @@ impl Configurator { result: test_result.clone(), }; result.results.push(resource_result); - progress.set_result( &serde_json::to_value(test_result)?); + progress.set_result(&serde_json::to_value(test_result)?); progress.write_increment(1); } - result.metadata = Some( - self.get_result_metadata(Operation::Test) - ); + result.metadata = Some(self.get_result_metadata(Operation::Test)); Ok(result) } @@ -693,21 +752,29 @@ impl Configurator { progress.write_increment(1); continue; } - let Some(dsc_resource) = discovery.find_resource(&resource.resource_type, resource.api_version.as_deref()) else { - return Err(DscError::ResourceNotFound(resource.resource_type.clone(), resource.api_version.as_deref().unwrap_or("").to_string())); + let Some(dsc_resource) = discovery.find_resource(&resource.resource_type, resource.api_version.as_deref()) + else { + return Err(DscError::ResourceNotFound( + resource.resource_type.clone(), + resource.api_version.as_deref().unwrap_or("").to_string(), + )); }; let properties = self.get_properties(resource, &dsc_resource.kind)?; let input = add_metadata(dsc_resource, properties, resource.metadata.clone())?; trace!("{}", t!("configure.mod.exportInput", input = input)); - let export_result = match add_resource_export_results_to_configuration(dsc_resource, &mut conf, input.as_str()) { - Ok(result) => result, - Err(e) => { - progress.set_failure(get_failure_from_error(&e)); - progress.write_increment(1); - return Err(e); - }, - }; - self.context.references.insert(format!("{}:{}", resource.resource_type, evaluated_name), serde_json::to_value(&export_result.actual_state)?); + let export_result = + match add_resource_export_results_to_configuration(dsc_resource, &mut conf, input.as_str()) { + Ok(result) => result, + Err(e) => { + progress.set_failure(get_failure_from_error(&e)); + progress.write_increment(1); + return Err(e); + } + }; + self.context.references.insert( + format!("{}:{}", resource.resource_type, evaluated_name), + serde_json::to_value(&export_result.actual_state)?, + ); progress.set_result(&serde_json::to_value(export_result)?); progress.write_increment(1); } @@ -717,10 +784,10 @@ impl Configurator { Some(mut metadata) => { metadata.microsoft = export_metadata.microsoft; conf.metadata = Some(metadata); - }, + } _ => { conf.metadata = Some(export_metadata); - }, + } } result.result = Some(conf); @@ -731,7 +798,15 @@ impl Configurator { if let Some(condition) = &resource.condition { let condition_result = self.statement_parser.parse_and_execute(condition, &self.context)?; if condition_result != Value::Bool(true) { - info!("{}", t!("configure.config_doc.skippingResource", name = resource.name, condition = condition, result = condition_result)); + info!( + "{}", + t!( + "configure.config_doc.skippingResource", + name = resource.name, + condition = condition, + result = condition_result + ) + ); return Ok(true); } } @@ -772,14 +847,17 @@ impl Configurator { info!("{}", t!("configure.mod.noParameters")); return Ok(()); } - return Err(DscError::Validation(t!("configure.mod.noParametersDefined").to_string())); + return Err(DscError::Validation( + t!("configure.mod.noParametersDefined").to_string(), + )); }; // process input parameters first if let Some(parameters_input) = parameters_input { trace!("parameters_input: {parameters_input}"); - let input_parameters: HashMap = serde_json::from_value::(parameters_input.clone())?.parameters; - + let input_parameters: HashMap = + serde_json::from_value::(parameters_input.clone())?.parameters; + for (name, value) in input_parameters { if let Some(constraint) = parameters.get(&name) { debug!("Validating parameter '{name}'"); @@ -790,21 +868,26 @@ impl Configurator { // TODO: object constraints validate_parameter_type(&name, &value, &constraint.parameter_type)?; - if constraint.parameter_type == DataType::SecureString || constraint.parameter_type == DataType::SecureObject { + if constraint.parameter_type == DataType::SecureString + || constraint.parameter_type == DataType::SecureObject + { info!("{}", t!("configure.mod.setSecureParameter", name = name)); } else { info!("{}", t!("configure.mod.setParameter", name = name, value = value)); } - self.context.parameters.insert(name.clone(), (value.clone(), constraint.parameter_type.clone())); + self.context + .parameters + .insert(name.clone(), (value.clone(), constraint.parameter_type.clone())); if let Some(parameters) = &mut self.config.parameters { if let Some(parameter) = parameters.get_mut(&name) { parameter.default_value = Some(value); } } - } - else { - return Err(DscError::Validation(t!("configure.mod.parameterNotDefined", name = name).to_string())); + } else { + return Err(DscError::Validation( + t!("configure.mod.parameterNotDefined", name = name).to_string(), + )); } } } @@ -818,7 +901,7 @@ impl Configurator { while !unresolved_parameters.is_empty() { let mut resolved_in_this_pass = Vec::new(); - + for (name, parameter) in &unresolved_parameters { debug!("{}", t!("configure.mod.processingParameter", name = name)); if let Some(default_value) = ¶meter.default_value { @@ -830,7 +913,9 @@ impl Configurator { self.context.process_mode = ProcessMode::Normal; result } else { - return Err(DscError::Parser(t!("configure.mod.defaultStringNotDefined").to_string())); + return Err(DscError::Parser( + t!("configure.mod.defaultStringNotDefined").to_string(), + )); } } else { Ok(default_value.clone()) @@ -838,7 +923,9 @@ impl Configurator { if let Ok(value) = value_result { validate_parameter_type(name, &value, ¶meter.parameter_type)?; - self.context.parameters.insert(name.to_string(), (value, parameter.parameter_type.clone())); + self.context + .parameters + .insert(name.to_string(), (value, parameter.parameter_type.clone())); resolved_in_this_pass.push(name.clone()); } } else { @@ -848,7 +935,13 @@ impl Configurator { if resolved_in_this_pass.is_empty() { let unresolved_names: Vec<_> = unresolved_parameters.keys().map(std::string::String::as_str).collect(); - return Err(DscError::Validation(t!("configure.mod.circularDependency", parameters = unresolved_names.join(", ")).to_string())); + return Err(DscError::Validation( + t!( + "configure.mod.circularDependency", + parameters = unresolved_names.join(", ") + ) + .to_string(), + )); } for name in &resolved_in_this_pass { @@ -868,8 +961,7 @@ impl Configurator { for (name, value) in variables { let new_value = if let Some(string) = value.as_str() { self.statement_parser.parse_and_execute(string, &self.context)? - } - else { + } else { value.clone() }; info!("{}", t!("configure.mod.setVariable", name = name, value = new_value)); @@ -885,11 +977,31 @@ impl Configurator { for user_function in functions { for (function_name, function_definition) in &user_function.members { - if self.context.user_functions.contains_key(&format!("{}.{}", user_function.namespace, function_name)) { - return Err(DscError::Validation(t!("configure.mod.userFunctionAlreadyDefined", name = function_name, namespace = user_function.namespace).to_string())); + if self + .context + .user_functions + .contains_key(&format!("{}.{}", user_function.namespace, function_name)) + { + return Err(DscError::Validation( + t!( + "configure.mod.userFunctionAlreadyDefined", + name = function_name, + namespace = user_function.namespace + ) + .to_string(), + )); } - debug!("{}", t!("configure.mod.addingUserFunction", name = format!("{}.{}", user_function.namespace, function_name))); - self.context.user_functions.insert(format!("{}.{}", user_function.namespace, function_name), function_definition.clone()); + debug!( + "{}", + t!( + "configure.mod.addingUserFunction", + name = format!("{}.{}", user_function.namespace, function_name) + ) + ); + self.context.user_functions.insert( + format!("{}.{}", user_function.namespace, function_name), + function_definition.clone(), + ); } } Ok(()) @@ -903,18 +1015,20 @@ impl Configurator { .clone() .unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_string()); Metadata { - microsoft: Some( - MicrosoftDscMetadata { - version: Some(version), - operation: Some(operation), - execution_type: Some(self.context.execution_type.clone()), - start_datetime: Some(self.context.start_datetime.to_rfc3339()), - end_datetime: Some(end_datetime.to_rfc3339()), - duration: Some(end_datetime.signed_duration_since(self.context.start_datetime).to_string()), - security_context: Some(self.context.security_context.clone()), - restart_required: self.context.restart_required.clone(), - } - ), + microsoft: Some(MicrosoftDscMetadata { + version: Some(version), + operation: Some(operation), + execution_type: Some(self.context.execution_type.clone()), + start_datetime: Some(self.context.start_datetime.to_rfc3339()), + end_datetime: Some(end_datetime.to_rfc3339()), + duration: Some( + end_datetime + .signed_duration_since(self.context.start_datetime) + .to_string(), + ), + security_context: Some(self.context.security_context.clone()), + restart_required: self.context.restart_required.clone(), + }), other: Map::new(), } } @@ -934,12 +1048,19 @@ impl Configurator { } // defer actual unrolling until parameters are available if let Some(copy) = &resource.copy { - debug!("{}", t!("configure.mod.validateCopy", name = ©.name, count = copy.count)); + debug!( + "{}", + t!("configure.mod.validateCopy", name = ©.name, count = copy.count) + ); if copy.mode.is_some() { - return Err(DscError::Validation(t!("configure.mod.copyModeNotSupported").to_string())); + return Err(DscError::Validation( + t!("configure.mod.copyModeNotSupported").to_string(), + )); } if copy.batch_size.is_some() { - return Err(DscError::Validation(t!("configure.mod.copyBatchSizeNotSupported").to_string())); + return Err(DscError::Validation( + t!("configure.mod.copyBatchSizeNotSupported").to_string(), + )); } } } @@ -958,15 +1079,22 @@ impl Configurator { for resource in config_copy.resources { // if the resource contains `Copy`, unroll it if let Some(copy) = &resource.copy { - debug!("{}", t!("configure.mod.unrollingCopy", name = ©.name, count = copy.count)); + debug!( + "{}", + t!("configure.mod.unrollingCopy", name = ©.name, count = copy.count) + ); self.context.process_mode = ProcessMode::Copy; self.context.copy_current_loop_name.clone_from(©.name); let mut copy_resources = Vec::::new(); for i in 0..copy.count { self.context.copy.insert(copy.name.clone(), i); let mut new_resource = resource.clone(); - let Value::String(new_name) = self.statement_parser.parse_and_execute(&resource.name, &self.context)? else { - return Err(DscError::Parser(t!("configure.mod.copyNameResultNotString").to_string())) + let Value::String(new_name) = + self.statement_parser.parse_and_execute(&resource.name, &self.context)? + else { + return Err(DscError::Parser( + t!("configure.mod.copyNameResultNotString").to_string(), + )); }; new_resource.name = new_name.to_string(); @@ -1008,13 +1136,16 @@ impl Configurator { // evaluate the resource name (handles both expressions and literals) let Value::String(evaluated_name) = self.statement_parser.parse_and_execute(name, &self.context)? else { - return Err(DscError::Parser(t!("configure.mod.nameResultNotString").to_string())) + return Err(DscError::Parser(t!("configure.mod.nameResultNotString").to_string())); }; Ok(evaluated_name) } - fn invoke_property_expressions(&mut self, properties: Option<&Map>) -> Result>, DscError> { + fn invoke_property_expressions( + &mut self, + properties: Option<&Map>, + ) -> Result>, DscError> { debug!("{}", t!("configure.mod.invokePropertyExpressions")); if properties.is_none() { return Ok(None); @@ -1028,7 +1159,7 @@ impl Configurator { Value::Object(object) => { let value = self.invoke_property_expressions(Some(object))?; result.insert(name.clone(), serde_json::to_value(value)?); - }, + } Value::Array(array) => { let mut result_array: Vec = Vec::new(); for element in array { @@ -1036,18 +1167,25 @@ impl Configurator { Value::Object(object) => { let value = self.invoke_property_expressions(Some(object))?; result_array.push(serde_json::to_value(value)?); - }, + } Value::Array(_) => { - return Err(DscError::Parser(t!("configure.mod.nestedArraysNotSupported").to_string())); - }, + return Err(DscError::Parser( + t!("configure.mod.nestedArraysNotSupported").to_string(), + )); + } Value::String(_) => { // use as_str() so that the enclosing quotes are not included for strings let Some(statement) = element.as_str() else { - return Err(DscError::Parser(t!("configure.mod.arrayElementCouldNotTransformAsString").to_string())); + return Err(DscError::Parser( + t!("configure.mod.arrayElementCouldNotTransformAsString").to_string(), + )); }; - let statement_result = self.statement_parser.parse_and_execute(statement, &self.context)?; + let statement_result = + self.statement_parser.parse_and_execute(statement, &self.context)?; let Some(string_result) = statement_result.as_str() else { - return Err(DscError::Parser(t!("configure.mod.arrayElementCouldNotTransformAsString").to_string())); + return Err(DscError::Parser( + t!("configure.mod.arrayElementCouldNotTransformAsString").to_string(), + )); }; result_array.push(Value::String(string_result.to_string())); } @@ -1057,11 +1195,13 @@ impl Configurator { } } result.insert(name.clone(), serde_json::to_value(result_array)?); - }, + } Value::String(_) => { // use as_str() so that the enclosing quotes are not included for strings let Some(statement) = value.as_str() else { - return Err(DscError::Parser(t!("configure.mod.valueCouldNotBeTransformedAsString", value = value).to_string())); + return Err(DscError::Parser( + t!("configure.mod.valueCouldNotBeTransformedAsString", value = value).to_string(), + )); }; let statement_result = self.statement_parser.parse_and_execute(statement, &self.context)?; if let Some(string_result) = statement_result.as_str() { @@ -1069,10 +1209,10 @@ impl Configurator { } else { result.insert(name.clone(), statement_result); } - }, + } _ => { result.insert(name.clone(), value.clone()); - }, + } } } } @@ -1097,29 +1237,39 @@ pub fn validate_parameter_type(name: &str, value: &Value, parameter_type: &DataT match parameter_type { DataType::String | DataType::SecureString => { if !value.is_string() { - return Err(DscError::Validation(t!("configure.mod.parameterNotString", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.mod.parameterNotString", name = name).to_string(), + )); } - }, + } DataType::Int => { if !value.is_i64() { - return Err(DscError::Validation(t!("configure.mod.parameterNotInteger", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.mod.parameterNotInteger", name = name).to_string(), + )); } - }, + } DataType::Bool => { if !value.is_boolean() { - return Err(DscError::Validation(t!("configure.mod.parameterNotBoolean", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.mod.parameterNotBoolean", name = name).to_string(), + )); } - }, + } DataType::Array => { if !value.is_array() { - return Err(DscError::Validation(t!("configure.mod.parameterNotArray", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.mod.parameterNotArray", name = name).to_string(), + )); } - }, + } DataType::Object | DataType::SecureObject => { if !value.is_object() { - return Err(DscError::Validation(t!("configure.mod.parameterNotObject", name = name).to_string())); + return Err(DscError::Validation( + t!("configure.mod.parameterNotObject", name = name).to_string(), + )); } - }, + } } Ok(()) @@ -1127,18 +1277,14 @@ pub fn validate_parameter_type(name: &str, value: &Value, parameter_type: &DataT fn get_failure_from_error(err: &DscError) -> Option { match err { - DscError::CommandExit(_resource, exit_code, reason) => { - Some(Failure { - message: reason.to_string(), - exit_code: *exit_code, - }) - }, - DscError::CommandExitFromManifest(_resource, exit_code, reason) => { - Some(Failure { - message: reason.to_string(), - exit_code: *exit_code, - }) - }, + DscError::CommandExit(_resource, exit_code, reason) => Some(Failure { + message: reason.to_string(), + exit_code: *exit_code, + }), + DscError::CommandExitFromManifest(_resource, exit_code, reason) => Some(Failure { + message: reason.to_string(), + exit_code: *exit_code, + }), _ => None, } } diff --git a/dsc_lib/src/discovery/command_discovery.rs b/dsc_lib/src/discovery/command_discovery.rs index 9cba08498..0d97cc126 100644 --- a/dsc_lib/src/discovery/command_discovery.rs +++ b/dsc_lib/src/discovery/command_discovery.rs @@ -1,34 +1,35 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::discovery::{ - discovery_trait::{DiscoveryFilter, DiscoveryKind, ResourceDiscovery} -}; -use crate::{locked_is_empty, locked_extend, locked_clone, locked_get}; +use crate::discovery::discovery_trait::{DiscoveryFilter, DiscoveryKind, ResourceDiscovery}; +use crate::dscerror::DscError; +use crate::dscresources::command_resource::invoke_command; use crate::dscresources::dscresource::{Capability, DscResource, ImplementedAs}; use crate::dscresources::resource_manifest::{import_manifest, validate_semver, Kind, ResourceManifest, SchemaKind}; -use crate::dscresources::command_resource::invoke_command; -use crate::dscerror::DscError; -use crate::extensions::dscextension::{self, DscExtension, Capability as ExtensionCapability}; +use crate::extensions::dscextension::{self, Capability as ExtensionCapability, DscExtension}; use crate::extensions::extension_manifest::ExtensionManifest; use crate::progress::{ProgressBar, ProgressFormat}; use crate::util::convert_wildcard_to_regex; +use crate::{locked_clone, locked_extend, locked_get, locked_is_empty}; use regex::RegexBuilder; use rust_i18n::t; -use semver::{Version, VersionReq}; use schemars::JsonSchema; +use semver::{Version, VersionReq}; use serde::{Deserialize, Serialize}; -use std::{collections::{BTreeMap, HashMap, HashSet}, sync::{LazyLock, RwLock}}; use std::env; use std::ffi::OsStr; use std::fs; use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + sync::{LazyLock, RwLock}, +}; use tracing::{debug, info, trace, warn}; use which::which; -use crate::util::get_setting; use crate::util::get_exe_path; +use crate::util::get_setting; const DSC_RESOURCE_EXTENSIONS: [&str; 3] = [".dsc.resource.json", ".dsc.resource.yaml", ".dsc.resource.yml"]; const DSC_EXTENSION_EXTENSIONS: [&str; 3] = [".dsc.extension.json", ".dsc.extension.yaml", ".dsc.extension.yml"]; @@ -37,7 +38,8 @@ const DSC_EXTENSION_EXTENSIONS: [&str; 3] = [".dsc.extension.json", ".dsc.extens static ADAPTERS: LazyLock>>> = LazyLock::new(|| RwLock::new(BTreeMap::new())); static RESOURCES: LazyLock>>> = LazyLock::new(|| RwLock::new(BTreeMap::new())); static EXTENSIONS: LazyLock>> = LazyLock::new(|| RwLock::new(BTreeMap::new())); -static ADAPTED_RESOURCES: LazyLock>>> = LazyLock::new(|| RwLock::new(BTreeMap::new())); +static ADAPTED_RESOURCES: LazyLock>>> = + LazyLock::new(|| RwLock::new(BTreeMap::new())); #[derive(Clone, Serialize, Deserialize, JsonSchema)] pub enum ImportedManifest { @@ -45,7 +47,6 @@ pub enum ImportedManifest { Extension(DscExtension), } - #[derive(Clone)] pub struct CommandDiscovery { progress_format: ProgressFormat, @@ -60,7 +61,7 @@ pub struct ResourcePathSetting { #[serde(rename = "appendEnvPath")] append_env_path: bool, /// array of directories that DSC should search for non-built-in resources - directories: Vec + directories: Vec, } impl Default for ResourcePathSetting { @@ -76,46 +77,50 @@ impl Default for ResourcePathSetting { impl CommandDiscovery { #[must_use] pub fn new(progress_format: ProgressFormat) -> CommandDiscovery { - CommandDiscovery { - progress_format, - } + CommandDiscovery { progress_format } } #[must_use] - pub fn get_extensions(&self) -> BTreeMap { locked_clone!(EXTENSIONS) } + pub fn get_extensions(&self) -> BTreeMap { + locked_clone!(EXTENSIONS) + } - fn get_resource_path_setting() -> Result - { + fn get_resource_path_setting() -> Result { if let Ok(v) = get_setting("resourcePath") { // if there is a policy value defined - use it; otherwise use setting value if v.policy != serde_json::Value::Null { match serde_json::from_value::(v.policy) { Ok(v) => { return Ok(v); - }, - Err(e) => { return Err(DscError::Setting(format!("{e}"))); } + } + Err(e) => { + return Err(DscError::Setting(format!("{e}"))); + } } } else if v.setting != serde_json::Value::Null { match serde_json::from_value::(v.setting) { Ok(v) => { return Ok(v); - }, - Err(e) => { return Err(DscError::Setting(format!("{e}"))); } + } + Err(e) => { + return Err(DscError::Setting(format!("{e}"))); + } } } } - Err(DscError::Setting(t!("discovery.commandDiscovery.couldNotReadSetting").to_string())) + Err(DscError::Setting( + t!("discovery.commandDiscovery.couldNotReadSetting").to_string(), + )) } - fn get_resource_paths() -> Result, DscError> - { + fn get_resource_paths() -> Result, DscError> { let mut resource_path_setting = ResourcePathSetting::default(); match Self::get_resource_path_setting() { Ok(v) => { resource_path_setting = v; - }, + } Err(e) => { debug!("{e}"); } @@ -141,11 +146,19 @@ impl CommandDiscovery { debug!("{}", t!("discovery.commandDiscovery.appendingEnvPath")); match env::var_os("PATH") { Some(value) => { - trace!("{}", t!("discovery.commandDiscovery.originalPath", path = value.to_string_lossy())); + trace!( + "{}", + t!( + "discovery.commandDiscovery.originalPath", + path = value.to_string_lossy() + ) + ); paths.append(&mut env::split_paths(&value).collect::>()); - }, + } None => { - return Err(DscError::Operation(t!("discovery.commandDiscovery.failedGetEnvPath").to_string())); + return Err(DscError::Operation( + t!("discovery.commandDiscovery.failedGetEnvPath").to_string(), + )); } } } @@ -153,7 +166,7 @@ impl CommandDiscovery { // remove duplicate entries let mut uniques: HashSet = HashSet::new(); - paths.retain(|e|uniques.insert((*e).clone())); + paths.retain(|e| uniques.insert((*e).clone())); if using_custom_path { // when using custom path, intent is to isolate the search of manifests and executables to the custom path @@ -161,16 +174,30 @@ impl CommandDiscovery { if let Ok(new_path) = env::join_paths(paths.clone()) { env::set_var("PATH", new_path); } else { - return Err(DscError::Operation(t!("discovery.commandDiscovery.failedJoinEnvPath").to_string())); + return Err(DscError::Operation( + t!("discovery.commandDiscovery.failedJoinEnvPath").to_string(), + )); } } else { // if exe home is not already in PATH env var then add it to env var and list of searched paths if let Some(exe_home) = get_exe_path()?.parent() { let exe_home_pb = exe_home.to_path_buf(); if paths.contains(&exe_home_pb) { - trace!("{}", t!("discovery.commandDiscovery.exeHomeAlreadyInPath", path = exe_home.to_string_lossy())); + trace!( + "{}", + t!( + "discovery.commandDiscovery.exeHomeAlreadyInPath", + path = exe_home.to_string_lossy() + ) + ); } else { - trace!("{}", t!("discovery.commandDiscovery.addExeHomeToPath", path = exe_home.to_string_lossy())); + trace!( + "{}", + t!( + "discovery.commandDiscovery.addExeHomeToPath", + path = exe_home.to_string_lossy() + ) + ); paths.push(exe_home_pb); if let Ok(new_path) = env::join_paths(paths.clone()) { @@ -181,7 +208,13 @@ impl CommandDiscovery { } if let Ok(final_resource_path) = env::join_paths(paths.clone()) { - debug!("{}", t!("discovery.commandDiscovery.usingResourcePath", path = final_resource_path.to_string_lossy())); + debug!( + "{}", + t!( + "discovery.commandDiscovery.usingResourcePath", + path = final_resource_path.to_string_lossy() + ) + ); } Ok(paths) @@ -195,10 +228,12 @@ impl Default for CommandDiscovery { } impl ResourceDiscovery for CommandDiscovery { - #[allow(clippy::too_many_lines)] fn discover(&mut self, kind: &DiscoveryKind, filter: &str) -> Result<(), DscError> { - info!("{}", t!("discovery.commandDiscovery.discoverResources", kind = kind : {:?}, filter = filter)); + info!( + "{}", + t!("discovery.commandDiscovery.discoverResources", kind = kind : {:?}, filter = filter) + ); // if kind is DscResource, we need to discover extensions first if *kind == DiscoveryKind::Resource { @@ -210,14 +245,16 @@ impl ResourceDiscovery for CommandDiscovery { let mut regex_builder = RegexBuilder::new(®ex_str); regex_builder.case_insensitive(true); let Ok(regex) = regex_builder.build() else { - return Err(DscError::Operation(t!("discovery.commandDiscovery.invalidAdapterFilter").to_string())); + return Err(DscError::Operation( + t!("discovery.commandDiscovery.invalidAdapterFilter").to_string(), + )); }; let mut progress = ProgressBar::new(1, self.progress_format)?; match kind { DiscoveryKind::Resource => { progress.write_activity(t!("discovery.commandDiscovery.progressSearching").to_string().as_str()); - }, + } DiscoveryKind::Extension => { progress.write_activity(t!("discovery.commandDiscovery.extensionSearching").to_string().as_str()); } @@ -243,11 +280,23 @@ impl ResourceDiscovery for CommandDiscovery { continue; }; let file_name_lowercase = file_name.to_lowercase(); - if (kind == &DiscoveryKind::Resource && DSC_RESOURCE_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext))) || - (kind == &DiscoveryKind::Extension && DSC_EXTENSION_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext))) { - trace!("{}", t!("discovery.commandDiscovery.foundResourceManifest", path = path.to_string_lossy())); - let resource = match load_manifest(&path) - { + if (kind == &DiscoveryKind::Resource + && DSC_RESOURCE_EXTENSIONS + .iter() + .any(|ext| file_name_lowercase.ends_with(ext))) + || (kind == &DiscoveryKind::Extension + && DSC_EXTENSION_EXTENSIONS + .iter() + .any(|ext| file_name_lowercase.ends_with(ext))) + { + trace!( + "{}", + t!( + "discovery.commandDiscovery.foundResourceManifest", + path = path.to_string_lossy() + ) + ); + let resource = match load_manifest(&path) { Ok(r) => r, Err(e) => { // At this point we can't determine whether or not the bad manifest contains @@ -256,20 +305,42 @@ impl ResourceDiscovery for CommandDiscovery { // and here we just write as warning warn!("{e}"); continue; - }, + } }; match resource { ImportedManifest::Extension(extension) => { if regex.is_match(&extension.type_name) { - trace!("{}", t!("discovery.commandDiscovery.extensionFound", extension = extension.type_name, version = extension.version)); + trace!( + "{}", + t!( + "discovery.commandDiscovery.extensionFound", + extension = extension.type_name, + version = extension.version + ) + ); // we only keep newest version of the extension so compare the version and only keep the newest if let Some(existing_extension) = extensions.get_mut(&extension.type_name) { - let Ok(existing_version) = Version::parse(&existing_extension.version) else { - return Err(DscError::Operation(t!("discovery.commandDiscovery.extensionInvalidVersion", extension = existing_extension.type_name, version = existing_extension.version).to_string())); + let Ok(existing_version) = Version::parse(&existing_extension.version) + else { + return Err(DscError::Operation( + t!( + "discovery.commandDiscovery.extensionInvalidVersion", + extension = existing_extension.type_name, + version = existing_extension.version + ) + .to_string(), + )); }; let Ok(new_version) = Version::parse(&extension.version) else { - return Err(DscError::Operation(t!("discovery.commandDiscovery.extensionInvalidVersion", extension = extension.type_name, version = extension.version).to_string())); + return Err(DscError::Operation( + t!( + "discovery.commandDiscovery.extensionInvalidVersion", + extension = extension.type_name, + version = extension.version + ) + .to_string(), + )); }; if new_version > existing_version { extensions.insert(extension.type_name.clone(), extension.clone()); @@ -278,17 +349,31 @@ impl ResourceDiscovery for CommandDiscovery { extensions.insert(extension.type_name.clone(), extension.clone()); } } - }, + } ImportedManifest::Resource(resource) => { if regex.is_match(&resource.type_name) { if let Some(ref manifest) = resource.manifest { let manifest = import_manifest(manifest.clone())?; if manifest.kind == Some(Kind::Adapter) { - trace!("{}", t!("discovery.commandDiscovery.adapterFound", adapter = resource.type_name, version = resource.version)); + trace!( + "{}", + t!( + "discovery.commandDiscovery.adapterFound", + adapter = resource.type_name, + version = resource.version + ) + ); insert_resource(&mut adapters, &resource); } // also make sure to add adapters as a resource as well - trace!("{}", t!("discovery.commandDiscovery.resourceFound", resource = resource.type_name, version = resource.version)); + trace!( + "{}", + t!( + "discovery.commandDiscovery.resourceFound", + resource = resource.type_name, + version = resource.version + ) + ); insert_resource(&mut resources, &resource); } } @@ -308,18 +393,44 @@ impl ResourceDiscovery for CommandDiscovery { // Now we need to call discover extensions and add those resource to the list of resources for extension in locked_clone!(EXTENSIONS).values() { if extension.capabilities.contains(&ExtensionCapability::Discover) { - debug!("{}", t!("discovery.commandDiscovery.callingExtension", extension = extension.type_name)); + debug!( + "{}", + t!( + "discovery.commandDiscovery.callingExtension", + extension = extension.type_name + ) + ); let discovered_resources = match extension.discover() { Ok(res) => res, Err(e) => { - warn!("{}", t!("discovery.commandDiscovery.extensionDiscoverFailed", extension = extension.type_name, error = e)); + warn!( + "{}", + t!( + "discovery.commandDiscovery.extensionDiscoverFailed", + extension = extension.type_name, + error = e + ) + ); continue; } }; - debug!("{}", t!("discovery.commandDiscovery.extensionFoundResources", extension = extension.type_name, count = discovered_resources.len())); + debug!( + "{}", + t!( + "discovery.commandDiscovery.extensionFoundResources", + extension = extension.type_name, + count = discovered_resources.len() + ) + ); for resource in discovered_resources { if regex.is_match(&resource.type_name) { - trace!("{}", t!("discovery.commandDiscovery.extensionResourceFound", resource = resource.type_name)); + trace!( + "{}", + t!( + "discovery.commandDiscovery.extensionResourceFound", + resource = resource.type_name + ) + ); insert_resource(&mut resources, &resource); } } @@ -327,7 +438,7 @@ impl ResourceDiscovery for CommandDiscovery { } locked_extend!(ADAPTERS, adapters); locked_extend!(RESOURCES, resources); - }, + } DiscoveryKind::Extension => { locked_extend!(EXTENSIONS, extensions); } @@ -336,6 +447,7 @@ impl ResourceDiscovery for CommandDiscovery { Ok(()) } + #[allow(clippy::too_many_lines)] fn discover_adapted_resources(&mut self, name_filter: &str, adapter_filter: &str) -> Result<(), DscError> { if locked_is_empty!(RESOURCES) && locked_is_empty!(ADAPTERS) { self.discover(&DiscoveryKind::Resource, "*")?; @@ -351,7 +463,9 @@ impl ResourceDiscovery for CommandDiscovery { let mut regex_builder = RegexBuilder::new(®ex_str); regex_builder.case_insensitive(true); let Ok(regex) = regex_builder.build() else { - return Err(DscError::Operation("Could not build Regex filter for adapter name".to_string())); + return Err(DscError::Operation( + "Could not build Regex filter for adapter name".to_string(), + )); }; let name_regex_str = convert_wildcard_to_regex(name_filter); @@ -359,7 +473,9 @@ impl ResourceDiscovery for CommandDiscovery { let mut name_regex_builder = RegexBuilder::new(&name_regex_str); name_regex_builder.case_insensitive(true); let Ok(name_regex) = name_regex_builder.build() else { - return Err(DscError::Operation("Could not build Regex filter for resource name".to_string())); + return Err(DscError::Operation( + "Could not build Regex filter for resource name".to_string(), + )); }; let mut progress = ProgressBar::new(adapters.len() as u64, self.progress_format)?; @@ -384,7 +500,10 @@ impl ResourceDiscovery for CommandDiscovery { if let Ok(manifest) = import_manifest(manifest.clone()) { manifest } else { - return Err(DscError::Operation(format!("Failed to import manifest for '{}'", adapter_name.clone()))); + return Err(DscError::Operation(format!( + "Failed to import manifest for '{}'", + adapter_name.clone() + ))); } } else { return Err(DscError::MissingManifest(adapter_name.clone())); @@ -393,14 +512,20 @@ impl ResourceDiscovery for CommandDiscovery { let mut adapter_resources_count = 0; // invoke the list command let list_command = manifest.adapter.unwrap().list; - let (exit_code, stdout, stderr) = match invoke_command(&list_command.executable, list_command.args, None, Some(&adapter.directory), None, manifest.exit_codes.as_ref()) - { + let (exit_code, stdout, stderr) = match invoke_command( + &list_command.executable, + list_command.args, + None, + Some(&adapter.directory), + None, + manifest.exit_codes.as_ref(), + ) { Ok((exit_code, stdout, stderr)) => (exit_code, stdout, stderr), Err(e) => { // In case of error, log and continue warn!("{e}"); continue; - }, + } }; if exit_code != 0 { @@ -410,10 +535,14 @@ impl ResourceDiscovery for CommandDiscovery { } for line in stdout.lines() { - match serde_json::from_str::(line){ + match serde_json::from_str::(line) { Result::Ok(resource) => { if resource.require_adapter.is_none() { - warn!("{}", DscError::MissingRequires(adapter_name.clone(), resource.type_name.clone()).to_string()); + warn!( + "{}", + DscError::MissingRequires(adapter_name.clone(), resource.type_name.clone()) + .to_string() + ); continue; } @@ -421,7 +550,7 @@ impl ResourceDiscovery for CommandDiscovery { insert_resource(&mut adapted_resources, &resource); adapter_resources_count += 1; } - }, + } Result::Err(err) => { warn!("Failed to parse resource: {line} -> {err}"); } @@ -429,7 +558,10 @@ impl ResourceDiscovery for CommandDiscovery { } adapter_progress.write_increment(1); - debug!("Adapter '{}' listed {} resources", adapter_name, adapter_resources_count); + debug!( + "Adapter '{}' listed {} resources", + adapter_name, adapter_resources_count + ); } } @@ -442,16 +574,33 @@ impl ResourceDiscovery for CommandDiscovery { Ok(()) } - fn list_available(&mut self, kind: &DiscoveryKind, type_name_filter: &str, adapter_name_filter: &str) -> Result>, DscError> { + fn list_available( + &mut self, + kind: &DiscoveryKind, + type_name_filter: &str, + adapter_name_filter: &str, + ) -> Result>, DscError> { let mut resources = BTreeMap::>::new(); if *kind == DiscoveryKind::Resource { if adapter_name_filter.is_empty() { self.discover(kind, type_name_filter)?; for (resource_name, resources_vec) in &locked_clone!(RESOURCES) { - resources.insert(resource_name.clone(), resources_vec.iter().map(|r| ImportedManifest::Resource(r.clone())).collect()); + resources.insert( + resource_name.clone(), + resources_vec + .iter() + .map(|r| ImportedManifest::Resource(r.clone())) + .collect(), + ); } for (adapter_name, adapter_vec) in &locked_clone!(ADAPTERS) { - resources.insert(adapter_name.clone(), adapter_vec.iter().map(|r| ImportedManifest::Resource(r.clone())).collect()); + resources.insert( + adapter_name.clone(), + adapter_vec + .iter() + .map(|r| ImportedManifest::Resource(r.clone())) + .collect(), + ); } } else { self.discover(kind, "*")?; @@ -462,21 +611,36 @@ impl ResourceDiscovery for CommandDiscovery { add_resources_to_lookup_table(&adapted_resources); for (adapted_name, adapted_vec) in &adapted_resources { - resources.insert(adapted_name.clone(), adapted_vec.iter().map(|r| ImportedManifest::Resource(r.clone())).collect()); + resources.insert( + adapted_name.clone(), + adapted_vec + .iter() + .map(|r| ImportedManifest::Resource(r.clone())) + .collect(), + ); } } } else { self.discover(kind, type_name_filter)?; for (extension_name, extension) in &locked_clone!(EXTENSIONS) { - resources.insert(extension_name.clone(), vec![ImportedManifest::Extension(extension.clone())]); + resources.insert( + extension_name.clone(), + vec![ImportedManifest::Extension(extension.clone())], + ); } } Ok(resources) } - fn find_resources(&mut self, required_resource_types: &[DiscoveryFilter]) -> Result>, DscError> { - debug!("{}", t!("discovery.commandDiscovery.searchingForResources", resources = required_resource_types : {:?})); + fn find_resources( + &mut self, + required_resource_types: &[DiscoveryFilter], + ) -> Result>, DscError> { + debug!( + "{}", + t!("discovery.commandDiscovery.searchingForResources", resources = required_resource_types : {:?}) + ); if locked_is_empty!(RESOURCES) { self.discover(&DiscoveryKind::Resource, "*")?; } @@ -494,7 +658,13 @@ impl ResourceDiscovery for CommandDiscovery { break; } } - debug!("{}", t!("discovery.commandDiscovery.foundNonAdapterResources", count = found_resources.len())); + debug!( + "{}", + t!( + "discovery.commandDiscovery.foundNonAdapterResources", + count = found_resources.len() + ) + ); if required_resources.values().all(|&v| v) { return Ok(found_resources); @@ -506,7 +676,12 @@ impl ResourceDiscovery for CommandDiscovery { add_resources_to_lookup_table(&locked_clone!(ADAPTED_RESOURCES)); for filter in required_resource_types { if let Some(adapted_resources) = locked_get!(ADAPTED_RESOURCES, filter.resource_type()) { - filter_resources(&mut found_resources, &mut required_resources, &adapted_resources, filter); + filter_resources( + &mut found_resources, + &mut required_resources, + &adapted_resources, + filter, + ); } if required_resources.values().all(|&v| v) { break; @@ -528,24 +703,49 @@ impl ResourceDiscovery for CommandDiscovery { } } -fn filter_resources(found_resources: &mut BTreeMap>, required_resources: &mut HashMap, resources: &[DscResource], filter: &DiscoveryFilter) { +fn filter_resources( + found_resources: &mut BTreeMap>, + required_resources: &mut HashMap, + resources: &[DscResource], + filter: &DiscoveryFilter, +) { for resource in resources { if let Some(required_version) = filter.version() { if let Ok(resource_version) = Version::parse(&resource.version) { if let Ok(version_req) = VersionReq::parse(required_version) { if version_req.matches(&resource_version) { - found_resources.entry(filter.resource_type().to_string()).or_default().push(resource.clone()); + found_resources + .entry(filter.resource_type().to_string()) + .or_default() + .push(resource.clone()); required_resources.insert(filter.clone(), true); - debug!("{}", t!("discovery.commandDiscovery.foundResourceWithVersion", resource = resource.type_name, version = resource.version)); + debug!( + "{}", + t!( + "discovery.commandDiscovery.foundResourceWithVersion", + resource = resource.type_name, + version = resource.version + ) + ); break; } } } else { // if not semver, we do a string comparison if resource.version == *required_version { - found_resources.entry(filter.resource_type().to_string()).or_default().push(resource.clone()); + found_resources + .entry(filter.resource_type().to_string()) + .or_default() + .push(resource.clone()); required_resources.insert(filter.clone(), true); - debug!("{}", t!("discovery.commandDiscovery.foundResourceWithVersion", resource = resource.type_name, version = resource.version)); + debug!( + "{}", + t!( + "discovery.commandDiscovery.foundResourceWithVersion", + resource = resource.type_name, + version = resource.version + ) + ); break; } } @@ -553,7 +753,10 @@ fn filter_resources(found_resources: &mut BTreeMap>, re // if no version specified, get first one which will be latest if let Some(resource) = resources.first() { required_resources.insert(filter.clone(), true); - found_resources.entry(filter.resource_type().to_string()).or_default().push(resource.clone()); + found_resources + .entry(filter.resource_type().to_string()) + .or_default() + .push(resource.clone()); break; } } @@ -573,13 +776,13 @@ fn insert_resource(resources: &mut BTreeMap>, resource: Ok(v) => v, Err(_err) => { continue; - }, + } }; let resource_version = match Version::parse(&resource.version) { Ok(v) => v, Err(_err) => { continue; - }, + } }; if resource_instance_version < resource_version { @@ -616,7 +819,14 @@ pub fn load_manifest(path: &Path) -> Result { let manifest = match serde_json::from_str::(&contents) { Ok(manifest) => manifest, Err(err) => { - return Err(DscError::Manifest(t!("discovery.commandDiscovery.invalidManifest", resource = path.to_string_lossy()).to_string(), err)); + return Err(DscError::Manifest( + t!( + "discovery.commandDiscovery.invalidManifest", + resource = path.to_string_lossy() + ) + .to_string(), + err, + )); } }; let resource = load_resource_manifest(path, &manifest)?; @@ -630,7 +840,14 @@ pub fn load_manifest(path: &Path) -> Result { let manifest = match serde_yaml::from_str::(&contents) { Ok(manifest) => manifest, Err(err) => { - return Err(DscError::Validation(t!("discovery.commandDiscovery.invalidManifest", path = path.to_string_lossy(), err = err).to_string())); + return Err(DscError::Validation( + t!( + "discovery.commandDiscovery.invalidManifest", + path = path.to_string_lossy(), + err = err + ) + .to_string(), + )); } }; let extension = load_extension_manifest(path, &manifest)?; @@ -639,7 +856,15 @@ pub fn load_manifest(path: &Path) -> Result { fn load_resource_manifest(path: &Path, manifest: &ResourceManifest) -> Result { if let Err(err) = validate_semver(&manifest.version) { - warn!("{}", t!("discovery.commandDiscovery.invalidManifestVersion", path = path.to_string_lossy(), err = err).to_string()); + warn!( + "{}", + t!( + "discovery.commandDiscovery.invalidManifestVersion", + path = path.to_string_lossy(), + err = err + ) + .to_string() + ); } let kind = if let Some(kind) = manifest.kind.clone() { @@ -704,7 +929,15 @@ fn load_resource_manifest(path: &Path, manifest: &ResourceManifest) -> Result Result { if let Err(err) = validate_semver(&manifest.version) { - warn!("{}", t!("discovery.commandDiscovery.invalidManifestVersion", path = path.to_string_lossy(), err = err).to_string()); + warn!( + "{}", + t!( + "discovery.commandDiscovery.invalidManifestVersion", + path = path.to_string_lossy(), + err = err + ) + .to_string() + ); } let mut capabilities: Vec = vec![]; @@ -720,7 +953,13 @@ fn load_extension_manifest(path: &Path, manifest: &ExtensionManifest) -> Result< verify_executable(&manifest.r#type, "import", &import.executable); capabilities.push(dscextension::Capability::Import); if import.file_extensions.is_empty() { - warn!("{}", t!("discovery.commandDiscovery.importExtensionsEmpty", extension = manifest.r#type)); + warn!( + "{}", + t!( + "discovery.commandDiscovery.importExtensionsEmpty", + extension = manifest.r#type + ) + ); None } else { Some(import.file_extensions.clone()) @@ -746,12 +985,19 @@ fn load_extension_manifest(path: &Path, manifest: &ExtensionManifest) -> Result< fn verify_executable(resource: &str, operation: &str, executable: &str) { if which(executable).is_err() { - info!("{}", t!("discovery.commandDiscovery.executableNotFound", resource = resource, operation = operation, executable = executable)); + info!( + "{}", + t!( + "discovery.commandDiscovery.executableNotFound", + resource = resource, + operation = operation, + executable = executable + ) + ); } } -fn add_resources_to_lookup_table(adapted_resources: &BTreeMap>) -{ +fn add_resources_to_lookup_table(adapted_resources: &BTreeMap>) { let mut lookup_table = load_adapted_resources_lookup_table(); let mut lookup_table_changed = false; @@ -763,7 +1009,13 @@ fn add_resources_to_lookup_table(adapted_resources: &BTreeMap) -{ +fn save_adapted_resources_lookup_table(lookup_table: &HashMap) { if let Ok(lookup_table_json) = serde_json::to_string(&lookup_table) { let file_path = get_lookup_table_file_path(); - debug!("Saving lookup table with {} items to {:?}", lookup_table.len(), file_path); + debug!( + "Saving lookup table with {} items to {:?}", + lookup_table.len(), + file_path + ); let path = std::path::Path::new(&file_path); if let Some(prefix) = path.parent() { - if fs::create_dir_all(prefix).is_ok() { + if fs::create_dir_all(prefix).is_ok() { if fs::write(file_path.clone(), lookup_table_json).is_err() { info!("Unable to write lookup_table file {file_path:?}"); } @@ -795,32 +1050,45 @@ fn save_adapted_resources_lookup_table(lookup_table: &HashMap) } } -fn load_adapted_resources_lookup_table() -> HashMap -{ +fn load_adapted_resources_lookup_table() -> HashMap { let file_path = get_lookup_table_file_path(); - let lookup_table: HashMap = match fs::read(file_path.clone()){ - Ok(data) => { serde_json::from_slice(&data).unwrap_or_default() }, - Err(_) => { HashMap::new() } + let lookup_table: HashMap = match fs::read(file_path.clone()) { + Ok(data) => serde_json::from_slice(&data).unwrap_or_default(), + Err(_) => HashMap::new(), }; - debug!("Read {} items into lookup table from {:?}", lookup_table.len(), file_path); + debug!( + "Read {} items into lookup table from {:?}", + lookup_table.len(), + file_path + ); lookup_table } #[cfg(target_os = "windows")] -fn get_lookup_table_file_path() -> String -{ +fn get_lookup_table_file_path() -> String { // $env:LocalAppData+"dsc\AdaptedResourcesLookupTable.json" - let Ok(local_app_data_path) = std::env::var("LocalAppData") else { return String::new(); }; + let Ok(local_app_data_path) = std::env::var("LocalAppData") else { + return String::new(); + }; - Path::new(&local_app_data_path).join("dsc").join("AdaptedResourcesLookupTable.json").display().to_string() + Path::new(&local_app_data_path) + .join("dsc") + .join("AdaptedResourcesLookupTable.json") + .display() + .to_string() } #[cfg(not(target_os = "windows"))] -fn get_lookup_table_file_path() -> String -{ +fn get_lookup_table_file_path() -> String { // $env:HOME+".dsc/AdaptedResourcesLookupTable.json" - let Ok(home_path) = std::env::var("HOME") else { return String::new(); }; - Path::new(&home_path).join(".dsc").join("AdaptedResourcesLookupTable.json").display().to_string() + let Ok(home_path) = std::env::var("HOME") else { + return String::new(); + }; + Path::new(&home_path) + .join(".dsc") + .join("AdaptedResourcesLookupTable.json") + .display() + .to_string() } diff --git a/dsc_lib/src/discovery/discovery_trait.rs b/dsc_lib/src/discovery/discovery_trait.rs index 70e4d6e5e..394799201 100644 --- a/dsc_lib/src/discovery/discovery_trait.rs +++ b/dsc_lib/src/discovery/discovery_trait.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use super::{command_discovery::ImportedManifest, fix_semver}; use crate::{dscerror::DscError, dscresources::dscresource::DscResource, extensions::dscextension::DscExtension}; use std::collections::BTreeMap; -use super::{command_discovery::ImportedManifest, fix_semver}; #[derive(Debug, PartialEq)] pub enum DiscoveryKind { @@ -86,7 +86,12 @@ pub trait ResourceDiscovery { /// # Errors /// /// This function will return an error if the underlying discovery fails. - fn list_available(&mut self, kind: &DiscoveryKind, type_name_filter: &str, adapter_name_filter: &str) -> Result>, DscError>; + fn list_available( + &mut self, + kind: &DiscoveryKind, + type_name_filter: &str, + adapter_name_filter: &str, + ) -> Result>, DscError>; /// Find resources based on the required resource types. /// This is not applicable for extensions. @@ -102,7 +107,10 @@ pub trait ResourceDiscovery { /// # Errors /// /// This function will return an error if the underlying discovery fails. - fn find_resources(&mut self, required_resource_types: &[DiscoveryFilter]) -> Result>, DscError>; + fn find_resources( + &mut self, + required_resource_types: &[DiscoveryFilter], + ) -> Result>, DscError>; /// Get the available extensions. /// diff --git a/dsc_lib/src/discovery/mod.rs b/dsc_lib/src/discovery/mod.rs index f96f94d09..4662f84b6 100644 --- a/dsc_lib/src/discovery/mod.rs +++ b/dsc_lib/src/discovery/mod.rs @@ -4,13 +4,13 @@ pub mod command_discovery; pub mod discovery_trait; -use crate::discovery::discovery_trait::{DiscoveryKind, ResourceDiscovery, DiscoveryFilter}; +use crate::discovery::discovery_trait::{DiscoveryFilter, DiscoveryKind, ResourceDiscovery}; use crate::extensions::dscextension::{Capability, DscExtension}; use crate::{dscresources::dscresource::DscResource, progress::ProgressFormat}; +use command_discovery::{CommandDiscovery, ImportedManifest}; use core::result::Result::Ok; use semver::{Version, VersionReq}; use std::collections::BTreeMap; -use command_discovery::{CommandDiscovery, ImportedManifest}; use tracing::error; #[derive(Clone)] @@ -45,16 +45,21 @@ impl Discovery { /// # Returns /// /// A vector of `DscResource` instances. - pub fn list_available(&mut self, kind: &DiscoveryKind, type_name_filter: &str, adapter_name_filter: &str, progress_format: ProgressFormat) -> Vec { - let discovery_types: Vec> = vec![ - Box::new(command_discovery::CommandDiscovery::new(progress_format)), - ]; + pub fn list_available( + &mut self, + kind: &DiscoveryKind, + type_name_filter: &str, + adapter_name_filter: &str, + progress_format: ProgressFormat, + ) -> Vec { + let discovery_types: Vec> = + vec![Box::new(command_discovery::CommandDiscovery::new(progress_format))]; let mut resources: Vec = Vec::new(); for mut discovery_type in discovery_types { - - let discovered_resources = match discovery_type.list_available(kind, type_name_filter, adapter_name_filter) { + let discovered_resources = match discovery_type.list_available(kind, type_name_filter, adapter_name_filter) + { Ok(value) => value, Err(err) => { error!("{err}"); @@ -66,7 +71,7 @@ impl Discovery { for resource in found_resources { resources.push(resource.clone()); } - }; + } if let Ok(extensions) = discovery_type.get_extensions() { self.extensions.extend(extensions); @@ -80,7 +85,8 @@ impl Discovery { if self.extensions.is_empty() { self.list_available(&DiscoveryKind::Extension, "*", "", ProgressFormat::None); } - self.extensions.values() + self.extensions + .values() .filter(|ext| ext.capabilities.contains(capability)) .cloned() .collect() @@ -89,7 +95,8 @@ impl Discovery { #[must_use] pub fn find_resource(&mut self, type_name: &str, version_string: Option<&str>) -> Option<&DscResource> { if self.resources.is_empty() { - let discovery_filter = DiscoveryFilter::new(type_name, version_string.map(std::string::ToString::to_string)); + let discovery_filter = + DiscoveryFilter::new(type_name, version_string.map(std::string::ToString::to_string)); self.find_resources(&[discovery_filter], ProgressFormat::None); } @@ -134,11 +141,8 @@ impl Discovery { } let command_discovery = CommandDiscovery::new(progress_format); - let discovery_types: Vec> = vec![ - Box::new(command_discovery), - ]; + let discovery_types: Vec> = vec![Box::new(command_discovery)]; for mut discovery_type in discovery_types { - let discovered_resources = match discovery_type.find_resources(required_resource_types) { Ok(value) => value, Err(err) => { @@ -189,10 +193,7 @@ pub struct ResourceIterator { impl ResourceIterator { #[must_use] pub fn new(resources: Vec) -> ResourceIterator { - ResourceIterator { - resources, - index: 0, - } + ResourceIterator { resources, index: 0 } } } diff --git a/dsc_lib/src/dscerror.rs b/dsc_lib/src/dscerror.rs index 2df370729..f52040510 100644 --- a/dsc_lib/src/dscerror.rs +++ b/dsc_lib/src/dscerror.rs @@ -117,10 +117,7 @@ pub enum DscError { Utf8Conversion(#[from] Utf8Error), #[error("{t}: {code:?} {message:?}", t = t!("dscerror.unknown"))] - Unknown { - code: i32, - message: String, - }, + Unknown { code: i32, message: String }, #[error("{t}: {0}. {t2}: {1:?}", t = t!("dscerror.unrecognizedSchemaUri"), t2 = t!("dscerror.validSchemaUrisAre"))] UnrecognizedSchemaUri(String, Vec), diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs index b5220fed7..fe7fc6ffd 100644 --- a/dsc_lib/src/dscresources/command_resource.rs +++ b/dsc_lib/src/dscresources/command_resource.rs @@ -1,17 +1,30 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use super::{ + dscresource::{get_diff, redact}, + invoke_result::{ + get_in_desired_state, ExportResult, GetResult, ResolveResult, ResourceGetResponse, ResourceSetResponse, + ResourceTestResponse, SetResult, TestResult, ValidateResult, + }, + resource_manifest::{ArgKind, InputKind, Kind, ResourceManifest, ReturnKind, SchemaKind}, +}; +use crate::configure::{ + config_doc::ExecutionKind, + config_result::{ResourceGetResult, ResourceTestResult}, +}; +use crate::dscerror::DscError; use clap::ValueEnum; use jsonschema::Validator; use rust_i18n::t; use serde::Deserialize; use serde_json::{Map, Value}; use std::{collections::HashMap, env, process::Stdio}; -use crate::configure::{config_doc::ExecutionKind, config_result::{ResourceGetResult, ResourceTestResult}}; -use crate::dscerror::DscError; -use super::{dscresource::{get_diff, redact}, invoke_result::{ExportResult, GetResult, ResolveResult, SetResult, TestResult, ValidateResult, ResourceGetResponse, ResourceSetResponse, ResourceTestResponse, get_in_desired_state}, resource_manifest::{ArgKind, InputKind, Kind, ResourceManifest, ReturnKind, SchemaKind}}; -use tracing::{error, warn, info, debug, trace}; -use tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, process::Command}; +use tokio::{ + io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, + process::Command, +}; +use tracing::{debug, error, info, trace, warn}; pub const EXIT_PROCESS_TERMINATED: i32 = 0x102; @@ -25,8 +38,19 @@ pub const EXIT_PROCESS_TERMINATED: i32 = 0x102; /// # Errors /// /// Error returned if the resource does not successfully get the current state -pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str, target_resource: Option<&str>) -> Result { - debug!("{}", t!("dscresources.commandResource.invokeGet", resource = &resource.resource_type)); +pub fn invoke_get( + resource: &ResourceManifest, + cwd: &str, + filter: &str, + target_resource: Option<&str>, +) -> Result { + debug!( + "{}", + t!( + "dscresources.commandResource.invokeGet", + resource = &resource.resource_type + ) + ); let mut command_input = CommandInput { env: None, stdin: None }; let Some(get) = &resource.get else { return Err(DscError::NotImplemented("get".to_string())); @@ -41,26 +65,57 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str, target_r command_input = get_command_input(get.input.as_ref(), filter)?; } - info!("{}", t!("dscresources.commandResource.invokeGetUsing", resource = &resource.resource_type, executable = &get.executable)); - let (_exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?; + info!( + "{}", + t!( + "dscresources.commandResource.invokeGetUsing", + resource = &resource.resource_type, + executable = &get.executable + ) + ); + let (_exit_code, stdout, stderr) = invoke_command( + &get.executable, + args, + command_input.stdin.as_deref(), + Some(cwd), + command_input.env, + resource.exit_codes.as_ref(), + )?; if resource.kind == Some(Kind::Resource) { - debug!("{}", t!("dscresources.commandResource.verifyOutputUsing", resource = &resource.resource_type, executable = &get.executable)); + debug!( + "{}", + t!( + "dscresources.commandResource.verifyOutputUsing", + resource = &resource.resource_type, + executable = &get.executable + ) + ); verify_json(resource, cwd, &stdout)?; } let result: GetResult = if let Ok(group_response) = serde_json::from_str::>(&stdout) { - trace!("{}", t!("dscresources.commandResource.groupGetResponse", response = &group_response : {:?})); + trace!( + "{}", + t!("dscresources.commandResource.groupGetResponse", response = &group_response : {:?}) + ); GetResult::Group(group_response) } else { let result: Value = match serde_json::from_str(&stdout) { - Ok(r) => {r}, + Ok(r) => r, Err(err) => { - return Err(DscError::Operation(t!("dscresources.commandResource.failedParseJson", executable = &get.executable, stdout = stdout, stderr = stderr, err = err).to_string())) + return Err(DscError::Operation( + t!( + "dscresources.commandResource.failedParseJson", + executable = &get.executable, + stdout = stdout, + stderr = stderr, + err = err + ) + .to_string(), + )) } }; - GetResult::Resource(ResourceGetResponse{ - actual_state: result, - }) + GetResult::Resource(ResourceGetResponse { actual_state: result }) }; Ok(result) @@ -78,15 +133,28 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str, target_r /// /// Error returned if the resource does not successfully set the desired state #[allow(clippy::too_many_lines)] -pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_test: bool, execution_type: &ExecutionKind, target_resource: Option<&str>) -> Result { - debug!("{}", t!("dscresources.commandResource.invokeSet", resource = &resource.resource_type)); +pub fn invoke_set( + resource: &ResourceManifest, + cwd: &str, + desired: &str, + skip_test: bool, + execution_type: &ExecutionKind, + target_resource: Option<&str>, +) -> Result { + debug!( + "{}", + t!( + "dscresources.commandResource.invokeSet", + resource = &resource.resource_type + ) + ); let operation_type: String; let mut is_synthetic_what_if = false; let set_method = match execution_type { ExecutionKind::Actual => { operation_type = "set".to_string(); &resource.set - }, + } ExecutionKind::WhatIf => { operation_type = "whatif".to_string(); if resource.what_if.is_none() { @@ -104,7 +172,13 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te // if resource doesn't implement a pre-test, we execute test first to see if a set is needed if !skip_test && set.pre_test != Some(true) { - info!("{}", t!("dscresources.commandResource.noPretest", resource = &resource.resource_type)); + info!( + "{}", + t!( + "dscresources.commandResource.noPretest", + resource = &resource.resource_type + ) + ); let test_result = invoke_test(resource, cwd, desired, target_resource)?; if is_synthetic_what_if { return Ok(test_result.into()); @@ -117,15 +191,13 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te result_array.push(serde_json::to_value(result)?); } (in_desired_state, Value::from(result_array)) - }, - TestResult::Resource(response) => { - (response.in_desired_state, response.actual_state.clone()) } + TestResult::Resource(response) => (response.in_desired_state, response.actual_state.clone()), }; if in_desired_state && execution_type == &ExecutionKind::Actual { let before_state = redact(&serde_json::from_str(desired)?); - return Ok(SetResult::Resource(ResourceSetResponse{ + return Ok(SetResult::Resource(ResourceSetResponse { before_state, after_state: actual_state, changed_properties: None, @@ -134,7 +206,9 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te } if is_synthetic_what_if { - return Err(DscError::NotImplemented(t!("dscresources.commandResource.syntheticWhatIf").to_string())); + return Err(DscError::NotImplemented( + t!("dscresources.commandResource.syntheticWhatIf").to_string(), + )); } let Some(get) = &resource.get else { @@ -147,25 +221,48 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te let args = process_args(get.args.as_ref(), desired, resource_type); let command_input = get_command_input(get.input.as_ref(), desired)?; - info!("{}", t!("dscresources.commandResource.setGetCurrent", resource = &resource.resource_type, executable = &get.executable)); - let (exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?; + info!( + "{}", + t!( + "dscresources.commandResource.setGetCurrent", + resource = &resource.resource_type, + executable = &get.executable + ) + ); + let (exit_code, stdout, stderr) = invoke_command( + &get.executable, + args, + command_input.stdin.as_deref(), + Some(cwd), + command_input.env, + resource.exit_codes.as_ref(), + )?; if resource.kind == Some(Kind::Resource) { - debug!("{}", t!("dscresources.commandResource.setVerifyGet", resource = &resource.resource_type, executable = &get.executable)); + debug!( + "{}", + t!( + "dscresources.commandResource.setVerifyGet", + resource = &resource.resource_type, + executable = &get.executable + ) + ); verify_json(resource, cwd, &stdout)?; } let pre_state_value: Value = if exit_code == 0 { serde_json::from_str(&stdout)? - } - else { + } else { return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); }; let mut pre_state = if pre_state_value.is_object() { let mut pre_state_map: Map = serde_json::from_value(pre_state_value)?; // if the resource is an adapter, then the `get` will return a `result`, but a full `set` expects the before state to be `resources` - if resource.kind == Some(Kind::Adapter) && pre_state_map.contains_key("result") && !pre_state_map.contains_key("resources") { + if resource.kind == Some(Kind::Adapter) + && pre_state_map.contains_key("result") + && !pre_state_map.contains_key("resources") + { pre_state_map.insert("resources".to_string(), pre_state_map["result"].clone()); pre_state_map.remove("result"); } @@ -180,52 +277,86 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te match &set.input { Some(InputKind::Env) => { env = Some(json_to_hashmap(desired)?); - }, + } Some(InputKind::Stdin) => { input_desired = Some(desired); - }, + } None => { // leave input as none - }, + } } - info!("Invoking {} '{}' using '{}'", operation_type, &resource.resource_type, &set.executable); - let (exit_code, stdout, stderr) = invoke_command(&set.executable, args, input_desired, Some(cwd), env, resource.exit_codes.as_ref())?; + info!( + "Invoking {} '{}' using '{}'", + operation_type, &resource.resource_type, &set.executable + ); + let (exit_code, stdout, stderr) = invoke_command( + &set.executable, + args, + input_desired, + Some(cwd), + env, + resource.exit_codes.as_ref(), + )?; match set.returns { Some(ReturnKind::State) => { - if resource.kind == Some(Kind::Resource) { - debug!("{}", t!("dscresources.commandResource.setVerifyOutput", operation = operation_type, resource = &resource.resource_type, executable = &set.executable)); + debug!( + "{}", + t!( + "dscresources.commandResource.setVerifyOutput", + operation = operation_type, + resource = &resource.resource_type, + executable = &set.executable + ) + ); verify_json(resource, cwd, &stdout)?; } - let actual_value: Value = match serde_json::from_str(&stdout){ - Result::Ok(r) => {r}, + let actual_value: Value = match serde_json::from_str(&stdout) { + Result::Ok(r) => r, Result::Err(err) => { - return Err(DscError::Operation(t!("dscresources.commandResource.failedParseJson", executable = &get.executable, stdout = stdout, stderr = stderr, err = err).to_string())) + return Err(DscError::Operation( + t!( + "dscresources.commandResource.failedParseJson", + executable = &get.executable, + stdout = stdout, + stderr = stderr, + err = err + ) + .to_string(), + )) } }; // for changed_properties, we compare post state to pre state - let diff_properties = get_diff( &actual_value, &pre_state); + let diff_properties = get_diff(&actual_value, &pre_state); pre_state = redact(&pre_state); - Ok(SetResult::Resource(ResourceSetResponse{ + Ok(SetResult::Resource(ResourceSetResponse { before_state: pre_state, after_state: actual_value, changed_properties: Some(diff_properties), })) - }, + } Some(ReturnKind::StateAndDiff) => { // command should be returning actual state as a JSON line and a list of properties that differ as separate JSON line let mut lines = stdout.lines(); let Some(actual_line) = lines.next() else { - return Err(DscError::Command(resource.resource_type.clone(), exit_code, t!("dscresources.commandResource.setUnexpectedOutput").to_string())); + return Err(DscError::Command( + resource.resource_type.clone(), + exit_code, + t!("dscresources.commandResource.setUnexpectedOutput").to_string(), + )); }; let actual_value: Value = serde_json::from_str(actual_line)?; // TODO: need schema for diff_properties to validate against let Some(diff_line) = lines.next() else { - return Err(DscError::Command(resource.resource_type.clone(), exit_code, t!("dscresources.commandResource.setUnexpectedDiff").to_string())); + return Err(DscError::Command( + resource.resource_type.clone(), + exit_code, + t!("dscresources.commandResource.setUnexpectedDiff").to_string(), + )); }; let diff_properties: Vec = serde_json::from_str(diff_line)?; Ok(SetResult::Resource(ResourceSetResponse { @@ -233,7 +364,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te after_state: actual_value, changed_properties: Some(diff_properties), })) - }, + } None => { // perform a get and compare the result to the expected state let get_result = invoke_get(resource, cwd, desired, target_resource)?; @@ -245,19 +376,17 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te result_array.push(serde_json::to_value(result)?); } Value::from(result_array) - }, - GetResult::Resource(response) => { - response.actual_state } + GetResult::Resource(response) => response.actual_state, }; - let diff_properties = get_diff( &actual_state, &pre_state); + let diff_properties = get_diff(&actual_state, &pre_state); pre_state = redact(&pre_state); Ok(SetResult::Resource(ResourceSetResponse { before_state: pre_state, after_state: actual_state, changed_properties: Some(diff_properties), })) - }, + } } } @@ -271,10 +400,28 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te /// # Errors /// /// Error is returned if the underlying command returns a non-zero exit code. -pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str, target_resource: Option<&str>) -> Result { - debug!("{}", t!("dscresources.commandResource.invokeTest", resource = &resource.resource_type)); +#[allow(clippy::too_many_lines)] +pub fn invoke_test( + resource: &ResourceManifest, + cwd: &str, + expected: &str, + target_resource: Option<&str>, +) -> Result { + debug!( + "{}", + t!( + "dscresources.commandResource.invokeTest", + resource = &resource.resource_type + ) + ); let Some(test) = &resource.test else { - info!("{}", t!("dscresources.commandResource.testSyntheticTest", resource = &resource.resource_type)); + info!( + "{}", + t!( + "dscresources.commandResource.testSyntheticTest", + resource = &resource.resource_type + ) + ); return invoke_synthetic_test(resource, cwd, expected, target_resource); }; @@ -287,11 +434,32 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str, targe let args = process_args(test.args.as_ref(), expected, resource_type); let command_input = get_command_input(test.input.as_ref(), expected)?; - info!("{}", t!("dscresources.commandResource.invokeTestUsing", resource = &resource.resource_type, executable = &test.executable)); - let (exit_code, stdout, stderr) = invoke_command(&test.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?; + info!( + "{}", + t!( + "dscresources.commandResource.invokeTestUsing", + resource = &resource.resource_type, + executable = &test.executable + ) + ); + let (exit_code, stdout, stderr) = invoke_command( + &test.executable, + args, + command_input.stdin.as_deref(), + Some(cwd), + command_input.env, + resource.exit_codes.as_ref(), + )?; if resource.kind == Some(Kind::Resource) { - debug!("{}", t!("dscresources.commandResource.testVerifyOutput", resource = &resource.resource_type, executable = &test.executable)); + debug!( + "{}", + t!( + "dscresources.commandResource.testVerifyOutput", + resource = &resource.resource_type, + executable = &test.executable + ) + ); verify_json(resource, cwd, &stdout)?; } @@ -304,10 +472,19 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str, targe let mut expected_value: Value = serde_json::from_str(expected)?; match test.returns { Some(ReturnKind::State) => { - let actual_value: Value = match serde_json::from_str(&stdout){ - Result::Ok(r) => {r}, + let actual_value: Value = match serde_json::from_str(&stdout) { + Result::Ok(r) => r, Result::Err(err) => { - return Err(DscError::Operation(t!("dscresources.commandResource.failedParseJson", executable = &test.executable, stdout = stdout, stderr = stderr, err = err).to_string())) + return Err(DscError::Operation( + t!( + "dscresources.commandResource.failedParseJson", + executable = &test.executable, + stdout = stdout, + stderr = stderr, + err = err + ) + .to_string(), + )) } }; let in_desired_state = get_desired_state(&actual_value)?; @@ -319,16 +496,24 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str, targe in_desired_state: in_desired_state.unwrap_or(diff_properties.is_empty()), diff_properties, })) - }, + } Some(ReturnKind::StateAndDiff) => { // command should be returning actual state as a JSON line and a list of properties that differ as separate JSON line let mut lines = stdout.lines(); let Some(actual_value) = lines.next() else { - return Err(DscError::Command(resource.resource_type.clone(), exit_code, t!("dscresources.commandResource.testNoActualState").to_string())); + return Err(DscError::Command( + resource.resource_type.clone(), + exit_code, + t!("dscresources.commandResource.testNoActualState").to_string(), + )); }; let actual_value: Value = serde_json::from_str(actual_value)?; let Some(diff_properties) = lines.next() else { - return Err(DscError::Command(resource.resource_type.clone(), exit_code, t!("dscresources.commandResource.testNoDiff").to_string())); + return Err(DscError::Command( + resource.resource_type.clone(), + exit_code, + t!("dscresources.commandResource.testNoDiff").to_string(), + )); }; let diff_properties: Vec = serde_json::from_str(diff_properties)?; expected_value = redact(&expected_value); @@ -339,7 +524,7 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str, targe in_desired_state: in_desired_state.unwrap_or(diff_properties.is_empty()), diff_properties, })) - }, + } None => { // perform a get and compare the result to the expected state let get_result = invoke_get(resource, cwd, expected, target_resource)?; @@ -350,12 +535,10 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str, targe result_array.push(serde_json::to_value(&result)?); } Value::from(result_array) - }, - GetResult::Resource(response) => { - response.actual_state } + GetResult::Resource(response) => response.actual_state, }; - let diff_properties = get_diff( &expected_value, &actual_state); + let diff_properties = get_diff(&expected_value, &actual_state); expected_value = redact(&expected_value); Ok(TestResult::Resource(ResourceTestResponse { desired_state: expected_value, @@ -363,7 +546,7 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str, targe in_desired_state: diff_properties.is_empty(), diff_properties, })) - }, + } } } @@ -374,13 +557,20 @@ fn get_desired_state(actual: &Value) -> Result, DscError> { if let Some(desired_state) = in_desired_state_value.as_bool() { in_desired_state = Some(desired_state); } else { - return Err(DscError::Operation(t!("dscresources.commandResource.inDesiredStateNotBool").to_string())); + return Err(DscError::Operation( + t!("dscresources.commandResource.inDesiredStateNotBool").to_string(), + )); } } Ok(in_desired_state) } -fn invoke_synthetic_test(resource: &ResourceManifest, cwd: &str, expected: &str, target_resource: Option<&str>) -> Result { +fn invoke_synthetic_test( + resource: &ResourceManifest, + cwd: &str, + expected: &str, + target_resource: Option<&str>, +) -> Result { let get_result = invoke_get(resource, cwd, expected, target_resource)?; let actual_state = match get_result { GetResult::Group(results) => { @@ -389,10 +579,8 @@ fn invoke_synthetic_test(resource: &ResourceManifest, cwd: &str, expected: &str, result_array.push(serde_json::to_value(&result)?); } Value::from(result_array) - }, - GetResult::Resource(response) => { - response.actual_state } + GetResult::Resource(response) => response.actual_state, }; let expected_value: Value = serde_json::from_str(expected)?; let diff_properties = get_diff(&expected_value, &actual_state); @@ -415,7 +603,12 @@ fn invoke_synthetic_test(resource: &ResourceManifest, cwd: &str, expected: &str, /// # Errors /// /// Error is returned if the underlying command returns a non-zero exit code. -pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str, target_resource: Option<&str>) -> Result<(), DscError> { +pub fn invoke_delete( + resource: &ResourceManifest, + cwd: &str, + filter: &str, + target_resource: Option<&str>, +) -> Result<(), DscError> { let Some(delete) = &resource.delete else { return Err(DscError::NotImplemented("delete".to_string())); }; @@ -429,8 +622,22 @@ pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str, targe let args = process_args(delete.args.as_ref(), filter, resource_type); let command_input = get_command_input(delete.input.as_ref(), filter)?; - info!("{}", t!("dscresources.commandResource.invokeDeleteUsing", resource = resource_type, executable = &delete.executable)); - let (_exit_code, _stdout, _stderr) = invoke_command(&delete.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?; + info!( + "{}", + t!( + "dscresources.commandResource.invokeDeleteUsing", + resource = resource_type, + executable = &delete.executable + ) + ); + let (_exit_code, _stdout, _stderr) = invoke_command( + &delete.executable, + args, + command_input.stdin.as_deref(), + Some(cwd), + command_input.env, + resource.exit_codes.as_ref(), + )?; Ok(()) } @@ -450,8 +657,20 @@ pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str, targe /// # Errors /// /// Error is returned if the underlying command returns a non-zero exit code. -pub fn invoke_validate(resource: &ResourceManifest, cwd: &str, config: &str, target_resource: Option<&str>) -> Result { - trace!("{}", t!("dscresources.commandResource.invokeValidateConfig", resource = &resource.resource_type, config = &config)); +pub fn invoke_validate( + resource: &ResourceManifest, + cwd: &str, + config: &str, + target_resource: Option<&str>, +) -> Result { + trace!( + "{}", + t!( + "dscresources.commandResource.invokeValidateConfig", + resource = &resource.resource_type, + config = &config + ) + ); // TODO: use schema to validate config if validate is not implemented let Some(validate) = resource.validate.as_ref() else { return Err(DscError::NotImplemented("validate".to_string())); @@ -464,8 +683,22 @@ pub fn invoke_validate(resource: &ResourceManifest, cwd: &str, config: &str, tar let args = process_args(validate.args.as_ref(), config, resource_type); let command_input = get_command_input(validate.input.as_ref(), config)?; - info!("{}", t!("dscresources.commandResource.invokeValidateUsing", resource = resource_type, executable = &validate.executable)); - let (_exit_code, stdout, _stderr) = invoke_command(&validate.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?; + info!( + "{}", + t!( + "dscresources.commandResource.invokeValidateUsing", + resource = resource_type, + executable = &validate.executable + ) + ); + let (_exit_code, stdout, _stderr) = invoke_command( + &validate.executable, + args, + command_input.stdin.as_deref(), + Some(cwd), + command_input.env, + resource.exit_codes.as_ref(), + )?; let result: ValidateResult = serde_json::from_str(&stdout)?; Ok(result) } @@ -486,13 +719,20 @@ pub fn get_schema(resource: &ResourceManifest, cwd: &str) -> Result { - let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, command.args.clone(), None, Some(cwd), None, resource.exit_codes.as_ref())?; + let (_exit_code, stdout, _stderr) = invoke_command( + &command.executable, + command.args.clone(), + None, + Some(cwd), + None, + resource.exit_codes.as_ref(), + )?; Ok(stdout) - }, + } SchemaKind::Embedded(ref schema) => { let json = serde_json::to_string(schema)?; Ok(json) - }, + } } } @@ -511,11 +751,22 @@ pub fn get_schema(resource: &ResourceManifest, cwd: &str) -> Result, target_resource: Option<&str>) -> Result { +pub fn invoke_export( + resource: &ResourceManifest, + cwd: &str, + input: Option<&str>, + target_resource: Option<&str>, +) -> Result { let Some(export) = resource.export.as_ref() else { // see if get is supported and use that instead if resource.get.is_some() { - info!("{}", t!("dscresources.commandResource.exportNotSupportedUsingGet", resource = &resource.resource_type)); + info!( + "{}", + t!( + "dscresources.commandResource.exportNotSupportedUsingGet", + resource = &resource.resource_type + ) + ); let get_result = invoke_get(resource, cwd, input.unwrap_or(""), target_resource)?; let mut instances: Vec = Vec::new(); match get_result { @@ -523,7 +774,7 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str> for result in group_response { instances.push(serde_json::to_value(result)?); } - }, + } GetResult::Resource(response) => { instances.push(response.actual_state); } @@ -533,7 +784,13 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str> }); } // if neither export nor get is supported, return an error - return Err(DscError::Operation(t!("dscresources.commandResource.exportNotSupported", resource = &resource.resource_type).to_string())) + return Err(DscError::Operation( + t!( + "dscresources.commandResource.exportNotSupported", + resource = &resource.resource_type + ) + .to_string(), + )); }; let mut command_input: CommandInput = CommandInput { env: None, stdin: None }; @@ -554,18 +811,40 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str> args = process_args(export.args.as_ref(), "", resource_type); } - let (_exit_code, stdout, stderr) = invoke_command(&export.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?; + let (_exit_code, stdout, stderr) = invoke_command( + &export.executable, + args, + command_input.stdin.as_deref(), + Some(cwd), + command_input.env, + resource.exit_codes.as_ref(), + )?; let mut instances: Vec = Vec::new(); - for line in stdout.lines() - { - let instance: Value = match serde_json::from_str(line){ - Result::Ok(r) => {r}, + for line in stdout.lines() { + let instance: Value = match serde_json::from_str(line) { + Result::Ok(r) => r, Result::Err(err) => { - return Err(DscError::Operation(t!("dscresources.commandResource.failedParseJson", executable = &export.executable, stdout = stdout, stderr = stderr, err = err).to_string())) + return Err(DscError::Operation( + t!( + "dscresources.commandResource.failedParseJson", + executable = &export.executable, + stdout = stdout, + stderr = stderr, + err = err + ) + .to_string(), + )) } }; if resource.kind == Some(Kind::Resource) { - debug!("{}", t!("dscresources.commandResource.exportVerifyOutput", resource = &resource.resource_type, executable = &export.executable)); + debug!( + "{}", + t!( + "dscresources.commandResource.exportVerifyOutput", + resource = &resource.resource_type, + executable = &export.executable + ) + ); verify_json(resource, cwd, line)?; } instances.push(instance); @@ -593,14 +872,34 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str> /// Error returned if the resource does not successfully resolve the input pub fn invoke_resolve(resource: &ResourceManifest, cwd: &str, input: &str) -> Result { let Some(resolve) = &resource.resolve else { - return Err(DscError::Operation(t!("dscresources.commandResource.resolveNotSupported", resource = &resource.resource_type).to_string())); + return Err(DscError::Operation( + t!( + "dscresources.commandResource.resolveNotSupported", + resource = &resource.resource_type + ) + .to_string(), + )); }; let args = process_args(resolve.args.as_ref(), input, &resource.resource_type); let command_input = get_command_input(resolve.input.as_ref(), input)?; - info!("{}", t!("dscresources.commandResource.invokeResolveUsing", resource = &resource.resource_type, executable = &resolve.executable)); - let (_exit_code, stdout, _stderr) = invoke_command(&resolve.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?; + info!( + "{}", + t!( + "dscresources.commandResource.invokeResolveUsing", + resource = &resource.resource_type, + executable = &resolve.executable + ) + ); + let (_exit_code, stdout, _stderr) = invoke_command( + &resolve.executable, + args, + command_input.stdin.as_deref(), + Some(cwd), + command_input.env, + resource.exit_codes.as_ref(), + )?; let result: ResolveResult = serde_json::from_str(&stdout)?; Ok(result) } @@ -620,11 +919,18 @@ pub fn invoke_resolve(resource: &ResourceManifest, cwd: &str, input: &str) -> Re /// /// Error is returned if the command fails to execute or stdin/stdout/stderr cannot be opened. /// -async fn run_process_async(executable: &str, args: Option>, input: Option<&str>, cwd: Option<&str>, env: Option>, exit_codes: Option<&HashMap>) -> Result<(i32, String, String), DscError> { - +#[allow(clippy::too_many_lines)] +async fn run_process_async( + executable: &str, + args: Option>, + input: Option<&str>, + cwd: Option<&str>, + env: Option>, + exit_codes: Option<&HashMap>, +) -> Result<(i32, String, String), DscError> { // use somewhat large initial buffer to avoid early string reallocations; // the value is based on list result of largest of built-in adapters - WMI adapter ~500KB - const INITIAL_BUFFER_CAPACITY: usize = 1024*1024; + const INITIAL_BUFFER_CAPACITY: usize = 1024 * 1024; let mut command = Command::new(executable); if input.is_some() { @@ -648,16 +954,20 @@ async fn run_process_async(executable: &str, args: Option>, input: O let mut child = match command.spawn() { Ok(c) => c, - Err(e) => { - return Err(DscError::CommandOperation(e.to_string(), executable.to_string())) - } + Err(e) => return Err(DscError::CommandOperation(e.to_string(), executable.to_string())), }; let Some(stdout) = child.stdout.take() else { - return Err(DscError::CommandOperation(t!("dscresources.commandResource.processChildStdout").to_string(), executable.to_string())); + return Err(DscError::CommandOperation( + t!("dscresources.commandResource.processChildStdout").to_string(), + executable.to_string(), + )); }; let Some(stderr) = child.stderr.take() else { - return Err(DscError::CommandOperation(t!("dscresources.commandResource.processChildStderr").to_string(), executable.to_string())); + return Err(DscError::CommandOperation( + t!("dscresources.commandResource.processChildStderr").to_string(), + executable.to_string(), + )); }; let mut stdout_reader = BufReader::new(stdout).lines(); let mut stderr_reader = BufReader::new(stderr).lines(); @@ -665,21 +975,28 @@ async fn run_process_async(executable: &str, args: Option>, input: O if let Some(input) = input { trace!("Writing to command STDIN: {input}"); let Some(mut stdin) = child.stdin.take() else { - return Err(DscError::CommandOperation(t!("dscresources.commandResource.processChildStdin").to_string(), executable.to_string())); + return Err(DscError::CommandOperation( + t!("dscresources.commandResource.processChildStdin").to_string(), + executable.to_string(), + )); }; if stdin.write_all(input.as_bytes()).await.is_err() { - return Err(DscError::CommandOperation(t!("dscresources.commandResource.processWriteStdin").to_string(), executable.to_string())); + return Err(DscError::CommandOperation( + t!("dscresources.commandResource.processWriteStdin").to_string(), + executable.to_string(), + )); } drop(stdin); } let Some(child_id) = child.id() else { - return Err(DscError::CommandOperation(t!("dscresources.commandResource.processChildId").to_string(), executable.to_string())); + return Err(DscError::CommandOperation( + t!("dscresources.commandResource.processChildId").to_string(), + executable.to_string(), + )); }; - let child_task = tokio::spawn(async move { - child.wait().await - }); + let child_task = tokio::spawn(async move { child.wait().await }); let stdout_task = tokio::spawn(async move { let mut stdout_result = String::with_capacity(INITIAL_BUFFER_CAPACITY); @@ -707,12 +1024,24 @@ async fn run_process_async(executable: &str, args: Option>, input: O let stderr_result = stderr_task.await.unwrap(); if let Some(code) = exit_code { - debug!("{}", t!("dscresources.commandResource.processChildExit", executable = executable, id = child_id, code = code)); + debug!( + "{}", + t!( + "dscresources.commandResource.processChildExit", + executable = executable, + id = child_id, + code = code + ) + ); if code != 0 { if let Some(exit_codes) = exit_codes { if let Some(error_message) = exit_codes.get(&code) { - return Err(DscError::CommandExitFromManifest(executable.to_string(), code, error_message.to_string())); + return Err(DscError::CommandExitFromManifest( + executable.to_string(), + code, + error_message.to_string(), + )); } } return Err(DscError::Command(executable.to_string(), code, stderr_result)); @@ -720,8 +1049,18 @@ async fn run_process_async(executable: &str, args: Option>, input: O Ok((code, stdout_result, stderr_result)) } else { - debug!("{}", t!("dscresources.commandResource.processChildTerminated", executable = executable, id = child_id)); - Err(DscError::CommandOperation(t!("dscresources.commandResource.processTerminated").to_string(), executable.to_string())) + debug!( + "{}", + t!( + "dscresources.commandResource.processChildTerminated", + executable = executable, + id = child_id + ) + ); + Err(DscError::CommandOperation( + t!("dscresources.commandResource.processTerminated").to_string(), + executable.to_string(), + )) } } @@ -745,25 +1084,42 @@ async fn run_process_async(executable: &str, args: Option>, input: O /// Will panic if tokio runtime can't be created. /// #[allow(clippy::implicit_hasher)] -pub fn invoke_command(executable: &str, args: Option>, input: Option<&str>, cwd: Option<&str>, env: Option>, exit_codes: Option<&HashMap>) -> Result<(i32, String, String), DscError> { - tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on( - async { - trace!("{}", t!("dscresources.commandResource.commandInvoke", executable = executable, args = args : {:?})); +pub fn invoke_command( + executable: &str, + args: Option>, + input: Option<&str>, + cwd: Option<&str>, + env: Option>, + exit_codes: Option<&HashMap>, +) -> Result<(i32, String, String), DscError> { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + trace!( + "{}", + t!("dscresources.commandResource.commandInvoke", executable = executable, args = args : {:?}) + ); if let Some(cwd) = cwd { trace!("{}", t!("dscresources.commandResource.commandCwd", cwd = cwd)); } match run_process_async(executable, args, input, cwd, env, exit_codes).await { - Ok((code, stdout, stderr)) => { - Ok((code, stdout, stderr)) - }, + Ok((code, stdout, stderr)) => Ok((code, stdout, stderr)), Err(err) => { - error!("{}", t!("dscresources.commandResource.runProcessError", executable = executable, error = err)); + error!( + "{}", + t!( + "dscresources.commandResource.runProcessError", + executable = executable, + error = err + ) + ); Err(err) } } - } - ) + }) } /// Process the arguments for a command resource. @@ -787,15 +1143,18 @@ pub fn process_args(args: Option<&Vec>, input: &str, resource_type: &st match arg { ArgKind::String(s) => { processed_args.push(s.clone()); - }, - ArgKind::Json { json_input_arg, mandatory } => { + } + ArgKind::Json { + json_input_arg, + mandatory, + } => { if input.is_empty() && *mandatory != Some(true) { continue; } processed_args.push(json_input_arg.clone()); processed_args.push(input.to_string()); - }, + } ArgKind::ResourceType { resource_type_arg } => { processed_args.push(resource_type_arg.clone()); processed_args.push(resource_type.to_string()); @@ -818,26 +1177,28 @@ fn get_command_input(input_kind: Option<&InputKind>, input: &str) -> Result { debug!("{}", t!("dscresources.commandResource.parseAsEnvVars")); env = Some(json_to_hashmap(input)?); - }, + } Some(InputKind::Stdin) => { debug!("{}", t!("dscresources.commandResource.parseAsStdin")); stdin = Some(input.to_string()); - }, + } None => { debug!("{}", t!("dscresources.commandResource.noInput")); // leave input as none - }, + } } - Ok(CommandInput { - env, - stdin, - }) + Ok(CommandInput { env, stdin }) } fn verify_json(resource: &ResourceManifest, cwd: &str, json: &str) -> Result<(), DscError> { - - debug!("{}", t!("dscresources.commandResource.verifyJson", resource = resource.resource_type)); + debug!( + "{}", + t!( + "dscresources.commandResource.verifyJson", + resource = resource.resource_type + ) + ); // see if resource implements validate if resource.validate.is_some() { @@ -847,7 +1208,9 @@ fn verify_json(resource: &ResourceManifest, cwd: &str, json: &str) -> Result<(), return Ok(()); } - return Err(DscError::Validation(t!("dscresources.commandResource.resourceInvalidJson").to_string())); + return Err(DscError::Validation( + t!("dscresources.commandResource.resourceInvalidJson").to_string(), + )); } // otherwise, use schema validation @@ -857,7 +1220,7 @@ fn verify_json(resource: &ResourceManifest, cwd: &str, json: &str) -> Result<(), Ok(schema) => schema, Err(e) => { return Err(DscError::Schema(e.to_string())); - }, + } }; let json: Value = serde_json::from_str(json)?; if let Err(err) = compiled_schema.validate(&json) { @@ -875,13 +1238,13 @@ fn json_to_hashmap(json: &str) -> Result, DscError> { match value { Value::String(s) => { map.insert(key, s); - }, + } Value::Bool(b) => { map.insert(key, b.to_string()); - }, + } Value::Number(n) => { map.insert(key, n.to_string()); - }, + } Value::Array(a) => { // only array of number or strings is supported let mut array = Vec::new(); @@ -889,23 +1252,27 @@ fn json_to_hashmap(json: &str) -> Result, DscError> { match v { Value::String(s) => { array.push(s); - }, + } Value::Number(n) => { array.push(n.to_string()); - }, + } _ => { - return Err(DscError::Operation(t!("dscresources.commandResource.invalidArrayKey", key = key).to_string())); - }, + return Err(DscError::Operation( + t!("dscresources.commandResource.invalidArrayKey", key = key).to_string(), + )); + } } } map.insert(key, array.join(",")); - }, + } Value::Null => { // ignore null values - }, + } Value::Object(_) => { - return Err(DscError::Operation(t!("dscresources.commandResource.invalidKey", key = key).to_string())); - }, + return Err(DscError::Operation( + t!("dscresources.commandResource.invalidKey", key = key).to_string(), + )); + } } } } @@ -919,44 +1286,48 @@ fn json_to_hashmap(json: &str) -> Result, DscError> { /// * `process_name` - The name of the process /// * `process_id` - The ID of the process /// * `trace_line` - The stderr line from the process -pub fn log_stderr_line<'a>(process_id: &u32, trace_line: &'a str) -> &'a str -{ - if !trace_line.is_empty() - { +pub fn log_stderr_line<'a>(process_id: &u32, trace_line: &'a str) -> &'a str { + if !trace_line.is_empty() { if let Ok(trace_object) = serde_json::from_str::(trace_line) { let mut include_target = trace_object.level == TraceLevel::Debug || trace_object.level == TraceLevel::Trace; - let target = if let Some(t) = trace_object.target.as_deref() { t } else { + let target = if let Some(t) = trace_object.target.as_deref() { + t + } else { include_target = false; "" }; - let line_number = if let Some(l) = trace_object.line_number { l } else { + let line_number = if let Some(l) = trace_object.line_number { + l + } else { include_target = false; 0 }; let trace_message = if include_target { - format!("PID {process_id}: {target}: {line_number}: {}", trace_object.fields.message) + format!( + "PID {process_id}: {target}: {line_number}: {}", + trace_object.fields.message + ) } else { format!("PID {process_id}: {}", trace_object.fields.message) }; match trace_object.level { TraceLevel::Error => { error!(trace_message); - }, + } TraceLevel::Warn => { warn!(trace_message); - }, + } TraceLevel::Info => { info!(trace_message); - }, + } TraceLevel::Debug => { debug!(trace_message); - }, + } TraceLevel::Trace => { trace!(trace_message); - }, + } } - } - else if let Ok(json_obj) = serde_json::from_str::(trace_line) { + } else if let Ok(json_obj) = serde_json::from_str::(trace_line) { if let Some(msg) = json_obj.get("error") { error!("PID {process_id}: {}", msg.as_str().unwrap_or_default()); } else if let Some(msg) = json_obj.get("warn") { diff --git a/dsc_lib/src/dscresources/dscresource.rs b/dsc_lib/src/dscresources/dscresource.rs index 07d2b470e..ff15fb88b 100644 --- a/dsc_lib/src/dscresources/dscresource.rs +++ b/dsc_lib/src/dscresources/dscresource.rs @@ -1,8 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::{configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}, context::ProcessMode, parameters::{SECURE_VALUE_REDACTED, is_secure_value}}, dscresources::resource_manifest::{AdapterInputKind, Kind}}; use crate::dscresources::invoke_result::{ResourceGetResponse, ResourceSetResponse}; +use crate::{ + configure::{ + config_doc::{Configuration, ExecutionKind, Resource}, + context::ProcessMode, + parameters::{is_secure_value, SECURE_VALUE_REDACTED}, + Configurator, + }, + dscresources::resource_manifest::{AdapterInputKind, Kind}, +}; use dscerror::DscError; use jsonschema::Validator; use rust_i18n::t; @@ -13,21 +21,18 @@ use std::collections::HashMap; use tracing::{debug, info, trace}; use super::{ - command_resource, - dscerror, + command_resource, dscerror, invoke_result::{ - ExportResult, GetResult, ResolveResult, ResourceTestResponse, SetResult, TestResult, ValidateResult + ExportResult, GetResult, ResolveResult, ResourceTestResponse, SetResult, TestResult, ValidateResult, }, - resource_manifest::{ - import_manifest, ResourceManifest - } + resource_manifest::{import_manifest, ResourceManifest}, }; #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct DscResource { /// The namespaced name of the resource. - #[serde(rename="type")] + #[serde(rename = "type")] pub type_name: String, /// The kind of resource. pub kind: Kind, @@ -42,14 +47,14 @@ pub struct DscResource { // The directory path to the resource. pub directory: String, /// The implementation of the resource. - #[serde(rename="implementedAs")] + #[serde(rename = "implementedAs")] pub implemented_as: ImplementedAs, /// The author of the resource. pub author: Option, /// The properties of the resource. pub properties: Vec, /// The required resource adapter for the resource. - #[serde(rename="requireAdapter")] + #[serde(rename = "requireAdapter")] pub require_adapter: Option, /// The target resource for the resource adapter. pub target_resource: Option, @@ -143,21 +148,67 @@ impl DscResource { let result = configurator.invoke_get()?; let GetResult::Resource(ref resource_result) = result.results[0].result else { - return Err(DscError::Operation(t!("dscresources.dscresource.invokeReturnedWrongResult", operation = "get", resource = self.type_name).to_string())); + return Err(DscError::Operation( + t!( + "dscresources.dscresource.invokeReturnedWrongResult", + operation = "get", + resource = self.type_name + ) + .to_string(), + )); }; - let properties = resource_result.actual_state - .as_object().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "actualState", property_type = "object").to_string()))? - .get("result").ok_or(DscError::Operation(t!("dscresources.dscresource.propertyNotFound", property = "result").to_string()))? - .as_array().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "result", property_type = "array").to_string()))?[0] - .as_object().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "result", property_type = "object").to_string()))? - .get("properties").ok_or(DscError::Operation(t!("dscresources.dscresource.propertyNotFound", property = "properties").to_string()))?.clone(); + let properties = resource_result + .actual_state + .as_object() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "actualState", + property_type = "object" + ) + .to_string(), + ))? + .get("result") + .ok_or(DscError::Operation( + t!("dscresources.dscresource.propertyNotFound", property = "result").to_string(), + ))? + .as_array() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "result", + property_type = "array" + ) + .to_string(), + ))?[0] + .as_object() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "result", + property_type = "object" + ) + .to_string(), + ))? + .get("properties") + .ok_or(DscError::Operation( + t!("dscresources.dscresource.propertyNotFound", property = "properties").to_string(), + ))? + .clone(); let get_result = GetResult::Resource(ResourceGetResponse { actual_state: properties.clone(), }); Ok(get_result) } - fn invoke_set_with_adapter(&self, adapter: &str, resource_name: &str, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result { + fn invoke_set_with_adapter( + &self, + adapter: &str, + resource_name: &str, + desired: &str, + skip_test: bool, + execution_type: &ExecutionKind, + ) -> Result { let mut configurator = self.clone().create_config_for_adapter(adapter, desired)?; let mut adapter = Self::get_adapter_resource(&mut configurator, adapter)?; if get_adapter_input_kind(&adapter)? == AdapterInputKind::Single { @@ -167,20 +218,91 @@ impl DscResource { let result = configurator.invoke_set(false)?; let SetResult::Resource(ref resource_result) = result.results[0].result else { - return Err(DscError::Operation(t!("dscresources.dscresource.invokeReturnedWrongResult", operation = "set", resource = self.type_name).to_string())); + return Err(DscError::Operation( + t!( + "dscresources.dscresource.invokeReturnedWrongResult", + operation = "set", + resource = self.type_name + ) + .to_string(), + )); }; - let before_state = resource_result.before_state - .as_object().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "beforeState", property_type = "object").to_string()))? - .get("resources").ok_or(DscError::Operation(t!("dscresources.dscresource.propertyNotFound", property = "resources").to_string()))? - .as_array().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "result", property_type = "array").to_string()))?[0] - .as_object().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "result", property_type = "object").to_string()))? - .get("properties").ok_or(DscError::Operation(t!("dscresources.dscresource.propertyNotFound", property = "properties").to_string()))?.clone(); - let after_state = resource_result.after_state - .as_object().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "afterState", property_type = "object").to_string()))? - .get("result").ok_or(DscError::Operation(t!("dscresources.dscresource.propertyNotFound", property = "result").to_string()))? - .as_array().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "result", property_type = "array").to_string()))?[0] - .as_object().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "result", property_type = "object").to_string()))? - .get("properties").ok_or(DscError::Operation(t!("dscresources.dscresource.propertyNotFound", property = "properties").to_string()))?.clone(); + let before_state = resource_result + .before_state + .as_object() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "beforeState", + property_type = "object" + ) + .to_string(), + ))? + .get("resources") + .ok_or(DscError::Operation( + t!("dscresources.dscresource.propertyNotFound", property = "resources").to_string(), + ))? + .as_array() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "result", + property_type = "array" + ) + .to_string(), + ))?[0] + .as_object() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "result", + property_type = "object" + ) + .to_string(), + ))? + .get("properties") + .ok_or(DscError::Operation( + t!("dscresources.dscresource.propertyNotFound", property = "properties").to_string(), + ))? + .clone(); + let after_state = resource_result + .after_state + .as_object() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "afterState", + property_type = "object" + ) + .to_string(), + ))? + .get("result") + .ok_or(DscError::Operation( + t!("dscresources.dscresource.propertyNotFound", property = "result").to_string(), + ))? + .as_array() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "result", + property_type = "array" + ) + .to_string(), + ))?[0] + .as_object() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "result", + property_type = "object" + ) + .to_string(), + ))? + .get("properties") + .ok_or(DscError::Operation( + t!("dscresources.dscresource.propertyNotFound", property = "properties").to_string(), + ))? + .clone(); let diff = get_diff(&before_state, &after_state); let set_result = SetResult::Resource(ResourceSetResponse { before_state: before_state.clone(), @@ -190,7 +312,13 @@ impl DscResource { Ok(set_result) } - fn invoke_test_with_adapter(&self, adapter: &str, resource_name: &str, expected: &str) -> Result { + #[allow(clippy::too_many_lines)] + fn invoke_test_with_adapter( + &self, + adapter: &str, + resource_name: &str, + expected: &str, + ) -> Result { let mut configurator = self.clone().create_config_for_adapter(adapter, expected)?; let mut adapter = Self::get_adapter_resource(&mut configurator, adapter)?; if get_adapter_input_kind(&adapter)? == AdapterInputKind::Single { @@ -200,20 +328,91 @@ impl DscResource { let result = configurator.invoke_test()?; let TestResult::Resource(ref resource_result) = result.results[0].result else { - return Err(DscError::Operation(t!("dscresources.dscresource.invokeReturnedWrongResult", operation = "test", resource = self.type_name).to_string())); + return Err(DscError::Operation( + t!( + "dscresources.dscresource.invokeReturnedWrongResult", + operation = "test", + resource = self.type_name + ) + .to_string(), + )); }; - let desired_state = resource_result.desired_state - .as_object().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "desiredState", property_type = "object").to_string()))? - .get("resources").ok_or(DscError::Operation(t!("dscresources.dscresource.propertyNotFound", property = "resources").to_string()))? - .as_array().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "resources", property_type = "array").to_string()))?[0] - .as_object().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "resources", property_type = "object").to_string()))? - .get("properties").ok_or(DscError::Operation(t!("dscresources.dscresource.propertyNotFound", property = "properties").to_string()))?.clone(); - let actual_state = resource_result.actual_state - .as_object().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "actualState", property_type = "object").to_string()))? - .get("result").ok_or(DscError::Operation(t!("dscresources.dscresource.propertyNotFound", property = "result").to_string()))? - .as_array().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "result", property_type = "array").to_string()))?[0] - .as_object().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "result", property_type = "object").to_string()))? - .get("properties").ok_or(DscError::Operation(t!("dscresources.dscresource.propertyNotFound", property = "properties").to_string()))?.clone(); + let desired_state = resource_result + .desired_state + .as_object() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "desiredState", + property_type = "object" + ) + .to_string(), + ))? + .get("resources") + .ok_or(DscError::Operation( + t!("dscresources.dscresource.propertyNotFound", property = "resources").to_string(), + ))? + .as_array() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "resources", + property_type = "array" + ) + .to_string(), + ))?[0] + .as_object() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "resources", + property_type = "object" + ) + .to_string(), + ))? + .get("properties") + .ok_or(DscError::Operation( + t!("dscresources.dscresource.propertyNotFound", property = "properties").to_string(), + ))? + .clone(); + let actual_state = resource_result + .actual_state + .as_object() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "actualState", + property_type = "object" + ) + .to_string(), + ))? + .get("result") + .ok_or(DscError::Operation( + t!("dscresources.dscresource.propertyNotFound", property = "result").to_string(), + ))? + .as_array() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "result", + property_type = "array" + ) + .to_string(), + ))?[0] + .as_object() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "result", + property_type = "object" + ) + .to_string(), + ))? + .get("properties") + .ok_or(DscError::Operation( + t!("dscresources.dscresource.propertyNotFound", property = "properties").to_string(), + ))? + .clone(); let diff_properties = get_diff(&desired_state, &actual_state); let test_result = TestResult::Resource(ResourceTestResponse { desired_state, @@ -232,7 +431,13 @@ impl DscResource { adapter.target_resource = Some(resource_name.to_string()); return adapter.delete(filter); } - return Err(DscError::NotSupported(t!("dscresources.dscresource.adapterDoesNotSupportDelete", adapter = adapter.type_name).to_string())); + return Err(DscError::NotSupported( + t!( + "dscresources.dscresource.adapterDoesNotSupportDelete", + adapter = adapter.type_name + ) + .to_string(), + )); } configurator.invoke_set(false)?; @@ -249,7 +454,13 @@ impl DscResource { let result = configurator.invoke_export()?; let Some(configuration) = result.result else { - return Err(DscError::Operation(t!("dscresources.dscresource.invokeExportReturnedNoResult", resource = self.type_name).to_string())); + return Err(DscError::Operation( + t!( + "dscresources.dscresource.invokeExportReturnedNoResult", + resource = self.type_name + ) + .to_string(), + )); }; let mut export_result = ExportResult { actual_state: Vec::new(), @@ -257,11 +468,28 @@ impl DscResource { debug!("Export result: {}", serde_json::to_string(&configuration)?); for resource in configuration.resources { let Some(properties) = resource.properties else { - return Err(DscError::Operation(t!("dscresources.dscresource.invokeExportReturnedNoResult", resource = self.type_name).to_string())); + return Err(DscError::Operation( + t!( + "dscresources.dscresource.invokeExportReturnedNoResult", + resource = self.type_name + ) + .to_string(), + )); }; let results = properties - .get("result").ok_or(DscError::Operation(t!("dscresources.dscresource.propertyNotFound", property = "result").to_string()))? - .as_array().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "result", property_type = "array").to_string()))?; + .get("result") + .ok_or(DscError::Operation( + t!("dscresources.dscresource.propertyNotFound", property = "result").to_string(), + ))? + .as_array() + .ok_or(DscError::Operation( + t!( + "dscresources.dscresource.propertyIncorrectType", + property = "result", + property_type = "array" + ) + .to_string(), + ))?; for result in results { export_result.actual_state.push(serde_json::to_value(result.clone())?); } @@ -273,7 +501,9 @@ impl DscResource { if let Some(adapter_resource) = configurator.discovery().find_resource(adapter, None) { return Ok(adapter_resource.clone()); } - Err(DscError::Operation(t!("dscresources.dscresource.adapterResourceNotFound", adapter = adapter).to_string())) + Err(DscError::Operation( + t!("dscresources.dscresource.adapterResourceNotFound", adapter = adapter).to_string(), + )) } } @@ -373,55 +603,76 @@ pub trait Invoke { impl Invoke for DscResource { fn get(&self, filter: &str) -> Result { - debug!("{}", t!("dscresources.dscresource.invokeGet", resource = self.type_name)); + debug!( + "{}", + t!("dscresources.dscresource.invokeGet", resource = self.type_name) + ); if let Some(adapter) = &self.require_adapter { return self.invoke_get_with_adapter(adapter, &self.type_name, filter); } match &self.implemented_as { - ImplementedAs::Custom(_custom) => { - Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) - }, + ImplementedAs::Custom(_custom) => Err(DscError::NotImplemented( + t!("dscresources.dscresource.customResourceNotSupported").to_string(), + )), ImplementedAs::Command => { let Some(manifest) = &self.manifest else { return Err(DscError::MissingManifest(self.type_name.clone())); }; let resource_manifest = import_manifest(manifest.clone())?; - command_resource::invoke_get(&resource_manifest, &self.directory, filter, self.target_resource.as_deref()) - }, + command_resource::invoke_get( + &resource_manifest, + &self.directory, + filter, + self.target_resource.as_deref(), + ) + } } } fn set(&self, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result { - debug!("{}", t!("dscresources.dscresource.invokeSet", resource = self.type_name)); + debug!( + "{}", + t!("dscresources.dscresource.invokeSet", resource = self.type_name) + ); if let Some(adapter) = &self.require_adapter { return self.invoke_set_with_adapter(adapter, &self.type_name, desired, skip_test, execution_type); } match &self.implemented_as { - ImplementedAs::Custom(_custom) => { - Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) - }, + ImplementedAs::Custom(_custom) => Err(DscError::NotImplemented( + t!("dscresources.dscresource.customResourceNotSupported").to_string(), + )), ImplementedAs::Command => { let Some(manifest) = &self.manifest else { return Err(DscError::MissingManifest(self.type_name.clone())); }; let resource_manifest = import_manifest(manifest.clone())?; - command_resource::invoke_set(&resource_manifest, &self.directory, desired, skip_test, execution_type, self.target_resource.as_deref()) - }, + command_resource::invoke_set( + &resource_manifest, + &self.directory, + desired, + skip_test, + execution_type, + self.target_resource.as_deref(), + ) + } } } fn test(&self, expected: &str) -> Result { - debug!("{}", t!("dscresources.dscresource.invokeTest", resource = self.type_name)); + debug!( + "{}", + t!("dscresources.dscresource.invokeTest", resource = self.type_name) + ); if let Some(adapter) = &self.require_adapter { return self.invoke_test_with_adapter(adapter, &self.type_name, expected); } match &self.implemented_as { - ImplementedAs::Custom(_custom) => { - Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) - }, + ImplementedAs::Custom(_custom) => Err(DscError::NotImplemented( + t!("dscresources.dscresource.customResourceNotSupported").to_string(), + )), ImplementedAs::Command => { let Some(manifest) = &self.manifest else { return Err(DscError::MissingManifest(self.type_name.clone())); @@ -439,12 +690,10 @@ impl Invoke for DscResource { result_array.push(serde_json::to_value(result)?); } Value::from(result_array) - }, - GetResult::Resource(response) => { - response.actual_state } + GetResult::Resource(response) => response.actual_state, }; - let diff_properties = get_diff( &desired_state, &actual_state); + let diff_properties = get_diff(&desired_state, &actual_state); desired_state = redact(&desired_state); let test_result = TestResult::Resource(ResourceTestResponse { desired_state, @@ -453,76 +702,114 @@ impl Invoke for DscResource { diff_properties, }); Ok(test_result) + } else { + command_resource::invoke_test( + &resource_manifest, + &self.directory, + expected, + self.target_resource.as_deref(), + ) } - else { - command_resource::invoke_test(&resource_manifest, &self.directory, expected, self.target_resource.as_deref()) - } - }, + } } } fn delete(&self, filter: &str) -> Result<(), DscError> { - debug!("{}", t!("dscresources.dscresource.invokeDelete", resource = self.type_name)); + debug!( + "{}", + t!("dscresources.dscresource.invokeDelete", resource = self.type_name) + ); if let Some(adapter) = &self.require_adapter { return self.invoke_delete_with_adapter(adapter, &self.type_name, filter); } match &self.implemented_as { - ImplementedAs::Custom(_custom) => { - Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) - }, + ImplementedAs::Custom(_custom) => Err(DscError::NotImplemented( + t!("dscresources.dscresource.customResourceNotSupported").to_string(), + )), ImplementedAs::Command => { let Some(manifest) = &self.manifest else { return Err(DscError::MissingManifest(self.type_name.clone())); }; let resource_manifest = import_manifest(manifest.clone())?; - command_resource::invoke_delete(&resource_manifest, &self.directory, filter, self.target_resource.as_deref()) - }, + command_resource::invoke_delete( + &resource_manifest, + &self.directory, + filter, + self.target_resource.as_deref(), + ) + } } } fn validate(&self, config: &str) -> Result { - debug!("{}", t!("dscresources.dscresource.invokeValidate", resource = self.type_name)); + debug!( + "{}", + t!("dscresources.dscresource.invokeValidate", resource = self.type_name) + ); if self.require_adapter.is_some() { - return Err(DscError::NotSupported(t!("dscresources.dscresource.invokeValidateNotSupported", resource = self.type_name).to_string())); + return Err(DscError::NotSupported( + t!( + "dscresources.dscresource.invokeValidateNotSupported", + resource = self.type_name + ) + .to_string(), + )); } match &self.implemented_as { - ImplementedAs::Custom(_custom) => { - Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) - }, + ImplementedAs::Custom(_custom) => Err(DscError::NotImplemented( + t!("dscresources.dscresource.customResourceNotSupported").to_string(), + )), ImplementedAs::Command => { let Some(manifest) = &self.manifest else { return Err(DscError::MissingManifest(self.type_name.clone())); }; let resource_manifest = import_manifest(manifest.clone())?; - command_resource::invoke_validate(&resource_manifest, &self.directory, config, self.target_resource.as_deref()) - }, + command_resource::invoke_validate( + &resource_manifest, + &self.directory, + config, + self.target_resource.as_deref(), + ) + } } } fn schema(&self) -> Result { - debug!("{}", t!("dscresources.dscresource.invokeSchema", resource = self.type_name)); + debug!( + "{}", + t!("dscresources.dscresource.invokeSchema", resource = self.type_name) + ); if self.require_adapter.is_some() { - return Err(DscError::NotSupported(t!("dscresources.dscresource.invokeSchemaNotSupported", resource = self.type_name).to_string())); + return Err(DscError::NotSupported( + t!( + "dscresources.dscresource.invokeSchemaNotSupported", + resource = self.type_name + ) + .to_string(), + )); } match &self.implemented_as { - ImplementedAs::Custom(_custom) => { - Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) - }, + ImplementedAs::Custom(_custom) => Err(DscError::NotImplemented( + t!("dscresources.dscresource.customResourceNotSupported").to_string(), + )), ImplementedAs::Command => { let Some(manifest) = &self.manifest else { return Err(DscError::MissingManifest(self.type_name.clone())); }; let resource_manifest = import_manifest(manifest.clone())?; command_resource::get_schema(&resource_manifest, &self.directory) - }, + } } } fn export(&self, input: &str) -> Result { - debug!("{}", t!("dscresources.dscresource.invokeExport", resource = self.type_name)); + debug!( + "{}", + t!("dscresources.dscresource.invokeExport", resource = self.type_name) + ); if let Some(adapter) = &self.require_adapter { return self.invoke_export_with_adapter(adapter, input); } @@ -531,13 +818,27 @@ impl Invoke for DscResource { return Err(DscError::MissingManifest(self.type_name.clone())); }; let resource_manifest = import_manifest(manifest.clone())?; - command_resource::invoke_export(&resource_manifest, &self.directory, Some(input), self.target_resource.as_deref()) + command_resource::invoke_export( + &resource_manifest, + &self.directory, + Some(input), + self.target_resource.as_deref(), + ) } fn resolve(&self, input: &str) -> Result { - debug!("{}", t!("dscresources.dscresource.invokeResolve", resource = self.type_name)); + debug!( + "{}", + t!("dscresources.dscresource.invokeResolve", resource = self.type_name) + ); if self.require_adapter.is_some() { - return Err(DscError::NotSupported(t!("dscresources.dscresource.invokeResolveNotSupported", resource = self.type_name).to_string())); + return Err(DscError::NotSupported( + t!( + "dscresources.dscresource.invokeResolveNotSupported", + resource = self.type_name + ) + .to_string(), + )); } let Some(manifest) = &self.manifest else { @@ -550,9 +851,7 @@ impl Invoke for DscResource { #[must_use] pub fn get_well_known_properties() -> HashMap { - HashMap::::from([ - ("_exist".to_string(), Value::Bool(true)), - ]) + HashMap::::from([("_exist".to_string(), Value::Bool(true))]) } /// Checks if the JSON value is sensitive and should be redacted @@ -603,7 +902,13 @@ pub fn get_adapter_input_kind(adapter: &DscResource) -> Result Vec { debug!("{}", t!("dscresources.dscresource.subDiff", key = key)); diff_properties.push(key.to_string()); } - } - else { + } else { // skip `$schema` key as that is provided as input, but not output typically if key == "$schema" { continue; @@ -708,24 +1012,41 @@ pub fn validate_properties(resource: &DscResource, properties: &Value) -> Result // convert to resource_manifest`` let manifest: ResourceManifest = serde_json::from_value(manifest)?; if manifest.validate.is_some() { - debug!("{}: {type_name} ", t!("dscresources.dscresource.resourceImplementsValidate")); + debug!( + "{}: {type_name} ", + t!("dscresources.dscresource.resourceImplementsValidate") + ); let resource_config = properties.to_string(); let result = resource.validate(&resource_config)?; if !result.valid { - let reason = result.reason.unwrap_or(t!("dscresources.dscresource.noReason").to_string()); - return Err(DscError::Validation(format!("{}: {type_name} {reason}", t!("dscresources.dscresource.resourceValidationFailed")))); + let reason = result + .reason + .unwrap_or(t!("dscresources.dscresource.noReason").to_string()); + return Err(DscError::Validation(format!( + "{}: {type_name} {reason}", + t!("dscresources.dscresource.resourceValidationFailed") + ))); } - return Ok(()) + return Ok(()); } // use schema validation - trace!("{}: {type_name}", t!("dscresources.dscresource.resourceDoesNotImplementValidate")); + trace!( + "{}: {type_name}", + t!("dscresources.dscresource.resourceDoesNotImplementValidate") + ); let Ok(schema) = resource.schema() else { - return Err(DscError::Validation(format!("{}: {type_name}", t!("dscresources.dscresource.noSchemaOrValidate")))); + return Err(DscError::Validation(format!( + "{}: {type_name}", + t!("dscresources.dscresource.noSchemaOrValidate") + ))); }; let schema = serde_json::from_str(&schema)?; - return validate_json(&resource.type_name, &schema, properties) + return validate_json(&resource.type_name, &schema, properties); } - Err(DscError::Validation(format!("{}: {type_name}", t!("dscresources.dscresource.noManifest")))) + Err(DscError::Validation(format!( + "{}: {type_name}", + t!("dscresources.dscresource.noManifest") + ))) } /// Validate the JSON against the schema. @@ -750,12 +1071,18 @@ pub fn validate_json(source: &str, schema: &Value, json: &Value) -> Result<(), D let compiled_schema = match Validator::new(schema) { Ok(compiled_schema) => compiled_schema, Err(err) => { - return Err(DscError::Validation(format!("{}: {err}", t!("dscresources.dscresource.failedToCompileSchema")))); + return Err(DscError::Validation(format!( + "{}: {err}", + t!("dscresources.dscresource.failedToCompileSchema") + ))); } }; if let Err(err) = compiled_schema.validate(json) { - return Err(DscError::Validation(format!("{}: '{source}' {err}", t!("dscresources.dscresource.validationFailed")))); + return Err(DscError::Validation(format!( + "{}: '{source}' {err}", + t!("dscresources.dscresource.validationFailed") + ))); } Ok(()) @@ -882,15 +1209,35 @@ fn array_with_duplicates_out_of_order() { #[test] fn same_array_with_nested_array() { use serde_json::json; - let array_one = vec![json!("a"), json!(1), json!({"a":"b"}), json!(vec![json!("a"), json!(1)])]; - let array_two = vec![json!("a"), json!(1), json!({"a":"b"}), json!(vec![json!("a"), json!(1)])]; + let array_one = vec![ + json!("a"), + json!(1), + json!({"a":"b"}), + json!(vec![json!("a"), json!(1)]), + ]; + let array_two = vec![ + json!("a"), + json!(1), + json!({"a":"b"}), + json!(vec![json!("a"), json!(1)]), + ]; assert_eq!(is_same_array(&array_one, &array_two), true); } #[test] fn different_array_with_nested_array() { use serde_json::json; - let array_one = vec![json!("a"), json!(1), json!({"a":"b"}), json!(vec![json!("a"), json!(1)])]; - let array_two = vec![json!("a"), json!(1), json!({"a":"b"}), json!(vec![json!("a"), json!(2)])]; + let array_one = vec![ + json!("a"), + json!(1), + json!({"a":"b"}), + json!(vec![json!("a"), json!(1)]), + ]; + let array_two = vec![ + json!("a"), + json!(1), + json!({"a":"b"}), + json!(vec![json!("a"), json!(2)]), + ]; assert_eq!(is_same_array(&array_one, &array_two), false); } diff --git a/dsc_lib/src/dscresources/invoke_result.rs b/dsc_lib/src/dscresources/invoke_result.rs index 9d7404a12..67e854e98 100644 --- a/dsc_lib/src/dscresources/invoke_result.rs +++ b/dsc_lib/src/dscresources/invoke_result.rs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::configure::config_result::{ResourceGetResult, ResourceSetResult, ResourceTestResult}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; -use crate::configure::config_result::{ResourceGetResult, ResourceSetResult, ResourceTestResult}; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(untagged)] @@ -23,12 +23,10 @@ impl From for GetResult { results.push(result.into()); } GetResult::Group(results) - }, - TestResult::Resource(resource) => { - GetResult::Resource(ResourceGetResponse { - actual_state: resource.actual_state - }) } + TestResult::Resource(resource) => GetResult::Resource(ResourceGetResponse { + actual_state: resource.actual_state, + }), } } } @@ -57,14 +55,16 @@ impl From for SetResult { results.push(result.into()); } SetResult::Group(results) - }, - TestResult::Resource(resource) => { - SetResult::Resource(ResourceSetResponse { - before_state: resource.actual_state, - after_state: resource.desired_state, - changed_properties: if resource.diff_properties.is_empty() { None } else { Some(resource.diff_properties) }, - }) } + TestResult::Resource(resource) => SetResult::Resource(ResourceSetResponse { + before_state: resource.actual_state, + after_state: resource.desired_state, + changed_properties: if resource.diff_properties.is_empty() { + None + } else { + Some(resource.diff_properties) + }, + }), } } } @@ -93,9 +93,7 @@ pub enum TestResult { #[must_use] pub fn get_in_desired_state(test_result: &TestResult) -> bool { match test_result { - TestResult::Resource(ref resource_test_result) => { - resource_test_result.in_desired_state - }, + TestResult::Resource(ref resource_test_result) => resource_test_result.in_desired_state, TestResult::Group(ref group_test_result) => { for result in group_test_result { if !get_in_desired_state(&(result.result)) { diff --git a/dsc_lib/src/dscresources/resource_manifest.rs b/dsc_lib/src/dscresources/resource_manifest.rs index 34d3f070a..8d7593f41 100644 --- a/dsc_lib/src/dscresources/resource_manifest.rs +++ b/dsc_lib/src/dscresources/resource_manifest.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use rust_i18n::t; -use schemars::{Schema, JsonSchema, json_schema}; +use schemars::{json_schema, JsonSchema, Schema}; use semver::Version; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -90,7 +90,7 @@ pub enum ArgKind { /// The argument that accepts the resource type name. #[serde(rename = "resourceTypeArg")] resource_type_arg: String, - } + }, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] @@ -185,7 +185,8 @@ pub struct DeleteMethod { } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] -pub struct ValidateMethod { // TODO: enable validation via schema or command +pub struct ValidateMethod { + // TODO: enable validation via schema or command /// The command to run to validate the state of the resource. pub executable: String, /// The arguments to pass to the command to perform a Validate. @@ -311,17 +312,13 @@ pub fn validate_semver(version: &str) -> Result<(), semver::Error> { #[cfg(test)] mod test { - use crate::{ - dscerror::DscError, - dscresources::resource_manifest::ResourceManifest, - schemas::DscRepoSchema - }; + use crate::{dscerror::DscError, dscresources::resource_manifest::ResourceManifest, schemas::DscRepoSchema}; #[test] fn test_validate_schema_uri_with_invalid_uri() { let invalid_uri = "https://invalid.schema.uri".to_string(); - let manifest = ResourceManifest{ + let manifest = ResourceManifest { schema_version: invalid_uri.clone(), resource_type: "Microsoft.Dsc.Test/InvalidSchemaUri".to_string(), version: "0.1.0".to_string(), @@ -336,16 +333,19 @@ mod test { DscError::UnrecognizedSchemaUri(actual, recognized) => { assert_eq!(actual, &invalid_uri); assert_eq!(recognized, &ResourceManifest::recognized_schema_uris()) - }, + } _ => { - panic!("Expected validate_schema_uri() to error on unrecognized schema uri, but was {:?}", result.as_ref().unwrap_err()) + panic!( + "Expected validate_schema_uri() to error on unrecognized schema uri, but was {:?}", + result.as_ref().unwrap_err() + ) } } } #[test] fn test_validate_schema_uri_with_valid_uri() { - let manifest = ResourceManifest{ + let manifest = ResourceManifest { schema_version: ResourceManifest::default_schema_id_uri(), resource_type: "Microsoft.Dsc.Test/ValidSchemaUri".to_string(), version: "0.1.0".to_string(), diff --git a/dsc_lib/src/extensions/discover.rs b/dsc_lib/src/extensions/discover.rs index 5fd2cef91..c0ca8c88f 100644 --- a/dsc_lib/src/extensions/discover.rs +++ b/dsc_lib/src/extensions/discover.rs @@ -2,22 +2,15 @@ // Licensed under the MIT License. use crate::{ - discovery::command_discovery::{ - load_manifest, ImportedManifest - }, + discovery::command_discovery::{load_manifest, ImportedManifest}, dscerror::DscError, dscresources::{ - command_resource::{ - invoke_command, process_args - }, + command_resource::{invoke_command, process_args}, dscresource::DscResource, resource_manifest::ArgKind, }, extensions::{ - dscextension::{ - Capability, - DscExtension, - }, + dscextension::{Capability, DscExtension}, extension_manifest::ExtensionManifest, }, }; @@ -63,7 +56,10 @@ impl DscExtension { } }; let Some(discover) = extension.discover else { - return Err(DscError::UnsupportedCapability(self.type_name.clone(), Capability::Discover.to_string())); + return Err(DscError::UnsupportedCapability( + self.type_name.clone(), + Capability::Discover.to_string(), + )); }; let args = process_args(discover.args.as_ref(), "", &self.type_name); let (_exit_code, stdout, _stderr) = invoke_command( @@ -75,10 +71,20 @@ impl DscExtension { extension.exit_codes.as_ref(), )?; if stdout.is_empty() { - info!("{}", t!("extensions.dscextension.discoverNoResults", extension = self.type_name)); + info!( + "{}", + t!("extensions.dscextension.discoverNoResults", extension = self.type_name) + ); } else { for line in stdout.lines() { - trace!("{}", t!("extensions.dscextension.extensionReturned", extension = self.type_name, line = line)); + trace!( + "{}", + t!( + "extensions.dscextension.extensionReturned", + extension = self.type_name, + line = line + ) + ); let discover_result: DiscoverResult = match serde_json::from_str(line) { Ok(result) => result, Err(err) => { @@ -86,7 +92,14 @@ impl DscExtension { } }; if !Path::new(&discover_result.manifest_path).is_absolute() { - return Err(DscError::Extension(t!("extensions.dscextension.discoverNotAbsolutePath", extension = self.type_name.clone(), path = discover_result.manifest_path.clone()).to_string())); + return Err(DscError::Extension( + t!( + "extensions.dscextension.discoverNotAbsolutePath", + extension = self.type_name.clone(), + path = discover_result.manifest_path.clone() + ) + .to_string(), + )); } let manifest_path = Path::new(&discover_result.manifest_path); // Currently we don't support extensions discovering other extensions @@ -100,7 +113,7 @@ impl DscExtension { } else { Err(DscError::UnsupportedCapability( self.type_name.clone(), - Capability::Discover.to_string() + Capability::Discover.to_string(), )) } } diff --git a/dsc_lib/src/extensions/dscextension.rs b/dsc_lib/src/extensions/dscextension.rs index d7b01c5d3..51d36d8ce 100644 --- a/dsc_lib/src/extensions/dscextension.rs +++ b/dsc_lib/src/extensions/dscextension.rs @@ -1,16 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; -use schemars::JsonSchema; use std::fmt::Display; #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct DscExtension { /// The namespaced name of the resource. - #[serde(rename="type")] + #[serde(rename = "type")] pub type_name: String, /// The version of the resource. pub version: String, diff --git a/dsc_lib/src/extensions/extension_manifest.rs b/dsc_lib/src/extensions/extension_manifest.rs index f43006252..13021705e 100644 --- a/dsc_lib/src/extensions/extension_manifest.rs +++ b/dsc_lib/src/extensions/extension_manifest.rs @@ -2,14 +2,14 @@ // Licensed under the MIT License. use rust_i18n::t; -use schemars::{Schema, JsonSchema, json_schema}; +use schemars::{json_schema, JsonSchema, Schema}; use semver::Version; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; -use crate::{dscerror::DscError, schemas::DscRepoSchema}; use crate::extensions::{discover::DiscoverMethod, import::ImportMethod, secret::SecretMethod}; +use crate::{dscerror::DscError, schemas::DscRepoSchema}; #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] @@ -101,15 +101,13 @@ pub fn validate_semver(version: &str) -> Result<(), semver::Error> { #[cfg(test)] mod test { - use crate::{ - dscerror::DscError, extensions::extension_manifest::ExtensionManifest, schemas::DscRepoSchema - }; + use crate::{dscerror::DscError, extensions::extension_manifest::ExtensionManifest, schemas::DscRepoSchema}; #[test] fn test_validate_schema_uri_with_invalid_uri() { let invalid_uri = "https://invalid.schema.uri".to_string(); - let manifest = ExtensionManifest{ + let manifest = ExtensionManifest { schema_version: invalid_uri.clone(), r#type: "Microsoft.Dsc.Test/InvalidSchemaUri".to_string(), version: "0.1.0".to_string(), @@ -124,16 +122,19 @@ mod test { DscError::UnrecognizedSchemaUri(actual, recognized) => { assert_eq!(actual, &invalid_uri); assert_eq!(recognized, &ExtensionManifest::recognized_schema_uris()) - }, + } _ => { - panic!("Expected validate_schema_uri() to error on unrecognized schema uri, but was {:?}", result.as_ref().unwrap_err()) + panic!( + "Expected validate_schema_uri() to error on unrecognized schema uri, but was {:?}", + result.as_ref().unwrap_err() + ) } } } #[test] fn test_validate_schema_uri_with_valid_uri() { - let manifest = ExtensionManifest{ + let manifest = ExtensionManifest { schema_version: ExtensionManifest::default_schema_id_uri(), r#type: "Microsoft.Dsc.Test/ValidSchemaUri".to_string(), version: "0.1.0".to_string(), diff --git a/dsc_lib/src/extensions/import.rs b/dsc_lib/src/extensions/import.rs index 79e83dd94..850952371 100644 --- a/dsc_lib/src/extensions/import.rs +++ b/dsc_lib/src/extensions/import.rs @@ -3,16 +3,9 @@ use crate::{ dscerror::DscError, - dscresources::{ - command_resource::{ - invoke_command, - }, - }, + dscresources::command_resource::invoke_command, extensions::{ - dscextension::{ - Capability, - DscExtension, - }, + dscextension::{Capability, DscExtension}, extension_manifest::ExtensionManifest, }, }; @@ -64,13 +57,40 @@ impl DscExtension { pub fn import(&self, file: &str) -> Result { if self.capabilities.contains(&Capability::Import) { let file_path = Path::new(file); - let file_extension = file_path.extension().and_then(|s| s.to_str()).unwrap_or_default().to_string(); - if self.import_file_extensions.as_ref().is_some_and(|exts| exts.contains(&file_extension)) { - debug!("{}", t!("extensions.dscextension.importingFile", file = file, extension = self.type_name)); + let file_extension = file_path + .extension() + .and_then(|s| s.to_str()) + .unwrap_or_default() + .to_string(); + if self + .import_file_extensions + .as_ref() + .is_some_and(|exts| exts.contains(&file_extension)) + { + debug!( + "{}", + t!( + "extensions.dscextension.importingFile", + file = file, + extension = self.type_name + ) + ); } else { - debug!("{}", t!("extensions.dscextension.importNotSupported", file = file, extension = self.type_name)); + debug!( + "{}", + t!( + "extensions.dscextension.importNotSupported", + file = file, + extension = self.type_name + ) + ); return Err(DscError::NotSupported( - t!("extensions.dscextension.importNotSupported", file = file, extension = self.type_name).to_string(), + t!( + "extensions.dscextension.importNotSupported", + file = file, + extension = self.type_name + ) + .to_string(), )); } @@ -81,7 +101,10 @@ impl DscExtension { } }; let Some(import) = extension.import else { - return Err(DscError::UnsupportedCapability(self.type_name.clone(), Capability::Import.to_string())); + return Err(DscError::UnsupportedCapability( + self.type_name.clone(), + Capability::Import.to_string(), + )); }; let args = process_import_args(import.args.as_ref(), file)?; let (_exit_code, stdout, _stderr) = invoke_command( @@ -93,16 +116,19 @@ impl DscExtension { extension.exit_codes.as_ref(), )?; if stdout.is_empty() { - info!("{}", t!("extensions.dscextension.importNoResults", extension = self.type_name)); + info!( + "{}", + t!("extensions.dscextension.importNoResults", extension = self.type_name) + ); } else { return Ok(stdout); } } Err(DscError::UnsupportedCapability( self.type_name.clone(), - Capability::Import.to_string() + Capability::Import.to_string(), )) - } + } } fn process_import_args(args: Option<&Vec>, file: &str) -> Result>, DscError> { @@ -114,7 +140,9 @@ fn process_import_args(args: Option<&Vec>, file: &str) -> Result< // make path absolute let path = Path::new(file); let Ok(full_path) = path.absolutize() else { - return Err(DscError::Extension(t!("util.failedToAbsolutizePath", path = path : {:?}).to_string())); + return Err(DscError::Extension( + t!("util.failedToAbsolutizePath", path = path : {:?}).to_string(), + )); }; let mut processed_args = Vec::::new(); @@ -122,13 +150,13 @@ fn process_import_args(args: Option<&Vec>, file: &str) -> Result< match arg { ImportArgKind::String(s) => { processed_args.push(s.clone()); - }, + } ImportArgKind::File { file_arg } => { if !file_arg.is_empty() { processed_args.push(file_arg.to_string()); } processed_args.push(full_path.to_string_lossy().to_string()); - }, + } } } diff --git a/dsc_lib/src/extensions/mod.rs b/dsc_lib/src/extensions/mod.rs index 3bf58ceca..23ad08db0 100644 --- a/dsc_lib/src/extensions/mod.rs +++ b/dsc_lib/src/extensions/mod.rs @@ -4,5 +4,5 @@ pub mod discover; pub mod dscextension; pub mod extension_manifest; -pub mod secret; pub mod import; +pub mod secret; diff --git a/dsc_lib/src/extensions/secret.rs b/dsc_lib/src/extensions/secret.rs index 01d7fe3b2..51c5d4c56 100644 --- a/dsc_lib/src/extensions/secret.rs +++ b/dsc_lib/src/extensions/secret.rs @@ -3,14 +3,9 @@ use crate::{ dscerror::DscError, - dscresources::{ - command_resource::invoke_command, - }, + dscresources::command_resource::invoke_command, extensions::{ - dscextension::{ - Capability, - DscExtension, - }, + dscextension::{Capability, DscExtension}, extension_manifest::ExtensionManifest, }, }; @@ -63,7 +58,14 @@ impl DscExtension { /// This function will return an error if the secret retrieval fails or if the extension does not support the secret capability. pub fn secret(&self, name: &str, vault: Option<&str>) -> Result, DscError> { if self.capabilities.contains(&Capability::Secret) { - debug!("{}", t!("extensions.dscextension.retrievingSecretFromExtension", name = name, extension = self.type_name)); + debug!( + "{}", + t!( + "extensions.dscextension.retrievingSecretFromExtension", + name = name, + extension = self.type_name + ) + ); let extension = match serde_json::from_value::(self.manifest.clone()) { Ok(manifest) => manifest, Err(err) => { @@ -71,7 +73,10 @@ impl DscExtension { } }; let Some(secret) = extension.secret else { - return Err(DscError::UnsupportedCapability(self.type_name.clone(), Capability::Secret.to_string())); + return Err(DscError::UnsupportedCapability( + self.type_name.clone(), + Capability::Secret.to_string(), + )); }; let args = process_secret_args(secret.args.as_ref(), name, vault); let (_exit_code, stdout, _stderr) = invoke_command( @@ -83,14 +88,32 @@ impl DscExtension { extension.exit_codes.as_ref(), )?; if stdout.is_empty() { - debug!("{}", t!("extensions.dscextension.extensionReturnedNoSecret", extension = self.type_name)); + debug!( + "{}", + t!( + "extensions.dscextension.extensionReturnedNoSecret", + extension = self.type_name + ) + ); Ok(None) } else { // see if multiple lines were returned let secret = if stdout.lines().count() > 1 { - return Err(DscError::NotSupported(t!("extensions.dscextension.secretMultipleLinesReturned", extension = self.type_name).to_string())); + return Err(DscError::NotSupported( + t!( + "extensions.dscextension.secretMultipleLinesReturned", + extension = self.type_name + ) + .to_string(), + )); } else { - debug!("{}", t!("extensions.dscextension.extensionReturnedSecret", extension = self.type_name)); + debug!( + "{}", + t!( + "extensions.dscextension.extensionReturnedSecret", + extension = self.type_name + ) + ); // remove any trailing newline characters stdout.trim_end_matches('\n').to_string() }; @@ -99,10 +122,10 @@ impl DscExtension { } else { Err(DscError::UnsupportedCapability( self.type_name.clone(), - Capability::Secret.to_string() + Capability::Secret.to_string(), )) } - } + } } fn process_secret_args(args: Option<&Vec>, name: &str, vault: Option<&str>) -> Option> { @@ -116,17 +139,17 @@ fn process_secret_args(args: Option<&Vec>, name: &str, vault: Opt match arg { SecretArgKind::String(s) => { processed_args.push(s.clone()); - }, + } SecretArgKind::Name { name_arg } => { processed_args.push(name_arg.to_string()); processed_args.push(name.to_string()); - }, + } SecretArgKind::Vault { vault_arg } => { if let Some(value) = vault { processed_args.push(vault_arg.to_string()); processed_args.push(value.to_string()); } - }, + } } } diff --git a/dsc_lib/src/functions/add.rs b/dsc_lib/src/functions/add.rs index 608ea1659..24ec81a84 100644 --- a/dsc_lib/src/functions/add.rs +++ b/dsc_lib/src/functions/add.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -19,10 +19,7 @@ impl Function for Add { category: vec![FunctionCategory::Numeric], min_args: 2, max_args: 2, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::Number], - vec![FunctionArgKind::Number], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::Number], vec![FunctionArgKind::Number]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Number], } @@ -53,7 +50,9 @@ mod tests { #[test] fn nested() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[add(2, add(3, 4))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[add(2, add(3, 4))]", &Context::new()) + .unwrap(); assert_eq!(result, 9); } diff --git a/dsc_lib/src/functions/and.rs b/dsc_lib/src/functions/and.rs index fbc3d0d22..009689987 100644 --- a/dsc_lib/src/functions/and.rs +++ b/dsc_lib/src/functions/and.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -19,10 +19,7 @@ impl Function for And { category: vec![FunctionCategory::Logical], min_args: 2, max_args: usize::MAX, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::Boolean], - vec![FunctionArgKind::Boolean], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::Boolean], vec![FunctionArgKind::Boolean]], remaining_arg_accepted_types: Some(vec![FunctionArgKind::Boolean]), return_types: vec![FunctionArgKind::Boolean], } @@ -58,14 +55,18 @@ mod tests { #[test] fn multiple_values() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[and(true, false, true)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[and(true, false, true)]", &Context::new()) + .unwrap(); assert_eq!(result, false); } #[test] fn all_false() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[and(false, false)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[and(false, false)]", &Context::new()) + .unwrap(); assert_eq!(result, false); } diff --git a/dsc_lib/src/functions/array.rs b/dsc_lib/src/functions/array.rs index bebfe4af6..3d2283559 100644 --- a/dsc_lib/src/functions/array.rs +++ b/dsc_lib/src/functions/array.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -19,9 +19,12 @@ impl Function for Array { category: vec![FunctionCategory::Array], min_args: 1, max_args: 1, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::String, FunctionArgKind::Number, FunctionArgKind::Object, FunctionArgKind::Array], - ], + accepted_arg_ordered_types: vec![vec![ + FunctionArgKind::String, + FunctionArgKind::Number, + FunctionArgKind::Object, + FunctionArgKind::Array, + ]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Array], } @@ -29,7 +32,7 @@ impl Function for Array { fn invoke(&self, args: &[Value], _context: &Context) -> Result { debug!("{}", t!("functions.array.invoked")); - + Ok(Value::Array(vec![args[0].clone()])) } } @@ -56,14 +59,18 @@ mod tests { #[test] fn single_object() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[array(createObject('key', 'value'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[array(createObject('key', 'value'))]", &Context::new()) + .unwrap(); assert_eq!(result.to_string(), r#"[{"key":"value"}]"#); } #[test] fn single_array() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[array(createArray('a','b'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[array(createArray('a','b'))]", &Context::new()) + .unwrap(); assert_eq!(result.to_string(), r#"[["a","b"]]"#); } diff --git a/dsc_lib/src/functions/base64.rs b/dsc_lib/src/functions/base64.rs index 66adedfdf..5d4f961c3 100644 --- a/dsc_lib/src/functions/base64.rs +++ b/dsc_lib/src/functions/base64.rs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use base64::{Engine as _, engine::general_purpose}; +use base64::{engine::general_purpose, Engine as _}; -use crate::DscError; +use super::Function; use crate::configure::context::Context; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; -use super::Function; #[derive(Debug, Default)] pub struct Base64 {} @@ -28,7 +28,9 @@ impl Function for Base64 { } fn invoke(&self, args: &[Value], _context: &Context) -> Result { - Ok(Value::String(general_purpose::STANDARD.encode(args[0].as_str().unwrap_or_default()))) + Ok(Value::String( + general_purpose::STANDARD.encode(args[0].as_str().unwrap_or_default()), + )) } } @@ -40,7 +42,9 @@ mod tests { #[test] fn strings() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[base64('hello world')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[base64('hello world')]", &Context::new()) + .unwrap(); assert_eq!(result, "aGVsbG8gd29ybGQ="); } @@ -54,7 +58,9 @@ mod tests { #[test] fn nested() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[base64(base64('hello world'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[base64(base64('hello world'))]", &Context::new()) + .unwrap(); assert_eq!(result, "YUdWc2JHOGdkMjl5YkdRPQ=="); } } diff --git a/dsc_lib/src/functions/base64_to_string.rs b/dsc_lib/src/functions/base64_to_string.rs index 823a30838..f71dbb6ff 100644 --- a/dsc_lib/src/functions/base64_to_string.rs +++ b/dsc_lib/src/functions/base64_to_string.rs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use base64::{Engine as _, engine::general_purpose}; +use base64::{engine::general_purpose, Engine as _}; -use crate::DscError; +use super::Function; use crate::configure::context::Context; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; -use super::Function; use tracing::debug; #[derive(Debug, Default)] @@ -30,7 +30,7 @@ impl Function for Base64ToString { fn invoke(&self, args: &[Value], _context: &Context) -> Result { debug!("{}", t!("functions.base64ToString.invoked")); - + let base64_value = args[0].as_str().unwrap(); let decoded_bytes = general_purpose::STANDARD.decode(base64_value).map_err(|_| { @@ -115,4 +115,4 @@ mod tests { .unwrap(); assert_eq!(result, Value::String("{\"key\":\"value\"}".to_string())); } -} \ No newline at end of file +} diff --git a/dsc_lib/src/functions/bool.rs b/dsc_lib/src/functions/bool.rs index 28ec13de2..c45d0388f 100644 --- a/dsc_lib/src/functions/bool.rs +++ b/dsc_lib/src/functions/bool.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; diff --git a/dsc_lib/src/functions/coalesce.rs b/dsc_lib/src/functions/coalesce.rs index 7d2f26af9..229e5f9c5 100644 --- a/dsc_lib/src/functions/coalesce.rs +++ b/dsc_lib/src/functions/coalesce.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -61,9 +61,9 @@ impl Function for Coalesce { #[cfg(test)] mod tests { + use super::*; use crate::configure::context::Context; use crate::parser::Statement; - use super::*; #[test] fn direct_function_call_with_nulls() { @@ -88,7 +88,11 @@ mod tests { let coalesce = Coalesce {}; let context = Context::new(); - let args = vec![Value::Null, serde_json::json!(42), Value::String("fallback".to_string())]; + let args = vec![ + Value::Null, + serde_json::json!(42), + Value::String("fallback".to_string()), + ]; let result = coalesce.invoke(&args, &context).unwrap(); assert_eq!(result, serde_json::json!(42)); @@ -150,10 +154,14 @@ mod tests { #[test] fn parser_with_values() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[coalesce('hello', 'world')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[coalesce('hello', 'world')]", &Context::new()) + .unwrap(); assert_eq!(result, "hello"); - let result = parser.parse_and_execute("[coalesce(42, 'fallback')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[coalesce(42, 'fallback')]", &Context::new()) + .unwrap(); assert_eq!(result, 42); let result = parser.parse_and_execute("[coalesce(true)]", &Context::new()).unwrap(); diff --git a/dsc_lib/src/functions/concat.rs b/dsc_lib/src/functions/concat.rs index 6ff90e3e8..a5e818eb0 100644 --- a/dsc_lib/src/functions/concat.rs +++ b/dsc_lib/src/functions/concat.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -32,7 +32,7 @@ impl Function for Concat { debug!("{}", t!("functions.concat.invoked")); let mut string_result = String::new(); let mut array_result: Vec = Vec::new(); - let mut input_type : Option = None; + let mut input_type: Option = None; for value in args { if value.is_string() { if input_type.is_none() { @@ -68,15 +68,9 @@ impl Function for Concat { } match input_type { - Some(FunctionArgKind::String) => { - Ok(Value::String(string_result)) - }, - Some(FunctionArgKind::Array) => { - Ok(Value::Array(array_result.into_iter().map(Value::String).collect())) - }, - _ => { - Err(DscError::Parser(t!("functions.invalidArgType").to_string())) - } + Some(FunctionArgKind::String) => Ok(Value::String(string_result)), + Some(FunctionArgKind::Array) => Ok(Value::Array(array_result.into_iter().map(Value::String).collect())), + _ => Err(DscError::Parser(t!("functions.invalidArgType").to_string())), } } } @@ -96,14 +90,18 @@ mod tests { #[test] fn strings_with_spaces() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[concat('a ', ' ', ' b')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[concat('a ', ' ', ' b')]", &Context::new()) + .unwrap(); assert_eq!(result, "a b"); } #[test] fn arrays() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[concat(createArray('a','b'), createArray('c','d'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[concat(createArray('a','b'), createArray('c','d'))]", &Context::new()) + .unwrap(); assert_eq!(result.to_string(), r#"["a","b","c","d"]"#); } @@ -117,7 +115,9 @@ mod tests { #[test] fn nested() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[concat('a', concat('b', 'c'), 'd')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[concat('a', concat('b', 'c'), 'd')]", &Context::new()) + .unwrap(); assert_eq!(result, "abcd"); } diff --git a/dsc_lib/src/functions/contains.rs b/dsc_lib/src/functions/contains.rs index 4604bd676..b403c7869 100644 --- a/dsc_lib/src/functions/contains.rs +++ b/dsc_lib/src/functions/contains.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -103,22 +103,27 @@ mod tests { #[test] fn string_contains_string() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[contains('hello', 'lo')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[contains('hello', 'lo')]", &Context::new()) + .unwrap(); assert_eq!(result, true); } #[test] fn string_does_not_contain_string() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[contains('hello', 'world')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[contains('hello', 'world')]", &Context::new()) + .unwrap(); assert_eq!(result, false); } #[test] fn string_contains_number() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[contains('hello123', 123)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[contains('hello123', 123)]", &Context::new()) + .unwrap(); assert_eq!(result, true); } } - diff --git a/dsc_lib/src/functions/context.rs b/dsc_lib/src/functions/context.rs index c9e06215a..682d5c916 100644 --- a/dsc_lib/src/functions/context.rs +++ b/dsc_lib/src/functions/context.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::configure::{config_doc::SecurityContextKind, context::Context as ConfigContext}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; use crate::DscError; -use crate::configure::{context::Context as ConfigContext, config_doc::SecurityContextKind}; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; use osinfo_lib::OsInfo; use rust_i18n::t; use serde::Serialize; diff --git a/dsc_lib/src/functions/copy_index.rs b/dsc_lib/src/functions/copy_index.rs index 4503b20ed..7e8ce203d 100644 --- a/dsc_lib/src/functions/copy_index.rs +++ b/dsc_lib/src/functions/copy_index.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::{Context, ProcessMode}; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -31,7 +31,9 @@ impl Function for CopyIndex { fn invoke(&self, args: &[Value], context: &Context) -> Result { debug!("{}", t!("functions.copyIndex.invoked")); if context.process_mode != ProcessMode::Copy { - return Err(DscError::Parser(t!("functions.copyIndex.cannotUseOutsideCopy").to_string())); + return Err(DscError::Parser( + t!("functions.copyIndex.cannotUseOutsideCopy").to_string(), + )); } match args.len() { // no args, we return the current index of the current loop @@ -48,7 +50,9 @@ impl Function for CopyIndex { if let Some(index) = context.copy.get(loop_name) { Ok(Value::Number((*index).into())) } else { - Err(DscError::Parser(t!("functions.copyIndex.loopNameNotFound", name = loop_name).to_string())) + Err(DscError::Parser( + t!("functions.copyIndex.loopNameNotFound", name = loop_name).to_string(), + )) } } else { Err(DscError::Parser(t!("functions.invalidArguments").to_string())) @@ -64,7 +68,9 @@ impl Function for CopyIndex { Err(DscError::Parser(t!("functions.invalidArguments").to_string())) } } else { - Err(DscError::Parser(t!("functions.copyIndex.loopNameNotFound", name = loop_name).to_string())) + Err(DscError::Parser( + t!("functions.copyIndex.loopNameNotFound", name = loop_name).to_string(), + )) } } else { Err(DscError::Parser(t!("functions.invalidArguments").to_string())) diff --git a/dsc_lib/src/functions/create_array.rs b/dsc_lib/src/functions/create_array.rs index 41cb66acf..b41047d64 100644 --- a/dsc_lib/src/functions/create_array.rs +++ b/dsc_lib/src/functions/create_array.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -33,31 +33,39 @@ impl Function for CreateArray { fn invoke(&self, args: &[Value], _context: &Context) -> Result { debug!("{}", t!("functions.createArray.invoked")); let mut array_result = Vec::::new(); - let mut input_type : Option = None; + let mut input_type: Option = None; for value in args { if value.is_array() { if input_type.is_none() { input_type = Some(FunctionArgKind::Array); } else if input_type != Some(FunctionArgKind::Array) { - return Err(DscError::Parser(t!("functions.createArray.argsMustAllBeArrays").to_string())); + return Err(DscError::Parser( + t!("functions.createArray.argsMustAllBeArrays").to_string(), + )); } } else if value.is_number() { if input_type.is_none() { input_type = Some(FunctionArgKind::Number); } else if input_type != Some(FunctionArgKind::Number) { - return Err(DscError::Parser(t!("functions.createArray.argsMustAllBeIntegers").to_string())); + return Err(DscError::Parser( + t!("functions.createArray.argsMustAllBeIntegers").to_string(), + )); } } else if value.is_object() { if input_type.is_none() { input_type = Some(FunctionArgKind::Object); } else if input_type != Some(FunctionArgKind::Object) { - return Err(DscError::Parser(t!("functions.createArray.argsMustAllBeObjects").to_string())); + return Err(DscError::Parser( + t!("functions.createArray.argsMustAllBeObjects").to_string(), + )); } } else if value.is_string() { if input_type.is_none() { input_type = Some(FunctionArgKind::String); } else if input_type != Some(FunctionArgKind::String) { - return Err(DscError::Parser(t!("functions.createArray.argsMustAllBeStrings").to_string())); + return Err(DscError::Parser( + t!("functions.createArray.argsMustAllBeStrings").to_string(), + )); } } else { return Err(DscError::Parser(t!("functions.invalidArgType").to_string())); @@ -77,21 +85,30 @@ mod tests { #[test] fn strings() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[createArray('a', 'b')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[createArray('a', 'b')]", &Context::new()) + .unwrap(); assert_eq!(result.to_string(), r#"["a","b"]"#); } #[test] fn integers() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[createArray(1,2,3)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[createArray(1,2,3)]", &Context::new()) + .unwrap(); assert_eq!(result.to_string(), "[1,2,3]"); } #[test] fn arrays() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[createArray(createArray('a','b'), createArray('c','d'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute( + "[createArray(createArray('a','b'), createArray('c','d'))]", + &Context::new(), + ) + .unwrap(); assert_eq!(result.to_string(), r#"[["a","b"],["c","d"]]"#); } diff --git a/dsc_lib/src/functions/create_object.rs b/dsc_lib/src/functions/create_object.rs index 878fa5b58..9a6d2557b 100644 --- a/dsc_lib/src/functions/create_object.rs +++ b/dsc_lib/src/functions/create_object.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::{Map, Value}; use tracing::debug; @@ -36,7 +36,9 @@ impl Function for CreateObject { debug!("{}", t!("functions.createObject.invoked")); if !args.len().is_multiple_of(2) { - return Err(DscError::Parser(t!("functions.createObject.argsMustBePairs").to_string())); + return Err(DscError::Parser( + t!("functions.createObject.argsMustBePairs").to_string(), + )); } let mut object_result = Map::::new(); @@ -46,7 +48,9 @@ impl Function for CreateObject { let value = &chunk[1]; if !key.is_string() { - return Err(DscError::Parser(t!("functions.createObject.keyMustBeString").to_string())); + return Err(DscError::Parser( + t!("functions.createObject.keyMustBeString").to_string(), + )); } let key_str = key.as_str().unwrap().to_string(); @@ -65,21 +69,30 @@ mod tests { #[test] fn simple_object() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[createObject('name', 'test')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[createObject('name', 'test')]", &Context::new()) + .unwrap(); assert_eq!(result.to_string(), r#"{"name":"test"}"#); } #[test] fn multiple_properties() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[createObject('name', 'test', 'value', 42)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[createObject('name', 'test', 'value', 42)]", &Context::new()) + .unwrap(); assert_eq!(result.to_string(), r#"{"name":"test","value":42}"#); } #[test] fn mixed_value_types() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[createObject('string', 'hello', 'number', 123, 'boolean', true)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute( + "[createObject('string', 'hello', 'number', 123, 'boolean', true)]", + &Context::new(), + ) + .unwrap(); let json: serde_json::Value = serde_json::from_str(&result.to_string()).unwrap(); assert_eq!(json["string"], "hello"); @@ -90,14 +103,21 @@ mod tests { #[test] fn nested_objects() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[createObject('outer', createObject('inner', 'value'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute( + "[createObject('outer', createObject('inner', 'value'))]", + &Context::new(), + ) + .unwrap(); assert_eq!(result.to_string(), r#"{"outer":{"inner":"value"}}"#); } #[test] fn with_arrays() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[createObject('items', createArray('a', 'b', 'c'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[createObject('items', createArray('a', 'b', 'c'))]", &Context::new()) + .unwrap(); assert_eq!(result.to_string(), r#"{"items":["a","b","c"]}"#); } diff --git a/dsc_lib/src/functions/div.rs b/dsc_lib/src/functions/div.rs index eec6ce673..2fd76f972 100644 --- a/dsc_lib/src/functions/div.rs +++ b/dsc_lib/src/functions/div.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -19,10 +19,7 @@ impl Function for Div { category: vec![FunctionCategory::Numeric], min_args: 2, max_args: 2, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::Number], - vec![FunctionArgKind::Number], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::Number], vec![FunctionArgKind::Number]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Number], } @@ -57,7 +54,9 @@ mod tests { #[test] fn nested() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[div(18, div(9, 3))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[div(18, div(9, 3))]", &Context::new()) + .unwrap(); assert_eq!(result, 6); } @@ -83,4 +82,3 @@ mod tests { assert!(result.is_err()); } } - diff --git a/dsc_lib/src/functions/empty.rs b/dsc_lib/src/functions/empty.rs index 8e6c6a300..16fa7aff8 100644 --- a/dsc_lib/src/functions/empty.rs +++ b/dsc_lib/src/functions/empty.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -16,10 +16,18 @@ impl Function for Empty { FunctionMetadata { name: "empty".to_string(), description: t!("functions.empty.description").to_string(), - category: vec![FunctionCategory::Array, FunctionCategory::Object, FunctionCategory::String], + category: vec![ + FunctionCategory::Array, + FunctionCategory::Object, + FunctionCategory::String, + ], min_args: 1, max_args: 1, - accepted_arg_ordered_types: vec![vec![FunctionArgKind::Array, FunctionArgKind::Object, FunctionArgKind::String]], + accepted_arg_ordered_types: vec![vec![ + FunctionArgKind::Array, + FunctionArgKind::Object, + FunctionArgKind::String, + ]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Boolean], } @@ -43,7 +51,6 @@ impl Function for Empty { } } - #[cfg(test)] mod tests { use crate::configure::context::Context; diff --git a/dsc_lib/src/functions/ends_with.rs b/dsc_lib/src/functions/ends_with.rs index 7030d07d0..8de1051b7 100644 --- a/dsc_lib/src/functions/ends_with.rs +++ b/dsc_lib/src/functions/ends_with.rs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; +use super::Function; use crate::configure::context::Context; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; -use super::Function; use tracing::debug; #[derive(Debug, Default)] @@ -20,10 +20,7 @@ impl Function for EndsWith { category: vec![FunctionCategory::String], min_args: 2, max_args: 2, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::String], - vec![FunctionArgKind::String], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::String], vec![FunctionArgKind::String]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Boolean], } @@ -48,14 +45,18 @@ mod tests { #[test] fn does_ends_with() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[endsWith('hello', 'lo')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[endsWith('hello', 'lo')]", &Context::new()) + .unwrap(); assert_eq!(result, Value::Bool(true)); } #[test] fn does_not_ends_with() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[endsWith('hello', 'world')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[endsWith('hello', 'world')]", &Context::new()) + .unwrap(); assert_eq!(result, Value::Bool(false)); } } diff --git a/dsc_lib/src/functions/envvar.rs b/dsc_lib/src/functions/envvar.rs index bb4a77263..f9d07933b 100644 --- a/dsc_lib/src/functions/envvar.rs +++ b/dsc_lib/src/functions/envvar.rs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; +use super::Function; use crate::configure::context::Context; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; -use super::Function; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use std::env; @@ -31,7 +31,10 @@ impl Function for Envvar { return Ok(Value::String(val)); } - Err(DscError::Function("envvar".to_string(), t!("functions.envvar.notFound").to_string())) + Err(DscError::Function( + "envvar".to_string(), + t!("functions.envvar.notFound").to_string(), + )) } } diff --git a/dsc_lib/src/functions/equals.rs b/dsc_lib/src/functions/equals.rs index b96c276ed..de8105d5e 100644 --- a/dsc_lib/src/functions/equals.rs +++ b/dsc_lib/src/functions/equals.rs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; +use super::Function; use crate::configure::context::Context; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; -use super::Function; use serde_json::Value; #[derive(Debug, Default)] @@ -20,8 +20,18 @@ impl Function for Equals { min_args: 2, max_args: 2, accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::Number, FunctionArgKind::String, FunctionArgKind::Array, FunctionArgKind::Object], - vec![FunctionArgKind::Number, FunctionArgKind::String, FunctionArgKind::Array, FunctionArgKind::Object], + vec![ + FunctionArgKind::Number, + FunctionArgKind::String, + FunctionArgKind::Array, + FunctionArgKind::Object, + ], + vec![ + FunctionArgKind::Number, + FunctionArgKind::String, + FunctionArgKind::Array, + FunctionArgKind::Object, + ], ], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Boolean], @@ -56,21 +66,27 @@ mod tests { #[test] fn string_equal() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[equals('test','test')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[equals('test','test')]", &Context::new()) + .unwrap(); assert_eq!(result, Value::Bool(true)); } #[test] fn string_notequal() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[equals('test','TEST')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[equals('test','TEST')]", &Context::new()) + .unwrap(); assert_eq!(result, Value::Bool(false)); } #[test] fn different_types() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[equals(1,'string')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[equals(1,'string')]", &Context::new()) + .unwrap(); assert_eq!(result, Value::Bool(false)); } diff --git a/dsc_lib/src/functions/false.rs b/dsc_lib/src/functions/false.rs index 4b17da112..c06cbd5bc 100644 --- a/dsc_lib/src/functions/false.rs +++ b/dsc_lib/src/functions/false.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; diff --git a/dsc_lib/src/functions/first.rs b/dsc_lib/src/functions/first.rs index f3d442afa..9e123e07c 100644 --- a/dsc_lib/src/functions/first.rs +++ b/dsc_lib/src/functions/first.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -21,13 +21,18 @@ impl Function for First { max_args: 1, accepted_arg_ordered_types: vec![vec![FunctionArgKind::Array, FunctionArgKind::String]], remaining_arg_accepted_types: None, - return_types: vec![FunctionArgKind::String, FunctionArgKind::Number, FunctionArgKind::Array, FunctionArgKind::Object], + return_types: vec![ + FunctionArgKind::String, + FunctionArgKind::Number, + FunctionArgKind::Array, + FunctionArgKind::Object, + ], } } fn invoke(&self, args: &[Value], _context: &Context) -> Result { debug!("{}", t!("functions.first.invoked")); - + if let Some(array) = args[0].as_array() { if array.is_empty() { return Err(DscError::Parser(t!("functions.first.emptyArray").to_string())); @@ -54,21 +59,27 @@ mod tests { #[test] fn array_of_strings() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[first(createArray('hello', 'world'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[first(createArray('hello', 'world'))]", &Context::new()) + .unwrap(); assert_eq!(result.as_str(), Some("hello")); } #[test] fn array_of_numbers() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[first(createArray(1, 2, 3))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[first(createArray(1, 2, 3))]", &Context::new()) + .unwrap(); assert_eq!(result.to_string(), "1"); } #[test] fn array_of_single_element() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[first(array('hello'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[first(array('hello'))]", &Context::new()) + .unwrap(); assert_eq!(result.as_str(), Some("hello")); } diff --git a/dsc_lib/src/functions/format.rs b/dsc_lib/src/functions/format.rs index e58336d1c..dacb48800 100644 --- a/dsc_lib/src/functions/format.rs +++ b/dsc_lib/src/functions/format.rs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; -use rt_format::{Format as RtFormat, FormatArgument, ParsedFormat, argument::NoNamedArguments}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; +use rt_format::{argument::NoNamedArguments, Format as RtFormat, FormatArgument, ParsedFormat}; use rust_i18n::t; use serde_json::Value; use tracing::warn; @@ -19,8 +19,18 @@ enum Variant { impl FormatArgument for Variant { fn supports_format(&self, specifier: &rt_format::Specifier) -> bool { match self { - Variant::Boolean(_) | Variant::String(_)=> matches!(specifier.format, RtFormat::Display), - Variant::Number(_) => matches!(specifier.format, RtFormat::Display | RtFormat::Binary | RtFormat::Octal | RtFormat::LowerHex | RtFormat::UpperHex | RtFormat::Debug | RtFormat::LowerExp | RtFormat::UpperExp), + Variant::Boolean(_) | Variant::String(_) => matches!(specifier.format, RtFormat::Display), + Variant::Number(_) => matches!( + specifier.format, + RtFormat::Display + | RtFormat::Binary + | RtFormat::Octal + | RtFormat::LowerHex + | RtFormat::UpperHex + | RtFormat::Debug + | RtFormat::LowerExp + | RtFormat::UpperExp + ), } } @@ -105,7 +115,7 @@ impl Function for Format { let Some(format_string) = args[0].as_str() else { return Err(DscError::Parser(t!("functions.format.formatInvalid").to_string())); }; - let mut position_args = Vec::new(); + let mut position_args = Vec::new(); for value in &args[1..] { let arg = match value { Value::Bool(b) => Variant::Boolean(*b), @@ -137,56 +147,72 @@ mod tests { #[test] fn position() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[format('world {0} - {1}', 'hello', 2)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[format('world {0} - {1}', 'hello', 2)]", &Context::new()) + .unwrap(); assert_eq!(result, "world hello - 2"); } #[test] fn reverse_position() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[format('two{1} - {0}world', 'hello', 2)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[format('two{1} - {0}world', 'hello', 2)]", &Context::new()) + .unwrap(); assert_eq!(result, "two2 - helloworld"); } #[test] fn repeated_position() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[format('{0} - {0}{1}', 'hello', 2)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[format('{0} - {0}{1}', 'hello', 2)]", &Context::new()) + .unwrap(); assert_eq!(result, "hello - hello2"); } #[test] fn numbers_as_hex() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[format('{0:x} = {1:X}', 12, 13)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[format('{0:x} = {1:X}', 12, 13)]", &Context::new()) + .unwrap(); assert_eq!(result, "c = D"); } #[test] fn numbers_as_octal() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[format('{0:o} == {1:o}', 12, 13)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[format('{0:o} == {1:o}', 12, 13)]", &Context::new()) + .unwrap(); assert_eq!(result, "14 == 15"); } #[test] fn numbers_as_binary() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[format('{0:b} = {1:b}', 12, 13)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[format('{0:b} = {1:b}', 12, 13)]", &Context::new()) + .unwrap(); assert_eq!(result, "1100 = 1101"); } #[test] fn numbers_as_exp() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[format('{0:e} = {1:E}', 12, 13)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[format('{0:e} = {1:E}', 12, 13)]", &Context::new()) + .unwrap(); assert_eq!(result, "1.2e1 = 1.3E1"); } #[test] fn numbers_as_display_just_one() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[format('hello {0} there', 12, 13)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[format('hello {0} there', 12, 13)]", &Context::new()) + .unwrap(); assert_eq!(result, "hello 12 there"); } @@ -284,7 +310,9 @@ mod tests { #[test] fn missing_format_type() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[format('hello {0:} there', 12, 13)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[format('hello {0:} there', 12, 13)]", &Context::new()) + .unwrap(); assert_eq!(result, "hello 12 there"); } } diff --git a/dsc_lib/src/functions/greater.rs b/dsc_lib/src/functions/greater.rs index 4083749c1..0a0c4edac 100644 --- a/dsc_lib/src/functions/greater.rs +++ b/dsc_lib/src/functions/greater.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -84,6 +84,9 @@ mod tests { let mut parser = Statement::new().unwrap(); let result = parser.parse_and_execute("[greater('5', 3)]", &Context::new()); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Arguments must be of the same type")); + assert!(result + .unwrap_err() + .to_string() + .contains("Arguments must be of the same type")); } } diff --git a/dsc_lib/src/functions/greater_or_equals.rs b/dsc_lib/src/functions/greater_or_equals.rs index aeb9cf76e..cdf917343 100644 --- a/dsc_lib/src/functions/greater_or_equals.rs +++ b/dsc_lib/src/functions/greater_or_equals.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -54,28 +54,36 @@ mod tests { #[test] fn number_greater_or_equals() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[greaterOrEquals(5,3)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[greaterOrEquals(5,3)]", &Context::new()) + .unwrap(); assert_eq!(result, true); } #[test] fn number_not_greater_or_equals() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[greaterOrEquals(3,5)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[greaterOrEquals(3,5)]", &Context::new()) + .unwrap(); assert_eq!(result, false); } #[test] fn number_equal() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[greaterOrEquals(5,5)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[greaterOrEquals(5,5)]", &Context::new()) + .unwrap(); assert_eq!(result, true); } #[test] fn string_greater_or_equals() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[greaterOrEquals('b','a')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[greaterOrEquals('b','a')]", &Context::new()) + .unwrap(); assert_eq!(result, true); } @@ -84,6 +92,9 @@ mod tests { let mut parser = Statement::new().unwrap(); let result = parser.parse_and_execute("[greaterOrEquals('5', 3)]", &Context::new()); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Arguments must be of the same type")); + assert!(result + .unwrap_err() + .to_string() + .contains("Arguments must be of the same type")); } } diff --git a/dsc_lib/src/functions/if.rs b/dsc_lib/src/functions/if.rs index 12dfed340..fe3f69217 100644 --- a/dsc_lib/src/functions/if.rs +++ b/dsc_lib/src/functions/if.rs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; +use super::Function; use crate::configure::context::Context; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; -use super::Function; +use crate::DscError; use rust_i18n::t; use serde_json::Value; @@ -21,17 +21,35 @@ impl Function for If { max_args: 3, accepted_arg_ordered_types: vec![ vec![FunctionArgKind::Boolean], - vec![FunctionArgKind::String, FunctionArgKind::Number, FunctionArgKind::Array, FunctionArgKind::Object], - vec![FunctionArgKind::String, FunctionArgKind::Number, FunctionArgKind::Array, FunctionArgKind::Object], + vec![ + FunctionArgKind::String, + FunctionArgKind::Number, + FunctionArgKind::Array, + FunctionArgKind::Object, + ], + vec![ + FunctionArgKind::String, + FunctionArgKind::Number, + FunctionArgKind::Array, + FunctionArgKind::Object, ], + ], remaining_arg_accepted_types: None, - return_types: vec![FunctionArgKind::String, FunctionArgKind::Number, FunctionArgKind::Array, FunctionArgKind::Object], + return_types: vec![ + FunctionArgKind::String, + FunctionArgKind::Number, + FunctionArgKind::Array, + FunctionArgKind::Object, + ], } } fn invoke(&self, args: &[Value], _context: &Context) -> Result { let Some(condition) = args[0].as_bool() else { - return Err(DscError::Function("if".to_string(), t!("functions.if.conditionNotBoolean").to_string())); + return Err(DscError::Function( + "if".to_string(), + t!("functions.if.conditionNotBoolean").to_string(), + )); }; if condition { @@ -57,14 +75,18 @@ mod tests { #[test] fn condition_true() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[if(true, 'left', 'right')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[if(true, 'left', 'right')]", &Context::new()) + .unwrap(); assert_eq!(result, "left"); } #[test] fn condition_false() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[if(false, 'left', 'right')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[if(false, 'left', 'right')]", &Context::new()) + .unwrap(); assert_eq!(result, "right"); } } diff --git a/dsc_lib/src/functions/index_of.rs b/dsc_lib/src/functions/index_of.rs index 136157785..a70a19a4f 100644 --- a/dsc_lib/src/functions/index_of.rs +++ b/dsc_lib/src/functions/index_of.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -21,7 +21,12 @@ impl Function for IndexOf { max_args: 2, accepted_arg_ordered_types: vec![ vec![FunctionArgKind::Array], - vec![FunctionArgKind::String, FunctionArgKind::Number, FunctionArgKind::Array, FunctionArgKind::Object], + vec![ + FunctionArgKind::String, + FunctionArgKind::Number, + FunctionArgKind::Array, + FunctionArgKind::Object, + ], ], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Number], @@ -30,7 +35,7 @@ impl Function for IndexOf { fn invoke(&self, args: &[Value], _context: &Context) -> Result { debug!("{}", t!("functions.indexOf.invoked")); - + let Some(array) = args[0].as_array() else { return Err(DscError::Parser(t!("functions.indexOf.invalidArrayArg").to_string())); }; @@ -39,9 +44,8 @@ impl Function for IndexOf { for (index, item) in array.iter().enumerate() { if item == item_to_find { - let index_i64 = i64::try_from(index).map_err(|_| { - DscError::Parser("Array index too large to represent as integer".to_string()) - })?; + let index_i64 = i64::try_from(index) + .map_err(|_| DscError::Parser("Array index too large to represent as integer".to_string()))?; return Ok(Value::Number(index_i64.into())); } } @@ -59,42 +63,60 @@ mod tests { #[test] fn find_string_in_array() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[indexOf(createArray('apple', 'banana', 'cherry'), 'banana')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute( + "[indexOf(createArray('apple', 'banana', 'cherry'), 'banana')]", + &Context::new(), + ) + .unwrap(); assert_eq!(result, 1); } #[test] fn find_number_in_array() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[indexOf(createArray(10, 20, 30), 20)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[indexOf(createArray(10, 20, 30), 20)]", &Context::new()) + .unwrap(); assert_eq!(result, 1); } #[test] fn find_first_occurrence() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[indexOf(createArray('a', 'b', 'a', 'c'), 'a')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[indexOf(createArray('a', 'b', 'a', 'c'), 'a')]", &Context::new()) + .unwrap(); assert_eq!(result, 0); } #[test] fn item_not_found() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[indexOf(createArray('apple', 'banana'), 'orange')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[indexOf(createArray('apple', 'banana'), 'orange')]", &Context::new()) + .unwrap(); assert_eq!(result, -1); } #[test] fn case_sensitive_string() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[indexOf(createArray('Apple', 'Banana'), 'apple')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[indexOf(createArray('Apple', 'Banana'), 'apple')]", &Context::new()) + .unwrap(); assert_eq!(result, -1); } #[test] fn find_array_in_array() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[indexOf(createArray(createArray('a', 'b'), createArray('c', 'd')), createArray('c', 'd'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute( + "[indexOf(createArray(createArray('a', 'b'), createArray('c', 'd')), createArray('c', 'd'))]", + &Context::new(), + ) + .unwrap(); assert_eq!(result, 1); } @@ -108,7 +130,9 @@ mod tests { #[test] fn empty_array() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[indexOf(createArray(), 'test')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[indexOf(createArray(), 'test')]", &Context::new()) + .unwrap(); assert_eq!(result, -1); } diff --git a/dsc_lib/src/functions/int.rs b/dsc_lib/src/functions/int.rs index e1ae91fa9..92d0cf01d 100644 --- a/dsc_lib/src/functions/int.rs +++ b/dsc_lib/src/functions/int.rs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; +use super::Function; use crate::configure::context::Context; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use num_traits::cast::NumCast; use rust_i18n::t; use serde_json::Value; -use super::Function; #[derive(Debug, Default)] pub struct Int {} @@ -30,13 +30,27 @@ impl Function for Int { let arg = &args[0]; let value: i64; if arg.is_string() { - let input = arg.as_str().ok_or(DscError::FunctionArg("int".to_string(), t!("functions.int.invalidInput").to_string()))?; - let result = input.parse::().map_err(|_| DscError::FunctionArg("int".to_string(), t!("functions.int.parseStringError").to_string()))?; - value = NumCast::from(result).ok_or(DscError::FunctionArg("int".to_string(), t!("functions.int.castError").to_string()))?; + let input = arg.as_str().ok_or(DscError::FunctionArg( + "int".to_string(), + t!("functions.int.invalidInput").to_string(), + ))?; + let result = input.parse::().map_err(|_| { + DscError::FunctionArg("int".to_string(), t!("functions.int.parseStringError").to_string()) + })?; + value = NumCast::from(result).ok_or(DscError::FunctionArg( + "int".to_string(), + t!("functions.int.castError").to_string(), + ))?; } else if arg.is_number() { - value = arg.as_i64().ok_or(DscError::FunctionArg("int".to_string(), t!("functions.int.parseNumError").to_string()))?; + value = arg.as_i64().ok_or(DscError::FunctionArg( + "int".to_string(), + t!("functions.int.parseNumError").to_string(), + ))?; } else { - return Err(DscError::FunctionArg("int".to_string(), t!("functions.invalidArgType").to_string())); + return Err(DscError::FunctionArg( + "int".to_string(), + t!("functions.invalidArgType").to_string(), + )); } Ok(Value::Number(value.into())) } diff --git a/dsc_lib/src/functions/intersection.rs b/dsc_lib/src/functions/intersection.rs index 292529ea3..f98b162ec 100644 --- a/dsc_lib/src/functions/intersection.rs +++ b/dsc_lib/src/functions/intersection.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::{Map, Value}; use tracing::debug; @@ -30,13 +30,13 @@ impl Function for Intersection { fn invoke(&self, args: &[Value], _context: &Context) -> Result { debug!("{}", t!("functions.intersection.invoked")); - + if let Some(first_array) = args[0].as_array() { let mut result = Vec::new(); - + for item in first_array { let mut found_in_all = true; - + for arg in &args[1..] { if let Some(array) = arg.as_array() { if !array.contains(item) { @@ -44,24 +44,26 @@ impl Function for Intersection { break; } } else { - return Err(DscError::Parser(t!("functions.intersection.invalidArgType").to_string())); + return Err(DscError::Parser( + t!("functions.intersection.invalidArgType").to_string(), + )); } } - + if found_in_all && !result.contains(item) { result.push(item.clone()); } } - + return Ok(Value::Array(result)); } if let Some(first_object) = args[0].as_object() { let mut result = Map::new(); - + for (key, value) in first_object { let mut found_in_all = true; - + for arg in &args[1..] { if let Some(object) = arg.as_object() { if let Some(other_value) = object.get(key) { @@ -74,19 +76,23 @@ impl Function for Intersection { break; } } else { - return Err(DscError::Parser(t!("functions.intersection.invalidArgType").to_string())); + return Err(DscError::Parser( + t!("functions.intersection.invalidArgType").to_string(), + )); } } - + if found_in_all { result.insert(key.clone(), value.clone()); } } - + return Ok(Value::Object(result)); } - Err(DscError::Parser(t!("functions.intersection.invalidArgType").to_string())) + Err(DscError::Parser( + t!("functions.intersection.invalidArgType").to_string(), + )) } } @@ -98,56 +104,91 @@ mod tests { #[test] fn array_intersection() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[intersection(createArray(1, 2, 3), createArray(2, 3, 4))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute( + "[intersection(createArray(1, 2, 3), createArray(2, 3, 4))]", + &Context::new(), + ) + .unwrap(); assert_eq!(result, serde_json::json!([2, 3])); } #[test] fn array_intersection_three_arrays() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[intersection(createArray(1, 2, 3, 4), createArray(2, 3, 4, 5), createArray(3, 4, 5, 6))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute( + "[intersection(createArray(1, 2, 3, 4), createArray(2, 3, 4, 5), createArray(3, 4, 5, 6))]", + &Context::new(), + ) + .unwrap(); assert_eq!(result, serde_json::json!([3, 4])); } #[test] fn array_intersection_no_common_elements() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[intersection(createArray(1, 2), createArray(3, 4))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[intersection(createArray(1, 2), createArray(3, 4))]", &Context::new()) + .unwrap(); assert_eq!(result, serde_json::json!([])); } #[test] fn array_intersection_with_duplicates() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[intersection(createArray(1, 2, 2, 3), createArray(2, 2, 3, 4))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute( + "[intersection(createArray(1, 2, 2, 3), createArray(2, 2, 3, 4))]", + &Context::new(), + ) + .unwrap(); assert_eq!(result, serde_json::json!([2, 3])); } #[test] fn object_intersection() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[intersection(createObject('a', 1, 'b', 2), createObject('b', 2, 'c', 3))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute( + "[intersection(createObject('a', 1, 'b', 2), createObject('b', 2, 'c', 3))]", + &Context::new(), + ) + .unwrap(); assert_eq!(result, serde_json::json!({"b": 2})); } #[test] fn object_intersection_different_values() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[intersection(createObject('a', 1, 'b', 2), createObject('a', 2, 'b', 2))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute( + "[intersection(createObject('a', 1, 'b', 2), createObject('a', 2, 'b', 2))]", + &Context::new(), + ) + .unwrap(); assert_eq!(result, serde_json::json!({"b": 2})); } #[test] fn object_intersection_no_common_keys() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[intersection(createObject('a', 1), createObject('b', 2))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute( + "[intersection(createObject('a', 1), createObject('b', 2))]", + &Context::new(), + ) + .unwrap(); assert_eq!(result, serde_json::json!({})); } #[test] fn mixed_types_error() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[intersection(createArray(1, 2), createObject('a', 1))]", &Context::new()); + let result = parser.parse_and_execute( + "[intersection(createArray(1, 2), createObject('a', 1))]", + &Context::new(), + ); assert!(result.is_err()); } -} \ No newline at end of file +} diff --git a/dsc_lib/src/functions/join.rs b/dsc_lib/src/functions/join.rs index 1f7354bcb..94d70394d 100644 --- a/dsc_lib/src/functions/join.rs +++ b/dsc_lib/src/functions/join.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -30,10 +30,7 @@ impl Function for Join { category: vec![FunctionCategory::String], min_args: 2, max_args: 2, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::Array], - vec![FunctionArgKind::String], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::Array], vec![FunctionArgKind::String]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::String], } @@ -56,29 +53,35 @@ impl Function for Join { #[cfg(test)] mod tests { - use crate::configure::context::Context; - use crate::parser::Statement; use super::Join; + use crate::configure::context::Context; use crate::functions::Function; + use crate::parser::Statement; #[test] fn join_array_of_strings() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[join(createArray('a','b','c'), '-')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[join(createArray('a','b','c'), '-')]", &Context::new()) + .unwrap(); assert_eq!(result, "a-b-c"); } #[test] fn join_empty_array_returns_empty() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[join(createArray(), '-')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[join(createArray(), '-')]", &Context::new()) + .unwrap(); assert_eq!(result, ""); } #[test] fn join_array_of_integers() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[join(createArray(1,2,3), ',')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[join(createArray(1,2,3), ',')]", &Context::new()) + .unwrap(); assert_eq!(result, "1,2,3"); } @@ -88,7 +91,10 @@ mod tests { let result = parser.parse_and_execute("[join(createArray('a', null()), ',')]", &Context::new()); assert!(result.is_err()); // The error comes from argument validation, not our function - assert!(result.unwrap_err().to_string().contains("does not accept null arguments")); + assert!(result + .unwrap_err() + .to_string() + .contains("does not accept null arguments")); } #[test] @@ -103,7 +109,10 @@ mod tests { #[test] fn join_array_with_object_fails() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[join(createArray('a', createObject('key', 'value')), ',')]", &Context::new()); + let result = parser.parse_and_execute( + "[join(createArray('a', createObject('key', 'value')), ',')]", + &Context::new(), + ); assert!(result.is_err()); let error_msg = result.unwrap_err().to_string(); assert!(error_msg.contains("Arguments must all be") || error_msg.contains("mixed types")); @@ -111,16 +120,16 @@ mod tests { #[test] fn join_direct_test_with_mixed_array() { - use serde_json::json; use crate::configure::context::Context; - + use serde_json::json; + let join_fn = Join::default(); let args = vec![ json!(["hello", {"key": "value"}]), // Array with string and object - json!(",") + json!(","), ]; let result = join_fn.invoke(&args, &Context::new()); - + assert!(result.is_err()); let error_msg = result.unwrap_err().to_string(); assert!(error_msg.contains("Array elements cannot be objects")); diff --git a/dsc_lib/src/functions/last_index_of.rs b/dsc_lib/src/functions/last_index_of.rs index fad0996f1..ed6147508 100644 --- a/dsc_lib/src/functions/last_index_of.rs +++ b/dsc_lib/src/functions/last_index_of.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -21,7 +21,12 @@ impl Function for LastIndexOf { max_args: 2, accepted_arg_ordered_types: vec![ vec![FunctionArgKind::Array], - vec![FunctionArgKind::String, FunctionArgKind::Number, FunctionArgKind::Array, FunctionArgKind::Object], + vec![ + FunctionArgKind::String, + FunctionArgKind::Number, + FunctionArgKind::Array, + FunctionArgKind::Object, + ], ], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Number], @@ -32,15 +37,16 @@ impl Function for LastIndexOf { debug!("{}", t!("functions.lastIndexOf.invoked")); let Some(array) = args[0].as_array() else { - return Err(DscError::Parser(t!("functions.lastIndexOf.invalidArrayArg").to_string())); + return Err(DscError::Parser( + t!("functions.lastIndexOf.invalidArrayArg").to_string(), + )); }; let item_to_find = &args[1]; if let Some(pos) = array.iter().rposition(|v| v == item_to_find) { - let index_i64 = i64::try_from(pos).map_err(|_| { - DscError::Parser("Array index too large to represent as integer".to_string()) - })?; + let index_i64 = i64::try_from(pos) + .map_err(|_| DscError::Parser("Array index too large to represent as integer".to_string()))?; return Ok(Value::Number(index_i64.into())); } @@ -56,21 +62,27 @@ mod tests { #[test] fn finds_last_occurrence_string() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[lastIndexOf(createArray('a','b','a','c'), 'a')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[lastIndexOf(createArray('a','b','a','c'), 'a')]", &Context::new()) + .unwrap(); assert_eq!(result, 2); } #[test] fn finds_last_occurrence_number() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[lastIndexOf(createArray(10,20,30,20), 20)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[lastIndexOf(createArray(10,20,30,20), 20)]", &Context::new()) + .unwrap(); assert_eq!(result, 3); } #[test] fn not_found_returns_minus_one() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[lastIndexOf(createArray('x','y'), 'z')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[lastIndexOf(createArray('x','y'), 'z')]", &Context::new()) + .unwrap(); assert_eq!(result, -1); } diff --git a/dsc_lib/src/functions/length.rs b/dsc_lib/src/functions/length.rs index f773ef8b1..e12bbdae5 100644 --- a/dsc_lib/src/functions/length.rs +++ b/dsc_lib/src/functions/length.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -16,10 +16,18 @@ impl Function for Length { FunctionMetadata { name: "length".to_string(), description: t!("functions.length.description").to_string(), - category: vec![FunctionCategory::Array, FunctionCategory::Object, FunctionCategory::String], + category: vec![ + FunctionCategory::Array, + FunctionCategory::Object, + FunctionCategory::String, + ], min_args: 1, max_args: 1, - accepted_arg_ordered_types: vec![vec![FunctionArgKind::Array, FunctionArgKind::Object, FunctionArgKind::String]], + accepted_arg_ordered_types: vec![vec![ + FunctionArgKind::Array, + FunctionArgKind::Object, + FunctionArgKind::String, + ]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Number], } @@ -43,7 +51,6 @@ impl Function for Length { } } - #[cfg(test)] mod tests { use crate::configure::context::Context; diff --git a/dsc_lib/src/functions/less.rs b/dsc_lib/src/functions/less.rs index 98dbccff5..028f961b0 100644 --- a/dsc_lib/src/functions/less.rs +++ b/dsc_lib/src/functions/less.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -84,6 +84,9 @@ mod tests { let mut parser = Statement::new().unwrap(); let result = parser.parse_and_execute("[less('5', 3)]", &Context::new()); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Arguments must be of the same type")); + assert!(result + .unwrap_err() + .to_string() + .contains("Arguments must be of the same type")); } } diff --git a/dsc_lib/src/functions/less_or_equals.rs b/dsc_lib/src/functions/less_or_equals.rs index 52404d602..98fa8d724 100644 --- a/dsc_lib/src/functions/less_or_equals.rs +++ b/dsc_lib/src/functions/less_or_equals.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -54,28 +54,36 @@ mod tests { #[test] fn number_less_or_equals() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[lessOrEquals(3,5)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[lessOrEquals(3,5)]", &Context::new()) + .unwrap(); assert_eq!(result, true); } #[test] fn number_not_less_or_equals() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[lessOrEquals(5,3)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[lessOrEquals(5,3)]", &Context::new()) + .unwrap(); assert_eq!(result, false); } #[test] fn number_equal() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[lessOrEquals(5,5)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[lessOrEquals(5,5)]", &Context::new()) + .unwrap(); assert_eq!(result, true); } #[test] fn string_less_or_equals() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[lessOrEquals('a','b')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[lessOrEquals('a','b')]", &Context::new()) + .unwrap(); assert_eq!(result, true); } @@ -84,6 +92,9 @@ mod tests { let mut parser = Statement::new().unwrap(); let result = parser.parse_and_execute("[lessOrEquals('5', 3)]", &Context::new()); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Arguments must be of the same type")); + assert!(result + .unwrap_err() + .to_string() + .contains("Arguments must be of the same type")); } } diff --git a/dsc_lib/src/functions/max.rs b/dsc_lib/src/functions/max.rs index 5047c0641..dec38a3a2 100644 --- a/dsc_lib/src/functions/max.rs +++ b/dsc_lib/src/functions/max.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -30,20 +30,27 @@ impl Function for Max { if args.len() == 1 { if let Some(array) = args[0].as_array() { find_max(array) - } - else { + } else { Err(DscError::Parser(t!("functions.max.emptyArray").to_string())) } - } - else { + } else { find_max(args) } } } fn find_max(args: &[Value]) -> Result { - let array = args.iter().map(|v| v.as_i64().ok_or(DscError::Parser(t!("functions.max.integersOnly").to_string()))).collect::, DscError>>()?; - let value = array.iter().max().ok_or(DscError::Parser(t!("functions.max.noMax").to_string()))?; + let array = args + .iter() + .map(|v| { + v.as_i64() + .ok_or(DscError::Parser(t!("functions.max.integersOnly").to_string())) + }) + .collect::, DscError>>()?; + let value = array + .iter() + .max() + .ok_or(DscError::Parser(t!("functions.max.noMax").to_string()))?; Ok(Value::Number((*value).into())) } @@ -69,14 +76,18 @@ mod tests { #[test] fn array() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[max(createArray(0, 3, 2, 7, 4))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[max(createArray(0, 3, 2, 7, 4))]", &Context::new()) + .unwrap(); assert_eq!(result, 7); } #[test] fn array_single_value() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[max(createArray(0))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[max(createArray(0))]", &Context::new()) + .unwrap(); assert_eq!(result, 0); } @@ -97,7 +108,9 @@ mod tests { #[test] fn nested() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[max(-10, max(-2, -9), -5)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[max(-10, max(-2, -9), -5)]", &Context::new()) + .unwrap(); assert_eq!(result, -2); } diff --git a/dsc_lib/src/functions/min.rs b/dsc_lib/src/functions/min.rs index f6e00ddf8..0802be025 100644 --- a/dsc_lib/src/functions/min.rs +++ b/dsc_lib/src/functions/min.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -30,20 +30,27 @@ impl Function for Min { if args.len() == 1 { if let Some(array) = args[0].as_array() { find_min(array) - } - else { + } else { Err(DscError::Parser(t!("functions.min.emptyArray").to_string())) } - } - else { + } else { find_min(args) } } } fn find_min(args: &[Value]) -> Result { - let array = args.iter().map(|v| v.as_i64().ok_or(DscError::Parser(t!("functions.min.integersOnly").to_string()))).collect::, DscError>>()?; - let value = array.iter().min().ok_or(DscError::Parser(t!("functions.min.noMin").to_string()))?; + let array = args + .iter() + .map(|v| { + v.as_i64() + .ok_or(DscError::Parser(t!("functions.min.integersOnly").to_string())) + }) + .collect::, DscError>>()?; + let value = array + .iter() + .min() + .ok_or(DscError::Parser(t!("functions.min.noMin").to_string()))?; Ok(Value::Number((*value).into())) } @@ -69,14 +76,18 @@ mod tests { #[test] fn array() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[min(createArray(0, 3, 2, 5, 4))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[min(createArray(0, 3, 2, 5, 4))]", &Context::new()) + .unwrap(); assert_eq!(result, 0); } #[test] fn array_single_value() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[min(createArray(0))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[min(createArray(0))]", &Context::new()) + .unwrap(); assert_eq!(result, 0); } @@ -97,7 +108,9 @@ mod tests { #[test] fn nested() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[min(8, min(2, -9), 3)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[min(8, min(2, -9), 3)]", &Context::new()) + .unwrap(); assert_eq!(result, -9); } diff --git a/dsc_lib/src/functions/mod.rs b/dsc_lib/src/functions/mod.rs index ce3441072..64926e997 100644 --- a/dsc_lib/src/functions/mod.rs +++ b/dsc_lib/src/functions/mod.rs @@ -3,9 +3,9 @@ use std::collections::HashMap; -use crate::DscError; use crate::configure::context::Context; use crate::functions::user_function::invoke_user_function; +use crate::DscError; use rust_i18n::t; use schemars::JsonSchema; use serde::Serialize; @@ -30,20 +30,20 @@ pub mod empty; pub mod ends_with; pub mod envvar; pub mod equals; -pub mod greater; -pub mod greater_or_equals; -pub mod r#if; pub mod r#false; pub mod first; -pub mod length; -pub mod less; -pub mod less_or_equals; pub mod format; -pub mod int; +pub mod greater; +pub mod greater_or_equals; +pub mod r#if; pub mod index_of; +pub mod int; pub mod intersection; pub mod join; pub mod last_index_of; +pub mod length; +pub mod less; +pub mod less_or_equals; pub mod max; pub mod min; pub mod mod_function; @@ -132,73 +132,71 @@ impl FunctionDispatcher { #[must_use] pub fn new() -> Self { let mut functions: HashMap> = HashMap::new(); - let function_list : Vec> = vec![ - Box::new(add::Add{}), - Box::new(and::And{}), - Box::new(array::Array{}), - Box::new(base64::Base64{}), - Box::new(base64_to_string::Base64ToString{}), - Box::new(bool::Bool{}), - Box::new(coalesce::Coalesce{}), - Box::new(concat::Concat{}), - Box::new(contains::Contains{}), - Box::new(context::Context{}), - Box::new(copy_index::CopyIndex{}), - Box::new(create_array::CreateArray{}), - Box::new(create_object::CreateObject{}), - Box::new(div::Div{}), - Box::new(empty::Empty{}), - Box::new(ends_with::EndsWith{}), - Box::new(envvar::Envvar{}), - Box::new(equals::Equals{}), - Box::new(greater::Greater{}), - Box::new(greater_or_equals::GreaterOrEquals{}), - Box::new(r#if::If{}), - Box::new(r#false::False{}), - Box::new(first::First{}), - Box::new(length::Length{}), - Box::new(less::Less{}), - Box::new(less_or_equals::LessOrEquals{}), - Box::new(format::Format{}), - Box::new(int::Int{}), - Box::new(index_of::IndexOf{}), - Box::new(intersection::Intersection{}), - Box::new(join::Join{}), - Box::new(last_index_of::LastIndexOf{}), - Box::new(max::Max{}), - Box::new(min::Min{}), - Box::new(mod_function::Mod{}), - Box::new(mul::Mul{}), - Box::new(not::Not{}), - Box::new(null::Null{}), - Box::new(or::Or{}), - Box::new(parameters::Parameters{}), - Box::new(path::Path{}), - Box::new(range::Range{}), - Box::new(reference::Reference{}), - Box::new(resource_id::ResourceId{}), - Box::new(secret::Secret{}), - Box::new(skip::Skip{}), - Box::new(starts_with::StartsWith{}), - Box::new(string::StringFn{}), - Box::new(sub::Sub{}), - Box::new(substring::Substring{}), - Box::new(system_root::SystemRoot{}), - Box::new(to_lower::ToLower{}), - Box::new(to_upper::ToUpper{}), - Box::new(r#true::True{}), - Box::new(utc_now::UtcNow{}), - Box::new(union::Union{}), - Box::new(unique_string::UniqueString{}), - Box::new(variables::Variables{}), + let function_list: Vec> = vec![ + Box::new(add::Add {}), + Box::new(and::And {}), + Box::new(array::Array {}), + Box::new(base64::Base64 {}), + Box::new(base64_to_string::Base64ToString {}), + Box::new(bool::Bool {}), + Box::new(coalesce::Coalesce {}), + Box::new(concat::Concat {}), + Box::new(contains::Contains {}), + Box::new(context::Context {}), + Box::new(copy_index::CopyIndex {}), + Box::new(create_array::CreateArray {}), + Box::new(create_object::CreateObject {}), + Box::new(div::Div {}), + Box::new(empty::Empty {}), + Box::new(ends_with::EndsWith {}), + Box::new(envvar::Envvar {}), + Box::new(equals::Equals {}), + Box::new(greater::Greater {}), + Box::new(greater_or_equals::GreaterOrEquals {}), + Box::new(r#if::If {}), + Box::new(r#false::False {}), + Box::new(first::First {}), + Box::new(length::Length {}), + Box::new(less::Less {}), + Box::new(less_or_equals::LessOrEquals {}), + Box::new(format::Format {}), + Box::new(int::Int {}), + Box::new(index_of::IndexOf {}), + Box::new(intersection::Intersection {}), + Box::new(join::Join {}), + Box::new(last_index_of::LastIndexOf {}), + Box::new(max::Max {}), + Box::new(min::Min {}), + Box::new(mod_function::Mod {}), + Box::new(mul::Mul {}), + Box::new(not::Not {}), + Box::new(null::Null {}), + Box::new(or::Or {}), + Box::new(parameters::Parameters {}), + Box::new(path::Path {}), + Box::new(range::Range {}), + Box::new(reference::Reference {}), + Box::new(resource_id::ResourceId {}), + Box::new(secret::Secret {}), + Box::new(skip::Skip {}), + Box::new(starts_with::StartsWith {}), + Box::new(string::StringFn {}), + Box::new(sub::Sub {}), + Box::new(substring::Substring {}), + Box::new(system_root::SystemRoot {}), + Box::new(to_lower::ToLower {}), + Box::new(to_upper::ToUpper {}), + Box::new(r#true::True {}), + Box::new(utc_now::UtcNow {}), + Box::new(union::Union {}), + Box::new(unique_string::UniqueString {}), + Box::new(variables::Variables {}), ]; for function in function_list { functions.insert(function.get_metadata().name.clone(), function); } - Self { - functions, - } + Self { functions } } /// Invoke a function. @@ -217,7 +215,9 @@ impl FunctionDispatcher { if name.contains('.') { return invoke_user_function(name, args, context); } - return Err(DscError::Parser(t!("functions.unknownFunction", name = name).to_string())); + return Err(DscError::Parser( + t!("functions.unknownFunction", name = name).to_string(), + )); }; let metadata = function.get_metadata(); @@ -227,16 +227,28 @@ impl FunctionDispatcher { let max_args = metadata.max_args; if args.len() < min_args || args.len() > max_args { if max_args == 0 { - return Err(DscError::Parser(t!("functions.noArgsAccepted", name = name).to_string())); - } - else if min_args == max_args { - return Err(DscError::Parser(t!("functions.invalidArgCount", name = name, count = min_args).to_string())); - } - else if max_args == usize::MAX { - return Err(DscError::Parser(t!("functions.minArgsRequired", name = name, count = min_args).to_string())); + return Err(DscError::Parser( + t!("functions.noArgsAccepted", name = name).to_string(), + )); + } else if min_args == max_args { + return Err(DscError::Parser( + t!("functions.invalidArgCount", name = name, count = min_args).to_string(), + )); + } else if max_args == usize::MAX { + return Err(DscError::Parser( + t!("functions.minArgsRequired", name = name, count = min_args).to_string(), + )); } - return Err(DscError::Parser(t!("functions.argCountRequired", name = name, min = min_args, max = max_args).to_string())); + return Err(DscError::Parser( + t!( + "functions.argCountRequired", + name = name, + min = min_args, + max = max_args + ) + .to_string(), + )); } for (index, value) in args.iter().enumerate() { @@ -257,38 +269,111 @@ impl FunctionDispatcher { function.invoke(args, context) } - fn check_arg_against_expected_types(name: &str, arg: &Value, expected_types: &[FunctionArgKind]) -> Result<(), DscError> { + fn check_arg_against_expected_types( + name: &str, + arg: &Value, + expected_types: &[FunctionArgKind], + ) -> Result<(), DscError> { if arg.is_array() && !expected_types.contains(&FunctionArgKind::Array) { - return Err(DscError::Parser(t!("functions.noArrayArgs", name = name, accepted_args_string = expected_types.iter().map(std::string::ToString::to_string).collect::>().join(", ")).to_string())); + return Err(DscError::Parser( + t!( + "functions.noArrayArgs", + name = name, + accepted_args_string = expected_types + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(", ") + ) + .to_string(), + )); } else if arg.is_boolean() && !expected_types.contains(&FunctionArgKind::Boolean) { - return Err(DscError::Parser(t!("functions.noBooleanArgs", name = name, accepted_args_string = expected_types.iter().map(std::string::ToString::to_string).collect::>().join(", ")).to_string())); + return Err(DscError::Parser( + t!( + "functions.noBooleanArgs", + name = name, + accepted_args_string = expected_types + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(", ") + ) + .to_string(), + )); } else if arg.is_null() && !expected_types.contains(&FunctionArgKind::Null) { - return Err(DscError::Parser(t!("functions.noNullArgs", name = name, accepted_args_string = expected_types.iter().map(std::string::ToString::to_string).collect::>().join(", ")).to_string())); + return Err(DscError::Parser( + t!( + "functions.noNullArgs", + name = name, + accepted_args_string = expected_types + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(", ") + ) + .to_string(), + )); } else if arg.is_number() && !expected_types.contains(&FunctionArgKind::Number) { - return Err(DscError::Parser(t!("functions.noNumberArgs", name = name, accepted_args_string = expected_types.iter().map(std::string::ToString::to_string).collect::>().join(", ")).to_string())); + return Err(DscError::Parser( + t!( + "functions.noNumberArgs", + name = name, + accepted_args_string = expected_types + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(", ") + ) + .to_string(), + )); } else if arg.is_object() && !expected_types.contains(&FunctionArgKind::Object) { - return Err(DscError::Parser(t!("functions.noObjectArgs", name = name, accepted_args_string = expected_types.iter().map(std::string::ToString::to_string).collect::>().join(", ")).to_string())); + return Err(DscError::Parser( + t!( + "functions.noObjectArgs", + name = name, + accepted_args_string = expected_types + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(", ") + ) + .to_string(), + )); } else if arg.is_string() && !expected_types.contains(&FunctionArgKind::String) { - return Err(DscError::Parser(t!("functions.noStringArgs", name = name, accepted_args_string = expected_types.iter().map(std::string::ToString::to_string).collect::>().join(", ")).to_string())); + return Err(DscError::Parser( + t!( + "functions.noStringArgs", + name = name, + accepted_args_string = expected_types + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(", ") + ) + .to_string(), + )); } Ok(()) } #[must_use] pub fn list(&self) -> Vec { - self.functions.iter().map(|(name, function)| { - let metadata = function.get_metadata(); - FunctionDefinition { - category: metadata.category.clone(), - name: name.clone(), - description: metadata.description, - min_args: metadata.min_args, - max_args: metadata.max_args, - accepted_arg_ordered_types: metadata.accepted_arg_ordered_types.clone(), - remaining_arg_accepted_types: metadata.remaining_arg_accepted_types.clone(), - return_types: metadata.return_types, - } - }).collect() + self.functions + .iter() + .map(|(name, function)| { + let metadata = function.get_metadata(); + FunctionDefinition { + category: metadata.category.clone(), + name: name.clone(), + description: metadata.description, + min_args: metadata.min_args, + max_args: metadata.max_args, + accepted_arg_ordered_types: metadata.accepted_arg_ordered_types.clone(), + remaining_arg_accepted_types: metadata.remaining_arg_accepted_types.clone(), + return_types: metadata.return_types, + } + }) + .collect() } } diff --git a/dsc_lib/src/functions/mod_function.rs b/dsc_lib/src/functions/mod_function.rs index 091bc9c6c..e5d7fc3fe 100644 --- a/dsc_lib/src/functions/mod_function.rs +++ b/dsc_lib/src/functions/mod_function.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -19,10 +19,7 @@ impl Function for Mod { category: vec![FunctionCategory::Numeric], min_args: 2, max_args: 2, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::Number], - vec![FunctionArgKind::Number], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::Number], vec![FunctionArgKind::Number]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Number], } @@ -56,7 +53,9 @@ mod tests { #[test] fn nested() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[mod(18, mod(8, 3))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[mod(18, mod(8, 3))]", &Context::new()) + .unwrap(); assert_eq!(result, 0); } diff --git a/dsc_lib/src/functions/mul.rs b/dsc_lib/src/functions/mul.rs index 9eb61b678..736780b05 100644 --- a/dsc_lib/src/functions/mul.rs +++ b/dsc_lib/src/functions/mul.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -19,10 +19,7 @@ impl Function for Mul { category: vec![FunctionCategory::Numeric], min_args: 2, max_args: 2, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::Number], - vec![FunctionArgKind::Number], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::Number], vec![FunctionArgKind::Number]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Number], } @@ -53,7 +50,9 @@ mod tests { #[test] fn nested() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[mul(2, mul(3, 4))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[mul(2, mul(3, 4))]", &Context::new()) + .unwrap(); assert_eq!(result, 24); } diff --git a/dsc_lib/src/functions/not.rs b/dsc_lib/src/functions/not.rs index b409ed591..60192616f 100644 --- a/dsc_lib/src/functions/not.rs +++ b/dsc_lib/src/functions/not.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; diff --git a/dsc_lib/src/functions/null.rs b/dsc_lib/src/functions/null.rs index 457407476..41e6230b3 100644 --- a/dsc_lib/src/functions/null.rs +++ b/dsc_lib/src/functions/null.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -33,9 +33,9 @@ impl Function for Null { #[cfg(test)] mod tests { + use super::*; use crate::configure::context::Context; use crate::parser::Statement; - use super::*; #[test] fn direct_function_call() { @@ -56,14 +56,18 @@ mod tests { #[test] fn null_with_coalesce() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[coalesce(null(), 'fallback')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[coalesce(null(), 'fallback')]", &Context::new()) + .unwrap(); assert_eq!(result, "fallback"); } #[test] fn null_in_object() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[createObject('key', null())]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[createObject('key', null())]", &Context::new()) + .unwrap(); assert_eq!(result.to_string(), r#"{"key":null}"#); } } diff --git a/dsc_lib/src/functions/or.rs b/dsc_lib/src/functions/or.rs index a38305b72..742878803 100644 --- a/dsc_lib/src/functions/or.rs +++ b/dsc_lib/src/functions/or.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -19,10 +19,7 @@ impl Function for Or { category: vec![FunctionCategory::Logical], min_args: 2, max_args: usize::MAX, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::Boolean], - vec![FunctionArgKind::Boolean], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::Boolean], vec![FunctionArgKind::Boolean]], remaining_arg_accepted_types: Some(vec![FunctionArgKind::Boolean]), return_types: vec![FunctionArgKind::Boolean], } @@ -58,7 +55,9 @@ mod tests { #[test] fn multiple_values() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[or(true, false, true)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[or(true, false, true)]", &Context::new()) + .unwrap(); assert_eq!(result, true); } diff --git a/dsc_lib/src/functions/parameters.rs b/dsc_lib/src/functions/parameters.rs index dd1ee34ca..1804c9625 100644 --- a/dsc_lib/src/functions/parameters.rs +++ b/dsc_lib/src/functions/parameters.rs @@ -2,10 +2,10 @@ // Licensed under the MIT License. use crate::configure::config_doc::DataType; +use crate::configure::context::Context; use crate::configure::parameters::{SecureObject, SecureString}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; use crate::DscError; -use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; use rust_i18n::t; use serde_json::Value; use tracing::{debug, trace}; @@ -23,7 +23,14 @@ impl Function for Parameters { max_args: 1, accepted_arg_ordered_types: vec![vec![FunctionArgKind::String]], remaining_arg_accepted_types: None, - return_types: vec![FunctionArgKind::String, FunctionArgKind::Number, FunctionArgKind::Boolean, FunctionArgKind::Object, FunctionArgKind::Array, FunctionArgKind::Null], + return_types: vec![ + FunctionArgKind::String, + FunctionArgKind::Number, + FunctionArgKind::Boolean, + FunctionArgKind::Object, + FunctionArgKind::Array, + FunctionArgKind::Null, + ], } } @@ -38,26 +45,27 @@ impl Function for Parameters { match data_type { DataType::SecureString => { let Some(value) = value.as_str() else { - return Err(DscError::Parser(t!("functions.parameters.keyNotString", key = key).to_string())); + return Err(DscError::Parser( + t!("functions.parameters.keyNotString", key = key).to_string(), + )); }; let secure_string = SecureString { secure_string: value.to_string(), }; Ok(serde_json::to_value(secure_string)?) - }, + } DataType::SecureObject => { let secure_object = SecureObject { secure_object: value.clone(), }; Ok(serde_json::to_value(secure_object)?) - }, - _ => { - Ok(value.clone()) } + _ => Ok(value.clone()), } - } - else { - Err(DscError::Parser(t!("functions.parameters.keyNotFound", key = key).to_string())) + } else { + Err(DscError::Parser( + t!("functions.parameters.keyNotFound", key = key).to_string(), + )) } } else { Err(DscError::Parser(t!("functions.invalidArgType").to_string())) diff --git a/dsc_lib/src/functions/path.rs b/dsc_lib/src/functions/path.rs index c348b1ea7..01908f4e5 100644 --- a/dsc_lib/src/functions/path.rs +++ b/dsc_lib/src/functions/path.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use std::path::PathBuf; @@ -23,10 +23,7 @@ impl Function for Path { category: vec![FunctionCategory::String], min_args: 2, max_args: usize::MAX, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::String], - vec![FunctionArgKind::String], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::String], vec![FunctionArgKind::String]], remaining_arg_accepted_types: Some(vec![FunctionArgKind::String]), return_types: vec![FunctionArgKind::String], } @@ -58,7 +55,9 @@ mod tests { #[test] fn start_with_drive_letter() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[path('C:\\','test')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[path('C:\\','test')]", &Context::new()) + .unwrap(); #[cfg(target_os = "windows")] assert_eq!(result, format!("C:{SEPARATOR}test")); @@ -70,7 +69,9 @@ mod tests { #[test] fn drive_letter_in_middle() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[path('a','C:\\','test')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[path('a','C:\\','test')]", &Context::new()) + .unwrap(); // if any part of the path is absolute, it replaces it instead of appending on Windows #[cfg(target_os = "windows")] @@ -84,7 +85,9 @@ mod tests { #[test] fn multiple_drive_letters() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[path('C:\\','D:\\','test')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[path('C:\\','D:\\','test')]", &Context::new()) + .unwrap(); // if any part of the path is absolute, it replaces it instead of appending on Windows #[cfg(target_os = "windows")] @@ -98,21 +101,27 @@ mod tests { #[test] fn relative_path() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[path('a','..','b')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[path('a','..','b')]", &Context::new()) + .unwrap(); assert_eq!(result, format!("a{SEPARATOR}..{SEPARATOR}b")); } #[test] fn path_segement_with_separator() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute(format!("[path('a','b{SEPARATOR}c')]").as_str(), &Context::new()).unwrap(); + let result = parser + .parse_and_execute(format!("[path('a','b{SEPARATOR}c')]").as_str(), &Context::new()) + .unwrap(); assert_eq!(result, format!("a{SEPARATOR}b{SEPARATOR}c")); } #[test] fn unix_absolute_path() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[path('/','a','b')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[path('/','a','b')]", &Context::new()) + .unwrap(); assert_eq!(result, format!("/a{SEPARATOR}b")); } @@ -126,7 +135,9 @@ mod tests { #[test] fn three_args() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[path('a','b','c')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[path('a','b','c')]", &Context::new()) + .unwrap(); assert_eq!(result, format!("a{SEPARATOR}b{SEPARATOR}c")); } } diff --git a/dsc_lib/src/functions/range.rs b/dsc_lib/src/functions/range.rs index 434e3c032..d294d4e56 100644 --- a/dsc_lib/src/functions/range.rs +++ b/dsc_lib/src/functions/range.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -19,10 +19,7 @@ impl Function for Range { category: vec![FunctionCategory::Array], min_args: 2, max_args: 2, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::Number], - vec![FunctionArgKind::Number], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::Number], vec![FunctionArgKind::Number]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Array], } @@ -31,28 +28,42 @@ impl Function for Range { fn invoke(&self, args: &[Value], _context: &Context) -> Result { debug!("{}", t!("functions.range.invoked")); - let start_index = args[0].as_i64() - .ok_or_else(|| DscError::FunctionArg("range".to_string(), t!("functions.range.startIndexNotInt").to_string()))?; - - let count = args[1].as_i64() + let start_index = args[0].as_i64().ok_or_else(|| { + DscError::FunctionArg("range".to_string(), t!("functions.range.startIndexNotInt").to_string()) + })?; + + let count = args[1] + .as_i64() .ok_or_else(|| DscError::FunctionArg("range".to_string(), t!("functions.range.countNotInt").to_string()))?; // validation checks if count < 0 { - return Err(DscError::FunctionArg("range".to_string(), t!("functions.range.countNegative").to_string())); + return Err(DscError::FunctionArg( + "range".to_string(), + t!("functions.range.countNegative").to_string(), + )); } if count > 10000 { - return Err(DscError::FunctionArg("range".to_string(), t!("functions.range.countTooLarge").to_string())); + return Err(DscError::FunctionArg( + "range".to_string(), + t!("functions.range.countTooLarge").to_string(), + )); } // should not exceed if let Some(sum) = start_index.checked_add(count) { if sum > 2_147_483_647 { - return Err(DscError::FunctionArg("range".to_string(), t!("functions.range.sumTooLarge").to_string())); + return Err(DscError::FunctionArg( + "range".to_string(), + t!("functions.range.sumTooLarge").to_string(), + )); } } else { - return Err(DscError::FunctionArg("range".to_string(), t!("functions.range.sumOverflow").to_string())); + return Err(DscError::FunctionArg( + "range".to_string(), + t!("functions.range.sumOverflow").to_string(), + )); } let mut result = Vec::::new(); @@ -75,25 +86,36 @@ mod tests { fn basic_range() { let mut parser = Statement::new().unwrap(); let result = parser.parse_and_execute("[range(1, 3)]", &Context::new()).unwrap(); - assert_eq!(result, Value::Array(vec![Value::from(1), Value::from(2), Value::from(3)])); + assert_eq!( + result, + Value::Array(vec![Value::from(1), Value::from(2), Value::from(3)]) + ); } #[test] fn range_starting_zero() { let mut parser = Statement::new().unwrap(); let result = parser.parse_and_execute("[range(0, 5)]", &Context::new()).unwrap(); - assert_eq!(result, Value::Array(vec![ - Value::from(0), Value::from(1), Value::from(2), Value::from(3), Value::from(4) - ])); + assert_eq!( + result, + Value::Array(vec![ + Value::from(0), + Value::from(1), + Value::from(2), + Value::from(3), + Value::from(4) + ]) + ); } #[test] fn range_negative_start() { let mut parser = Statement::new().unwrap(); let result = parser.parse_and_execute("[range(-2, 4)]", &Context::new()).unwrap(); - assert_eq!(result, Value::Array(vec![ - Value::from(-2), Value::from(-1), Value::from(0), Value::from(1) - ])); + assert_eq!( + result, + Value::Array(vec![Value::from(-2), Value::from(-1), Value::from(0), Value::from(1)]) + ); } #[test] @@ -127,7 +149,9 @@ mod tests { #[test] fn range_large_valid() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[range(2147473647, 10000)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[range(2147473647, 10000)]", &Context::new()) + .unwrap(); if let Value::Array(arr) = result { assert_eq!(arr.len(), 10000); assert_eq!(arr[0], Value::from(2147473647)); diff --git a/dsc_lib/src/functions/reference.rs b/dsc_lib/src/functions/reference.rs index 07e0b37a6..76876bda5 100644 --- a/dsc_lib/src/functions/reference.rs +++ b/dsc_lib/src/functions/reference.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::{Context, ProcessMode}; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -35,17 +35,23 @@ impl Function for Reference { debug!("{}", t!("functions.reference.invoked")); if context.process_mode == ProcessMode::Copy { - return Err(DscError::Parser(t!("functions.reference.cannotUseInCopyMode").to_string())); + return Err(DscError::Parser( + t!("functions.reference.cannotUseInCopyMode").to_string(), + )); } if context.process_mode == ProcessMode::UserFunction { - return Err(DscError::Parser(t!("functions.reference.unavailableInUserFunction").to_string())); + return Err(DscError::Parser( + t!("functions.reference.unavailableInUserFunction").to_string(), + )); } if let Some(key) = args[0].as_str() { if context.references.contains_key(key) { Ok(context.references[key].clone()) } else { - Err(DscError::Parser(t!("functions.reference.keyNotFound", key = key).to_string())) + Err(DscError::Parser( + t!("functions.reference.keyNotFound", key = key).to_string(), + )) } } else { Err(DscError::Parser(t!("functions.invalidArguments").to_string())) diff --git a/dsc_lib/src/functions/resource_id.rs b/dsc_lib/src/functions/resource_id.rs index b4b562cbf..fddf472f2 100644 --- a/dsc_lib/src/functions/resource_id.rs +++ b/dsc_lib/src/functions/resource_id.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; @@ -18,10 +18,7 @@ impl Function for ResourceId { category: vec![FunctionCategory::Resource], min_args: 2, max_args: 2, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::String], - vec![FunctionArgKind::String], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::String], vec![FunctionArgKind::String]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::String], } @@ -34,11 +31,16 @@ impl Function for ResourceId { if let Some(value) = resource_type.as_str() { let slash_count = value.chars().filter(|c| *c == '/').count(); if slash_count != 1 { - return Err(DscError::Function("resourceId".to_string(), t!("functions.resourceId.incorrectTypeFormat").to_string())); + return Err(DscError::Function( + "resourceId".to_string(), + t!("functions.resourceId.incorrectTypeFormat").to_string(), + )); } result.push_str(value); } else { - return Err(DscError::Parser(t!("functions.resourceId.invalidFirstArgType").to_string())); + return Err(DscError::Parser( + t!("functions.resourceId.invalidFirstArgType").to_string(), + )); } // ARM uses a slash separator, but here we use a colon which is not allowed for the type nor name result.push(':'); @@ -46,12 +48,17 @@ impl Function for ResourceId { let resource_name = &args[1]; if let Some(value) = resource_name.as_str() { if value.contains('/') { - return Err(DscError::Function("resourceId".to_string(), t!("functions.resourceId.incorrectNameFormat").to_string())); + return Err(DscError::Function( + "resourceId".to_string(), + t!("functions.resourceId.incorrectNameFormat").to_string(), + )); } result.push_str(value); } else { - return Err(DscError::Parser(t!("functions.resourceId.invalidSecondArgType").to_string())); + return Err(DscError::Parser( + t!("functions.resourceId.invalidSecondArgType").to_string(), + )); } Ok(Value::String(result)) @@ -66,14 +73,18 @@ mod tests { #[test] fn strings() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[resourceId('a/b', 'c')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[resourceId('a/b', 'c')]", &Context::new()) + .unwrap(); assert_eq!(result, "a/b:c"); } #[test] fn strings_with_dots() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[resourceId('a.b/c', 'd')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[resourceId('a.b/c', 'd')]", &Context::new()) + .unwrap(); assert_eq!(result, "a.b/c:d"); } diff --git a/dsc_lib/src/functions/secret.rs b/dsc_lib/src/functions/secret.rs index 9b5205232..f8cc02cba 100644 --- a/dsc_lib/src/functions/secret.rs +++ b/dsc_lib/src/functions/secret.rs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; +use super::Function; use crate::configure::context::Context; use crate::extensions::dscextension::Capability; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; -use super::Function; use tracing::warn; #[derive(Debug, Default)] @@ -21,19 +21,17 @@ impl Function for Secret { category: vec![FunctionCategory::Deployment], min_args: 1, max_args: 2, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::String], - vec![FunctionArgKind::String], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::String], vec![FunctionArgKind::String]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::String], } } fn invoke(&self, args: &[Value], context: &Context) -> Result { - let secret_name = args[0].as_str().ok_or_else(|| { - DscError::Function("secret".to_string(), t!("functions.secret.notString").to_string()) - })?.to_string(); + let secret_name = args[0] + .as_str() + .ok_or_else(|| DscError::Function("secret".to_string(), t!("functions.secret.notString").to_string()))? + .to_string(); let vault_name: Option = if args.len() > 1 { args[1].as_str().map(std::string::ToString::to_string) } else { @@ -42,35 +40,53 @@ impl Function for Secret { // we query all extensions supporting the secret method to see if any of them can provide the secret. // if none can or if multiple provide different values, we return an error. - let extensions = context.extensions.iter() + let extensions = context + .extensions + .iter() .filter(|ext| ext.capabilities.contains(&Capability::Secret)) .collect::>(); let mut secret_returned = false; let mut result: String = String::new(); if extensions.is_empty() { - return Err(DscError::Function("secret".to_string(), t!("functions.secret.noExtensions").to_string())); + return Err(DscError::Function( + "secret".to_string(), + t!("functions.secret.noExtensions").to_string(), + )); } for extension in extensions { match extension.secret(&secret_name, vault_name.as_deref()) { Ok(secret_result) => { if let Some(secret_value) = secret_result { if secret_returned && result != secret_value { - return Err(DscError::Function("secret".to_string(), t!("functions.secret.multipleSecrets", name = secret_name.clone()).to_string())); + return Err(DscError::Function( + "secret".to_string(), + t!("functions.secret.multipleSecrets", name = secret_name.clone()).to_string(), + )); } result = secret_value; secret_returned = true; } - }, + } Err(err) => { - warn!("{}", t!("functions.secret.extensionReturnedError", extension = extension.type_name.clone(), error = err)); + warn!( + "{}", + t!( + "functions.secret.extensionReturnedError", + extension = extension.type_name.clone(), + error = err + ) + ); } } } if secret_returned { Ok(Value::String(result)) } else { - Err(DscError::Function("secret".to_string(), t!("functions.secret.secretNotFound", name = secret_name).to_string())) + Err(DscError::Function( + "secret".to_string(), + t!("functions.secret.secretNotFound", name = secret_name).to_string(), + )) } } } diff --git a/dsc_lib/src/functions/skip.rs b/dsc_lib/src/functions/skip.rs index b506d1cb9..ba867f7b5 100644 --- a/dsc_lib/src/functions/skip.rs +++ b/dsc_lib/src/functions/skip.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -39,7 +39,9 @@ impl Function for Skip { }; if let Some(array) = args[0].as_array() { - if count >= array.len() { return Ok(Value::Array(vec![])); } + if count >= array.len() { + return Ok(Value::Array(vec![])); + } let skipped = array.iter().skip(count).cloned().collect::>(); return Ok(Value::Array(skipped)); } @@ -65,8 +67,13 @@ mod tests { #[test] fn skip_array_basic() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[skip(createArray('a','b','c','d'), 2)]", &Context::new()).unwrap(); - assert_eq!(result, Value::Array(vec![Value::String("c".into()), Value::String("d".into())])); + let result = parser + .parse_and_execute("[skip(createArray('a','b','c','d'), 2)]", &Context::new()) + .unwrap(); + assert_eq!( + result, + Value::Array(vec![Value::String("c".into()), Value::String("d".into())]) + ); } #[test] @@ -79,19 +86,26 @@ mod tests { #[test] fn skip_more_than_length() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[skip(createArray('a','b'), 5)]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[skip(createArray('a','b'), 5)]", &Context::new()) + .unwrap(); assert_eq!(result, Value::Array(vec![])); } #[test] fn skip_array_negative_is_zero() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[skip(createArray('a','b','c'), -1)]", &Context::new()).unwrap(); - assert_eq!(result, Value::Array(vec![ - Value::String("a".into()), - Value::String("b".into()), - Value::String("c".into()), - ])); + let result = parser + .parse_and_execute("[skip(createArray('a','b','c'), -1)]", &Context::new()) + .unwrap(); + assert_eq!( + result, + Value::Array(vec![ + Value::String("a".into()), + Value::String("b".into()), + Value::String("c".into()), + ]) + ); } #[test] diff --git a/dsc_lib/src/functions/starts_with.rs b/dsc_lib/src/functions/starts_with.rs index 40a73d618..1f2beae04 100644 --- a/dsc_lib/src/functions/starts_with.rs +++ b/dsc_lib/src/functions/starts_with.rs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; +use super::Function; use crate::configure::context::Context; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; -use super::Function; use tracing::debug; #[derive(Debug, Default)] @@ -20,10 +20,7 @@ impl Function for StartsWith { category: vec![FunctionCategory::String], min_args: 2, max_args: 2, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::String], - vec![FunctionArgKind::String], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::String], vec![FunctionArgKind::String]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Boolean], } @@ -48,14 +45,18 @@ mod tests { #[test] fn does_start_with() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[startsWith('hello', 'he')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[startsWith('hello', 'he')]", &Context::new()) + .unwrap(); assert_eq!(result, Value::Bool(true)); } #[test] fn does_not_start_with() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[startsWith('hello', 'world')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[startsWith('hello', 'world')]", &Context::new()) + .unwrap(); assert_eq!(result, Value::Bool(false)); } } diff --git a/dsc_lib/src/functions/string.rs b/dsc_lib/src/functions/string.rs index d40e7684e..0e3383845 100644 --- a/dsc_lib/src/functions/string.rs +++ b/dsc_lib/src/functions/string.rs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; +use super::Function; use crate::configure::context::Context; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; -use super::Function; #[derive(Debug, Default)] pub struct StringFn {} @@ -82,14 +82,18 @@ mod tests { #[test] fn string_from_array() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[string(createArray('a', 'b'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[string(createArray('a', 'b'))]", &Context::new()) + .unwrap(); assert_eq!(result, json!(["a", "b"]).to_string()); } #[test] fn string_from_object() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[string(createObject('a', 'hello'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[string(createObject('a', 'hello'))]", &Context::new()) + .unwrap(); assert_eq!(result, json!({"a": "hello"}).to_string()); } } diff --git a/dsc_lib/src/functions/sub.rs b/dsc_lib/src/functions/sub.rs index 94772aa75..f23667880 100644 --- a/dsc_lib/src/functions/sub.rs +++ b/dsc_lib/src/functions/sub.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -19,10 +19,7 @@ impl Function for Sub { category: vec![FunctionCategory::Numeric], min_args: 2, max_args: 2, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::Number], - vec![FunctionArgKind::Number], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::Number], vec![FunctionArgKind::Number]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::Number], } @@ -53,7 +50,9 @@ mod tests { #[test] fn nested() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[sub(9, sub(5, 3))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[sub(9, sub(5, 3))]", &Context::new()) + .unwrap(); assert_eq!(result, 7); } diff --git a/dsc_lib/src/functions/substring.rs b/dsc_lib/src/functions/substring.rs index 18318cdd6..333567ce4 100644 --- a/dsc_lib/src/functions/substring.rs +++ b/dsc_lib/src/functions/substring.rs @@ -87,11 +87,7 @@ impl Function for Substring { length }; - let result: String = string_to_parse - .chars() - .skip(start_index) - .take(length) - .collect(); + let result: String = string_to_parse.chars().skip(start_index).take(length).collect(); Ok(Value::String(result)) } @@ -215,9 +211,7 @@ mod tests { #[test] fn substring_empty_string_to_end() { let mut parser = Statement::new().unwrap(); - let result = parser - .parse_and_execute("[substring('', 0)]", &Context::new()) - .unwrap(); + let result = parser.parse_and_execute("[substring('', 0)]", &Context::new()).unwrap(); assert_eq!(result, Value::String("".to_string())); } @@ -230,4 +224,3 @@ mod tests { assert_eq!(result, Value::String("".to_string())); } } - diff --git a/dsc_lib/src/functions/system_root.rs b/dsc_lib/src/functions/system_root.rs index 30083a8e1..baaae6084 100644 --- a/dsc_lib/src/functions/system_root.rs +++ b/dsc_lib/src/functions/system_root.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -61,5 +61,4 @@ mod tests { let result = parser.parse_and_execute("[systemRoot()]", &context).unwrap(); assert_eq!(result, format!("{separator}mnt")); } - } diff --git a/dsc_lib/src/functions/to_lower.rs b/dsc_lib/src/functions/to_lower.rs index 683761e48..3c603b643 100644 --- a/dsc_lib/src/functions/to_lower.rs +++ b/dsc_lib/src/functions/to_lower.rs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; +use super::Function; use crate::configure::context::Context; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; -use super::Function; #[derive(Debug, Default)] pub struct ToLower {} @@ -40,21 +40,27 @@ mod tests { #[test] fn test_to_lower_uppercase() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[toLower('HELLO WORLD')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[toLower('HELLO WORLD')]", &Context::new()) + .unwrap(); assert_eq!(result, "hello world"); } #[test] fn test_to_lower_mixed_case() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[toLower('Hello World')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[toLower('Hello World')]", &Context::new()) + .unwrap(); assert_eq!(result, "hello world"); } #[test] fn test_to_lower_already_lowercase() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[toLower('hello world')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[toLower('hello world')]", &Context::new()) + .unwrap(); assert_eq!(result, "hello world"); } @@ -68,7 +74,9 @@ mod tests { #[test] fn test_to_lower_with_numbers_and_symbols() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[toLower('HELLO123!@#')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[toLower('HELLO123!@#')]", &Context::new()) + .unwrap(); assert_eq!(result, "hello123!@#"); } @@ -82,7 +90,9 @@ mod tests { #[test] fn test_to_lower_with_spaces() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[toLower(' HELLO WORLD ')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[toLower(' HELLO WORLD ')]", &Context::new()) + .unwrap(); assert_eq!(result, " hello world "); } @@ -96,7 +106,9 @@ mod tests { #[test] fn test_to_lower_nested_function() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[toLower(concat('HELLO', ' WORLD'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[toLower(concat('HELLO', ' WORLD'))]", &Context::new()) + .unwrap(); assert_eq!(result, "hello world"); } -} \ No newline at end of file +} diff --git a/dsc_lib/src/functions/to_upper.rs b/dsc_lib/src/functions/to_upper.rs index 2319708ad..7b653901e 100644 --- a/dsc_lib/src/functions/to_upper.rs +++ b/dsc_lib/src/functions/to_upper.rs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; +use super::Function; use crate::configure::context::Context; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; -use super::Function; #[derive(Debug, Default)] pub struct ToUpper {} @@ -40,21 +40,27 @@ mod tests { #[test] fn test_to_upper_lowercase() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[toUpper('hello world')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[toUpper('hello world')]", &Context::new()) + .unwrap(); assert_eq!(result, "HELLO WORLD"); } #[test] fn test_to_upper_mixed_case() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[toUpper('Hello World')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[toUpper('Hello World')]", &Context::new()) + .unwrap(); assert_eq!(result, "HELLO WORLD"); } #[test] fn test_to_upper_already_uppercase() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[toUpper('HELLO WORLD')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[toUpper('HELLO WORLD')]", &Context::new()) + .unwrap(); assert_eq!(result, "HELLO WORLD"); } @@ -68,7 +74,9 @@ mod tests { #[test] fn test_to_upper_with_numbers_and_symbols() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[toUpper('Hello123!@#')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[toUpper('Hello123!@#')]", &Context::new()) + .unwrap(); assert_eq!(result, "HELLO123!@#"); } @@ -82,7 +90,9 @@ mod tests { #[test] fn test_to_upper_with_spaces() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[toUpper(' hello world ')]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[toUpper(' hello world ')]", &Context::new()) + .unwrap(); assert_eq!(result, " HELLO WORLD "); } @@ -96,7 +106,9 @@ mod tests { #[test] fn test_to_upper_nested_function() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[toUpper(concat('hello', ' world'))]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[toUpper(concat('hello', ' world'))]", &Context::new()) + .unwrap(); assert_eq!(result, "HELLO WORLD"); } -} \ No newline at end of file +} diff --git a/dsc_lib/src/functions/true.rs b/dsc_lib/src/functions/true.rs index c01a3faed..c129cc7f0 100644 --- a/dsc_lib/src/functions/true.rs +++ b/dsc_lib/src/functions/true.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; diff --git a/dsc_lib/src/functions/union.rs b/dsc_lib/src/functions/union.rs index 7c3b47e4f..746965ea7 100644 --- a/dsc_lib/src/functions/union.rs +++ b/dsc_lib/src/functions/union.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::Context; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::{Map, Value}; use tracing::debug; diff --git a/dsc_lib/src/functions/unique_string.rs b/dsc_lib/src/functions/unique_string.rs index 884a9269c..6e3b4c81b 100644 --- a/dsc_lib/src/functions/unique_string.rs +++ b/dsc_lib/src/functions/unique_string.rs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; +use super::Function; use crate::configure::context::Context; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use base32::{self, Alphabet}; use murmurhash64::murmur_hash64a; use rust_i18n::t; use serde_json::Value; -use super::Function; use tracing::debug; #[derive(Debug, Default)] @@ -22,9 +22,7 @@ impl Function for UniqueString { category: vec![FunctionCategory::String], min_args: 1, max_args: usize::MAX, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::String], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::String]], remaining_arg_accepted_types: Some(vec![FunctionArgKind::String]), return_types: vec![FunctionArgKind::String], } @@ -33,7 +31,8 @@ impl Function for UniqueString { fn invoke(&self, args: &[Value], _context: &Context) -> Result { debug!("{}", t!("functions.uniqueString.invoked")); // concatenate all string arguments into a single string with dash separator - let concatenated = args.iter() + let concatenated = args + .iter() .filter_map(|arg| arg.as_str()) .collect::>() .join("-"); diff --git a/dsc_lib/src/functions/user_function.rs b/dsc_lib/src/functions/user_function.rs index fd76a97c6..e520e0f49 100644 --- a/dsc_lib/src/functions/user_function.rs +++ b/dsc_lib/src/functions/user_function.rs @@ -1,7 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::configure::{{config_doc::{DataType, UserFunctionDefinition}, context::ProcessMode}, validate_parameter_type}; +use crate::configure::{ + validate_parameter_type, + { + config_doc::{DataType, UserFunctionDefinition}, + context::ProcessMode, + }, +}; use crate::dscerror::DscError; use crate::functions::Context; use crate::parser::Statement; @@ -32,23 +38,41 @@ pub fn invoke_user_function(name: &str, args: &[Value], context: &Context) -> Re user_context.user_functions.clear(); for (i, arg) in args.iter().enumerate() { let Some(params) = &function_definition.parameters else { - return Err(DscError::Parser(t!("functions.userFunction.expectedNoParameters", name = name).to_string())); + return Err(DscError::Parser( + t!("functions.userFunction.expectedNoParameters", name = name).to_string(), + )); }; - user_context.parameters.insert(params[i].name.clone(), (arg.clone(), params[i].r#type.clone())); + user_context + .parameters + .insert(params[i].name.clone(), (arg.clone(), params[i].r#type.clone())); } let mut parser = Statement::new()?; let result = parser.parse_and_execute(&function_definition.output.value, &user_context)?; validate_output_type(name, function_definition, &result)?; Ok(result) } else { - Err(DscError::Parser(t!("functions.userFunction.unknownUserFunction", name = name).to_string())) + Err(DscError::Parser( + t!("functions.userFunction.unknownUserFunction", name = name).to_string(), + )) } } -fn validate_parameters(name: &str, function_definition: &UserFunctionDefinition, args: &[Value]) -> Result<(), DscError> { +fn validate_parameters( + name: &str, + function_definition: &UserFunctionDefinition, + args: &[Value], +) -> Result<(), DscError> { if let Some(expected_params) = &function_definition.parameters { if args.len() != expected_params.len() { - return Err(DscError::Parser(t!("functions.userFunction.wrongParamCount", name = name, expected = expected_params.len(), got = args.len()).to_string())); + return Err(DscError::Parser( + t!( + "functions.userFunction.wrongParamCount", + name = name, + expected = expected_params.len(), + got = args.len() + ) + .to_string(), + )); } for (arg, expected_param) in args.iter().zip(expected_params) { validate_parameter_type(name, arg, &expected_param.r#type)?; @@ -57,33 +81,72 @@ fn validate_parameters(name: &str, function_definition: &UserFunctionDefinition, Ok(()) } -fn validate_output_type(name: &str, function_definition: &UserFunctionDefinition, output: &Value) -> Result<(), DscError> { +fn validate_output_type( + name: &str, + function_definition: &UserFunctionDefinition, + output: &Value, +) -> Result<(), DscError> { match function_definition.output.r#type { DataType::String | DataType::SecureString => { if !output.is_string() { - return Err(DscError::Validation(t!("functions.userFunction.incorrectOutputType", name = name, expected_type = "string").to_string())); + return Err(DscError::Validation( + t!( + "functions.userFunction.incorrectOutputType", + name = name, + expected_type = "string" + ) + .to_string(), + )); } - }, + } DataType::Int => { if !output.is_i64() { - return Err(DscError::Validation(t!("functions.userFunction.incorrectOutputType", name = name, expected_type = "int").to_string())); + return Err(DscError::Validation( + t!( + "functions.userFunction.incorrectOutputType", + name = name, + expected_type = "int" + ) + .to_string(), + )); } - }, + } DataType::Bool => { if !output.is_boolean() { - return Err(DscError::Validation(t!("functions.userFunction.incorrectOutputType", name = name, expected_type = "bool").to_string())); + return Err(DscError::Validation( + t!( + "functions.userFunction.incorrectOutputType", + name = name, + expected_type = "bool" + ) + .to_string(), + )); } - }, + } DataType::Array => { if !output.is_array() { - return Err(DscError::Validation(t!("functions.userFunction.incorrectOutputType", name = name, expected_type = "array").to_string())); + return Err(DscError::Validation( + t!( + "functions.userFunction.incorrectOutputType", + name = name, + expected_type = "array" + ) + .to_string(), + )); } - }, + } DataType::Object | DataType::SecureObject => { if !output.is_object() { - return Err(DscError::Validation(t!("functions.userFunction.incorrectOutputType", name = name, expected_type = "object").to_string())); + return Err(DscError::Validation( + t!( + "functions.userFunction.incorrectOutputType", + name = name, + expected_type = "object" + ) + .to_string(), + )); } - }, + } } Ok(()) diff --git a/dsc_lib/src/functions/utc_now.rs b/dsc_lib/src/functions/utc_now.rs index 34912ec14..173a4155a 100644 --- a/dsc_lib/src/functions/utc_now.rs +++ b/dsc_lib/src/functions/utc_now.rs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; +use super::Function; use crate::configure::context::{Context, ProcessMode}; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use chrono::{SecondsFormat, Utc}; use rust_i18n::t; use serde_json::Value; -use super::Function; use tracing::debug; #[derive(Debug, Default)] @@ -21,9 +21,7 @@ impl Function for UtcNow { category: vec![FunctionCategory::Date], min_args: 0, max_args: 1, - accepted_arg_ordered_types: vec![ - vec![FunctionArgKind::String], - ], + accepted_arg_ordered_types: vec![vec![FunctionArgKind::String]], remaining_arg_accepted_types: None, return_types: vec![FunctionArgKind::String], } @@ -33,7 +31,9 @@ impl Function for UtcNow { debug!("{}", t!("functions.utcNow.invoked")); if context.process_mode != ProcessMode::ParametersDefault { - return Err(DscError::Parser(t!("functions.utcNow.onlyUsedAsParameterDefault").to_string())); + return Err(DscError::Parser( + t!("functions.utcNow.onlyUsedAsParameterDefault").to_string(), + )); } if args.is_empty() { @@ -55,24 +55,24 @@ impl Function for UtcNow { fn convert_dotnet_format_to_chrono(format: &str) -> String { const DOTNET_TO_CHRONO: &[(&str, &str)] = &[ ("yyyy", "%Y"), // Full year, zero padded to 4 digits - ("yy", "%y"), // Year, zero padded to 2 digits - ("y", "%-Y"), // Year without leading zeroes + ("yy", "%y"), // Year, zero padded to 2 digits + ("y", "%-Y"), // Year without leading zeroes ("dddd", "%A"), // Full weekday name - ("ddd", "%a"), // Abbreviated weekday name - ("dd", "%d"), // Day of the month, zero padded to 2 digits - ("d", "%-d"), // Day of the month without leading zeroes - ("HH", "%H"), // Hour in 24-hour format, zero padded to 2 digits - ("H", "%-H"), // Hour in 24-hour format without leading zeroes - ("mm", "%M"), // Minute, zero padded to 2 digits - ("m", "%-M"), // Minute without leading zeroes - ("ss", "%S"), // Second, zero padded to 2 digits - ("s", "%-S"), // Second without leading zeroes - ("fff", "%f"), // Milliseconds, zero padded to 3 digits + ("ddd", "%a"), // Abbreviated weekday name + ("dd", "%d"), // Day of the month, zero padded to 2 digits + ("d", "%-d"), // Day of the month without leading zeroes + ("HH", "%H"), // Hour in 24-hour format, zero padded to 2 digits + ("H", "%-H"), // Hour in 24-hour format without leading zeroes + ("mm", "%M"), // Minute, zero padded to 2 digits + ("m", "%-M"), // Minute without leading zeroes + ("ss", "%S"), // Second, zero padded to 2 digits + ("s", "%-S"), // Second without leading zeroes + ("fff", "%f"), // Milliseconds, zero padded to 3 digits ("MMMM", "%B"), // Full month name - ("MMM", "%b"), // Abbreviated month name - ("MM", "%m"), // Month, zero padded to 2 digits + ("MMM", "%b"), // Abbreviated month name + ("MM", "%m"), // Month, zero padded to 2 digits ("zzz", "%:z"), // Time zone offset without colon - ("tt", "%p"), // AM/PM designator (same as t) + ("tt", "%p"), // AM/PM designator (same as t) ]; let mut converted_format = String::new(); diff --git a/dsc_lib/src/functions/variables.rs b/dsc_lib/src/functions/variables.rs index 9f4dfcf9b..ffea6e148 100644 --- a/dsc_lib/src/functions/variables.rs +++ b/dsc_lib/src/functions/variables.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::DscError; use crate::configure::context::{Context, ProcessMode}; -use crate::functions::{FunctionArgKind, Function, FunctionCategory, FunctionMetadata}; +use crate::functions::{Function, FunctionArgKind, FunctionCategory, FunctionMetadata}; +use crate::DscError; use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -34,14 +34,18 @@ impl Function for Variables { fn invoke(&self, args: &[Value], context: &Context) -> Result { debug!("{}", t!("functions.variables.invoked")); if context.process_mode == ProcessMode::UserFunction { - return Err(DscError::Parser(t!("functions.variables.unavailableInUserFunction").to_string())); + return Err(DscError::Parser( + t!("functions.variables.unavailableInUserFunction").to_string(), + )); } if let Some(key) = args[0].as_str() { if context.variables.contains_key(key) { Ok(context.variables[key].clone()) } else { - Err(DscError::Parser(t!("functions.variables.keyNotFound", key = key).to_string())) + Err(DscError::Parser( + t!("functions.variables.keyNotFound", key = key).to_string(), + )) } } else { Err(DscError::Parser(t!("functions.invalidArguments").to_string())) diff --git a/dsc_lib/src/lib.rs b/dsc_lib/src/lib.rs index 26b53be75..399267d0c 100644 --- a/dsc_lib/src/lib.rs +++ b/dsc_lib/src/lib.rs @@ -1,13 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::discovery::{command_discovery::ImportedManifest, discovery_trait::DiscoveryFilter}; use crate::discovery::discovery_trait::DiscoveryKind; +use crate::discovery::{command_discovery::ImportedManifest, discovery_trait::DiscoveryFilter}; use crate::progress::ProgressFormat; use configure::config_doc::ExecutionKind; use dscerror::DscError; -use dscresources::{dscresource::{DscResource, Invoke}, invoke_result::{GetResult, SetResult, TestResult}}; +use dscresources::{ + dscresource::{DscResource, Invoke}, + invoke_result::{GetResult, SetResult, TestResult}, +}; use rust_i18n::i18n; pub mod configure; @@ -18,8 +21,8 @@ pub mod extensions; pub mod functions; pub mod parser; pub mod progress; -pub mod util; pub mod schemas; +pub mod util; i18n!("locales", fallback = "en-us"); @@ -52,8 +55,15 @@ impl DscManager { self.discovery.find_resource(name, version) } - pub fn list_available(&mut self, kind: &DiscoveryKind, type_name_filter: &str, adapter_name_filter: &str, progress_format: ProgressFormat) -> Vec { - self.discovery.list_available(kind, type_name_filter, adapter_name_filter, progress_format) + pub fn list_available( + &mut self, + kind: &DiscoveryKind, + type_name_filter: &str, + adapter_name_filter: &str, + progress_format: ProgressFormat, + ) -> Vec { + self.discovery + .list_available(kind, type_name_filter, adapter_name_filter, progress_format) } pub fn find_resources(&mut self, required_resource_types: &[DiscoveryFilter], progress_format: ProgressFormat) { diff --git a/dsc_lib/src/parser/expressions.rs b/dsc_lib/src/parser/expressions.rs index 16879126a..5c83fd17b 100644 --- a/dsc_lib/src/parser/expressions.rs +++ b/dsc_lib/src/parser/expressions.rs @@ -39,7 +39,9 @@ impl Expression { /// This function will return an error if the expression node is not valid. pub fn new(statement_bytes: &[u8], expression: &Node) -> Result { let Some(function) = expression.child_by_field_name("function") else { - return Err(DscError::Parser(t!("parser.expression.functionNodeNotFound").to_string())); + return Err(DscError::Parser( + t!("parser.expression.functionNodeNotFound").to_string(), + )); }; debug!("{}", t!("parser.expression.parsingFunction", name = function : {:?})); let function = Function::new(statement_bytes, &function)?; @@ -47,25 +49,35 @@ impl Expression { if let Some(accessor) = expression.child_by_field_name("accessor") { debug!("{}", t!("parser.expression.parsingAccessor", name = accessor : {:?})); if accessor.is_error() { - return Err(DscError::Parser(t!("parser.expression.accessorParsingError").to_string())); + return Err(DscError::Parser( + t!("parser.expression.accessorParsingError").to_string(), + )); } let mut cursor = accessor.walk(); for accessor in accessor.named_children(&mut cursor) { if accessor.is_error() { - return Err(DscError::Parser(t!("parser.expression.accessorParsingError").to_string())); + return Err(DscError::Parser( + t!("parser.expression.accessorParsingError").to_string(), + )); } let accessor_kind = accessor.kind(); let value = match accessor_kind { "memberAccess" => { - debug!("{}", t!("parser.expression.parsingMemberAccessor", name = accessor : {:?})); + debug!( + "{}", + t!("parser.expression.parsingMemberAccessor", name = accessor : {:?}) + ); let Some(member_name) = accessor.child_by_field_name("name") else { return Err(DscError::Parser(t!("parser.expression.memberNotFound").to_string())); }; let member = member_name.utf8_text(statement_bytes)?; Accessor::Member(member.to_string()) - }, + } "index" => { - debug!("{}", t!("parser.expression.parsingIndexAccessor", index = accessor : {:?})); + debug!( + "{}", + t!("parser.expression.parsingIndexAccessor", index = accessor : {:?}) + ); let Some(index_value) = accessor.child_by_field_name("indexValue") else { return Err(DscError::Parser(t!("parser.expression.indexNotFound").to_string())); }; @@ -74,28 +86,29 @@ impl Expression { let value = index_value.utf8_text(statement_bytes)?; let value = serde_json::from_str(value)?; Accessor::Index(value) - }, + } "expression" => { let expression = Expression::new(statement_bytes, &index_value)?; Accessor::IndexExpression(expression) - }, + } _ => { - return Err(DscError::Parser(t!("parser.expression.invalidAccessorKind", kind = accessor_kind).to_string())); - }, + return Err(DscError::Parser( + t!("parser.expression.invalidAccessorKind", kind = accessor_kind).to_string(), + )); + } } - }, + } _ => { - return Err(DscError::Parser(t!("parser.expression.invalidAccessorKind", kind = accessor_kind).to_string())); - }, + return Err(DscError::Parser( + t!("parser.expression.invalidAccessorKind", kind = accessor_kind).to_string(), + )); + } }; accessors.push(value); } } - Ok(Expression { - function, - accessors, - }) + Ok(Expression { function, accessors }) } /// Invoke the expression. @@ -122,8 +135,7 @@ impl Expression { } if self.accessors.is_empty() { Ok(result) - } - else { + } else { debug!("{}", t!("parser.expression.evalAccessors")); let mut value = result; let is_secure = is_secure_value(&value); @@ -144,7 +156,9 @@ impl Expression { Accessor::Member(member) => { if let Some(object) = value.as_object() { if !object.contains_key(member) { - return Err(DscError::Parser(t!("parser.expression.memberNameNotFound", member = member).to_string())); + return Err(DscError::Parser( + t!("parser.expression.memberNameNotFound", member = member).to_string(), + )); } if is_secure { value = convert_to_secure(&object[member]); @@ -154,18 +168,18 @@ impl Expression { } else { return Err(DscError::Parser(t!("parser.expression.accessOnNonObject").to_string())); } - }, + } Accessor::Index(index_value) => { if is_secure { index = convert_to_secure(index_value); } else { index = index_value.clone(); } - }, + } Accessor::IndexExpression(expression) => { index = expression.invoke(function_dispatcher, context)?; trace!("{}", t!("parser.expression.expressionResult", index = index : {:?})); - }, + } } if index.is_number() { @@ -185,8 +199,7 @@ impl Expression { } else { return Err(DscError::Parser(t!("parser.expression.indexOnNonArray").to_string())); } - } - else if !index.is_null() { + } else if !index.is_null() { return Err(DscError::Parser(t!("parser.expression.invalidIndexType").to_string())); } } diff --git a/dsc_lib/src/parser/functions.rs b/dsc_lib/src/parser/functions.rs index a01d3e8aa..92c6b6074 100644 --- a/dsc_lib/src/parser/functions.rs +++ b/dsc_lib/src/parser/functions.rs @@ -6,12 +6,9 @@ use serde_json::{Number, Value}; use tracing::debug; use tree_sitter::Node; -use crate::DscError; use crate::configure::context::Context; -use crate::parser::{ - expressions::Expression, - FunctionDispatcher, -}; +use crate::parser::{expressions::Expression, FunctionDispatcher}; +use crate::DscError; #[derive(Clone)] pub struct Function { @@ -55,9 +52,10 @@ impl Function { let args = convert_args_node(statement_bytes, function_args.as_ref())?; let name = name.utf8_text(statement_bytes)?; debug!("{}", t!("parser.functions.functionName", name = name)); - Ok(Function{ + Ok(Function { name: name.to_string(), - args}) + args, + }) } /// Invoke the function. @@ -75,7 +73,7 @@ impl Function { debug!("{}", t!("parser.functions.argIsExpression")); let value = expression.invoke(function_dispatcher, context)?; resolved_args.push(value.clone()); - }, + } FunctionArg::Value(value) => { debug!("{}", t!("parser.functions.argIsValue", value = value : {:?})); resolved_args.push(value.clone()); @@ -106,22 +104,24 @@ fn convert_args_node(statement_bytes: &[u8], args: Option<&Node>) -> Result { let value = arg.utf8_text(statement_bytes)?; result.push(FunctionArg::Value(Value::Number(Number::from(value.parse::()?)))); - }, + } "boolean" => { let value = arg.utf8_text(statement_bytes)?; result.push(FunctionArg::Value(Value::Bool(value.parse::()?))); - }, + } "expression" => { // TODO: this is recursive, we may want to stop at a specific depth let expression = Expression::new(statement_bytes, &arg)?; result.push(FunctionArg::Expression(expression)); - }, + } _ => { - return Err(DscError::Parser(t!("parser.functions.unknownArgType", kind = arg.kind()).to_string())); + return Err(DscError::Parser( + t!("parser.functions.unknownArgType", kind = arg.kind()).to_string(), + )); } } } diff --git a/dsc_lib/src/parser/mod.rs b/dsc_lib/src/parser/mod.rs index db6553b41..83052c67e 100644 --- a/dsc_lib/src/parser/mod.rs +++ b/dsc_lib/src/parser/mod.rs @@ -52,21 +52,29 @@ impl Statement { } let Some(tree) = &mut self.parser.parse(statement, None) else { - return Err(DscError::Parser(t!("parser.failedToParse", statement = statement).to_string())); + return Err(DscError::Parser( + t!("parser.failedToParse", statement = statement).to_string(), + )); }; let root_node = tree.root_node(); if root_node.is_error() { - return Err(DscError::Parser(t!("parser.failedToParseRoot", statement = statement).to_string())); + return Err(DscError::Parser( + t!("parser.failedToParseRoot", statement = statement).to_string(), + )); } if root_node.kind() != "statement" { - return Err(DscError::Parser(t!("parser.invalidStatement", statement = statement).to_string())); + return Err(DscError::Parser( + t!("parser.invalidStatement", statement = statement).to_string(), + )); } let statement_bytes = statement.as_bytes(); let mut cursor = root_node.walk(); let mut return_value = Value::Null; for child_node in root_node.named_children(&mut cursor) { if child_node.is_error() { - return Err(DscError::Parser(t!("parser.failedToParse", statement = statement).to_string())); + return Err(DscError::Parser( + t!("parser.failedToParse", statement = statement).to_string(), + )); } match child_node.kind() { @@ -76,22 +84,29 @@ impl Statement { }; debug!("{}", t!("parser.parsingStringLiteral", value = value.to_string())); return_value = Value::String(value.to_string()); - }, + } "escapedStringLiteral" => { // need to remove the first character: [[ => [ let Ok(value) = child_node.utf8_text(statement_bytes) else { - return Err(DscError::Parser(t!("parser.failedToParseEscapedStringLiteral").to_string())); + return Err(DscError::Parser( + t!("parser.failedToParseEscapedStringLiteral").to_string(), + )); }; - debug!("{}", t!("parser.parsingEscapedStringLiteral", value = value[1..].to_string())); + debug!( + "{}", + t!("parser.parsingEscapedStringLiteral", value = value[1..].to_string()) + ); return_value = Value::String(value[1..].to_string()); - }, + } "expression" => { debug!("{}", t!("parser.parsingExpression")); let expression = Expression::new(statement_bytes, &child_node)?; return_value = expression.invoke(&self.function_dispatcher, context)?; - }, + } _ => { - return Err(DscError::Parser(t!("parser.unknownExpressionType", kind = child_node.kind()).to_string())); + return Err(DscError::Parser( + t!("parser.unknownExpressionType", kind = child_node.kind()).to_string(), + )); } } } @@ -114,7 +129,9 @@ mod tests { #[test] fn bracket_string() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[[this is a string]", &Context::new()).unwrap(); + let result = parser + .parse_and_execute("[[this is a string]", &Context::new()) + .unwrap(); assert_eq!(result, "[this is a string]"); } diff --git a/dsc_lib/src/progress.rs b/dsc_lib/src/progress.rs index 41c4ae54d..2fa0a506d 100644 --- a/dsc_lib/src/progress.rs +++ b/dsc_lib/src/progress.rs @@ -8,9 +8,9 @@ use indicatif::ProgressStyle; use rust_i18n::t; use serde::Serialize; use serde_json::Value; -use tracing_indicatif::span_ext::IndicatifSpanExt; -use tracing::{trace, warn_span}; use tracing::span::Span; +use tracing::{trace, warn_span}; +use tracing_indicatif::span_ext::IndicatifSpanExt; use uuid::Uuid; #[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] @@ -73,13 +73,12 @@ impl Progress { } pub struct ProgressBar { - progress_value: Progress, + progress_value: Progress, console_bar: Span, - format: ProgressFormat + format: ProgressFormat, } impl ProgressBar { - /// Create a `ProgressBar` object to update progress /// /// # Arguments @@ -99,7 +98,7 @@ impl ProgressBar { let bar = warn_span!(""); if format == ProgressFormat::Default { bar.pb_set_style(&ProgressStyle::with_template( - "{spinner:.green} [{elapsed_precise:.cyan}] [{bar:40.cyan/blue}] {pos:>7}/{len:7} {msg:.yellow}" + "{spinner:.green} [{elapsed_precise:.cyan}] [{bar:40.cyan/blue}] {pos:>7}/{len:7} {msg:.yellow}", )?); bar.pb_set_length(total_items); let _guard = bar.enter(); @@ -108,7 +107,7 @@ impl ProgressBar { Ok(ProgressBar { progress_value: Progress::new(total_items), console_bar: bar, - format + format, }) } @@ -175,10 +174,10 @@ impl ProgressBar { ProgressFormat::Json => { self.progress_value.activity = Some(activity.to_string()); self.write_json(); - }, + } ProgressFormat::Default => { self.console_bar.pb_set_message(activity); - }, + } ProgressFormat::None => {} } } @@ -193,10 +192,10 @@ impl ProgressBar { match self.format { ProgressFormat::Json => { self.progress_value.total_items = len; - }, + } ProgressFormat::Default => { self.console_bar.pb_set_length(len); - }, + } ProgressFormat::None => {} } } @@ -205,7 +204,10 @@ impl ProgressBar { if let Ok(json) = serde_json::to_string(&self.progress_value) { eprintln!("{json}"); } else { - trace!("{}", t!("progress.failedToSerialize", json = self.progress_value : {:?})); + trace!( + "{}", + t!("progress.failedToSerialize", json = self.progress_value : {:?}) + ); } } } diff --git a/dsc_lib/src/schemas/mod.rs b/dsc_lib/src/schemas/mod.rs index 23658308a..220aee837 100644 --- a/dsc_lib/src/schemas/mod.rs +++ b/dsc_lib/src/schemas/mod.rs @@ -3,12 +3,12 @@ //! Contains helpers for JSON schemas and DSC -use schemars::{Schema, JsonSchema, json_schema}; +use schemars::{json_schema, JsonSchema, Schema}; use crate::dscerror::DscError; /// Defines the URI prefix for the hosted schemas. -/// +/// /// While the schemas are currently hosted in the GitHub repository, DSC provides the shortened /// `aka.ms` link for convenience. Using this enum simplifies migrating to a new URI for schemas /// in the future. @@ -32,10 +32,7 @@ impl SchemaUriPrefix { /// Returns every known URI prefix for convenient iteration. #[must_use] pub fn all() -> Vec { - vec![ - Self::AkaDotMs, - Self::Github, - ] + vec![Self::AkaDotMs, Self::Github] } } @@ -43,16 +40,16 @@ impl SchemaUriPrefix { #[derive(Debug, Default, Clone, Copy, Hash, Eq, PartialEq)] pub enum SchemaForm { /// Indicates that the schema is bundled using the 2020-12 schema bundling contract. - /// + /// /// These schemas include all of their references in the `$defs` keyword where the key for /// each reference is the `$id` of that subschema and the value is the subschema. - /// + /// /// The bundled schemas are preferred for offline usage or where network latency is a concern. #[default] Bundled, /// Indicates that the schema is enhanced for interactively viewing, authoring, and editing /// the data in VS Code. - /// + /// /// These schemas include keywords not recognized by JSON Schema libraries and clients outside /// of VS Code, like `markdownDescription` and `defaultSnippets`. The schema references and /// definitions do not follow the canonical bundling for schema 2020-12, as the VS Code @@ -66,9 +63,9 @@ pub enum SchemaForm { impl SchemaForm { /// Returns the file extension for a given form of schema. - /// + /// /// The extension for [`Bundled`] and [`Canonical`] schemas is `.json` - /// + /// /// The extension for [`VSCode`] schemas is `.vscode.json` #[must_use] pub fn to_extension(&self) -> String { @@ -79,14 +76,14 @@ impl SchemaForm { } /// Return the prefix for a schema's folder path. - /// + /// /// The [`Bundled`] and [`VSCode`] schemas are always published in the `bundled` folder /// immediately beneath the version folder. The [`Canonical`] schemas use the folder path /// as defined for that schema. #[must_use] pub fn to_folder_prefix(&self) -> String { match self { - Self::Bundled | Self::VSCode => "bundled/".to_string(), + Self::Bundled | Self::VSCode => "bundled/".to_string(), Self::Canonical => String::new(), } } @@ -94,34 +91,30 @@ impl SchemaForm { /// Returns every schema form for convenient iteration. #[must_use] pub fn all() -> Vec { - vec![ - Self::Bundled, - Self::VSCode, - Self::Canonical, - ] + vec![Self::Bundled, Self::VSCode, Self::Canonical] } } /// Defines the versions of DSC recognized for schema validation and handling. -/// +/// /// The DSC schemas are published into three folders: -/// +/// /// - `v..` always includes the exact JSON Schema that shipped in that release /// of DSC. /// - `v.` always includes the latest JSON Schema compatible with that minor version /// of DSC. /// - `v` always includes the latest JSON Schema compatible with that major version of DSC. -/// +/// /// Pinning to `v` requires the least-frequent updating of the `$schema` in configuration /// documents and resource manifests, but also introduces changes that affect those schemas /// (without breaking changes) regularly. Some of the added features may not be effective in the /// version of DSC a user has installed. -/// +/// /// Pinning to `v.` ensures that users always have the latest schemas for the version /// of DSC they're using without schema changes that they may not be able to take advantage of. /// However, it requires updating the resource manifests and configuration documents with each /// minor release of DSC. -/// +/// /// Pinning to `v..` is the most specific option, but requires the most /// frequent updating on the part of resource and configuration authors. #[derive(Debug, Default, Clone, Copy, Hash, Eq, PartialEq)] @@ -129,7 +122,6 @@ pub enum RecognizedSchemaVersion { // Before any relase is published, this enum must be updated with the new version variants. // Every release requires a patch version, like `V3_0_1` or `v3_1_0`. New minor releases also // require a new minor version, like `v3_1`. - /// Represents `v3` schema folder. #[default] V3, @@ -196,14 +188,14 @@ impl RecognizedSchemaVersion { } /// Returns the constructed URI for a hosted DSC schema. -/// +/// /// This convenience function simplifies constructing the URIs for the various published schemas /// that DSC recognizes, instead of needing to maintain long lists of those recognized schemas. /// This function should primarily be called by [`get_recognized_schema_uris`], not called /// directly. -/// +/// /// Parameters: -/// +/// /// - `schema_file_base_name` - specify the base name for the schema file, like `document` for /// the configuration document schema or `manifest` for the resource manifest schema. /// - `schema_folder_path` - specify the folder path for the schema file relative to the version @@ -218,7 +210,7 @@ pub(crate) fn get_recognized_schema_uri( schema_folder_path: &str, schema_version: RecognizedSchemaVersion, schema_form: SchemaForm, - schema_uri_prefix: SchemaUriPrefix + schema_uri_prefix: SchemaUriPrefix, ) -> String { format!( "{schema_uri_prefix}/{schema_version}/{}{schema_folder_path}/{schema_file_base_name}{}", @@ -228,13 +220,13 @@ pub(crate) fn get_recognized_schema_uri( } /// Returns the vector of recognized URIs for a given schema. -/// +/// /// This convenience function generates a vector containing every recognized JSON Schema `$id` URI /// for a specific schema. It handles returning the schemas for every recognized host, version, /// and form. -/// +/// /// Parameters: -/// +/// /// - `schema_file_base_name` - specify the base name for the schema file, like `document` for /// the configuration document schema or `manifest` for the resource manifest schema. /// - `schema_folder_path` - specify the folder path for the schema file relative to the version @@ -246,9 +238,9 @@ pub(crate) fn get_recognized_schema_uri( pub(crate) fn get_recognized_schema_uris( schema_file_base_name: &str, schema_folder_path: &str, - should_bundle: bool + should_bundle: bool, ) -> Vec { - let mut uris: Vec = Vec::new(); + let mut uris: Vec = Vec::new(); let schema_forms = if should_bundle { SchemaForm::all() } else { @@ -257,15 +249,13 @@ pub(crate) fn get_recognized_schema_uris( for uri_prefix in SchemaUriPrefix::all() { for schema_form in schema_forms.iter().copied() { for schema_version in RecognizedSchemaVersion::all() { - uris.push( - get_recognized_schema_uri( - schema_file_base_name, - schema_folder_path, - schema_version, - schema_form, - uri_prefix - ) - ); + uris.push(get_recognized_schema_uri( + schema_file_base_name, + schema_folder_path, + schema_version, + schema_form, + uri_prefix, + )); } } } @@ -275,7 +265,7 @@ pub(crate) fn get_recognized_schema_uris( /// Returns the JSON Schema to validate that a `$schema` keyword for a DSC type is one of the /// recognized URIs. -/// +/// /// This is a convenience function used by the [`DscRepoSchema`] trait. It's not intended for /// direct use. #[must_use] @@ -283,15 +273,13 @@ pub(crate) fn get_recognized_uris_subschema( metadata: &Schema, schema_file_base_name: &str, schema_folder_path: &str, - should_bundle: bool + should_bundle: bool, ) -> Schema { - let enums: Vec = get_recognized_schema_uris( - schema_file_base_name, - schema_folder_path, - should_bundle - ).iter().map( - |schema_uri| serde_json::Value::String(schema_uri.clone()) - ).collect(); + let enums: Vec = + get_recognized_schema_uris(schema_file_base_name, schema_folder_path, should_bundle) + .iter() + .map(|schema_uri| serde_json::Value::String(schema_uri.clone())) + .collect(); json_schema!({ "type": "string", @@ -304,13 +292,13 @@ pub(crate) fn get_recognized_uris_subschema( /// Returns the recognized schema URI for the latest major version with the /// `aka.ms` URI prefix. -/// +/// /// If the schema is published in bundled form, this function returns the URI for that form. /// Otherwise, it returns the URI for the canonical (non-bundled) form. The VS Code form of the /// schema is never returned as the default. -/// +/// /// Parameters: -/// +/// /// - `schema_file_base_name` - specify the base name for the schema file, like `document` for /// the configuration document schema or `manifest` for the resource manifest schema. /// - `schema_folder_path` - specify the folder path for the schema file relative to the version @@ -322,20 +310,20 @@ pub(crate) fn get_recognized_uris_subschema( pub(crate) fn get_default_schema_uri( schema_file_base_name: &str, schema_folder_path: &str, - should_bundle: bool + should_bundle: bool, ) -> String { get_recognized_schema_uri( schema_file_base_name, schema_folder_path, RecognizedSchemaVersion::default(), get_default_schema_form(should_bundle), - SchemaUriPrefix::default() + SchemaUriPrefix::default(), ) } /// Returns the default form for a schema depending on whether it publishes with its references /// bundled. -/// +/// /// If a schema is published in bundled form, the bundled form is the default. Otherwise, the /// default form is canonical (non-bundled). fn get_default_schema_form(should_bundle: bool) -> SchemaForm { @@ -348,18 +336,18 @@ fn get_default_schema_form(should_bundle: bool) -> SchemaForm { /// Defines a reusable trait to simplify managing multiple versions of JSON Schemas for DSC /// structs and enums. -/// +/// /// This trait is only intended for use by definitions in the DSC repository. -pub trait DscRepoSchema : JsonSchema { +pub trait DscRepoSchema: JsonSchema { /// Defines the base name for the exported JSON Schema, like `document` for /// [`Configuration`]. - /// + /// /// [`Configuration`]: crate::configure::config_doc::Configuration const SCHEMA_FILE_BASE_NAME: &'static str; /// Defines the folder path for the schema relative to the published version folder, like /// `config` for [`Configuration`]. - /// + /// /// [`Configuration`]: crate::configure::config_doc::Configuration const SCHEMA_FOLDER_PATH: &'static str; @@ -370,7 +358,7 @@ pub trait DscRepoSchema : JsonSchema { fn schema_metadata() -> Schema; /// Returns the default URI for the schema. - /// + /// /// An object representing an instance of the schema can specify any valid URI, but the /// default when creating an instance is the latest major version of the schema with the /// `aka.ms` prefix. If the schema is published in the bundled form, the default is for the @@ -380,7 +368,7 @@ pub trait DscRepoSchema : JsonSchema { get_default_schema_uri( Self::SCHEMA_FILE_BASE_NAME, Self::SCHEMA_FOLDER_PATH, - Self::SCHEMA_SHOULD_BUNDLE + Self::SCHEMA_SHOULD_BUNDLE, ) } @@ -389,20 +377,20 @@ pub trait DscRepoSchema : JsonSchema { fn get_schema_id_uri( schema_version: RecognizedSchemaVersion, schema_form: SchemaForm, - uri_prefix: SchemaUriPrefix + uri_prefix: SchemaUriPrefix, ) -> String { get_recognized_schema_uri( Self::SCHEMA_FILE_BASE_NAME, Self::SCHEMA_FOLDER_PATH, schema_version, schema_form, - uri_prefix + uri_prefix, ) } /// Returns the URI for the VS Code form of the schema with the default prefix for a given /// version. - /// + /// /// If the type isn't published in bundled form, this function returns `None`. #[must_use] fn get_enhanced_schema_id_uri(schema_version: RecognizedSchemaVersion) -> Option { @@ -415,7 +403,7 @@ pub trait DscRepoSchema : JsonSchema { Self::SCHEMA_FOLDER_PATH, schema_version, SchemaForm::VSCode, - SchemaUriPrefix::default() + SchemaUriPrefix::default(), )) } @@ -428,7 +416,7 @@ pub trait DscRepoSchema : JsonSchema { Self::SCHEMA_FOLDER_PATH, schema_version, SchemaForm::Canonical, - SchemaUriPrefix::default() + SchemaUriPrefix::default(), ) } @@ -445,12 +433,12 @@ pub trait DscRepoSchema : JsonSchema { Self::SCHEMA_FOLDER_PATH, schema_version, SchemaForm::Bundled, - SchemaUriPrefix::default() + SchemaUriPrefix::default(), )) } /// Returns the list of recognized schema URIs for the struct or enum. - /// + /// /// This convenience function generates a vector containing every recognized JSON Schema `$id` /// URI for a specific schema. It handles returning the schemas for every recognized prefix, /// version, and form. @@ -459,12 +447,12 @@ pub trait DscRepoSchema : JsonSchema { get_recognized_schema_uris( Self::SCHEMA_FILE_BASE_NAME, Self::SCHEMA_FOLDER_PATH, - Self::SCHEMA_SHOULD_BUNDLE + Self::SCHEMA_SHOULD_BUNDLE, ) } /// Returns the subschema to validate a `$schema` keyword pointing to the type. - /// + /// /// Every schema has a canonical `$id`, but DSC needs to maintain compatibility with schemas /// within a major version and ensure that previous schema versions can be correctly /// recognized and validated. This method generates the appropriate subschema with every @@ -476,7 +464,7 @@ pub trait DscRepoSchema : JsonSchema { &Self::schema_metadata(), Self::SCHEMA_FILE_BASE_NAME, Self::SCHEMA_FOLDER_PATH, - Self::SCHEMA_SHOULD_BUNDLE + Self::SCHEMA_SHOULD_BUNDLE, ) } @@ -487,19 +475,19 @@ pub trait DscRepoSchema : JsonSchema { } /// Validates the `$schema` keyword for deserializing instances. - /// + /// /// This method simplifies the validation of a type that has the `$schema` keyword and expects /// that instances of the type in data indicate which schema version DSC should use to validate /// them. - /// + /// /// This method includes a default implementation to avoid requiring the implementation for /// types that don't define the `$schema` keyword in their serialized form. - /// + /// /// Any DSC type that serializes with the `$schema` keyword **must** define this /// method to actually validate the instance. - /// + /// /// # Errors - /// + /// /// If the value for the schema field isn't a recognized schema, the method should raise the /// [`DscError::UnrecognizedSchemaUri`] error. fn validate_schema_uri(&self) -> Result<(), DscError> { @@ -521,7 +509,7 @@ mod test { "config", RecognizedSchemaVersion::V3, SchemaForm::Bundled, - SchemaUriPrefix::AkaDotMs + SchemaUriPrefix::AkaDotMs, ); assert_eq!(expected, actual) } @@ -551,19 +539,32 @@ mod test { "https://aka.ms/dsc/schemas/v3.0.1/config/document.json".to_string(), "https://aka.ms/dsc/schemas/v3.0.0/config/document.json".to_string(), "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1.0/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.2/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.1/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.0/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/bundled/config/document.vscode.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1/bundled/config/document.vscode.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1.0/bundled/config/document.vscode.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0/bundled/config/document.vscode.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.2/bundled/config/document.vscode.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.1/bundled/config/document.vscode.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.0/bundled/config/document.vscode.json".to_string(), + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1/bundled/config/document.json" + .to_string(), + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1.0/bundled/config/document.json" + .to_string(), + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0/bundled/config/document.json" + .to_string(), + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.2/bundled/config/document.json" + .to_string(), + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.1/bundled/config/document.json" + .to_string(), + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.0/bundled/config/document.json" + .to_string(), + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/bundled/config/document.vscode.json" + .to_string(), + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1/bundled/config/document.vscode.json" + .to_string(), + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1.0/bundled/config/document.vscode.json" + .to_string(), + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0/bundled/config/document.vscode.json" + .to_string(), + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.2/bundled/config/document.vscode.json" + .to_string(), + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.1/bundled/config/document.vscode.json" + .to_string(), + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.0/bundled/config/document.vscode.json" + .to_string(), "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/config/document.json".to_string(), "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1/config/document.json".to_string(), "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1.0/config/document.json".to_string(), @@ -573,11 +574,7 @@ mod test { "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.0/config/document.json".to_string(), ]; - let actual = get_recognized_schema_uris( - "document", - "config", - true - ); + let actual = get_recognized_schema_uris("document", "config", true); for (index, expected_uri) in expected.iter().enumerate() { assert_eq!(*expected_uri, actual[index]); @@ -592,9 +589,15 @@ mod test { let schema_file_base_name = "document"; let schema_folder_path = "config"; - - assert_eq!(expected_bundled, get_default_schema_uri(schema_file_base_name, schema_folder_path, true)); - assert_eq!(expected_canonical, get_default_schema_uri(schema_file_base_name, schema_folder_path, false)) + + assert_eq!( + expected_bundled, + get_default_schema_uri(schema_file_base_name, schema_folder_path, true) + ); + assert_eq!( + expected_canonical, + get_default_schema_uri(schema_file_base_name, schema_folder_path, false) + ) } #[test] @@ -621,10 +624,7 @@ mod test { let canonical_uri = "https://aka.ms/dsc/schemas/v3/example/schema.json".to_string(); let schema_version = RecognizedSchemaVersion::V3; - assert_eq!( - bundled_uri, - ExampleBundledSchema::default_schema_id_uri() - ); + assert_eq!(bundled_uri, ExampleBundledSchema::default_schema_id_uri()); assert_eq!( Some(bundled_uri), @@ -663,15 +663,9 @@ mod test { let canonical_uri = "https://aka.ms/dsc/schemas/v3/example/schema.json".to_string(); let schema_version = RecognizedSchemaVersion::V3; - assert_eq!( - canonical_uri, - ExampleNotBundledSchema::default_schema_id_uri() - ); + assert_eq!(canonical_uri, ExampleNotBundledSchema::default_schema_id_uri()); - assert_eq!( - None, - ExampleNotBundledSchema::get_bundled_schema_id_uri(schema_version) - ); + assert_eq!(None, ExampleNotBundledSchema::get_bundled_schema_id_uri(schema_version)); assert_eq!( None, diff --git a/dsc_lib/src/util.rs b/dsc_lib/src/util.rs index 1d7e4dac9..915a8fd7b 100644 --- a/dsc_lib/src/util.rs +++ b/dsc_lib/src/util.rs @@ -4,16 +4,16 @@ use crate::dscerror::DscError; use rust_i18n::t; use serde_json::Value; +use std::env; use std::fs; use std::fs::File; use std::io::BufReader; -use std::path::PathBuf; use std::path::Path; -use std::env; +use std::path::PathBuf; use tracing::debug; pub struct DscSettingValue { - pub setting: Value, + pub setting: Value, pub policy: Value, } @@ -42,21 +42,13 @@ impl Default for DscSettingValue { pub fn parse_input_to_json(value: &str) -> Result { match serde_json::from_str(value) { Ok(json) => Ok(json), - Err(_) => { - match serde_yaml::from_str::(value) { - Ok(yaml) => { - match serde_json::to_value(yaml) { - Ok(json) => Ok(json.to_string()), - Err(err) => { - Err(DscError::Json(err)) - } - } - }, - Err(err) => { - Err(DscError::Yaml(err)) - } - } - } + Err(_) => match serde_yaml::from_str::(value) { + Ok(yaml) => match serde_json::to_value(yaml) { + Ok(json) => Ok(json.to_string()), + Err(err) => Err(DscError::Json(err)), + }, + Err(err) => Err(DscError::Yaml(err)), + }, } } @@ -70,7 +62,11 @@ pub fn parse_input_to_json(value: &str) -> Result { /// A string that holds the regex pattern. #[must_use] pub fn convert_wildcard_to_regex(wildcard: &str) -> String { - let mut regex = wildcard.to_string().replace('.', "\\.").replace('?', ".").replace('*', ".*?"); + let mut regex = wildcard + .to_string() + .replace('.', "\\.") + .replace('?', ".") + .replace('*', ".*?"); regex.insert(0, '^'); regex.push('$'); regex @@ -108,14 +104,13 @@ mod tests { /// /// Will return `Err` if could not find requested setting. pub fn get_setting(value_name: &str) -> Result { - const SETTINGS_FILE_NAME: &str = "dsc.settings.json"; // Note that default settings file has root nodes as settings schema version that is specific to this version of dsc const DEFAULT_SETTINGS_FILE_NAME: &str = "dsc_default.settings.json"; const DEFAULT_SETTINGS_SCHEMA_VERSION: &str = "1"; let mut result: DscSettingValue = DscSettingValue::default(); - let mut settings_file_path : PathBuf; + let mut settings_file_path: PathBuf; if let Some(exe_home) = get_exe_path()?.parent() { // First, get setting from the default settings file @@ -123,19 +118,47 @@ pub fn get_setting(value_name: &str) -> Result { if let Ok(v) = load_value_from_json(&settings_file_path, DEFAULT_SETTINGS_SCHEMA_VERSION) { if let Some(n) = v.get(value_name) { result.setting = n.clone(); - debug!("{}", t!("util.foundSetting", name = value_name, path = settings_file_path.to_string_lossy())); + debug!( + "{}", + t!( + "util.foundSetting", + name = value_name, + path = settings_file_path.to_string_lossy() + ) + ); } } else { - debug!("{}", t!("util.notFoundSetting", name = value_name, path = settings_file_path.to_string_lossy())); + debug!( + "{}", + t!( + "util.notFoundSetting", + name = value_name, + path = settings_file_path.to_string_lossy() + ) + ); } // Second, get setting from the active settings file overwriting previous value settings_file_path = exe_home.join(SETTINGS_FILE_NAME); if let Ok(v) = load_value_from_json(&settings_file_path, value_name) { result.setting = v; - debug!("{}", t!("util.foundSetting", name = value_name, path = settings_file_path.to_string_lossy())); + debug!( + "{}", + t!( + "util.foundSetting", + name = value_name, + path = settings_file_path.to_string_lossy() + ) + ); } else { - debug!("{}", t!("util.notFoundSetting", name = value_name, path = settings_file_path.to_string_lossy())); + debug!( + "{}", + t!( + "util.notFoundSetting", + name = value_name, + path = settings_file_path.to_string_lossy() + ) + ); } } else { debug!("{}", t!("util.failedToGetExePath")); @@ -145,13 +168,29 @@ pub fn get_setting(value_name: &str) -> Result { settings_file_path = PathBuf::from(get_settings_policy_file_path()); if let Ok(v) = load_value_from_json(&settings_file_path, value_name) { result.policy = v; - debug!("{}", t!("util.foundSetting", name = value_name, path = settings_file_path.to_string_lossy())); + debug!( + "{}", + t!( + "util.foundSetting", + name = value_name, + path = settings_file_path.to_string_lossy() + ) + ); } else { - debug!("{}", t!("util.notFoundSetting", name = value_name, path = settings_file_path.to_string_lossy())); + debug!( + "{}", + t!( + "util.notFoundSetting", + name = value_name, + path = settings_file_path.to_string_lossy() + ) + ); } if (result.setting == serde_json::Value::Null) && (result.policy == serde_json::Value::Null) { - return Err(DscError::NotSupported(t!("util.settingNotFound", name = value_name).to_string())); + return Err(DscError::NotSupported( + t!("util.settingNotFound", name = value_name).to_string(), + )); } Ok(result) @@ -170,7 +209,7 @@ fn load_value_from_json(path: &PathBuf, value_name: &str) -> Result Result { } #[cfg(target_os = "windows")] -fn get_settings_policy_file_path() -> String -{ +fn get_settings_policy_file_path() -> String { // $env:ProgramData+"\dsc\dsc.settings.json" // This location is writable only by admins, but readable by all users - let Ok(local_program_data_path) = std::env::var("ProgramData") else { return String::new(); }; - Path::new(&local_program_data_path).join("dsc").join("dsc.settings.json").display().to_string() + let Ok(local_program_data_path) = std::env::var("ProgramData") else { + return String::new(); + }; + Path::new(&local_program_data_path) + .join("dsc") + .join("dsc.settings.json") + .display() + .to_string() } #[cfg(not(target_os = "windows"))] -fn get_settings_policy_file_path() -> String -{ +fn get_settings_policy_file_path() -> String { // "/etc/dsc/dsc.settings.json" // This location is writable only by admins, but readable by all users - Path::new("/etc").join("dsc").join("dsc.settings.json").display().to_string() + Path::new("/etc") + .join("dsc") + .join("dsc.settings.json") + .display() + .to_string() } #[macro_export] diff --git a/dscecho/src/args.rs b/dscecho/src/args.rs index fcca1ce43..f93c97a51 100644 --- a/dscecho/src/args.rs +++ b/dscecho/src/args.rs @@ -6,6 +6,10 @@ use clap::Parser; #[derive(Debug, Parser)] #[clap(name = "dscecho", version = "1.0.0", about = "Debugging helper resource that echos the input.", long_about = None)] pub struct Args { - #[clap(short, long, help = "The input to the echo command as JSON. If no input is provided the JSON schema for the input is returned.")] + #[clap( + short, + long, + help = "The input to the echo command as JSON. If no input is provided the JSON schema for the input is returned." + )] pub input: Option, } diff --git a/dscecho/src/main.rs b/dscecho/src/main.rs index 6845211dc..06404d3ec 100644 --- a/dscecho/src/main.rs +++ b/dscecho/src/main.rs @@ -4,12 +4,12 @@ mod args; mod echo; +use crate::echo::{Echo, Output}; use args::Args; use clap::Parser; use rust_i18n::{i18n, t}; use schemars::schema_for; use serde_json::{Map, Value}; -use crate::echo::{Echo, Output}; i18n!("locales", fallback = "en-us"); @@ -29,7 +29,7 @@ fn main() { match echo.output { Output::SecureString(_) | Output::SecureObject(_) => { echo.output = Output::String(SECURE_VALUE_REDACTED.to_string()); - }, + } Output::Array(ref mut arr) => { for item in arr.iter_mut() { if is_secure_value(item) { @@ -38,10 +38,10 @@ fn main() { *item = redact(item); } } - }, + } Output::Object(ref mut obj) => { *obj = redact(obj); - }, + } _ => {} } } diff --git a/lib/osinfo_lib/src/lib.rs b/lib/osinfo_lib/src/lib.rs index c20c63f6a..3c284788d 100644 --- a/lib/osinfo_lib/src/lib.rs +++ b/lib/osinfo_lib/src/lib.rs @@ -63,12 +63,10 @@ impl OsInfo { }; let version = os_info.version().to_string(); let name = if include_name { - Some( - match &architecture { - Some(arch) => format!("{family} {version} {arch}"), - None => format!("{family:?} {version}"), - } - ) + Some(match &architecture { + Some(arch) => format!("{family} {version} {arch}"), + None => format!("{family:?} {version}"), + }) } else { None }; diff --git a/pal/build.rs b/pal/build.rs index dfc619928..fcc057279 100644 --- a/pal/build.rs +++ b/pal/build.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use cc::windows_registry; use std::env; use std::process::Command; -use cc::windows_registry; // Environment variables used in this build file are documented at // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts @@ -26,7 +26,8 @@ fn get_tool_var(name: &str) -> Option { .or_else(|_| { println!("cargo:rerun-if-env-changed={name}"); env::var(name) - }).ok() + }) + .ok() } fn make_import_lib(name: &str) { @@ -44,15 +45,11 @@ fn make_import_lib(name: &str) { dlltool.args(["-d", &format!("{name}.def")]); dlltool.args(["-m", arch]); - dlltool.args([ - "-l", - &format!("{}/{}.lib", env::var("OUT_DIR").unwrap(), name), - ]); + dlltool.args(["-l", &format!("{}/{}.lib", env::var("OUT_DIR").unwrap(), name)]); assert!(dlltool.spawn().unwrap().wait().unwrap().success(), "dlltool failed"); } else { // Find the 'lib.exe' from Visual Studio tools' location. - let mut lib = windows_registry::find(&env::var("TARGET").unwrap(), "lib.exe") - .expect("cannot find lib.exe"); + let mut lib = windows_registry::find(&env::var("TARGET").unwrap(), "lib.exe").expect("cannot find lib.exe"); let arch = match env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() { "x86" => "X86", @@ -63,11 +60,7 @@ fn make_import_lib(name: &str) { lib.arg(format!("/machine:{arch}")); lib.arg(format!("/def:{name}.def")); - lib.arg(format!( - "/out:{}/{}.lib", - env::var("OUT_DIR").unwrap(), - name - )); + lib.arg(format!("/out:{}/{}.lib", env::var("OUT_DIR").unwrap(), name)); assert!(lib.spawn().unwrap().wait().unwrap().success(), "lib.exe failed"); } } diff --git a/pal/src/windows.rs b/pal/src/windows.rs index a736fc245..23e424fda 100644 --- a/pal/src/windows.rs +++ b/pal/src/windows.rs @@ -7,9 +7,9 @@ extern "C" { } /// # Safety -/// +/// /// This function is unsafe because it dereferences a raw pointer. -/// +/// /// # Panics /// /// Will panic if the api returns 0 diff --git a/process/src/main.rs b/process/src/main.rs index 7c9695ede..e606bfa76 100644 --- a/process/src/main.rs +++ b/process/src/main.rs @@ -2,14 +2,13 @@ // Licensed under the MIT License. mod process_info; +use crate::process_info::ProcessInfo; use std::env; -use std::process::exit; use std::io::{self, Read}; +use std::process::exit; use sysinfo::{ProcessesToUpdate, System}; -use crate::process_info::ProcessInfo; -fn get_task_list() -> Vec -{ +fn get_task_list() -> Vec { let mut result = Vec::new(); let mut s = System::new(); s.refresh_processes(ProcessesToUpdate::All, true); @@ -47,25 +46,24 @@ fn main() { // one argument passed match args[1].as_str() { "list" => { - for p in get_task_list() - { + for p in get_task_list() { let json = serde_json::to_string(&p).unwrap(); println!("{json}"); } exit(0); - }, - "get" | "set" | "test" => { // used for testing only + } + "get" | "set" | "test" => { + // used for testing only print_input(); exit(0); - }, + } _ => { help(); exit(1); - }, + } } - } - else { + } else { help(); exit(1); } -} \ No newline at end of file +} diff --git a/process/src/process_info.rs b/process/src/process_info.rs index c73833a9a..f5561fa9a 100644 --- a/process/src/process_info.rs +++ b/process/src/process_info.rs @@ -7,12 +7,11 @@ use serde::Serialize; pub struct ProcessInfo { pub pid: u32, pub name: String, - pub cmdline: String + pub cmdline: String, } impl ProcessInfo { pub fn new() -> Self { - Self { pid: 0, name: String::new(), diff --git a/registry/src/args.rs b/registry/src/args.rs index 8da9a8cdc..908ce01c6 100644 --- a/registry/src/args.rs +++ b/registry/src/args.rs @@ -7,7 +7,6 @@ use rust_i18n::t; #[derive(Parser)] #[clap(name = "registry", version = "0.0.1", about = t!("args.about").to_string(), long_about = None)] pub struct Arguments { - #[clap(subcommand)] pub subcommand: SubCommand, } diff --git a/registry/src/main.rs b/registry/src/main.rs index e1b025c0a..1c776b405 100644 --- a/registry/src/main.rs +++ b/registry/src/main.rs @@ -32,21 +32,35 @@ fn main() { let args = Arguments::parse(); match args.subcommand { - args::SubCommand::Query { key_path, value_name, recurse } => { + args::SubCommand::Query { + key_path, + value_name, + recurse, + } => { debug!("Get key_path: {key_path}, value_name: {value_name:?}, recurse: {recurse}"); - }, + } args::SubCommand::Set { key_path, value } => { debug!("Set key_path: {key_path}, value: {value}"); - }, - args::SubCommand::Remove { key_path, value_name, recurse } => { + } + args::SubCommand::Remove { + key_path, + value_name, + recurse, + } => { debug!("Remove key_path: {key_path}, value_name: {value_name:?}, recurse: {recurse}"); - }, - args::SubCommand::Find { key_path, find, recurse, keys_only, values_only } => { + } + args::SubCommand::Find { + key_path, + find, + recurse, + keys_only, + values_only, + } => { debug!("Find key_path: {key_path}, find: {find}, recurse: {recurse:?}, keys_only: {keys_only:?}, values_only: {values_only:?}"); - }, + } args::SubCommand::Config { subcommand } => { match subcommand { - args::ConfigSubCommand::Get{input} => { + args::ConfigSubCommand::Get { input } => { debug!("Get input: {input}"); let reg_helper = match RegistryHelper::new_from_json(&input) { Ok(reg_helper) => reg_helper, @@ -59,14 +73,14 @@ fn main() { Ok(reg_config) => { let json = serde_json::to_string(®_config).unwrap(); println!("{json}"); - }, + } Err(err) => { error!("{err}"); exit(EXIT_REGISTRY_ERROR); } } - }, - args::ConfigSubCommand::Set{input, what_if} => { + } + args::ConfigSubCommand::Set { input, what_if } => { debug!("Set input: {input}, what_if: {what_if}"); let mut reg_helper = match RegistryHelper::new_from_json(&input) { Ok(reg_helper) => reg_helper, @@ -75,7 +89,9 @@ fn main() { exit(EXIT_INVALID_INPUT); } }; - if what_if { reg_helper.enable_what_if(); } + if what_if { + reg_helper.enable_what_if(); + } // In what-if, if the desired state is _exist: false, route to delete if what_if { @@ -85,8 +101,8 @@ fn main() { Ok(Some(reg_config)) => { let json = serde_json::to_string(®_config).unwrap(); println!("{json}"); - }, - Ok(None) => {}, + } + Ok(None) => {} Err(err) => { error!("{err}"); exit(EXIT_REGISTRY_ERROR); @@ -102,14 +118,14 @@ fn main() { let json = serde_json::to_string(&config).unwrap(); println!("{json}"); } - }, + } Err(err) => { error!("{err}"); exit(EXIT_REGISTRY_ERROR); } } - }, - args::ConfigSubCommand::Delete{input} => { + } + args::ConfigSubCommand::Delete { input } => { debug!("Delete input: {input}"); let reg_helper = match RegistryHelper::new_from_json(&input) { Ok(reg_helper) => reg_helper, @@ -122,21 +138,21 @@ fn main() { Ok(Some(reg_config)) => { let json = serde_json::to_string(®_config).unwrap(); println!("{json}"); - }, - Ok(None) => {}, + } + Ok(None) => {} Err(err) => { error!("{err}"); exit(EXIT_REGISTRY_ERROR); } } - }, + } } - }, + } args::SubCommand::Schema => { let schema = schema_for!(Registry); - let json =serde_json::to_string(&schema).unwrap(); + let json = serde_json::to_string(&schema).unwrap(); println!("{json}"); - }, + } } exit(EXIT_SUCCESS); @@ -144,14 +160,17 @@ fn main() { pub fn enable_tracing() { // default filter to trace level - let filter = EnvFilter::builder().with_default_directive(LevelFilter::TRACE.into()).parse("").unwrap_or_default(); + let filter = EnvFilter::builder() + .with_default_directive(LevelFilter::TRACE.into()) + .parse("") + .unwrap_or_default(); let layer = tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr); let fmt = layer - .with_ansi(false) - .with_level(true) - .with_line_number(true) - .json() - .boxed(); + .with_ansi(false) + .with_level(true) + .with_line_number(true) + .json() + .boxed(); let subscriber = tracing_subscriber::Registry::default().with(fmt).with(filter); diff --git a/registry_lib/src/config.rs b/registry_lib/src/config.rs index 372180360..9cf45b353 100644 --- a/registry_lib/src/config.rs +++ b/registry_lib/src/config.rs @@ -38,5 +38,5 @@ pub struct Registry { #[serde(deny_unknown_fields)] pub struct Metadata { #[serde(rename = "whatIf", skip_serializing_if = "Option::is_none")] - pub what_if: Option> + pub what_if: Option>, } diff --git a/registry_lib/src/lib.rs b/registry_lib/src/lib.rs index 7b2326bb4..ec914f47a 100644 --- a/registry_lib/src/lib.rs +++ b/registry_lib/src/lib.rs @@ -1,16 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use registry::{Data, Hive, RegKey, Security, key, value}; -use rust_i18n::t; -use utfx::{U16CString, UCString}; use crate::config::{Metadata, Registry, RegistryValueData}; use crate::error::RegistryError; +use registry::{key, value, Data, Hive, RegKey, Security}; +use rust_i18n::t; +use utfx::{U16CString, UCString}; rust_i18n::i18n!("locales", fallback = "en-us"); -pub mod error; pub mod config; +pub mod error; pub struct RegistryHelper { config: Registry, @@ -37,14 +37,12 @@ impl RegistryHelper { let key_path = registry.key_path.clone(); let (hive, subkey) = get_hive_from_path(&key_path)?; - Ok( - Self { - config: registry, - hive, - subkey: subkey.to_string(), - what_if: false - } - ) + Ok(Self { + config: registry, + hive, + subkey: subkey.to_string(), + what_if: false, + }) } /// Create a new `RegistryHelper`. @@ -56,7 +54,11 @@ impl RegistryHelper { /// # Errors /// /// * `RegistryError` - The error that occurred. - pub fn new(key_path: &str, value_name: Option, value_data: Option) -> Result { + pub fn new( + key_path: &str, + value_name: Option, + value_data: Option, + ) -> Result { let (hive, subkey) = get_hive_from_path(key_path)?; let config = Registry { key_path: key_path.to_string(), @@ -65,14 +67,12 @@ impl RegistryHelper { metadata: None, exist: None, }; - Ok( - Self { - config, - hive, - subkey: subkey.to_string(), - what_if: false - } - ) + Ok(Self { + config, + hive, + subkey: subkey.to_string(), + what_if: false, + }) } pub fn enable_what_if(&mut self) { @@ -91,9 +91,7 @@ impl RegistryHelper { pub fn get(&self) -> Result { let exist: bool; let (reg_key, _subkey) = match self.open(Security::Read) { - Ok((reg_key, subkey)) => { - (reg_key, subkey) - }, + Ok((reg_key, subkey)) => (reg_key, subkey), Err(RegistryError::RegistryKeyNotFound(_)) => { exist = false; return Ok(Registry { @@ -101,14 +99,14 @@ impl RegistryHelper { exist: Some(exist), ..Default::default() }); - }, + } Err(e) => return Err(e), }; if let Some(value_name) = &self.config.value_name { let value = match reg_key.value(value_name) { Ok(value) => value, - Err(value::Error::NotFound(_,_)) => { + Err(value::Error::NotFound(_, _)) => { exist = false; return Ok(Registry { key_path: self.config.key_path.clone(), @@ -116,7 +114,7 @@ impl RegistryHelper { exist: Some(exist), ..Default::default() }); - }, + } Err(e) => return Err(RegistryError::RegistryValue(e)), }; @@ -161,19 +159,17 @@ impl RegistryHelper { if self.what_if { what_if_metadata.push(t!("registry_helper.whatIfCreateKey", subkey = subkey).to_string()); - } - else { + } else { reg_key = reg_key.create(path, Security::CreateSubKey)?; } } if self.what_if { None - } - else { + } else { Some(self.open(Security::Write)?.0) } - }, - Err(e) => return self.handle_error_or_what_if(e) + } + Err(e) => return self.handle_error_or_what_if(e), }; if let Some(value_name) = &self.config.value_name { @@ -192,35 +188,28 @@ impl RegistryHelper { return self.handle_error_or_what_if(RegistryError::Utf16Conversion("valueData".to_string())); }; Data::String(utf16) - }, + } RegistryValueData::ExpandString(s) => { let Ok(utf16) = U16CString::from_str(s) else { return self.handle_error_or_what_if(RegistryError::Utf16Conversion("valueData".to_string())); }; Data::ExpandString(utf16) - }, - RegistryValueData::Binary(b) => { - Data::Binary(b.clone()) - }, - RegistryValueData::DWord(d) => { - Data::U32(*d) - }, + } + RegistryValueData::Binary(b) => Data::Binary(b.clone()), + RegistryValueData::DWord(d) => Data::U32(*d), RegistryValueData::MultiString(m) => { let mut m16: Vec> = Vec::>::new(); for s in m { let Ok(utf16) = U16CString::from_str(s) else { - return self.handle_error_or_what_if(RegistryError::Utf16Conversion("valueData".to_string())); + return self + .handle_error_or_what_if(RegistryError::Utf16Conversion("valueData".to_string())); }; m16.push(utf16); } Data::MultiString(m16) - }, - RegistryValueData::QWord(q) => { - Data::U64(*q) - }, - RegistryValueData::None => { - Data::None - }, + } + RegistryValueData::QWord(q) => Data::U64(*q), + RegistryValueData::None => Data::None, }; if self.what_if { @@ -228,7 +217,13 @@ impl RegistryHelper { key_path: self.config.key_path.clone(), value_data: convert_reg_value(&data)?, value_name: self.config.value_name.clone(), - metadata: if what_if_metadata.is_empty() { None } else { Some(Metadata { what_if: Some(what_if_metadata) })}, + metadata: if what_if_metadata.is_empty() { + None + } else { + Some(Metadata { + what_if: Some(what_if_metadata), + }) + }, ..Default::default() })); } @@ -241,7 +236,13 @@ impl RegistryHelper { if self.what_if { return Ok(Some(Registry { key_path: self.config.key_path.clone(), - metadata: if what_if_metadata.is_empty() { None } else { Some(Metadata { what_if: Some(what_if_metadata) })}, + metadata: if what_if_metadata.is_empty() { + None + } else { + Some(Metadata { + what_if: Some(what_if_metadata), + }) + }, ..Default::default() })); } @@ -265,7 +266,7 @@ impl RegistryHelper { Err(RegistryError::RegistryKeyNotFound(_)) => { eprintln!("{}", t!("registry_helper.removeErrorKeyNotExist")); return Ok(None); - }, + } Err(e) => return self.handle_error_or_what_if(e), }; @@ -278,14 +279,16 @@ impl RegistryHelper { return Ok(Some(Registry { key_path: self.config.key_path.clone(), value_name: Some(value_name.clone()), - metadata: Some(Metadata { what_if: Some(what_if_metadata) }), + metadata: Some(Metadata { + what_if: Some(what_if_metadata), + }), ..Default::default() })); } match reg_key.delete_value(value_name) { Ok(()) | Err(value::Error::NotFound(_, _)) => { // if the value doesn't exist, we don't need to do anything - }, + } Err(e) => return self.handle_error_or_what_if(RegistryError::RegistryValue(e)), } } else { @@ -304,11 +307,20 @@ impl RegistryHelper { what_if_metadata.push(t!("registry_helper.whatIfDeleteSubkey", subkey_name = subkey_name).to_string()); return Ok(Some(Registry { key_path: self.config.key_path.clone(), - metadata: Some(Metadata { what_if: Some(what_if_metadata) }), + metadata: Some(Metadata { + what_if: Some(what_if_metadata), + }), ..Default::default() })); } - eprintln!("{}", t!("registry_helper.removeDeletingSubKey", name = subkey_name, parent = parent_reg_key)); + eprintln!( + "{}", + t!( + "registry_helper.removeDeletingSubKey", + name = subkey_name, + parent = parent_reg_key + ) + ); let Ok(subkey_name) = UCString::::from_str(subkey_name) else { return self.handle_error_or_what_if(RegistryError::Utf16Conversion("subkey_name".to_string())); }; @@ -316,7 +328,7 @@ impl RegistryHelper { match parent_reg_key.delete(subkey_name, true) { Ok(()) | Err(key::Error::NotFound(_, _)) => { // if the subkey doesn't exist, we don't need to do anything - }, + } Err(e) => return self.handle_error_or_what_if(RegistryError::RegistryKey(e)), } } @@ -333,7 +345,9 @@ impl RegistryHelper { let parent_key: RegKey; let mut subkeys: Vec<&str> = Vec::new(); let parent_key_path = get_parent_key_path(&self.subkey); - let subkey_name = if parent_key_path.is_empty() { &self.subkey } else { + let subkey_name = if parent_key_path.is_empty() { + &self.subkey + } else { &self.subkey[parent_key_path.len() + 1..] }; if !subkey_name.is_empty() { @@ -347,8 +361,8 @@ impl RegistryHelper { Ok(regkey) => { parent_key = regkey; break; - }, - Err(key::Error::NotFound(_,_)) => { + } + Err(key::Error::NotFound(_, _)) => { let parent_key_path = get_parent_key_path(current_key_path); if parent_key_path.is_empty() { subkeys.insert(0, current_key_path); @@ -358,10 +372,10 @@ impl RegistryHelper { subkeys.insert(0, subkey_name); current_key_path = parent_key_path; } - }, + } Err(e) => { return Err(RegistryError::RegistryKey(e)); - }, + } } } @@ -372,7 +386,9 @@ impl RegistryHelper { if self.what_if { return Ok(Some(Registry { key_path: self.config.key_path.clone(), - metadata: Some(Metadata { what_if: Some(vec![error.to_string()]) }), + metadata: Some(Metadata { + what_if: Some(vec![error.to_string()]), + }), ..Default::default() })); } @@ -382,12 +398,12 @@ impl RegistryHelper { fn get_hive_from_path(path: &str) -> Result<(Hive, &str), RegistryError> { // split the key path to hive and subkey otherwise it's just a hive - let (hive, subkey)= match path.find('\\') { + let (hive, subkey) = match path.find('\\') { Some(index) => { // split at index, but don't include the character at index let (hive, subkey) = path.split_at(index); (hive, &subkey[1..]) - }, + } None => (path, ""), }; @@ -396,8 +412,8 @@ fn get_hive_from_path(path: &str) -> Result<(Hive, &str), RegistryError> { "HKCU" | "HKEY_CURRENT_USER" => Ok((Hive::CurrentUser, subkey)), "HKCR" | "HKEY_CLASSES_ROOT" => Ok((Hive::ClassesRoot, subkey)), "HKLM" | "HKEY_LOCAL_MACHINE" => Ok((Hive::LocalMachine, subkey)), - "HKU" | "HKEY_USERS" => Ok((Hive::Users, subkey)), - _ => Err(RegistryError::InvalidHive(hive.to_string())) + "HKU" | "HKEY_USERS" => Ok((Hive::Users, subkey)), + _ => Err(RegistryError::InvalidHive(hive.to_string())), } } @@ -406,9 +422,7 @@ fn open_regkey(path: &str, permission: Security) -> Result<(RegKey, &str), Regis match hive.open(subkey, permission) { Ok(regkey) => Ok((regkey, subkey)), // handle NotFound error - Err(key::Error::NotFound(_, _)) => { - Err(RegistryError::RegistryKeyNotFound(path.to_string())) - }, + Err(key::Error::NotFound(_, _)) => Err(RegistryError::RegistryKeyNotFound(path.to_string())), Err(e) => Err(RegistryError::RegistryKey(e)), } } @@ -429,10 +443,10 @@ fn convert_reg_value(value: &Data) -> Result, Registry Data::MultiString(m) => { let m: Vec = m.iter().map(|s| s.to_string_lossy()).collect(); Ok(Some(RegistryValueData::MultiString(m))) - }, + } Data::U64(q) => Ok(Some(RegistryValueData::QWord(*q))), Data::None => Ok(None), - _ => Err(RegistryError::UnsupportedValueDataType) + _ => Err(RegistryError::UnsupportedValueDataType), } } @@ -447,9 +461,15 @@ fn get_hklm_key() { #[test] fn get_product_name() { - let reg_helper = RegistryHelper::new_from_json(r#"{"keyPath":"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion","valueName":"ProductName"}"#).unwrap(); + let reg_helper = RegistryHelper::new_from_json( + r#"{"keyPath":"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion","valueName":"ProductName"}"#, + ) + .unwrap(); let reg_config = reg_helper.get().unwrap(); - assert_eq!(reg_config.key_path, r#"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion"#); + assert_eq!( + reg_config.key_path, + r#"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion"# + ); assert_eq!(reg_config.value_name, Some("ProductName".to_string())); assert!(matches!(reg_config.value_data, Some(RegistryValueData::String(s)) if s.starts_with("Windows "))); } @@ -466,7 +486,8 @@ fn get_nonexisting_key() { #[test] fn get_nonexisting_value() { - let reg_helper = RegistryHelper::new_from_json(r#"{"keyPath":"HKCU\\Software","valueName":"DoesNotExist"}"#).unwrap(); + let reg_helper = + RegistryHelper::new_from_json(r#"{"keyPath":"HKCU\\Software","valueName":"DoesNotExist"}"#).unwrap(); let reg_config = reg_helper.get().unwrap(); assert_eq!(reg_config.key_path, r#"HKCU\Software"#); assert_eq!(reg_config.value_name, Some("DoesNotExist".to_string())); @@ -476,7 +497,10 @@ fn get_nonexisting_value() { #[test] fn set_and_remove_test_value() { - let reg_helper = RegistryHelper::new_from_json(r#"{"keyPath":"HKCU\\DSCTest\\DSCSubKey","valueName":"TestValue","valueData": { "String": "Hello"} }"#).unwrap(); + let reg_helper = RegistryHelper::new_from_json( + r#"{"keyPath":"HKCU\\DSCTest\\DSCSubKey","valueName":"TestValue","valueData": { "String": "Hello"} }"#, + ) + .unwrap(); reg_helper.set().unwrap(); let result = reg_helper.get().unwrap(); assert_eq!(result.key_path, r#"HKCU\DSCTest\DSCSubKey"#); @@ -503,7 +527,10 @@ fn set_and_remove_test_value() { #[test] fn delete_tree() { - let reg_helper = RegistryHelper::new_from_json(r#"{"keyPath":"HKCU\\DSCTest2\\DSCSubKey","valueName":"TestValue","valueData": { "String": "Hello"} }"#).unwrap(); + let reg_helper = RegistryHelper::new_from_json( + r#"{"keyPath":"HKCU\\DSCTest2\\DSCSubKey","valueName":"TestValue","valueData": { "String": "Hello"} }"#, + ) + .unwrap(); reg_helper.set().unwrap(); let result = reg_helper.get().unwrap(); assert_eq!(result.key_path, r#"HKCU\DSCTest2\DSCSubKey"#); diff --git a/runcommandonset/src/args.rs b/runcommandonset/src/args.rs index ab546d2da..fbe3ce938 100644 --- a/runcommandonset/src/args.rs +++ b/runcommandonset/src/args.rs @@ -16,13 +16,12 @@ pub enum TraceLevel { Warn, Info, Debug, - Trace + Trace, } #[derive(Parser)] #[clap(name = "runcommandonset", version = "0.0.1", about = "Run a command on set", long_about = None)] pub struct Arguments { - #[clap(subcommand)] pub subcommand: SubCommand, #[clap(short = 'l', long, help = "Trace level to use", value_enum, default_value = "info")] @@ -39,7 +38,12 @@ pub enum SubCommand { arguments: Option>, #[clap(short = 'e', long, help = "The executable to run.")] executable: Option, - #[clap(short = 'c', long, help = "The expected exit code to indicate success, if non-zero.", default_value = "0")] + #[clap( + short = 'c', + long, + help = "The expected exit code to indicate success, if non-zero.", + default_value = "0" + )] exit_code: i32, }, #[clap(name = "set", about = "Run formatted command.")] @@ -48,7 +52,12 @@ pub enum SubCommand { arguments: Option>, #[clap(short = 'e', long, help = "The executable to run.")] executable: Option, - #[clap(short = 'c', long, help = "The expected exit code to indicate success, if non-zero.", default_value = "0")] + #[clap( + short = 'c', + long, + help = "The expected exit code to indicate success, if non-zero.", + default_value = "0" + )] exit_code: i32, - } + }, } diff --git a/runcommandonset/src/main.rs b/runcommandonset/src/main.rs index 6ac8b4374..058382f28 100644 --- a/runcommandonset/src/main.rs +++ b/runcommandonset/src/main.rs @@ -3,8 +3,11 @@ use clap::Parser; use rust_i18n::{i18n, t}; -use std::{io::{self, Read, IsTerminal}, process::exit}; -use tracing::{error, warn, debug, trace}; +use std::{ + io::{self, IsTerminal, Read}, + process::exit, +}; +use tracing::{debug, error, trace, warn}; use args::{Arguments, SubCommand, TraceLevel}; use runcommand::RunCommand; @@ -54,14 +57,13 @@ fn main() { Err(e) => { error!("{}: {e}", t!("main.invalidUtf8")); exit(EXIT_INVALID_ARGS); - }, + } }; // parse_input expects at most 1 input, so wrapping Some(empty input) would throw it off if stdin.is_empty() { debug!("{}", t!("main.emptyStdin")); None - } - else { + } else { Some(stdin) } }; @@ -69,10 +71,18 @@ fn main() { let mut command: RunCommand; match args.subcommand { - SubCommand::Get { arguments, executable, exit_code } => { + SubCommand::Get { + arguments, + executable, + exit_code, + } => { command = parse_input(arguments, executable, exit_code, stdin); } - SubCommand::Set { arguments, executable, exit_code } => { + SubCommand::Set { + arguments, + executable, + exit_code, + } => { command = parse_input(arguments, executable, exit_code, stdin); let (exit_code, stdout, stderr) = invoke_command(command.executable.as_ref(), command.arguments.clone()); trace!("Stdout: {stdout}"); diff --git a/runcommandonset/src/utils.rs b/runcommandonset/src/utils.rs index 54330743a..589bef06a 100644 --- a/runcommandonset/src/utils.rs +++ b/runcommandonset/src/utils.rs @@ -2,8 +2,11 @@ // Licensed under the MIT License. use rust_i18n::t; -use std::{io::Read, process::{Command, exit, Stdio}}; -use tracing::{Level, error, debug, trace}; +use std::{ + io::Read, + process::{exit, Command, Stdio}, +}; +use tracing::{debug, error, trace, Level}; use tracing_subscriber::{filter::EnvFilter, layer::SubscriberExt, Layer}; use crate::args::{TraceFormat, TraceLevel}; @@ -27,7 +30,12 @@ pub const EXIT_PROCESS_TERMINATED: i32 = 5; /// # Errors /// /// Error message then exit if the `RunCommand` struct cannot be initialized from the provided inputs. -pub fn parse_input(arguments: Option>, executable: Option, exit_code: i32, stdin: Option) -> runcommand::RunCommand { +pub fn parse_input( + arguments: Option>, + executable: Option, + exit_code: i32, + stdin: Option, +) -> runcommand::RunCommand { let command: runcommand::RunCommand; if let Some(input) = stdin { debug!("Input: {}", input); @@ -44,8 +52,7 @@ pub fn parse_input(arguments: Option>, executable: Option, e arguments, exit_code, }; - } - else { + } else { error!("{}", t!("utils.executableRequired")); exit(EXIT_INVALID_INPUT); } @@ -78,28 +85,14 @@ pub fn enable_tracing(trace_level: &TraceLevel, trace_format: &TraceFormat) { .add_directive(tracing_level.into()); let layer = tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr); let fmt = match trace_format { - TraceFormat::Default => { - layer - .with_ansi(true) - .with_level(true) - .with_line_number(true) - .boxed() - }, - TraceFormat::Plaintext => { - layer - .with_ansi(false) - .with_level(true) - .with_line_number(false) - .boxed() - }, - TraceFormat::Json => { - layer - .with_ansi(false) - .with_level(true) - .with_line_number(true) - .json() - .boxed() - } + TraceFormat::Default => layer.with_ansi(true).with_level(true).with_line_number(true).boxed(), + TraceFormat::Plaintext => layer.with_ansi(false).with_level(true).with_line_number(false).boxed(), + TraceFormat::Json => layer + .with_ansi(false) + .with_level(true) + .with_line_number(true) + .json() + .boxed(), }; let subscriber = tracing_subscriber::Registry::default().with(fmt).with(filter); diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..5ea265989 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,28 @@ +# rustfmt.toml for DSC +# Note that some of these are only available in the nightly channel of rustfmt. + +# Basic layout +max_width = 120 +hard_tabs = false +tab_spaces = 4 + +# Imports +reorder_imports = true +imports_granularity = "Crate" +group_imports = "StdExternalCrate" + +# Comments & docs +wrap_comments = true +format_code_in_doc_comments = true +normalize_doc_attributes = true + +# Macro bodies, etc. +format_macro_bodies = false + +# Edition (for parsing behavior) +edition = "2021" + +# Error handling +error_on_line_overflow = true +error_on_unformatted = true +overflow_delimited_expr = false diff --git a/sshdconfig/src/args.rs b/sshdconfig/src/args.rs index 616ec4073..f734321e9 100644 --- a/sshdconfig/src/args.rs +++ b/sshdconfig/src/args.rs @@ -24,12 +24,12 @@ pub enum Command { /// Set default shell, eventually to be used for `sshd_config` and repeatable keywords Set { #[clap(short = 'i', long, help = t!("args.setInput").to_string())] - input: String + input: String, }, /// Export `sshd_config`, eventually to be used for repeatable keywords Export { #[clap(short = 'i', long, help = t!("args.exportInput").to_string())] - input: Option + input: Option, }, Schema { // Used to inform which schema to generate @@ -51,5 +51,5 @@ pub struct DefaultShell { #[derive(Clone, Debug, Eq, PartialEq, ValueEnum)] pub enum Setting { SshdConfig, - WindowsGlobal -} \ No newline at end of file + WindowsGlobal, +} diff --git a/sshdconfig/src/get.rs b/sshdconfig/src/get.rs index 4ff01022d..d42ead198 100644 --- a/sshdconfig/src/get.rs +++ b/sshdconfig/src/get.rs @@ -3,9 +3,12 @@ #[cfg(windows)] use { - registry_lib::{config::{Registry, RegistryValueData}, RegistryHelper}, crate::args::DefaultShell, crate::metadata::windows::{DEFAULT_SHELL, DEFAULT_SHELL_CMD_OPTION, DEFAULT_SHELL_ESCAPE_ARGS, REGISTRY_PATH}, + registry_lib::{ + config::{Registry, RegistryValueData}, + RegistryHelper, + }, }; use rust_i18n::t; @@ -16,12 +19,7 @@ use crate::args::Setting; use crate::error::SshdConfigError; use crate::inputs::CommandInfo; use crate::parser::parse_text_to_map; -use crate::util::{ - build_command_info, - extract_sshd_defaults, - invoke_sshd_config_validation, - read_sshd_config -}; +use crate::util::{build_command_info, extract_sshd_defaults, invoke_sshd_config_validation, read_sshd_config}; /// Invoke the get command. /// @@ -35,7 +33,7 @@ pub fn invoke_get(input: Option<&String>, setting: &Setting) -> Result { let cmd_info = build_command_info(input, true)?; get_sshd_settings(&cmd_info, true) - }, + } Setting::WindowsGlobal => { get_default_shell()?; Ok(Map::new()) @@ -54,7 +52,11 @@ fn get_default_shell() -> Result<(), SshdConfigError> { RegistryValueData::String(s) => { shell = Some(s); } - _ => return Err(SshdConfigError::InvalidInput(t!("get.defaultShellMustBeString").to_string())), + _ => { + return Err(SshdConfigError::InvalidInput( + t!("get.defaultShellMustBeString").to_string(), + )) + } } } @@ -64,7 +66,11 @@ fn get_default_shell() -> Result<(), SshdConfigError> { if let Some(value) = option.value_data { match value { RegistryValueData::String(s) => cmd_option = Some(s), - _ => return Err(SshdConfigError::InvalidInput(t!("get.defaultShellCmdOptionMustBeString").to_string())), + _ => { + return Err(SshdConfigError::InvalidInput( + t!("get.defaultShellCmdOptionMustBeString").to_string(), + )) + } } } @@ -76,17 +82,21 @@ fn get_default_shell() -> Result<(), SshdConfigError> { if b == 0 || b == 1 { escape_arguments = if b == 1 { Some(true) } else { Some(false) }; } else { - return Err(SshdConfigError::InvalidInput(t!("get.defaultShellEscapeArgsMustBe0Or1", input = b).to_string())); + return Err(SshdConfigError::InvalidInput( + t!("get.defaultShellEscapeArgsMustBe0Or1", input = b).to_string(), + )); } } else { - return Err(SshdConfigError::InvalidInput(t!("get.defaultShellEscapeArgsMustBeDWord").to_string())); + return Err(SshdConfigError::InvalidInput( + t!("get.defaultShellEscapeArgsMustBeDWord").to_string(), + )); } } let result = DefaultShell { shell, cmd_option, - escape_arguments + escape_arguments, }; let output = serde_json::to_string(&result)?; @@ -160,10 +170,16 @@ pub fn get_sshd_settings(cmd_info: &CommandInfo, is_get: bool) -> Result + pub sshd_args: Option, } impl CommandInfo { @@ -24,7 +24,7 @@ impl CommandInfo { include_defaults, input: Map::new(), metadata: Metadata::new(), - sshd_args: None + sshd_args: None, } } } @@ -33,15 +33,13 @@ impl CommandInfo { pub struct Metadata { /// Filepath for the `sshd_config` file to be processed #[serde(skip_serializing_if = "Option::is_none")] - pub filepath: Option + pub filepath: Option, } impl Metadata { /// Create a new `Metadata` instance. pub fn new() -> Self { - Self { - filepath: None - } + Self { filepath: None } } } diff --git a/sshdconfig/src/main.rs b/sshdconfig/src/main.rs index bf6440ee5..4902eccb9 100644 --- a/sshdconfig/src/main.rs +++ b/sshdconfig/src/main.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use clap::{Parser}; +use clap::Parser; use rust_i18n::{i18n, t}; use schemars::schema_for; use serde_json::Map; @@ -40,27 +40,25 @@ fn main() { Ok(cmd_info) => get_sshd_settings(&cmd_info, false), Err(e) => Err(e), } - }, - Command::Get { input, setting } => { - invoke_get(input.as_ref(), setting) - }, + } + Command::Get { input, setting } => invoke_get(input.as_ref(), setting), Command::Schema { setting } => { debug!("{}; {:?}", t!("main.schema").to_string(), setting); let schema = match setting { Setting::SshdConfig => { schema_for!(SshdConfigParser) - }, + } Setting::WindowsGlobal => { schema_for!(DefaultShell) } }; println!("{}", serde_json::to_string(&schema).unwrap()); Ok(Map::new()) - }, + } Command::Set { input } => { debug!("{}", t!("main.set", input = input).to_string()); invoke_set(input) - }, + } }; match result { @@ -75,7 +73,7 @@ fn main() { } } exit(EXIT_SUCCESS); - }, + } Err(e) => { error!("{}", e); exit(EXIT_FAILURE); diff --git a/sshdconfig/src/metadata.rs b/sshdconfig/src/metadata.rs index 7a94c5109..ab3dde074 100644 --- a/sshdconfig/src/metadata.rs +++ b/sshdconfig/src/metadata.rs @@ -19,7 +19,7 @@ pub const MULTI_ARG_KEYWORDS: [&str; 16] = [ "permituserenvironment", "persourcepenalties", "persourcepenaltyexemptlist", - "pubkeyacceptedalgorithms" + "pubkeyacceptedalgorithms", ]; // keywords that can be repeated over multiple lines and should be represented as arrays. @@ -36,7 +36,7 @@ pub const REPEATABLE_KEYWORDS: [&str; 12] = [ "match", "port", "setenv", - "subsystem" + "subsystem", ]; #[cfg(windows)] diff --git a/sshdconfig/src/parser.rs b/sshdconfig/src/parser.rs index 53447ced1..7bb6d2137 100644 --- a/sshdconfig/src/parser.rs +++ b/sshdconfig/src/parser.rs @@ -12,15 +12,13 @@ use crate::metadata::{MULTI_ARG_KEYWORDS, REPEATABLE_KEYWORDS}; #[derive(Debug, JsonSchema)] pub struct SshdConfigParser { - map: Map + map: Map, } impl SshdConfigParser { /// Create a new `SshdConfigParser` instance. pub fn new() -> Self { - Self { - map: Map::new() - } + Self { map: Map::new() } } /// Parse `sshd_config` to map. @@ -37,14 +35,20 @@ impl SshdConfigParser { parser.set_language(&tree_sitter_ssh_server_config::LANGUAGE.into())?; let Some(tree) = &mut parser.parse(input, None) else { - return Err(SshdConfigError::ParserError(t!("parser.failedToParse", input = input).to_string())); + return Err(SshdConfigError::ParserError( + t!("parser.failedToParse", input = input).to_string(), + )); }; let root_node = tree.root_node(); if root_node.is_error() { - return Err(SshdConfigError::ParserError(t!("parser.failedToParseRoot", input = input).to_string())); + return Err(SshdConfigError::ParserError( + t!("parser.failedToParseRoot", input = input).to_string(), + )); } if root_node.kind() != "server_config" { - return Err(SshdConfigError::ParserError(t!("parser.invalidConfig", input = input).to_string())); + return Err(SshdConfigError::ParserError( + t!("parser.invalidConfig", input = input).to_string(), + )); } let input_bytes = input.as_bytes(); let mut cursor = root_node.walk(); @@ -54,18 +58,32 @@ impl SshdConfigParser { Ok(()) } - fn parse_child_node(&mut self, node: tree_sitter::Node, input: &str, input_bytes: &[u8]) -> Result<(), SshdConfigError> { + fn parse_child_node( + &mut self, + node: tree_sitter::Node, + input: &str, + input_bytes: &[u8], + ) -> Result<(), SshdConfigError> { if node.is_error() { - return Err(SshdConfigError::ParserError(t!("parser.failedToParse", input = input).to_string())); + return Err(SshdConfigError::ParserError( + t!("parser.failedToParse", input = input).to_string(), + )); } match node.kind() { "keyword" => self.parse_keyword_node(node, input, input_bytes), "comment" | "empty_line" | "match" => Ok(()), // TODO: do not ignore match nodes when parsing - _ => Err(SshdConfigError::ParserError(t!("parser.unknownNodeType", node = node.kind()).to_string())), + _ => Err(SshdConfigError::ParserError( + t!("parser.unknownNodeType", node = node.kind()).to_string(), + )), } } - fn parse_keyword_node(&mut self, keyword_node: tree_sitter::Node, input: &str, input_bytes: &[u8]) -> Result<(), SshdConfigError> { + fn parse_keyword_node( + &mut self, + keyword_node: tree_sitter::Node, + input: &str, + input_bytes: &[u8], + ) -> Result<(), SshdConfigError> { let mut cursor = keyword_node.walk(); let mut key = None; let mut value = Value::Null; @@ -75,7 +93,7 @@ impl SshdConfigParser { if let Some(keyword) = keyword_node.child_by_field_name("keyword") { let Ok(text) = keyword.utf8_text(input_bytes) else { return Err(SshdConfigError::ParserError( - t!("parser.failedToParseChildNode", input = input).to_string() + t!("parser.failedToParseChildNode", input = input).to_string(), )); }; debug!("{}", t!("parser.keywordDebug", text = text).to_string()); @@ -90,7 +108,9 @@ impl SshdConfigParser { for node in keyword_node.named_children(&mut cursor) { if node.is_error() { - return Err(SshdConfigError::ParserError(t!("parser.failedToParseChildNode", input = input).to_string())); + return Err(SshdConfigError::ParserError( + t!("parser.failedToParseChildNode", input = input).to_string(), + )); } if node.kind() == "arguments" { value = parse_arguments_node(node, input, input_bytes, is_vec)?; @@ -99,11 +119,15 @@ impl SshdConfigParser { } if let Some(key) = key { if value.is_null() { - return Err(SshdConfigError::ParserError(t!("parser.missingValueInChildNode", input = input).to_string())); + return Err(SshdConfigError::ParserError( + t!("parser.missingValueInChildNode", input = input).to_string(), + )); } return self.update_map(&key, value, is_repeatable); } - Err(SshdConfigError::ParserError(t!("parser.missingKeyInChildNode", input = input).to_string())) + Err(SshdConfigError::ParserError( + t!("parser.missingKeyInChildNode", input = input).to_string(), + )) } fn update_map(&mut self, key: &str, value: Value, is_repeatable: bool) -> Result<(), SshdConfigError> { @@ -118,19 +142,23 @@ impl SshdConfigParser { } } else { return Err(SshdConfigError::ParserError( - t!("parser.failedToParseAsArray").to_string() + t!("parser.failedToParseAsArray").to_string(), )); } } else { return Err(SshdConfigError::ParserError( - t!("parser.failedToParseAsArray").to_string() + t!("parser.failedToParseAsArray").to_string(), )); } } else { - return Err(SshdConfigError::ParserError(t!("parser.keyNotFound", key = key).to_string())); + return Err(SshdConfigError::ParserError( + t!("parser.keyNotFound", key = key).to_string(), + )); } } else { - return Err(SshdConfigError::ParserError(t!("parser.keyNotRepeatable", key = key).to_string())); + return Err(SshdConfigError::ParserError( + t!("parser.keyNotRepeatable", key = key).to_string(), + )); } } else { self.map.insert(key.to_string(), value); @@ -139,7 +167,12 @@ impl SshdConfigParser { } } -fn parse_arguments_node(arg_node: tree_sitter::Node, input: &str, input_bytes: &[u8], is_vec: bool) -> Result { +fn parse_arguments_node( + arg_node: tree_sitter::Node, + input: &str, + input_bytes: &[u8], + is_vec: bool, +) -> Result { let mut cursor = arg_node.walk(); let mut vec: Vec = Vec::new(); let mut value = Value::Null; @@ -147,18 +180,22 @@ fn parse_arguments_node(arg_node: tree_sitter::Node, input: &str, input_bytes: & // if there is more than one argument, but a vector is not expected for the keyword, throw an error let children: Vec<_> = arg_node.named_children(&mut cursor).collect(); if children.len() > 1 && !is_vec { - return Err(SshdConfigError::ParserError(t!("parser.invalidMultiArgNode", input = input).to_string())); + return Err(SshdConfigError::ParserError( + t!("parser.invalidMultiArgNode", input = input).to_string(), + )); } for node in children { if node.is_error() { - return Err(SshdConfigError::ParserError(t!("parser.failedToParseChildNode", input = input).to_string())); + return Err(SshdConfigError::ParserError( + t!("parser.failedToParseChildNode", input = input).to_string(), + )); } let argument: Value = match node.kind() { "boolean" | "string" => { let Ok(arg) = node.utf8_text(input_bytes) else { return Err(SshdConfigError::ParserError( - t!("parser.failedToParseNode", input = input).to_string() + t!("parser.failedToParseNode", input = input).to_string(), )); }; Value::String(arg.trim().to_string()) @@ -166,18 +203,20 @@ fn parse_arguments_node(arg_node: tree_sitter::Node, input: &str, input_bytes: & "number" => { let Ok(arg) = node.utf8_text(input_bytes) else { return Err(SshdConfigError::ParserError( - t!("parser.failedToParseNode", input = input).to_string() + t!("parser.failedToParseNode", input = input).to_string(), )); }; Value::Number(arg.parse::()?.into()) } "operator" => { // TODO: handle operator if not parsing from SSHD -T + return Err(SshdConfigError::ParserError(t!("parser.invalidValue").to_string())); + } + _ => { return Err(SshdConfigError::ParserError( - t!("parser.invalidValue").to_string() - )); + t!("parser.unknownNode", kind = node.kind()).to_string(), + )) } - _ => return Err(SshdConfigError::ParserError(t!("parser.unknownNode", kind = node.kind()).to_string())) }; if is_vec { vec.push(argument); @@ -187,7 +226,7 @@ fn parse_arguments_node(arg_node: tree_sitter::Node, input: &str, input_bytes: & } if is_vec { Ok(Value::Array(vec)) - } else{ + } else { Ok(value) } } @@ -201,12 +240,10 @@ fn parse_arguments_node(arg_node: tree_sitter::Node, input: &str, input_bytes: & /// # Errors /// /// This function will return an error if the input fails to parse. -pub fn parse_text_to_map(input: &str) -> Result, SshdConfigError> { +pub fn parse_text_to_map(input: &str) -> Result, SshdConfigError> { let mut parser = SshdConfigParser::new(); parser.parse_text(input)?; - let lowercased_map = parser.map.into_iter() - .map(|(k, v)| (k.to_lowercase(), v)) - .collect(); + let lowercased_map = parser.map.into_iter().map(|(k, v)| (k.to_lowercase(), v)).collect(); Ok(lowercased_map) } @@ -219,7 +256,8 @@ mod tests { let input = r#" port 1234 port 5678 - "#.trim_start(); + "# + .trim_start(); let result: Map = parse_text_to_map(input).unwrap(); let expected = vec![Value::Number(1234.into()), Value::Number(5678.into())]; assert_eq!(result.get("port").unwrap(), &Value::Array(expected)); diff --git a/sshdconfig/src/set.rs b/sshdconfig/src/set.rs index 90eb0b530..a529f5ea6 100644 --- a/sshdconfig/src/set.rs +++ b/sshdconfig/src/set.rs @@ -3,9 +3,9 @@ #[cfg(windows)] use { - std::path::Path, - registry_lib::{config::RegistryValueData, RegistryHelper}, crate::metadata::windows::{DEFAULT_SHELL, DEFAULT_SHELL_CMD_OPTION, DEFAULT_SHELL_ESCAPE_ARGS, REGISTRY_PATH}, + registry_lib::{config::RegistryValueData, RegistryHelper}, + std::path::Path, }; use rust_i18n::t; @@ -22,25 +22,37 @@ use crate::error::SshdConfigError; pub fn invoke_set(input: &str) -> Result, SshdConfigError> { match serde_json::from_str::(input) { Ok(default_shell) => { - set_default_shell(default_shell.shell, default_shell.cmd_option, default_shell.escape_arguments)?; + set_default_shell( + default_shell.shell, + default_shell.cmd_option, + default_shell.escape_arguments, + )?; Ok(Map::new()) - }, - Err(e) => { - Err(SshdConfigError::InvalidInput(t!("set.failedToParseInput", error = e).to_string())) } + Err(e) => Err(SshdConfigError::InvalidInput( + t!("set.failedToParseInput", error = e).to_string(), + )), } } #[cfg(windows)] -fn set_default_shell(shell: Option, cmd_option: Option, escape_arguments: Option) -> Result<(), SshdConfigError> { +fn set_default_shell( + shell: Option, + cmd_option: Option, + escape_arguments: Option, +) -> Result<(), SshdConfigError> { if let Some(shell) = shell { // TODO: if shell contains quotes, we need to remove them let shell_path = Path::new(&shell); if shell_path.is_relative() && shell_path.components().any(|c| c == std::path::Component::ParentDir) { - return Err(SshdConfigError::InvalidInput(t!("set.shellPathMustNotBeRelative").to_string())); + return Err(SshdConfigError::InvalidInput( + t!("set.shellPathMustNotBeRelative").to_string(), + )); } if !shell_path.exists() { - return Err(SshdConfigError::InvalidInput(t!("set.shellPathDoesNotExist", shell = shell).to_string())); + return Err(SshdConfigError::InvalidInput( + t!("set.shellPathDoesNotExist", shell = shell).to_string(), + )); } set_registry(DEFAULT_SHELL, RegistryValueData::String(shell))?; @@ -48,7 +60,6 @@ fn set_default_shell(shell: Option, cmd_option: Option, escape_a remove_registry(DEFAULT_SHELL)?; } - if let Some(cmd_option) = cmd_option { set_registry(DEFAULT_SHELL_CMD_OPTION, RegistryValueData::String(cmd_option.clone()))?; } else { @@ -69,7 +80,11 @@ fn set_default_shell(shell: Option, cmd_option: Option, escape_a } #[cfg(not(windows))] -fn set_default_shell(_shell: Option, _cmd_option: Option, _escape_arguments: Option) -> Result<(), SshdConfigError> { +fn set_default_shell( + _shell: Option, + _cmd_option: Option, + _escape_arguments: Option, +) -> Result<(), SshdConfigError> { Err(SshdConfigError::InvalidInput(t!("get.windowsOnly").to_string())) } diff --git a/sshdconfig/src/util.rs b/sshdconfig/src/util.rs index d095f3bb8..3f54925ed 100644 --- a/sshdconfig/src/util.rs +++ b/sshdconfig/src/util.rs @@ -5,7 +5,7 @@ use rust_i18n::t; use serde_json::{Map, Value}; use std::{path::Path, process::Command}; use tracing::debug; -use tracing_subscriber::{EnvFilter, filter::LevelFilter, Layer, prelude::__tracing_subscriber_SubscriberExt}; +use tracing_subscriber::{filter::LevelFilter, prelude::__tracing_subscriber_SubscriberExt, EnvFilter, Layer}; use crate::error::SshdConfigError; use crate::inputs::{CommandInfo, Metadata, SshdCommandArgs}; @@ -18,14 +18,17 @@ use crate::parser::parse_text_to_map; /// This function will return an error if it fails to initialize tracing. pub fn enable_tracing() { // default filter to trace level - let filter = EnvFilter::builder().with_default_directive(LevelFilter::TRACE.into()).parse("").unwrap_or_default(); + let filter = EnvFilter::builder() + .with_default_directive(LevelFilter::TRACE.into()) + .parse("") + .unwrap_or_default(); let layer = tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr); let fmt = layer - .with_ansi(false) - .with_level(true) - .with_line_number(true) - .json() - .boxed(); + .with_ansi(false) + .with_level(true) + .with_line_number(true) + .json() + .boxed(); let subscriber = tracing_subscriber::Registry::default().with(fmt).with(filter); @@ -52,20 +55,17 @@ pub fn invoke_sshd_config_validation(args: Option) -> Result Result, SshdConfigError> { drop(file); debug!("temporary file created at: {}", temp_path); - let args = Some( - SshdCommandArgs { - filepath: Some(temp_path.clone()), - additional_args: None, - } - ); + let args = Some(SshdCommandArgs { + filepath: Some(temp_path.clone()), + additional_args: None, + }); // Clean up the temporary file regardless of success or failure let output = invoke_sshd_config_validation(args); @@ -120,17 +118,17 @@ pub fn build_command_info(input: Option<&String>, is_get: bool) -> Result, is_get: bool) -> Result) -> Result) -> Result, /// The required resource adapter for the resource. - #[serde(rename="requireAdapter")] + #[serde(rename = "requireAdapter")] pub require_adapter: Option, } @@ -55,7 +55,12 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation) -> type_name: "Adapted/One".to_string(), kind: "resource".to_string(), version: "1.0.0".to_string(), - capabilities: vec!["get".to_string(), "set".to_string(), "test".to_string(), "export".to_string()], + capabilities: vec![ + "get".to_string(), + "set".to_string(), + "test".to_string(), + "export".to_string(), + ], path: "path/to/adapted/one".to_string(), directory: "path/to/adapted".to_string(), implemented_as: "TestAdapted".to_string(), @@ -66,7 +71,12 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation) -> type_name: "Adapted/Two".to_string(), kind: "resource".to_string(), version: "1.0.0".to_string(), - capabilities: vec!["get".to_string(), "set".to_string(), "test".to_string(), "export".to_string()], + capabilities: vec![ + "get".to_string(), + "set".to_string(), + "test".to_string(), + "export".to_string(), + ], path: "path/to/adapted/two".to_string(), directory: "path/to/adapted".to_string(), implemented_as: "TestAdapted".to_string(), @@ -76,74 +86,66 @@ pub fn adapt(resource_type: &str, input: &str, operation: &AdapterOperation) -> println!("{}", serde_json::to_string(&resource_one).unwrap()); println!("{}", serde_json::to_string(&resource_two).unwrap()); std::process::exit(0); - }, - AdapterOperation::Get => { - match resource_type { - "Adapted/One" => { - let adapted_one = AdaptedOne { - one: "value1".to_string(), - name: None, - }; - Ok(serde_json::to_string(&adapted_one).unwrap()) - }, - "Adapted/Two" => { - let adapted_two = AdaptedTwo { - two: "value2".to_string(), - name: None, - }; - Ok(serde_json::to_string(&adapted_two).unwrap()) - }, - _ => Err(format!("Unknown resource type: {resource_type}")), + } + AdapterOperation::Get => match resource_type { + "Adapted/One" => { + let adapted_one = AdaptedOne { + one: "value1".to_string(), + name: None, + }; + Ok(serde_json::to_string(&adapted_one).unwrap()) } - }, - AdapterOperation::Set | AdapterOperation::Test => { - match resource_type { - "Adapted/One" => { - let adapted_one: AdaptedOne = serde_json::from_str(input) - .map_err(|e| format!("Failed to parse input for Adapted/One: {e}"))?; - Ok(serde_json::to_string(&adapted_one).unwrap()) - }, - "Adapted/Two" => { - let adapted_two: AdaptedTwo = serde_json::from_str(input) - .map_err(|e| format!("Failed to parse input for Adapted/Two: {e}"))?; - Ok(serde_json::to_string(&adapted_two).unwrap()) - }, - _ => Err(format!("Unknown resource type: {resource_type}")), + "Adapted/Two" => { + let adapted_two = AdaptedTwo { + two: "value2".to_string(), + name: None, + }; + Ok(serde_json::to_string(&adapted_two).unwrap()) } + _ => Err(format!("Unknown resource type: {resource_type}")), }, - AdapterOperation::Export => { - match resource_type { - "Adapted/One" => { - let adapted_one = AdaptedOne { - one: "first1".to_string(), - name: Some("first".to_string()), - }; - println!("{}", serde_json::to_string(&adapted_one).unwrap()); - let adapted_one = AdaptedOne { - one: "second1".to_string(), - name: Some("second".to_string()), - }; - println!("{}", serde_json::to_string(&adapted_one).unwrap()); - std::process::exit(0); - }, - "Adapted/Two" => { - let adapted_two = AdaptedTwo { - two: "first2".to_string(), - name: Some("first".to_string()), - }; - println!("{}", serde_json::to_string(&adapted_two).unwrap()); - let adapted_two = AdaptedTwo { - two: "second2".to_string(), - name: Some("second".to_string()), - }; - println!("{}", serde_json::to_string(&adapted_two).unwrap()); - std::process::exit(0); - }, - _ => Err(format!("Unknown resource type: {resource_type}")), + AdapterOperation::Set | AdapterOperation::Test => match resource_type { + "Adapted/One" => { + let adapted_one: AdaptedOne = + serde_json::from_str(input).map_err(|e| format!("Failed to parse input for Adapted/One: {e}"))?; + Ok(serde_json::to_string(&adapted_one).unwrap()) + } + "Adapted/Two" => { + let adapted_two: AdaptedTwo = + serde_json::from_str(input).map_err(|e| format!("Failed to parse input for Adapted/Two: {e}"))?; + Ok(serde_json::to_string(&adapted_two).unwrap()) } + _ => Err(format!("Unknown resource type: {resource_type}")), }, - AdapterOperation::Validate => { - Ok("{\"valid\": true}".to_string()) + AdapterOperation::Export => match resource_type { + "Adapted/One" => { + let adapted_one = AdaptedOne { + one: "first1".to_string(), + name: Some("first".to_string()), + }; + println!("{}", serde_json::to_string(&adapted_one).unwrap()); + let adapted_one = AdaptedOne { + one: "second1".to_string(), + name: Some("second".to_string()), + }; + println!("{}", serde_json::to_string(&adapted_one).unwrap()); + std::process::exit(0); + } + "Adapted/Two" => { + let adapted_two = AdaptedTwo { + two: "first2".to_string(), + name: Some("first".to_string()), + }; + println!("{}", serde_json::to_string(&adapted_two).unwrap()); + let adapted_two = AdaptedTwo { + two: "second2".to_string(), + name: Some("second".to_string()), + }; + println!("{}", serde_json::to_string(&adapted_two).unwrap()); + std::process::exit(0); + } + _ => Err(format!("Unknown resource type: {resource_type}")), }, + AdapterOperation::Validate => Ok("{\"valid\": true}".to_string()), } } diff --git a/tools/dsctest/src/args.rs b/tools/dsctest/src/args.rs index ed896dbc8..b897fdb4b 100644 --- a/tools/dsctest/src/args.rs +++ b/tools/dsctest/src/args.rs @@ -89,7 +89,12 @@ pub enum SubCommand { #[clap(name = "in-desired-state", about = "Specify if the resource is in the desired state")] InDesiredState { - #[clap(name = "input", short, long, help = "The input to the in desired state command as JSON")] + #[clap( + name = "input", + short, + long, + help = "The input to the in desired state command as JSON" + )] input: String, }, @@ -125,13 +130,16 @@ pub enum SubCommand { Trace, #[clap(name = "version", about = "Test multiple versions of same resource")] - Version { - version: String, - }, + Version { version: String }, #[clap(name = "whatif", about = "Check if it is a whatif operation")] WhatIf { - #[clap(name = "whatif", short, long, help = "Run as a whatif executionType instead of actual executionType")] + #[clap( + name = "whatif", + short, + long, + help = "Run as a whatif executionType instead of actual executionType" + )] what_if: bool, - } + }, } diff --git a/tools/dsctest/src/main.rs b/tools/dsctest/src/main.rs index 04e2abd81..2a9aa6624 100644 --- a/tools/dsctest/src/main.rs +++ b/tools/dsctest/src/main.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +mod adapter; mod args; mod delete; mod exist; @@ -11,16 +12,11 @@ mod get; mod in_desired_state; mod metadata; mod operation; -mod adapter; mod sleep; mod trace; mod version; mod whatif; -use args::{Args, Schemas, SubCommand}; -use clap::Parser; -use schemars::schema_for; -use serde_json::Map; use crate::delete::Delete; use crate::exist::{Exist, State}; use crate::exit_code::ExitCode; @@ -34,19 +30,25 @@ use crate::sleep::Sleep; use crate::trace::Trace; use crate::version::Version; use crate::whatif::WhatIf; +use args::{Args, Schemas, SubCommand}; +use clap::Parser; +use schemars::schema_for; +use serde_json::Map; use std::{thread, time::Duration}; #[allow(clippy::too_many_lines)] fn main() { let args = Args::parse(); let json = match args.subcommand { - SubCommand::Adapter { input , resource_type, operation } => { - match adapter::adapt(&resource_type, &input, &operation) { - Ok(result) => result, - Err(err) => { - eprintln!("Error adapting resource: {err}"); - std::process::exit(1); - } + SubCommand::Adapter { + input, + resource_type, + operation, + } => match adapter::adapt(&resource_type, &input, &operation) { + Ok(result) => result, + Err(err) => { + eprintln!("Error adapting resource: {err}"); + std::process::exit(1); } }, SubCommand::Delete { input } => { @@ -59,7 +61,7 @@ fn main() { }; delete.delete_called = Some(true); serde_json::to_string(&delete).unwrap() - }, + } SubCommand::Exist { input } => { let mut exist = match serde_json::from_str::(&input) { Ok(exist) => exist, @@ -75,7 +77,7 @@ fn main() { } serde_json::to_string(&exist).unwrap() - }, + } SubCommand::ExitCode { input } => { let exit_code = match serde_json::from_str::(&input) { Ok(exit_code) => exit_code, @@ -89,7 +91,7 @@ fn main() { std::process::exit(exit_code.exit_code); } input - }, + } SubCommand::Export { input } => { let export = match serde_json::from_str::(&input) { Ok(export) => export, @@ -108,7 +110,7 @@ fn main() { println!("{}", serde_json::to_string(&instance).unwrap()); } String::new() - }, + } SubCommand::Exporter { input } => { let exporter = match serde_json::from_str::(&input) { Ok(exporter) => exporter, @@ -123,24 +125,28 @@ fn main() { r#type: type_name, properties: Map::new(), }; - resource.properties.insert("foo".to_string(), serde_json::Value::String("bar".to_string())); - resource.properties.insert("hello".to_string(), serde_json::Value::String("world".to_string())); + resource + .properties + .insert("foo".to_string(), serde_json::Value::String("bar".to_string())); + resource + .properties + .insert("hello".to_string(), serde_json::Value::String("world".to_string())); println!("{}", serde_json::to_string(&resource).unwrap()); } String::new() - }, + } SubCommand::Get { input } => { let instances = vec![ Get { - name : Some("one".to_string()), + name: Some("one".to_string()), id: Some(1), }, Get { - name : Some("two".to_string()), + name: Some("two".to_string()), id: Some(2), }, Get { - name : Some("three".to_string()), + name: Some("three".to_string()), id: Some(3), }, ]; @@ -161,10 +167,13 @@ fn main() { }; // depending on the input, return the appropriate instance whether it is name or id or both if let Some(name) = get.name { - instances.into_iter().find(|i| i.name.as_ref() == Some(&name)).unwrap_or_else(|| { - eprintln!("No instance found with name: {name}"); - std::process::exit(1); - }) + instances + .into_iter() + .find(|i| i.name.as_ref() == Some(&name)) + .unwrap_or_else(|| { + eprintln!("No instance found with name: {name}"); + std::process::exit(1); + }) } else if let Some(id) = get.id { instances.into_iter().find(|i| i.id == Some(id)).unwrap_or_else(|| { eprintln!("No instance found with id: {id}"); @@ -178,7 +187,7 @@ fn main() { } }; serde_json::to_string(&resource).unwrap() - }, + } SubCommand::InDesiredState { input } => { let mut in_desired_state = match serde_json::from_str::(&input) { Ok(in_desired_state) => in_desired_state, @@ -190,13 +199,9 @@ fn main() { in_desired_state.value_one = 1; in_desired_state.value_two = 2; serde_json::to_string(&in_desired_state).unwrap() - }, + } SubCommand::Metadata { input, export } => { - let count = if export { - 3 - } else { - 1 - }; + let count = if export { 3 } else { 1 }; for i in 0..count { let mut metadata = match serde_json::from_str::(&input) { Ok(metadata) => metadata, @@ -205,12 +210,12 @@ fn main() { std::process::exit(1); } }; - metadata.name = Some(format!("Metadata example {}", i+1)); + metadata.name = Some(format!("Metadata example {}", i + 1)); metadata.count = Some(i + 1); println!("{}", serde_json::to_string(&metadata).unwrap()); } String::new() - }, + } SubCommand::Operation { operation, input } => { let mut operation_result = match serde_json::from_str::(&input) { Ok(op) => op, @@ -221,54 +226,54 @@ fn main() { }; operation_result.operation = Some(operation.to_lowercase()); serde_json::to_string(&operation_result).unwrap() - }, + } SubCommand::Schema { subcommand } => { let schema = match subcommand { Schemas::Adapter => { schema_for!(adapter::DscResource) - }, + } Schemas::Delete => { schema_for!(Delete) - }, + } Schemas::Exist => { schema_for!(Exist) - }, + } Schemas::ExitCode => { schema_for!(ExitCode) - }, + } Schemas::Export => { schema_for!(Export) - }, + } Schemas::Exporter => { schema_for!(Exporter) - }, + } Schemas::Get => { schema_for!(Get) - }, + } Schemas::InDesiredState => { schema_for!(InDesiredState) - }, + } Schemas::Metadata => { schema_for!(Metadata) - }, + } Schemas::Operation => { schema_for!(Operation) - }, + } Schemas::Sleep => { schema_for!(Sleep) - }, + } Schemas::Trace => { schema_for!(Trace) - }, + } Schemas::Version => { schema_for!(Version) - }, + } Schemas::WhatIf => { schema_for!(WhatIf) - }, + } }; serde_json::to_string(&schema).unwrap() - }, + } SubCommand::Sleep { input } => { let sleep = match serde_json::from_str::(&input) { Ok(sleep) => sleep, @@ -279,32 +284,32 @@ fn main() { }; thread::sleep(Duration::from_secs(sleep.seconds)); serde_json::to_string(&sleep).unwrap() - }, + } SubCommand::Trace => { // get level from DSC_TRACE_LEVEL env var let level = match std::env::var("DSC_TRACE_LEVEL") { Ok(level) => level, Err(_) => "warn".to_string(), }; - let trace = trace::Trace { - level, - }; + let trace = trace::Trace { level }; serde_json::to_string(&trace).unwrap() - }, + } SubCommand::Version { version } => { - let version = Version { - version, - }; + let version = Version { version }; serde_json::to_string(&version).unwrap() - }, + } SubCommand::WhatIf { what_if } => { let result: WhatIf = if what_if { - WhatIf { execution_type: "WhatIf".to_string() } + WhatIf { + execution_type: "WhatIf".to_string(), + } } else { - WhatIf { execution_type: "Actual".to_string() } + WhatIf { + execution_type: "Actual".to_string(), + } }; serde_json::to_string(&result).unwrap() - }, + } }; if !json.is_empty() { diff --git a/tools/dsctest/src/metadata.rs b/tools/dsctest/src/metadata.rs index a10ceeab7..9a220c457 100644 --- a/tools/dsctest/src/metadata.rs +++ b/tools/dsctest/src/metadata.rs @@ -9,9 +9,9 @@ use serde_json::{Map, Value}; #[serde(deny_unknown_fields)] #[allow(clippy::struct_field_names)] pub struct Metadata { - #[serde(rename="_metadata", skip_serializing_if = "Option::is_none")] + #[serde(rename = "_metadata", skip_serializing_if = "Option::is_none")] pub metadata: Option>, - #[serde(rename="_name", skip_serializing_if = "Option::is_none")] + #[serde(rename = "_name", skip_serializing_if = "Option::is_none")] pub name: Option, #[serde(skip_serializing_if = "Option::is_none")] pub count: Option, diff --git a/tools/test_group_resource/src/args.rs b/tools/test_group_resource/src/args.rs index eb6034a9c..6ed403d07 100644 --- a/tools/test_group_resource/src/args.rs +++ b/tools/test_group_resource/src/args.rs @@ -15,6 +15,9 @@ pub struct Args { pub enum SubCommand { #[clap(name = "list", about = "Returns some test resources.")] List, - #[clap(name = "listmissingrequires", about = "Returns some test resources with invalid schema.")] + #[clap( + name = "listmissingrequires", + about = "Returns some test resources with invalid schema." + )] ListMissingRequires, } diff --git a/tools/test_group_resource/src/main.rs b/tools/test_group_resource/src/main.rs index 04f753ffc..c5b09431c 100644 --- a/tools/test_group_resource/src/main.rs +++ b/tools/test_group_resource/src/main.rs @@ -5,8 +5,8 @@ mod args; use args::{Args, SubCommand}; use clap::Parser; -use dsc_lib::dscresources::resource_manifest::{ResourceManifest, GetMethod, Kind}; use dsc_lib::dscresources::dscresource::{Capability, DscResource, ImplementedAs}; +use dsc_lib::dscresources::resource_manifest::{GetMethod, Kind, ResourceManifest}; use dsc_lib::schemas::DscRepoSchema; fn main() { @@ -26,18 +26,22 @@ fn main() { properties: vec!["Property1".to_string(), "Property2".to_string()], require_adapter: Some("Test/TestGroup".to_string()), target_resource: None, - manifest: Some(serde_json::to_value(ResourceManifest { - description: Some("This is a test resource.".to_string()), - schema_version: dsc_lib::dscresources::resource_manifest::ResourceManifest::default_schema_id_uri(), - resource_type: "Test/TestResource1".to_string(), - kind: Some(Kind::Resource), - version: "1.0.0".to_string(), - get: Some(GetMethod { - executable: String::new(), + manifest: Some( + serde_json::to_value(ResourceManifest { + description: Some("This is a test resource.".to_string()), + schema_version: + dsc_lib::dscresources::resource_manifest::ResourceManifest::default_schema_id_uri(), + resource_type: "Test/TestResource1".to_string(), + kind: Some(Kind::Resource), + version: "1.0.0".to_string(), + get: Some(GetMethod { + executable: String::new(), + ..Default::default() + }), ..Default::default() - }), - ..Default::default() - }).unwrap()), + }) + .unwrap(), + ), }; let resource2 = DscResource { type_name: "Test/TestResource2".to_string(), @@ -52,22 +56,26 @@ fn main() { properties: vec!["Property1".to_string(), "Property2".to_string()], require_adapter: Some("Test/TestGroup".to_string()), target_resource: None, - manifest: Some(serde_json::to_value(ResourceManifest { - description: Some("This is a test resource.".to_string()), - schema_version: dsc_lib::dscresources::resource_manifest::ResourceManifest::default_schema_id_uri(), - resource_type: "Test/TestResource2".to_string(), - kind: Some(Kind::Resource), - version: "1.0.1".to_string(), - get: Some(GetMethod { - executable: String::new(), + manifest: Some( + serde_json::to_value(ResourceManifest { + description: Some("This is a test resource.".to_string()), + schema_version: + dsc_lib::dscresources::resource_manifest::ResourceManifest::default_schema_id_uri(), + resource_type: "Test/TestResource2".to_string(), + kind: Some(Kind::Resource), + version: "1.0.1".to_string(), + get: Some(GetMethod { + executable: String::new(), + ..Default::default() + }), ..Default::default() - }), - ..Default::default() - }).unwrap()), + }) + .unwrap(), + ), }; println!("{}", serde_json::to_string(&resource1).unwrap()); println!("{}", serde_json::to_string(&resource2).unwrap()); - }, + } SubCommand::ListMissingRequires => { let resource1 = DscResource { type_name: "InvalidResource".to_string(), diff --git a/y2j/src/main.rs b/y2j/src/main.rs index d69c18bae..0ef98b027 100644 --- a/y2j/src/main.rs +++ b/y2j/src/main.rs @@ -1,10 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::{io::{self, Read, IsTerminal}, process::exit}; +use std::{ + io::{self, IsTerminal, Read}, + process::exit, +}; use syntect::easy::HighlightLines; +use syntect::highlighting::{Style, ThemeSet}; use syntect::parsing::SyntaxSet; -use syntect::highlighting::{ThemeSet, Style}; use syntect::util::{as_24_bit_terminal_escaped, LinesWithEndings}; const EXIT_SUCCESS: i32 = 0; @@ -27,7 +30,9 @@ fn main() { }; let mut is_json = true; - let input: serde_json::Value = if let Ok(json) = serde_json::from_str(&input) { json } else { + let input: serde_json::Value = if let Ok(json) = serde_json::from_str(&input) { + json + } else { is_json = false; match serde_yaml::from_str(&input) { Ok(yaml) => yaml,