diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 71d25fe65..b2c83c020 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -7,11 +7,11 @@ use clap_complete::generate; use dsc_lib::{progress::ProgressFormat, util::DSC_IGNORE_SETTINGS_FILE}; use server::start_server; use rust_i18n::{i18n, t}; -use std::{env::set_var, io, process::exit}; +use std::{env::set_var, io, process::ExitCode}; use sysinfo::{Process, RefreshKind, System, get_current_pid, ProcessRefreshKind}; use tracing::{error, info, warn, debug}; -use crate::util::{EXIT_INVALID_INPUT, get_input}; +use crate::util::get_input; #[cfg(debug_assertions)] use crossterm::event; @@ -28,7 +28,7 @@ pub mod util; i18n!("locales", fallback = "en-us"); -fn main() { +fn main() -> ExitCode { #[cfg(windows)] { let handle = match std::thread::Builder::new() @@ -39,27 +39,38 @@ fn main() { Ok(handle) => handle, Err(err) => { error!("{}", t!("main.failedToSpawnMain", error = err)); - exit(util::EXIT_DSC_ERROR); + return ExitCode::from(util::EXIT_DSC_ERROR); } }; - if let Err(err) = handle.join() { - error!("{}", t!("main.failedToJoinMain", error = err : {:?})); - exit(util::EXIT_DSC_ERROR); + match handle.join() { + Ok(result) => { + if let Err(code) = result { + return code; + } + }, + Err(err) => { + error!("{}", t!("main.failedToJoinMain", error = err : {:?})); + return ExitCode::from(util::EXIT_DSC_ERROR); + } } + ExitCode::from(util::EXIT_SUCCESS) } #[cfg(not(windows))] { - dsc_main(); + match dsc_main() { + Ok(_) => ExitCode::from(util::EXIT_SUCCESS), + Err(code) => code, + } } } -fn dsc_main() { +fn dsc_main() -> Result<(), ExitCode> { #[cfg(debug_assertions)] check_debug(); #[cfg(windows)] - check_store(); + check_store()?; if ctrlc::set_handler(ctrlc_handler).is_err() { error!("{}", t!("main.failedCtrlCHandler")); @@ -87,7 +98,9 @@ fn dsc_main() { generate(shell, &mut cmd, "dsc", &mut io::stdout()); }, SubCommand::Config { subcommand, parameters, parameters_file, system_root, as_group, as_assert, as_include } => { - let params = get_input(None, parameters_file.as_ref()); + let Ok(params) = get_input(None, parameters_file.as_ref()) else { + return Err(ExitCode::from(util::EXIT_INVALID_INPUT)); + }; let file_params = if params.is_empty() { None } else { @@ -101,7 +114,7 @@ fn dsc_main() { Ok(merged) => Some(merged), Err(err) => { error!("{}: {err}", t!("main.failedMergingParameters")); - exit(EXIT_INVALID_INPUT); + return Err(ExitCode::from(util::EXIT_INVALID_INPUT)) ; } } }, @@ -110,23 +123,23 @@ fn dsc_main() { (None, None) => None, }; - subcommand::config(&subcommand, &merged_parameters, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format); + subcommand::config(&subcommand, &merged_parameters, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format)?; }, SubCommand::Extension { subcommand } => { - subcommand::extension(&subcommand, progress_format); + subcommand::extension(&subcommand, progress_format)?; }, SubCommand::Function { subcommand } => { - subcommand::function(&subcommand); + subcommand::function(&subcommand)?; }, SubCommand::Server => { if let Err(err) = start_server() { error!("{}", t!("main.failedToStartServer", error = err)); - exit(util::EXIT_SERVER_FAILED); + return Err(ExitCode::from(util::EXIT_SERVER_FAILED)); } - exit(util::EXIT_SUCCESS); + return Ok(()); } SubCommand::Resource { subcommand } => { - subcommand::resource(&subcommand, progress_format); + subcommand::resource(&subcommand, progress_format)?; }, SubCommand::Schema { dsc_type , output_format } => { let schema = util::get_schema(dsc_type); @@ -134,17 +147,18 @@ fn dsc_main() { Ok(json) => json, Err(err) => { error!("JSON: {err}"); - exit(util::EXIT_JSON_ERROR); + return Err(ExitCode::from(util::EXIT_JSON_ERROR)); } }; - util::write_object(&json, output_format.as_ref(), false); + util::write_object(&json, output_format.as_ref(), false)?; }, } - exit(util::EXIT_SUCCESS); + Ok(()) } fn ctrlc_handler() { + use std::process::exit; warn!("{}", t!("main.ctrlCReceived")); // get process tree for current process and terminate all processes @@ -152,16 +166,16 @@ fn ctrlc_handler() { info!("{}: {}", t!("main.foundProcesses"), sys.processes().len()); let Ok(current_pid) = get_current_pid() else { error!("{}", t!("main.failedToGetPid")); - exit(util::EXIT_CTRL_C); + exit(i32::from(util::EXIT_CTRL_C)); }; info!("{}: {}", t!("main.currentPid"), current_pid); let Some(current_process) = sys.process(current_pid) else { error!("{}", t!("main.failedToGetProcess")); - exit(util::EXIT_CTRL_C); + exit(i32::from(util::EXIT_CTRL_C)); }; terminate_subprocesses(&sys, current_process); - exit(util::EXIT_CTRL_C); + exit(i32::from(util::EXIT_CTRL_C)); } fn terminate_subprocesses(sys: &System, process: &Process) { @@ -200,24 +214,24 @@ fn check_debug() { // Check if the dsc binary parent process is WinStore.App or Explorer.exe #[cfg(windows)] -fn check_store() { +fn check_store() -> Result<(), ExitCode> { use std::io::Read; let sys = System::new_with_specifics(RefreshKind::nothing().with_processes(ProcessRefreshKind::everything())); // get current process let Ok(current_pid) = get_current_pid() else { - return; + return Ok(()); }; // get parent process let Some(current_process) = sys.process(current_pid) else { - return; + return Ok(()); }; let Some(parent_process_pid) = current_process.parent() else { - return; + return Ok(()); }; let Some(parent_process) = sys.process(parent_process_pid) else { - return; + return Ok(()); }; // MS Store runs app using `sihost.exe` @@ -225,7 +239,9 @@ fn check_store() { eprintln!("{}", t!("main.storeMessage")); // wait for keypress let _ = io::stdin().read(&mut [0u8]).unwrap(); - exit(util::EXIT_INVALID_ARGS); + return Err(ExitCode::from(util::EXIT_INVALID_ARGS)); } + + Ok(()) } diff --git a/dsc/src/resource_command.rs b/dsc/src/resource_command.rs index 2d678434f..322057799 100644 --- a/dsc/src/resource_command.rs +++ b/dsc/src/resource_command.rs @@ -12,24 +12,24 @@ use dsc_lib::dscerror::DscError; use dsc_lib::types::{FullyQualifiedTypeName, ResourceVersionReq}; use rust_i18n::t; use serde_json::Value; +use std::process::ExitCode; use tracing::{debug, error, info}; use dsc_lib::{ dscresources::dscresource::{Invoke, DscResource}, DscManager }; -use std::process::exit; -pub fn get(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, input: &str, format: Option<&GetOutputFormat>) { +pub fn get(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, input: &str, format: Option<&GetOutputFormat>) -> Result<(), ExitCode> { let Some(resource) = get_resource(dsc, resource_type, version) else { error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.map_or(String::new(), |v| v.to_string()))); - exit(EXIT_DSC_RESOURCE_NOT_FOUND); + return Err(ExitCode::from(EXIT_DSC_RESOURCE_NOT_FOUND)); }; debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); if resource.kind == Kind::Adapter { error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } match resource.get(input) { @@ -40,11 +40,11 @@ pub fn get(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version Ok(json) => json, Err(err) => { error!("{}", t!("resource_command.jsonError", err = err)); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; - write_object(&json, Some(&OutputFormat::Json), false); - return; + write_object(&json, Some(&OutputFormat::Json), false)?; + return Ok(()); } // convert to json @@ -52,7 +52,7 @@ pub fn get(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version Ok(json) => json, Err(err) => { error!("{}", t!("resource_command.jsonError", err = err)); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; let format = match format { @@ -61,33 +61,34 @@ pub fn get(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version None => None, _ => Some(&OutputFormat::Json), }; - write_object(&json, format, false); + write_object(&json, format, false)?; } Err(err) => { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } } + Ok(()) } -pub fn get_all(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, format: Option<&GetOutputFormat>) { +pub fn get_all(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, format: Option<&GetOutputFormat>) -> Result<(), ExitCode> { let input = String::new(); let Some(resource) = get_resource(dsc, resource_type, version) else { error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.map_or(String::new(), |r| r.to_string()))); - exit(EXIT_DSC_RESOURCE_NOT_FOUND); + return Err(ExitCode::from(EXIT_DSC_RESOURCE_NOT_FOUND)); }; debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); if resource.kind == Kind::Adapter { error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } let export_result = match resource.export(&input) { Ok(export) => { export } Err(err) => { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } }; @@ -96,11 +97,11 @@ pub fn get_all(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, ver Ok(json) => json, Err(err) => { error!("{}", t!("resource_command.jsonError", err = err)); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; - write_object(&json, Some(&OutputFormat::Json), false); - return; + write_object(&json, Some(&OutputFormat::Json), false)?; + return Ok(()); } let mut include_separator = false; @@ -114,7 +115,7 @@ pub fn get_all(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, ver Ok(json) => json, Err(err) => { error!("{}", t!("resource_command.jsonError", err = err)); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; let format = match format { @@ -123,26 +124,27 @@ pub fn get_all(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, ver None => None, _ => Some(&OutputFormat::Json), }; - write_object(&json, format, include_separator); + write_object(&json, format, include_separator)?; include_separator = true; } + Ok(()) } -pub fn set(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, input: &str, format: Option<&OutputFormat>, what_if: bool) { +pub fn set(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, input: &str, format: Option<&OutputFormat>, what_if: bool) -> Result<(), ExitCode> { if input.is_empty() { error!("{}", t!("resource_command.setInputEmpty")); - exit(EXIT_INVALID_ARGS); + return Err(ExitCode::from(EXIT_INVALID_ARGS)); } let Some(resource) = get_resource(dsc, resource_type, version) else { error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.map_or(String::new(), |v| v.to_string()))); - exit(EXIT_DSC_RESOURCE_NOT_FOUND); + return Err(ExitCode::from(EXIT_DSC_RESOURCE_NOT_FOUND)); }; debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); if resource.kind == Kind::Adapter { error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } let execution_kind = if what_if { ExecutionKind::WhatIf } else { ExecutionKind::Actual }; @@ -166,13 +168,13 @@ pub fn set(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version Ok(_) => unreachable!(), Err(err) => { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } }; if let Err(err) = resource.delete(input, &ExecutionKind::Actual) { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } let after_state = match resource.get(input) { @@ -180,7 +182,7 @@ pub fn set(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version Ok(_) => unreachable!(), Err(err) => { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } }; @@ -196,11 +198,11 @@ pub fn set(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version Ok(json) => json, Err(err) => { error!("{}", t!("resource_command.jsonError", err = err)); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; - write_object(&json, format, false); - return; + write_object(&json, format, false)?; + return Ok(()); } match resource.set(input, true, &execution_kind) { @@ -210,33 +212,34 @@ pub fn set(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version Ok(json) => json, Err(err) => { error!("{}", t!("resource_command.jsonError", err = err)); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; - write_object(&json, format, false); + write_object(&json, format, false)?; } Err(err) => { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } } + Ok(()) } -pub fn test(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, input: &str, format: Option<&OutputFormat>) { +pub fn test(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, input: &str, format: Option<&OutputFormat>) -> Result<(), ExitCode> { if input.is_empty() { error!("{}", t!("resource_command.testInputEmpty")); - exit(EXIT_INVALID_ARGS); + return Err(ExitCode::from(EXIT_INVALID_ARGS)); } let Some(resource) = get_resource(dsc, resource_type, version) else { error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.map_or(String::new(), |v| v.to_string()))); - exit(EXIT_DSC_RESOURCE_NOT_FOUND); + return Err(ExitCode::from(EXIT_DSC_RESOURCE_NOT_FOUND)); }; debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); if resource.kind == Kind::Adapter { error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } match resource.test(input) { @@ -246,28 +249,29 @@ pub fn test(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, versio Ok(json) => json, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; - write_object(&json, format, false); + write_object(&json, format, false)?; } Err(err) => { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } } + Ok(()) } -pub fn delete(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, input: &str, format: Option<&OutputFormat>, what_if: bool) { +pub fn delete(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, input: &str, format: Option<&OutputFormat>, what_if: bool) -> Result<(), ExitCode> { let Some(resource) = get_resource(dsc, resource_type, version) else { error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.map_or(String::new(), |v| v.to_string()))); - exit(EXIT_DSC_RESOURCE_NOT_FOUND); + return Err(ExitCode::from(EXIT_DSC_RESOURCE_NOT_FOUND)); }; debug!("{} {} {:?}", resource.type_name, t!("resource_command.implementedAs"), resource.implemented_as); if resource.kind == Kind::Adapter { error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } let execution_kind = if what_if { ExecutionKind::WhatIf } else { ExecutionKind::Actual }; @@ -279,10 +283,10 @@ pub fn delete(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, vers }, DeleteResultKind::ResourceWhatIf(delete_result) => { match serde_json::to_string(&delete_result) { - Ok(json) => write_object(&json, format, false), + Ok(json) => write_object(&json, format, false)?, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } } }, @@ -293,19 +297,20 @@ pub fn delete(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, vers }, Err(err) => { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } } + Ok(()) } -pub fn schema(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, format: Option<&OutputFormat>) { +pub fn schema(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, format: Option<&OutputFormat>) -> Result<(), ExitCode> { let Some(resource) = get_resource(dsc, resource_type, version) else { error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.map_or(String::new(), |v| v.to_string()))); - exit(EXIT_DSC_RESOURCE_NOT_FOUND); + return Err(ExitCode::from(EXIT_DSC_RESOURCE_NOT_FOUND)); }; if resource.kind == Kind::Adapter { error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), resource.type_name); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } match resource.schema() { @@ -315,43 +320,45 @@ pub fn schema(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, vers Ok(_) => (), Err(err) => { error!("{err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } } - write_object(&json, format, false); + write_object(&json, format, false)?; } Err(err) => { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } } + Ok(()) } -pub fn export(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, input: &str, format: Option<&OutputFormat>) { +pub fn export(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version: Option<&ResourceVersionReq>, input: &str, format: Option<&OutputFormat>) -> Result<(), ExitCode> { let Some(dsc_resource) = get_resource(dsc, resource_type, version) else { error!("{}", DscError::ResourceNotFound(resource_type.to_string(), version.map_or(String::new(), |v| v.to_string())).to_string()); - exit(EXIT_DSC_RESOURCE_NOT_FOUND); + return Err(ExitCode::from(EXIT_DSC_RESOURCE_NOT_FOUND)); }; if dsc_resource.kind == Kind::Adapter { error!("{}: {}", t!("resource_command.invalidOperationOnAdapter"), dsc_resource.type_name); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } let mut conf = Configuration::new(); if let Err(err) = add_resource_export_results_to_configuration(dsc_resource, &mut conf, input) { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } let json = match serde_json::to_string(&conf) { Ok(json) => json, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; - write_object(&json, format, false); + write_object(&json, format, false)?; + Ok(()) } #[must_use] diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index 3e5afcc60..737284350 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -34,17 +34,15 @@ use dsc_lib::{ }; use regex::RegexBuilder; use rust_i18n::t; -use core::convert::AsRef; +use std::process::ExitCode; use std::{ collections::HashMap, io::{self, IsTerminal}, - path::Path, - process::exit + path::Path }; 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) -> Result<(), ExitCode> { match configurator.invoke_get() { Ok(result) => { if *as_group { @@ -52,34 +50,34 @@ pub fn config_get(configurator: &mut Configurator, format: Option<&OutputFormat> Ok(json) => json, Err(err) => { error!("JSON Error: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; - write_object(&json, format, false); + write_object(&json, format, false)?; } else { let json = match serde_json::to_string(&result) { Ok(json) => json, Err(err) => { error!("JSON Error: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; - write_object(&json, format, false); + write_object(&json, format, false)?; if result.had_errors { - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } } }, Err(err) => { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } } + Ok(()) } -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) -> Result<(), ExitCode> { match configurator.invoke_set(false) { Ok(result) => { if *as_group { @@ -87,34 +85,34 @@ pub fn config_set(configurator: &mut Configurator, format: Option<&OutputFormat> Ok(json) => json, Err(err) => { error!("JSON Error: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; - write_object(&json, format, false); + write_object(&json, format, false)?; } else { let json = match serde_json::to_string(&result) { Ok(json) => json, Err(err) => { error!("JSON Error: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; - write_object(&json, format, false); + write_object(&json, format, false)?; if result.had_errors { - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } } }, Err(err) => { error!("Error: {err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } } + Ok(()) } -pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat>, as_group: &bool, as_get: &bool, as_config: &bool, as_assert: &bool) -{ +pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat>, as_group: &bool, as_get: &bool, as_config: &bool, as_assert: &bool) -> Result<(), ExitCode> { match configurator.invoke_test() { Ok(result) => { if *as_group { @@ -124,7 +122,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat for test_result in result.results { if *as_assert && !in_desired_state(&test_result) { error!("{}", t!("subcommand.assertionFailed", resource_type = test_result.resource_type)); - exit(EXIT_DSC_ASSERTION_FAILED); + return Err(ExitCode::from(EXIT_DSC_ASSERTION_FAILED)); } let properties = match test_result.result { TestResult::Resource(test_response) => { @@ -153,7 +151,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat Ok(json) => json, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } } } @@ -162,7 +160,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat for test_result in result.results { if *as_assert && !in_desired_state(&test_result) { error!("{}", t!("subcommand.assertionFailed", resource_type = test_result.resource_type)); - exit(EXIT_DSC_ASSERTION_FAILED); + return Err(ExitCode::from(EXIT_DSC_ASSERTION_FAILED)); } group_result.push(test_result.into()); } @@ -170,7 +168,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat Ok(json) => json, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } } } @@ -179,7 +177,7 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat for test_result in &result.results { if !in_desired_state(test_result) { error!("{}", t!("subcommand.assertionFailed", resource_type = test_result.resource_type)); - exit(EXIT_DSC_ASSERTION_FAILED); + return Err(ExitCode::from(EXIT_DSC_ASSERTION_FAILED)); } } } @@ -187,45 +185,45 @@ pub fn config_test(configurator: &mut Configurator, format: Option<&OutputFormat Ok(json) => json, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } } }; - write_object(&json, format, false); + write_object(&json, format, false)?; } else { let json = match serde_json::to_string(&result) { Ok(json) => json, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; - write_object(&json, format, false); + write_object(&json, format, false)?; if result.had_errors { - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } } }, Err(err) => { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } } + Ok(()) } -pub fn config_export(configurator: &mut Configurator, format: Option<&OutputFormat>) -{ +pub fn config_export(configurator: &mut Configurator, format: Option<&OutputFormat>) -> Result<(), ExitCode> { match configurator.invoke_export() { Ok(result) => { let json = match serde_json::to_string(&result.result) { Ok(json) => json, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; - write_object(&json, format, false); + write_object(&json, format, false)?; if result.had_errors { for msg in result.messages @@ -233,22 +231,23 @@ pub fn config_export(configurator: &mut Configurator, format: Option<&OutputForm error!("{:?} {} {}", msg.level, t!("subcommand.message"), msg.message); }; - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } }, Err(err) => { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } } + Ok(()) } -fn initialize_config_root(path: Option<&String>) -> Option { +fn initialize_config_root(path: Option<&String>) -> Result, ExitCode> { // code that calls this pass in either None, Some("-"), or Some(path) // in the case of `-` we treat it as None, but need to pass it back as subsequent processing needs to handle it let use_stdin = if let Some(specified_path) = path { if specified_path != "-" { - return Some(set_dscconfigroot(specified_path)); + return Ok(Some(set_dscconfigroot(specified_path)?)); } true @@ -262,34 +261,34 @@ fn initialize_config_root(path: Option<&String>) -> Option { } else { let current_directory = std::env::current_dir().unwrap_or_default(); debug!("DSC_CONFIG_ROOT = {} '{current_directory:?}'", t!("subcommand.currentDirectory")); - set_dscconfigroot(current_directory.to_str().unwrap_or_default()); + set_dscconfigroot(current_directory.to_str().unwrap_or_default())?; } // if the path is "-", we need to return it so later processing can handle it correctly if use_stdin { - return Some("-".to_string()); + return Ok(Some("-".to_string())); } - None + Ok(None) } #[allow(clippy::too_many_lines)] #[allow(clippy::too_many_arguments)] -pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounted_path: Option<&String>, as_group: &bool, as_assert: &bool, as_include: &bool, progress_format: ProgressFormat) { +pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounted_path: Option<&String>, as_group: &bool, as_assert: &bool, as_include: &bool, progress_format: ProgressFormat) -> Result<(), ExitCode> { 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, .. } => { - let new_path = initialize_config_root(file.as_ref()); - let document = get_input(input.as_ref(), new_path.as_ref()); + let new_path = initialize_config_root(file.as_ref())?; + let document = get_input(input.as_ref(), new_path.as_ref())?; if *as_include { let (new_parameters, config_json) = match get_contents(&document) { Ok((parameters, config_json)) => (parameters, config_json), Err(err) => { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } }; (new_parameters, config_json) @@ -298,13 +297,13 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte } }, ConfigSubCommand::Resolve { input, file, .. } => { - let new_path = initialize_config_root(file.as_ref()); - let document = get_input(input.as_ref(), new_path.as_ref()); + let new_path = initialize_config_root(file.as_ref())?; + let document = get_input(input.as_ref(), new_path.as_ref())?; let (new_parameters, config_json) = match get_contents(&document) { Ok((parameters, config_json)) => (parameters, config_json), Err(err) => { error!("{err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } }; (new_parameters, config_json) @@ -315,7 +314,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte Ok(configurator) => configurator, Err(err) => { error!("Error: {err}"); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } }; @@ -345,13 +344,13 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte Ok(json) => Some(json), Err(err) => { error!("{}: {err}", t!("subcommand.failedConvertJson")); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } } }, Err(err) => { error!("{}: {err}", t!("subcommand.invalidParameters")); - exit(EXIT_INVALID_INPUT); + return Err(ExitCode::from(EXIT_INVALID_INPUT)); } } } @@ -362,7 +361,7 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte if let Some(path) = mounted_path { if !Path::new(&path).exists() { error!("{}: '{path}'", t!("subcommand.invalidPath")); - exit(EXIT_INVALID_ARGS); + return Err(ExitCode::from(EXIT_INVALID_ARGS)); } // make sure path has a trailing separator if it's a drive letter @@ -375,18 +374,18 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte if let Err(err) = configurator.set_context(parameters.as_ref()) { error!("{}: {err}", t!("subcommand.failedSetParameters")); - exit(EXIT_INVALID_INPUT); + return Err(ExitCode::from(EXIT_INVALID_INPUT)); } match subcommand { ConfigSubCommand::Get { output_format, .. } => { - config_get(&mut configurator, output_format.as_ref(), as_group); + config_get(&mut configurator, output_format.as_ref(), as_group)?; }, ConfigSubCommand::Set { output_format, .. } => { - config_set(&mut configurator, output_format.as_ref(), as_group); + 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); + 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 { @@ -394,8 +393,8 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte reason: None, }; if *as_include { - let new_path = initialize_config_root(file.as_ref()); - let input = get_input(input.as_ref(), new_path.as_ref()); + let new_path = initialize_config_root(file.as_ref())?; + let input = get_input(input.as_ref(), new_path.as_ref())?; match serde_json::from_str::(&input) { Ok(_) => { // valid, so do nothing @@ -419,20 +418,20 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte let Ok(json) = serde_json::to_string(&result) else { error!("{}", t!("subcommand.failedSerialize")); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); }; - write_object(&json, output_format.as_ref(), false); + write_object(&json, output_format.as_ref(), false)?; }, ConfigSubCommand::Export { output_format, .. } => { - config_export(&mut configurator, output_format.as_ref()); + config_export(&mut configurator, output_format.as_ref())?; }, ConfigSubCommand::Resolve { output_format, .. } => { let configuration = match serde_json::from_str(&json_string) { Ok(json) => json, Err(err) => { error!("{}: {err}", t!("subcommand.invalidConfiguration")); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } }; // get the parameters out of the configurator @@ -453,12 +452,13 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte Ok(json) => json, Err(err) => { error!("{}: {err}", t!("subcommand.failedSerializeResolve")); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; - write_object(&json_string, output_format.as_ref(), false); + write_object(&json_string, output_format.as_ref(), false)?; }, } + Ok(()) } /// Validate configuration. @@ -528,90 +528,93 @@ pub fn validate_config(config: &Configuration, progress_format: ProgressFormat) Ok(()) } -pub fn extension(subcommand: &ExtensionSubCommand, progress_format: ProgressFormat) { +pub fn extension(subcommand: &ExtensionSubCommand, progress_format: ProgressFormat) -> Result<(), ExitCode> { let mut dsc = DscManager::new(); match subcommand { ExtensionSubCommand::List{extension_name, output_format} => { - list_extensions(&mut dsc, extension_name, output_format.as_ref(), progress_format); + list_extensions(&mut dsc, extension_name, output_format.as_ref(), progress_format)?; }, } + Ok(()) } -pub fn function(subcommand: &FunctionSubCommand) { +pub fn function(subcommand: &FunctionSubCommand) -> Result<(), ExitCode> { let functions = FunctionDispatcher::new(); match subcommand { FunctionSubCommand::List { function_name, category, description, output_format } => { - list_functions(&functions, function_name.as_ref(), category, description.as_ref(), output_format.as_ref()); + list_functions(&functions, function_name.as_ref(), category, description.as_ref(), output_format.as_ref())?; }, } + Ok(()) } #[allow(clippy::too_many_lines)] -pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat) { +pub fn resource(subcommand: &ResourceSubCommand, progress_format: ProgressFormat) -> Result<(), ExitCode> { let mut dsc = DscManager::new(); match subcommand { ResourceSubCommand::List { resource_name, adapter_name, description, tags, output_format } => { - list_resources(&mut dsc, resource_name, adapter_name.as_ref(), description.as_ref(), tags.as_ref(), output_format.as_ref(), progress_format); + list_resources(&mut dsc, resource_name, adapter_name.as_ref(), description.as_ref(), tags.as_ref(), output_format.as_ref(), progress_format)?; }, ResourceSubCommand::Schema { resource, required_version: version, output_format } => { if let Err(err) = dsc.find_resources(&[DiscoveryFilter::new(resource, version.clone(), None)], progress_format) { error!("{}: {err}", t!("subcommand.failedDiscoverResource")); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } - resource_command::schema(&mut dsc, resource, version.as_ref(), output_format.as_ref()); + resource_command::schema(&mut dsc, resource, version.as_ref(), output_format.as_ref())?; }, ResourceSubCommand::Export { resource, required_version: version, input, file, output_format } => { if let Err(err) = dsc.find_resources(&[DiscoveryFilter::new(resource, version.clone(), None)], progress_format) { error!("{}: {err}", t!("subcommand.failedDiscoverResource")); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } - let parsed_input = get_input(input.as_ref(), file.as_ref()); - resource_command::export(&mut dsc, resource, version.as_ref(), &parsed_input, output_format.as_ref()); + let parsed_input = get_input(input.as_ref(), file.as_ref())?; + resource_command::export(&mut dsc, resource, version.as_ref(), &parsed_input, output_format.as_ref())?; }, ResourceSubCommand::Get { resource, required_version: version, input, file: path, all, output_format } => { if let Err(err) = dsc.find_resources(&[DiscoveryFilter::new(resource, version.clone(), None)], progress_format) { error!("{}: {err}", t!("subcommand.failedDiscoverResource")); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } if *all { - resource_command::get_all(&mut dsc, resource, version.as_ref(), output_format.as_ref()); + resource_command::get_all(&mut dsc, resource, version.as_ref(), output_format.as_ref())?; } else { if *output_format == Some(GetOutputFormat::JsonArray) { error!("{}", t!("subcommand.jsonArrayNotSupported")); - exit(EXIT_INVALID_ARGS); + return Err(ExitCode::from(EXIT_INVALID_ARGS)); } - let parsed_input = get_input(input.as_ref(), path.as_ref()); - resource_command::get(&mut dsc, resource, version.as_ref(), &parsed_input, output_format.as_ref()); + let parsed_input = get_input(input.as_ref(), path.as_ref())?; + resource_command::get(&mut dsc, resource, version.as_ref(), &parsed_input, output_format.as_ref())?; } }, ResourceSubCommand::Set { resource, required_version: version, input, file: path, output_format, what_if } => { if let Err(err) = dsc.find_resources(&[DiscoveryFilter::new(resource, version.clone(), None)], progress_format) { error!("{}: {err}", t!("subcommand.failedDiscoverResource")); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } - let parsed_input = get_input(input.as_ref(), path.as_ref()); - resource_command::set(&mut dsc, resource, version.as_ref(), &parsed_input, output_format.as_ref(), *what_if); + let parsed_input = get_input(input.as_ref(), path.as_ref())?; + resource_command::set(&mut dsc, resource, version.as_ref(), &parsed_input, output_format.as_ref(), *what_if)?; }, ResourceSubCommand::Test { resource, required_version: version, input, file: path, output_format } => { if let Err(err) = dsc.find_resources(&[DiscoveryFilter::new(resource, version.clone(), None)], progress_format) { error!("{}: {err}", t!("subcommand.failedDiscoverResource")); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } - let parsed_input = get_input(input.as_ref(), path.as_ref()); - resource_command::test(&mut dsc, resource, version.as_ref(), &parsed_input, output_format.as_ref()); + let parsed_input = get_input(input.as_ref(), path.as_ref())?; + resource_command::test(&mut dsc, resource, version.as_ref(), &parsed_input, output_format.as_ref())?; }, ResourceSubCommand::Delete { resource, required_version: version, input, file: path, output_format, what_if } => { if let Err(err) = dsc.find_resources(&[DiscoveryFilter::new(resource, version.clone(), None)], progress_format) { error!("{}: {err}", t!("subcommand.failedDiscoverResource")); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); } - let parsed_input = get_input(input.as_ref(), path.as_ref()); - resource_command::delete(&mut dsc, resource, version.as_ref(), &parsed_input, output_format.as_ref(), *what_if); + let parsed_input = get_input(input.as_ref(), path.as_ref())?; + resource_command::delete(&mut dsc, resource, version.as_ref(), &parsed_input, output_format.as_ref(), *what_if)?; }, } + Ok(()) } /// Indicates whether to emit a table based on the output format and whether stdout is a terminal. @@ -625,7 +628,7 @@ fn should_write_table(format: Option<&ListOutputFormat>) -> bool { } } -fn list_extensions(dsc: &mut DscManager, extension_name: &TypeNameFilter, format: Option<&ListOutputFormat>, progress_format: ProgressFormat) { +fn list_extensions(dsc: &mut DscManager, extension_name: &TypeNameFilter, format: Option<&ListOutputFormat>, progress_format: ProgressFormat) -> Result<(), ExitCode> { let write_table = should_write_table(format); let mut table = Table::new(&[ @@ -666,7 +669,7 @@ fn list_extensions(dsc: &mut DscManager, extension_name: &TypeNameFilter, format Ok(json) => json, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; let format = match format { @@ -675,7 +678,7 @@ fn list_extensions(dsc: &mut DscManager, extension_name: &TypeNameFilter, format Some(ListOutputFormat::Yaml) => Some(OutputFormat::Yaml), _ => None, }; - write_object(&json, format.as_ref(), include_separator); + 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!(); } @@ -687,9 +690,10 @@ fn list_extensions(dsc: &mut DscManager, extension_name: &TypeNameFilter, format let truncate = format != Some(&ListOutputFormat::TableNoTruncate); table.print(truncate); } + Ok(()) } -fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String>, category: &[FunctionCategory], description: Option<&String>, output_format: Option<&ListOutputFormat>) { +fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String>, category: &[FunctionCategory], description: Option<&String>, output_format: Option<&ListOutputFormat>) -> Result<(), ExitCode> { let write_table = should_write_table(output_format); let mut table = Table::new(&[ t!("subcommand.tableHeader_functionCategory").to_string().as_ref(), @@ -706,21 +710,25 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> regex_builder.case_insensitive(true); let Ok(regex) = regex_builder.build() else { error!("{}: {}", t!("subcommand.invalidFunctionFilter"), regex_str); - exit(EXIT_INVALID_ARGS); + return Err(ExitCode::from(EXIT_INVALID_ARGS)); }; - let description_regex = description.map(|description| { + let description_regex = if let Some(description) = description { let regex_str = convert_wildcard_to_regex(description); // strip the `^` and `$` anchors so the filter searches within the description text let regex_str = ®ex_str[1..regex_str.len() - 1]; let mut regex_builder = RegexBuilder::new(regex_str); regex_builder.case_insensitive(true); - let Ok(regex) = regex_builder.build() else { - error!("{}: {}", t!("subcommand.invalidFunctionDescriptionFilter"), regex_str); - exit(EXIT_INVALID_ARGS); - }; - regex - }); + match regex_builder.build() { + Ok(description_regex) => Some(description_regex), + Err(_) => { + error!("{}: {}", t!("subcommand.invalidFunctionDescriptionFilter"), regex_str); + return Err(ExitCode::from(EXIT_INVALID_ARGS)); + } + } + } else { + None + }; let mut functions_list = functions.list(); functions_list.sort(); @@ -752,7 +760,7 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> Ok(json) => json, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; let format = match output_format { @@ -761,7 +769,7 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> Some(ListOutputFormat::Yaml) => Some(OutputFormat::Yaml), _ => None, }; - write_object(&json, format.as_ref(), include_separator); + 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!(); } @@ -772,6 +780,7 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> let truncate = output_format != Some(&ListOutputFormat::TableNoTruncate); table.print(truncate); } + Ok(()) } pub fn list_resources( @@ -782,7 +791,7 @@ pub fn list_resources( tags: Option<&Vec>, format: Option<&ListOutputFormat>, progress_format: ProgressFormat -) { +) -> Result<(), ExitCode> { let mut write_table = false; let mut table = Table::new(&[ t!("subcommand.tableHeader_type").to_string().as_ref(), @@ -864,7 +873,7 @@ pub fn list_resources( Ok(json) => json, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; let format = match format { @@ -873,7 +882,7 @@ pub fn list_resources( Some(ListOutputFormat::Yaml) => Some(OutputFormat::Yaml), _ => None, }; - write_object(&json, format.as_ref(), include_separator); + 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!(); } @@ -885,4 +894,5 @@ pub fn list_resources( let truncate = format != Some(&ListOutputFormat::TableNoTruncate); table.print(truncate); } + Ok(()) } diff --git a/dsc/src/util.rs b/dsc/src/util.rs index 4b18fac6c..5f7f2afa2 100644 --- a/dsc/src/util.rs +++ b/dsc/src/util.rs @@ -55,7 +55,7 @@ use std::collections::HashMap; use std::env; use std::io::{IsTerminal, Read, stdout, Write}; use std::path::Path; -use std::process::exit; +use std::process::ExitCode; use syntect::{ easy::HighlightLines, highlighting::ThemeSet, @@ -64,17 +64,17 @@ use syntect::{ }; use tracing::{Level, debug, error, info, warn, trace}; -pub const EXIT_SUCCESS: i32 = 0; -pub const EXIT_INVALID_ARGS: i32 = 1; -pub const EXIT_DSC_ERROR: i32 = 2; -pub const EXIT_JSON_ERROR: i32 = 3; -pub const EXIT_INVALID_INPUT: i32 = 4; -pub const EXIT_VALIDATION_FAILED: i32 = 5; -pub const EXIT_CTRL_C: i32 = 6; -pub const EXIT_DSC_RESOURCE_NOT_FOUND: i32 = 7; -pub const EXIT_DSC_ASSERTION_FAILED: i32 = 8; -pub const EXIT_SERVER_FAILED: i32 = 9; -pub const EXIT_BICEP_FAILED: i32 = 10; +pub const EXIT_SUCCESS: u8 = 0; +pub const EXIT_INVALID_ARGS: u8 = 1; +pub const EXIT_DSC_ERROR: u8 = 2; +pub const EXIT_JSON_ERROR: u8 = 3; +pub const EXIT_INVALID_INPUT: u8 = 4; +pub const EXIT_VALIDATION_FAILED: u8 = 5; +pub const EXIT_CTRL_C: u8 = 6; +pub const EXIT_DSC_RESOURCE_NOT_FOUND: u8 = 7; +pub const EXIT_DSC_ASSERTION_FAILED: u8 = 8; +pub const EXIT_SERVER_FAILED: u8 = 9; +pub const EXIT_BICEP_FAILED: u8 = 10; pub const DSC_CONFIG_ROOT: &str = "DSC_CONFIG_ROOT"; pub const DSC_TRACE_LEVEL: &str = "DSC_TRACE_LEVEL"; @@ -109,14 +109,13 @@ impl Default for TracingSetting { /// # Returns /// /// * `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) -> Result { match serde_json::to_string(&json) { - Ok(json_string) => json_string, + Ok(json_string) => Ok(json_string), Err(err) => { error!("{}: {err}", t!("util.failedToConvertJsonToString")); - exit(EXIT_DSC_ERROR); + Err(ExitCode::from(EXIT_DSC_ERROR)) } } } @@ -238,7 +237,7 @@ pub fn get_schema(schema: SchemaType) -> Schema { /// * `json` - The JSON to write /// * `format` - The format to use /// * `include_separator` - Whether to include a separator for YAML before the object -pub fn write_object(json: &str, format: Option<&OutputFormat>, include_separator: bool) { +pub fn write_object(json: &str, format: Option<&OutputFormat>, include_separator: bool) -> Result<(), ExitCode> { let mut is_json = true; let mut output_format = format; let mut syntax_color = false; @@ -259,14 +258,14 @@ pub fn write_object(json: &str, format: Option<&OutputFormat>, include_separator Ok(value) => value, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; match serde_json::to_string_pretty(&value) { Ok(json) => json, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } } }, @@ -280,14 +279,14 @@ pub fn write_object(json: &str, format: Option<&OutputFormat>, include_separator Ok(value) => value, Err(err) => { error!("JSON: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } }; match serde_yaml::to_string(&value) { Ok(yaml) => yaml, Err(err) => { error!("YAML: {err}"); - exit(EXIT_JSON_ERROR); + return Err(ExitCode::from(EXIT_JSON_ERROR)); } } } @@ -302,7 +301,7 @@ pub fn write_object(json: &str, format: Option<&OutputFormat>, include_separator ps.find_syntax_by_extension("yaml") }) else { println!("{json}"); - return; + return Ok(()); }; let mut h = HighlightLines::new(syntax, &ts.themes["base16-ocean.dark"]); @@ -319,9 +318,11 @@ pub fn write_object(json: &str, format: Option<&OutputFormat>, include_separator 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) - exit(EXIT_SUCCESS); + return Ok(()); } } + + Ok(()) } #[allow(clippy::too_many_lines)] @@ -411,7 +412,7 @@ pub fn enable_tracing(trace_level_arg: Option<&TraceLevel>, trace_format_arg: Op info!("Trace-level is {:?}", tracing_setting.level); } -pub fn get_input(input: Option<&String>, file: Option<&String>) -> String { +pub fn get_input(input: Option<&String>, file: Option<&String>) -> Result { trace!("Input: {input:?}, File: {file:?}"); let value = if let Some(input) = input { debug!("{}", t!("util.readingInput")); @@ -419,7 +420,7 @@ pub fn get_input(input: Option<&String>, file: Option<&String>) -> String { // see if user accidentally passed in a file path if Path::new(input).exists() { error!("{}", t!("util.inputIsFile")); - exit(EXIT_INVALID_INPUT); + return Err(ExitCode::from(EXIT_INVALID_INPUT)); } input.clone() } else if let Some(path) = file { @@ -436,13 +437,13 @@ pub fn get_input(input: Option<&String>, file: Option<&String>) -> String { }, Err(err) => { error!("{}: {err}", t!("util.invalidUtf8")); - exit(EXIT_INVALID_INPUT); + return Err(ExitCode::from(EXIT_INVALID_INPUT)); } } }, Err(err) => { error!("{}: {err}", t!("util.failedToReadStdin")); - exit(EXIT_INVALID_INPUT); + return Err(ExitCode::from(EXIT_INVALID_INPUT)); } } } else { @@ -451,7 +452,7 @@ pub fn get_input(input: Option<&String>, file: Option<&String>) -> String { let path_buf = Path::new(path); for extension in discovery.get_extensions(&Capability::Import) { if let Ok(content) = extension.import(path_buf) { - return content; + return Ok(content); } } match std::fs::read_to_string(path) { @@ -466,25 +467,25 @@ pub fn get_input(input: Option<&String>, file: Option<&String>) -> String { }, Err(err) => { error!("{}: {err}", t!("util.failedToReadFile")); - exit(EXIT_INVALID_INPUT); + return Err(ExitCode::from(EXIT_INVALID_INPUT)); } } } } else { debug!("{}", t!("util.noInput")); - return String::new(); + return Ok(String::new()); }; if value.trim().is_empty() { error!("{}", t!("util.emptyInput")); - exit(EXIT_INVALID_INPUT); + return Err(ExitCode::from(EXIT_INVALID_INPUT)); } match parse_input_to_json(&value) { - Ok(json) => json, + Ok(json) => Ok(json), Err(err) => { error!("{}: {err}", t!("util.failedToParseInput")); - exit(EXIT_INVALID_INPUT); + Err(ExitCode::from(EXIT_INVALID_INPUT)) } } } @@ -499,21 +500,21 @@ pub fn get_input(input: Option<&String>, file: Option<&String>) -> String { /// /// 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) -> Result { let path = Path::new(config_path); // make path absolute let Ok(full_path) = path.absolutize() else { error!("{}", t!("util.failedToAbsolutizePath")); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); }; let config_root_path = if full_path.is_file() { let Some(config_root_path) = full_path.parent() else { // this should never happen because path was made absolute error!("{}", t!("util.failedToGetParentPath")); - exit(EXIT_DSC_ERROR); + return Err(ExitCode::from(EXIT_DSC_ERROR)); }; config_root_path.to_string_lossy().into_owned() } else { @@ -531,7 +532,7 @@ pub fn set_dscconfigroot(config_path: &str) -> String env::set_var(DSC_CONFIG_ROOT, config_root_path); } - full_path.to_string_lossy().into_owned() + Ok(full_path.to_string_lossy().into_owned()) }