diff --git a/build.ps1 b/build.ps1 index d14fc9405..e45404a45 100644 --- a/build.ps1 +++ b/build.ps1 @@ -6,6 +6,7 @@ param( [ValidateSet('none','aarch64-pc-windows-msvc','x86_64-pc-windows-msvc')] $architecture = 'none', [switch]$Clippy, + [switch]$Pedantic, [switch]$Test ) @@ -40,7 +41,12 @@ foreach ($project in $projects) { try { Push-Location "$PSScriptRoot/$project" -ErrorAction Stop if ($Clippy) { - cargo clippy @flags -- -Dwarnings + if ($Pedantic) { + cargo clippy @flags --% -- -Dwarnings -Dclippy::pedantic + } + else { + cargo clippy @flags -- -Dwarnings + } } else { cargo build @flags diff --git a/dsc/examples/parallel.dsc.yaml b/dsc/examples/parallel.dsc.yaml index 64b76acb7..c1df75041 100644 --- a/dsc/examples/parallel.dsc.yaml +++ b/dsc/examples/parallel.dsc.yaml @@ -34,5 +34,14 @@ resources: properties: keyPath: HKCU\example2 _ensure: present + - name: mygroup + type: DSC/Group + properties: + resources: + - name: myreg + type: Microsoft.Windows.Registry + properties: + keyPath: HKCU\example2 + _ensure: absent dependsOn: - '[DSC/ParallelGroup]myParallelGroup' diff --git a/dsc/examples/powershell.dsc.yaml b/dsc/examples/powershell.dsc.yaml new file mode 100644 index 000000000..0006540d0 --- /dev/null +++ b/dsc/examples/powershell.dsc.yaml @@ -0,0 +1,32 @@ +# Example on how concurrency would be defined in the configuration. +$schema: https://schemas.microsoft.com/dsc/2023/03/configuration.schema.json +resources: +- name: PS7 + type: DSC/PowerShell + properties: + resources: + - name: psgallery + type: Microsoft.PowerShell.PSGallery/PSGallery + properties: + name: foo + url: http://bar + - name: my file + type: xFile/File + properties: + path: xyz + content: hello world +- name: WinPS + type: DSC/WindowsPowerShell + properties: + resources: + - name: psgallery + type: Microsoft.PowerShell.PSGallery/PSGallery + properties: + name: foo + url: http://bar + - name: my file + type: xFile/File + properties: + path: xyz + content: hello world + diff --git a/dsc/src/args.rs b/dsc/src/args.rs index e0a72887a..e476c8e99 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -85,7 +85,7 @@ pub enum ResourceSubCommand { }, } -#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] pub enum DscType { GetResult, SetResult, @@ -93,7 +93,6 @@ pub enum DscType { DscResource, ResourceManifest, Configuration, - ConfigurationAndResources, ConfigurationGetResult, ConfigurationSetResult, ConfigurationTestResult, diff --git a/dsc/src/main.rs b/dsc/src/main.rs index d8aef1f3a..16ee2703d 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use args::*; +use args::{Args, ConfigSubCommand, DscType, OutputFormat, ResourceSubCommand, SubCommand}; use atty::Stream; use clap::Parser; use dsc_lib::{configure::{Configurator, ErrorAction, config_result::{ConfigurationGetResult, ConfigurationSetResult, ConfigurationTestResult}}, configure::config_doc::Configuration, DscManager, dscresources::dscresource::{DscResource, Invoke}, dscresources::invoke_result::{GetResult, SetResult, TestResult}, dscresources::resource_manifest::ResourceManifest}; -use schemars::schema_for; +use schemars::{schema_for, schema::RootSchema}; use serde_yaml::Value; use std::io::{self, Read}; use std::process::exit; @@ -41,329 +41,338 @@ fn main() { let input = match String::from_utf8(buffer) { Ok(input) => input, Err(e) => { - eprintln!("Invalid UTF-8 sequence: {}", e); + eprintln!("Invalid UTF-8 sequence: {e}"); exit(EXIT_INVALID_ARGS); }, }; Some(input) }; - let mut dsc = match DscManager::new() { - Ok(dsc) => dsc, - Err(err) => { - eprintln!("Error: {}", err); - exit(EXIT_DSC_ERROR); - } - }; - match args.subcommand { SubCommand::Config { subcommand } => { - if stdin.is_none() { - eprintln!("Configuration must be piped to STDIN"); - exit(EXIT_INVALID_ARGS); - } - - let json: serde_json::Value = match serde_json::from_str(stdin.as_ref().unwrap()) { + handle_config_subcommand(&subcommand, &args.format, &stdin); + }, + SubCommand::Resource { subcommand } => { + handle_resource_subcommand(&subcommand, &args.format, &stdin); + }, + SubCommand::Schema { dsc_type } => { + let schema = get_schema(dsc_type); + let json = match serde_json::to_string(&schema) { Ok(json) => json, - Err(_) => { - match serde_yaml::from_str::(stdin.as_ref().unwrap()) { - Ok(yaml) => { - match serde_json::to_value(yaml) { - Ok(json) => json, - Err(err) => { - eprintln!("Error: Failed to convert YAML to JSON: {}", err); - exit(EXIT_DSC_ERROR); - } - } - }, - Err(err) => { - eprintln!("Error: Input is not valid JSON or YAML: {}", err); - exit(EXIT_INVALID_INPUT); - } - } - } - }; - - let json_string = match serde_json::to_string(&json) { - Ok(json_string) => json_string, Err(err) => { - eprintln!("Error: Failed to convert JSON to string: {}", err); - exit(EXIT_DSC_ERROR); + eprintln!("JSON Error: {err}"); + exit(EXIT_JSON_ERROR); } }; + write_output(&json, &args.format); + }, + } - let configurator = match Configurator::new(&json_string) { - Ok(configurator) => configurator, - Err(err) => { - eprintln!("Error: {}", err); - exit(EXIT_DSC_ERROR); - } - }; - match subcommand { - ConfigSubCommand::Get => { - match configurator.invoke_get(ErrorAction::Continue, || { /* code */ }) { - Ok(result) => { - let json = match serde_json::to_string(&result) { - Ok(json) => json, - Err(err) => { - eprintln!("JSON Error: {}", err); - exit(EXIT_JSON_ERROR); - } - }; - write_output(&json, &args.format); - if result.had_errors { - exit(EXIT_DSC_ERROR); - } - }, + exit(EXIT_SUCCESS); +} + +fn handle_config_subcommand(subcommand: &ConfigSubCommand, format: &Option, stdin: &Option) { + if stdin.is_none() { + eprintln!("Configuration must be piped to STDIN"); + exit(EXIT_INVALID_ARGS); + } + + let json: serde_json::Value = match serde_json::from_str(stdin.as_ref().unwrap()) { + Ok(json) => json, + Err(_) => { + match serde_yaml::from_str::(stdin.as_ref().unwrap()) { + Ok(yaml) => { + match serde_json::to_value(yaml) { + Ok(json) => json, Err(err) => { - eprintln!("Error: {}", err); + eprintln!("Error: Failed to convert YAML to JSON: {err}"); exit(EXIT_DSC_ERROR); } } }, - ConfigSubCommand::Set => { - eprintln!("Setting configuration... NOT IMPLEMENTED YET"); - exit(EXIT_DSC_ERROR); - }, - ConfigSubCommand::Test => { - eprintln!("Testing configuration... NOT IMPLEMENTED YET"); - exit(EXIT_DSC_ERROR); - }, + Err(err) => { + eprintln!("Error: Input is not valid JSON or YAML: {err}"); + exit(EXIT_INVALID_INPUT); + } } - }, - SubCommand::Resource { subcommand } => { - match subcommand { - ResourceSubCommand::List { resource_name } => { - match dsc.initialize_discovery() { - Ok(_) => (), + } + }; + + let json_string = match serde_json::to_string(&json) { + Ok(json_string) => json_string, + Err(err) => { + eprintln!("Error: Failed to convert JSON to string: {err}"); + exit(EXIT_DSC_ERROR); + } + }; + + let configurator = match Configurator::new(&json_string) { + Ok(configurator) => configurator, + Err(err) => { + eprintln!("Error: {err}"); + exit(EXIT_DSC_ERROR); + } + }; + match subcommand { + ConfigSubCommand::Get => { + match configurator.invoke_get(ErrorAction::Continue, || { /* code */ }) { + Ok(result) => { + let json = match serde_json::to_string(&result) { + Ok(json) => json, Err(err) => { - eprintln!("Error: {}", err); - exit(EXIT_DSC_ERROR); + eprintln!("JSON Error: {err}"); + exit(EXIT_JSON_ERROR); } }; - for resource in dsc.find_resource(&resource_name.unwrap_or_default()) { - // convert to json - let json = match serde_json::to_string(&resource) { - Ok(json) => json, - Err(err) => { - eprintln!("JSON Error: {}", err); - exit(EXIT_JSON_ERROR); - } - }; - write_output(&json, &args.format); - // insert newline separating instances if writing to console - if atty::is(Stream::Stdout) { - println!(); - } + write_output(&json, format); + if result.had_errors { + exit(EXIT_DSC_ERROR); } }, - ResourceSubCommand::Get { resource, input } => { - // TODO: support streaming stdin which includes resource and input + Err(err) => { + eprintln!("Error: {err}"); + exit(EXIT_DSC_ERROR); + } + } + }, + ConfigSubCommand::Set => { + eprintln!("Setting configuration... NOT IMPLEMENTED YET"); + exit(EXIT_DSC_ERROR); + }, + ConfigSubCommand::Test => { + eprintln!("Testing configuration... NOT IMPLEMENTED YET"); + exit(EXIT_DSC_ERROR); + }, + } +} - let input = get_input(&input, &stdin); - let resource = get_resource(&mut dsc, resource.as_str()); - match resource.get(input.as_str()) { - Ok(result) => { - // convert to json - let json = match serde_json::to_string(&result) { - Ok(json) => json, - Err(err) => { - eprintln!("JSON Error: {}", err); - exit(EXIT_JSON_ERROR); - } - }; - write_output(&json, &args.format); - } - Err(err) => { - eprintln!("Error: {}", err); - exit(EXIT_DSC_ERROR); - } - } - }, - ResourceSubCommand::Set { resource, input } => { - let input = get_input(&input, &stdin); - let resource = get_resource(&mut dsc, resource.as_str()); - match resource.set(input.as_str()) { - Ok(result) => { - // convert to json - let json = match serde_json::to_string(&result) { - Ok(json) => json, - Err(err) => { - eprintln!("JSON Error: {}", err); - exit(EXIT_JSON_ERROR); - } - }; - write_output(&json, &args.format); - } - Err(err) => { - eprintln!("Error: {}", err); - exit(EXIT_DSC_ERROR); - } - } - }, - ResourceSubCommand::Test { resource, input } => { - let input = get_input(&input, &stdin); - let resource = get_resource(&mut dsc, resource.as_str()); - match resource.test(input.as_str()) { - Ok(result) => { - // convert to json - let json = match serde_json::to_string(&result) { - Ok(json) => json, - Err(err) => { - eprintln!("JSON Error: {}", err); - exit(EXIT_JSON_ERROR); - } - }; - write_output(&json, &args.format); - } - Err(err) => { - eprintln!("Error: {}", err); - exit(EXIT_DSC_ERROR); - } - } - }, - ResourceSubCommand::Schema { resource } => { - let resource = get_resource(&mut dsc, resource.as_str()); - match resource.schema() { - Ok(json) => { - // verify is json - match serde_json::from_str::(json.as_str()) { - Ok(_) => (), - Err(err) => { - eprintln!("Error: {}", err); - exit(EXIT_JSON_ERROR); - } - }; - write_output(&json, &args.format); - } - Err(err) => { - eprintln!("Error: {}", err); - exit(EXIT_DSC_ERROR); - } +fn handle_resource_subcommand(subcommand: &ResourceSubCommand, format: &Option, stdin: &Option) { + let mut dsc = match DscManager::new() { + Ok(dsc) => dsc, + Err(err) => { + eprintln!("Error: {err}"); + exit(EXIT_DSC_ERROR); + } + }; + + match subcommand { + ResourceSubCommand::List { resource_name } => { + match dsc.initialize_discovery() { + Ok(_) => (), + Err(err) => { + eprintln!("Error: {err}"); + exit(EXIT_DSC_ERROR); + } + }; + for resource in dsc.find_resource(&resource_name.clone().unwrap_or_default()) { + // convert to json + let json = match serde_json::to_string(&resource) { + Ok(json) => json, + Err(err) => { + eprintln!("JSON Error: {err}"); + exit(EXIT_JSON_ERROR); } - }, + }; + write_output(&json, format); + // insert newline separating instances if writing to console + if atty::is(Stream::Stdout) { + println!(); + } } }, - SubCommand::Schema { dsc_type } => { - let schema = match dsc_type { - DscType::GetResult => { - schema_for!(GetResult) - }, - DscType::SetResult => { - schema_for!(SetResult) - }, - DscType::TestResult => { - schema_for!(TestResult) - }, - DscType::DscResource => { - schema_for!(DscResource) - }, - DscType::ResourceManifest => { - schema_for!(ResourceManifest) - }, - DscType::Configuration => { - schema_for!(Configuration) - }, - DscType::ConfigurationGetResult => { - schema_for!(ConfigurationGetResult) - }, - DscType::ConfigurationSetResult => { - schema_for!(ConfigurationSetResult) - }, - DscType::ConfigurationTestResult => { - schema_for!(ConfigurationTestResult) - }, - DscType::ConfigurationAndResources => { - let input = get_input(&None, &stdin); - if input.is_empty() { - eprintln!("Error: Configuration input is required for this schema"); - exit(EXIT_DSC_ERROR); - } + ResourceSubCommand::Get { resource, input } => { + handle_resource_get(&mut dsc, resource, input, stdin, format); + }, + ResourceSubCommand::Set { resource, input } => { + handle_resource_set(&mut dsc, resource, input, stdin, format); + }, + ResourceSubCommand::Test { resource, input } => { + handle_resource_test(&mut dsc, resource, input, stdin, format); + }, + ResourceSubCommand::Schema { resource } => { + handle_resource_schema(&mut dsc, resource, format); + }, + } +} - let json = get_config_and_resource_schema(input); - write_output(&json, &args.format); - exit(EXIT_SUCCESS); - }, +fn handle_resource_get(dsc: &mut DscManager, resource: &str, input: &Option, stdin: &Option, format: &Option) { + // TODO: support streaming stdin which includes resource and input + let input = get_input(input, stdin); + let resource = get_resource(dsc, resource); + match resource.get(input.as_str()) { + Ok(result) => { + // convert to json + let json = match serde_json::to_string(&result) { + Ok(json) => json, + Err(err) => { + eprintln!("JSON Error: {err}"); + exit(EXIT_JSON_ERROR); + } }; - let json = match serde_json::to_string(&schema) { + write_output(&json, format); + } + Err(err) => { + eprintln!("Error: {err}"); + exit(EXIT_DSC_ERROR); + } + } +} + +fn handle_resource_set(dsc: &mut DscManager, resource: &str, input: &Option, stdin: &Option, format: &Option) { + let input = get_input(input, stdin); + let resource = get_resource(dsc, resource); + match resource.set(input.as_str()) { + Ok(result) => { + // convert to json + let json = match serde_json::to_string(&result) { Ok(json) => json, Err(err) => { - eprintln!("JSON Error: {}", err); + eprintln!("JSON Error: {err}"); exit(EXIT_JSON_ERROR); } }; - write_output(&json, &args.format); - }, + write_output(&json, format); + } + Err(err) => { + eprintln!("Error: {err}"); + exit(EXIT_DSC_ERROR); + } } +} - exit(EXIT_SUCCESS); +fn handle_resource_test(dsc: &mut DscManager, resource: &str, input: &Option, stdin: &Option, format: &Option) { + let input = get_input(input, stdin); + let resource = get_resource(dsc, resource); + match resource.test(input.as_str()) { + Ok(result) => { + // convert to json + let json = match serde_json::to_string(&result) { + Ok(json) => json, + Err(err) => { + eprintln!("JSON Error: {err}"); + exit(EXIT_JSON_ERROR); + } + }; + write_output(&json, format); + } + Err(err) => { + eprintln!("Error: {err}"); + exit(EXIT_DSC_ERROR); + } + } } -fn get_config_and_resource_schema(_input: String) -> String { - // TODO: fill in code - String::from("TODO") +fn handle_resource_schema(dsc: &mut DscManager, resource: &str, format: &Option) { + let resource = get_resource(dsc, resource); + match resource.schema() { + Ok(json) => { + // verify is json + match serde_json::from_str::(json.as_str()) { + Ok(_) => (), + Err(err) => { + eprintln!("Error: {err}"); + exit(EXIT_JSON_ERROR); + } + }; + write_output(&json, format); + } + Err(err) => { + eprintln!("Error: {err}"); + exit(EXIT_DSC_ERROR); + } + } +} + +fn get_schema(dsc_type: DscType) -> RootSchema { + match dsc_type { + DscType::GetResult => { + schema_for!(GetResult) + }, + DscType::SetResult => { + schema_for!(SetResult) + }, + DscType::TestResult => { + schema_for!(TestResult) + }, + DscType::DscResource => { + schema_for!(DscResource) + }, + DscType::ResourceManifest => { + schema_for!(ResourceManifest) + }, + DscType::Configuration => { + schema_for!(Configuration) + }, + DscType::ConfigurationGetResult => { + schema_for!(ConfigurationGetResult) + }, + DscType::ConfigurationSetResult => { + schema_for!(ConfigurationSetResult) + }, + DscType::ConfigurationTestResult => { + schema_for!(ConfigurationTestResult) + }, + } } fn write_output(json: &str, format: &Option) { let mut is_json = true; - match atty::is(Stream::Stdout) { - true => { - let output = match format { - Some(OutputFormat::Json) => json.to_string(), - Some(OutputFormat::PrettyJson) => { - let value: serde_json::Value = match serde_json::from_str(json) { - Ok(value) => value, - Err(err) => { - eprintln!("JSON Error: {}", err); - exit(EXIT_JSON_ERROR); - } - }; - match serde_json::to_string_pretty(&value) { - Ok(json) => json, - Err(err) => { - eprintln!("JSON Error: {}", err); - exit(EXIT_JSON_ERROR); - } + if atty::is(Stream::Stdout) { + let output = match format { + Some(OutputFormat::Json) => json.to_string(), + Some(OutputFormat::PrettyJson) => { + let value: serde_json::Value = match serde_json::from_str(json) { + Ok(value) => value, + Err(err) => { + eprintln!("JSON Error: {err}"); + exit(EXIT_JSON_ERROR); } - }, - Some(OutputFormat::Yaml) | None => { - is_json = false; - let value: serde_json::Value = match serde_json::from_str(json) { - Ok(value) => value, - Err(err) => { - eprintln!("JSON Error: {}", err); - exit(EXIT_JSON_ERROR); - } - }; - match serde_yaml::to_string(&value) { - Ok(yaml) => yaml, - Err(err) => { - eprintln!("YAML Error: {}", err); - exit(EXIT_JSON_ERROR); - } + }; + match serde_json::to_string_pretty(&value) { + Ok(json) => json, + Err(err) => { + eprintln!("JSON Error: {err}"); + exit(EXIT_JSON_ERROR); } } - }; + }, + Some(OutputFormat::Yaml) | None => { + is_json = false; + let value: serde_json::Value = match serde_json::from_str(json) { + Ok(value) => value, + Err(err) => { + eprintln!("JSON Error: {err}"); + exit(EXIT_JSON_ERROR); + } + }; + match serde_yaml::to_string(&value) { + Ok(yaml) => yaml, + Err(err) => { + eprintln!("YAML Error: {err}"); + exit(EXIT_JSON_ERROR); + } + } + } + }; - let ps = SyntaxSet::load_defaults_newlines(); - let ts = ThemeSet::load_defaults(); - let syntax = match is_json { - true => ps.find_syntax_by_extension("json").unwrap(), - false => ps.find_syntax_by_extension("yaml").unwrap(), - }; + let ps = SyntaxSet::load_defaults_newlines(); + let ts = ThemeSet::load_defaults(); + let syntax = if is_json { + ps.find_syntax_by_extension("json").unwrap() + } else { + ps.find_syntax_by_extension("yaml").unwrap() + }; - let mut h = HighlightLines::new(syntax, &ts.themes["base16-ocean.dark"]); + let mut h = HighlightLines::new(syntax, &ts.themes["base16-ocean.dark"]); - for line in LinesWithEndings::from(&output) { - let ranges: Vec<(Style, &str)> = h.highlight_line(line, &ps).unwrap(); - let escaped = as_24_bit_terminal_escaped(&ranges[..], false); - print!("{}", escaped); - } - }, - false => { - println!("{}", json); + for line in LinesWithEndings::from(&output) { + let ranges: Vec<(Style, &str)> = h.highlight_line(line, &ps).unwrap(); + let escaped = as_24_bit_terminal_escaped(&ranges[..], false); + print!("{escaped}"); } - }; + } else { + println!("{json}"); + } } fn get_resource(dsc: &mut DscManager, resource: &str) -> DscResource { @@ -372,21 +381,21 @@ fn get_resource(dsc: &mut DscManager, resource: &str) -> DscResource { Ok(resource) => resource, Err(err) => { if resource.contains('{') { - eprintln!("Not valid resource JSON: {}\nInput was: {}", err, resource); + eprintln!("Not valid resource JSON: {err}\nInput was: {resource}"); exit(EXIT_INVALID_ARGS); } match dsc.initialize_discovery() { Ok(_) => (), Err(err) => { - eprintln!("Error: {}", err); + eprintln!("Error: {err}"); exit(EXIT_DSC_ERROR); } }; let resources: Vec = dsc.find_resource(resource).collect(); match resources.len() { 0 => { - eprintln!("Error: Resource not found: '{}'", resource); + eprintln!("Error: Resource not found: '{resource}'"); exit(EXIT_INVALID_ARGS); } 1 => resources[0].clone(), @@ -424,17 +433,17 @@ fn get_input(input: &Option, stdin: &Option) -> String { match serde_json::to_string(&yaml) { Ok(json) => json, Err(err) => { - eprintln!("Error: Cannot convert YAML to JSON: {}", err); + eprintln!("Error: Cannot convert YAML to JSON: {err}"); exit(EXIT_INVALID_ARGS); } } }, Err(err) => { if input.contains('{') { - eprintln!("Error: Input is not valid JSON: {}", json_err); + eprintln!("Error: Input is not valid JSON: {json_err}"); } else { - eprintln!("Error: Input is not valid YAML: {}", err); + eprintln!("Error: Input is not valid YAML: {err}"); } exit(EXIT_INVALID_ARGS); } @@ -449,17 +458,14 @@ fn check_debug() { eprintln!("attach debugger to pid {} and press a key to continue", std::process::id()); loop { let event = event::read().unwrap(); - match event { - event::Event::Key(key) => { - // workaround bug in 0.26+ https://github.com/crossterm-rs/crossterm/issues/752#issuecomment-1414909095 - if key.kind == event::KeyEventKind::Press { - break; - } - } - _ => { - eprintln!("Unexpected event: {:?}", event); - continue; + if let event::Event::Key(key) = event { + // workaround bug in 0.26+ https://github.com/crossterm-rs/crossterm/issues/752#issuecomment-1414909095 + if key.kind == event::KeyEventKind::Press { + break; } + } else { + eprintln!("Unexpected event: {event:?}"); + continue; } } } diff --git a/dsc_lib/src/configure/config_result.rs b/dsc_lib/src/configure/config_result.rs index e6585bc7c..dffbb5a7d 100644 --- a/dsc_lib/src/configure/config_result.rs +++ b/dsc_lib/src/configure/config_result.rs @@ -41,6 +41,7 @@ pub struct ConfigurationGetResult { } impl ConfigurationGetResult { + #[must_use] pub fn new() -> Self { Self { results: Vec::new(), @@ -50,6 +51,12 @@ impl ConfigurationGetResult { } } +impl Default for ConfigurationGetResult { + fn default() -> Self { + Self::new() + } +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ResourceSetResult { @@ -69,6 +76,7 @@ pub struct ConfigurationSetResult { } impl ConfigurationSetResult { + #[must_use] pub fn new() -> Self { Self { results: Vec::new(), @@ -78,6 +86,12 @@ impl ConfigurationSetResult { } } +impl Default for ConfigurationSetResult { + fn default() -> Self { + Self::new() + } +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ResourceTestResult { @@ -97,6 +111,7 @@ pub struct ConfigurationTestResult { } impl ConfigurationTestResult { + #[must_use] pub fn new() -> Self { Self { results: Vec::new(), @@ -105,3 +120,9 @@ impl ConfigurationTestResult { } } } + +impl Default for ConfigurationTestResult { + fn default() -> Self { + Self::new() + } +} diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index f56fad0fc..43c3a46cf 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -17,21 +17,41 @@ pub struct Configurator { discovery: Discovery, } +#[derive(Debug, Clone, Copy)] pub enum ErrorAction { Continue, Stop, } impl Configurator { - pub fn new(config: &String) -> Result { + /// Create a new `Configurator` instance. + /// + /// # Arguments + /// + /// * `config` - The configuration to use in JSON. + /// + /// # Errors + /// + /// This function will return an error if the configuration is invalid or the underlying discovery fails. + pub fn new(config: &str) -> Result { let mut discovery = Discovery::new()?; discovery.initialize()?; Ok(Configurator { - config: config.clone(), + config: config.to_owned(), discovery, }) } + /// Invoke the get operation on a resource. + /// + /// # Arguments + /// + /// * `error_action` - The error action to use. + /// * `progress_callback` - A callback to call when progress is made. + /// + /// # Errors + /// + /// This function will return an error if the underlying resource fails. pub fn invoke_get(&self, _error_action: ErrorAction, _progress_callback: impl Fn() + 'static) -> Result { let (config, messages, had_errors) = self.validate_config()?; let mut result = ConfigurationGetResult::new(); @@ -42,11 +62,8 @@ impl Configurator { } for resource in &config.resources { - let dsc_resource = match self.discovery.find_resource(&resource.resource_type).next() { - Some(dsc_resource) => dsc_resource, - None => { - return Err(DscError::ResourceNotFound(resource.resource_type.clone())); - } + let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type).next() else { + return Err(DscError::ResourceNotFound(resource.resource_type.clone())); }; let filter = serde_json::to_string(&resource.properties)?; let get_result = dsc_resource.get(&filter)?; @@ -66,11 +83,8 @@ impl Configurator { let mut messages: Vec = Vec::new(); let mut has_errors = false; for resource in &config.resources { - let dsc_resource = match self.discovery.find_resource(&resource.resource_type).next() { - Some(dsc_resource) => dsc_resource, - None => { - return Err(DscError::ResourceNotFound(resource.resource_type.clone())); - } + let Some(dsc_resource) = self.discovery.find_resource(&resource.resource_type).next() else { + return Err(DscError::ResourceNotFound(resource.resource_type.clone())); }; let input = serde_json::to_string(&resource.properties)?; let schema = match dsc_resource.schema() { @@ -95,7 +109,7 @@ impl Configurator { messages.push(ResourceMessage { name: resource.name.clone(), resource_type: resource.resource_type.clone(), - message: format!("Failed to compile schema: {}", e), + message: format!("Failed to compile schema: {e}"), level: MessageLevel::Error, }); has_errors = true; @@ -103,22 +117,19 @@ impl Configurator { }, }; let input = serde_json::from_str(&input)?; - match compiled_schema.validate(&input) { - Err(err) => { - let mut error = format!("Resource '{}' failed validation: ", resource.name); - for e in err { - error.push_str(&format!("\n{} ", e)); - } - messages.push(ResourceMessage { - name: resource.name.clone(), - resource_type: resource.resource_type.clone(), - message: error, - level: MessageLevel::Error, - }); - has_errors = true; - continue; - }, - Ok(_) => {}, + if let Err(err) = compiled_schema.validate(&input) { + let mut error = format!("Resource '{}' failed validation: ", resource.name); + for e in err { + error.push_str(&format!("\n{e} ")); + } + messages.push(ResourceMessage { + name: resource.name.clone(), + resource_type: resource.resource_type.clone(), + message: error, + level: MessageLevel::Error, + }); + has_errors = true; + continue; }; } diff --git a/dsc_lib/src/discovery/command_discovery.rs b/dsc_lib/src/discovery/command_discovery.rs index 82810185d..ad23cdbe5 100644 --- a/dsc_lib/src/discovery/command_discovery.rs +++ b/dsc_lib/src/discovery/command_discovery.rs @@ -32,9 +32,10 @@ impl Default for CommandDiscovery { impl ResourceDiscovery for CommandDiscovery { fn discover(&self) -> Box> { - match self.initialized { - true => Box::new(self.resources.clone().into_iter()), - false => Box::new(vec![].into_iter()), + if self.initialized { + Box::new(self.resources.clone().into_iter()) + } else { + Box::new(vec![].into_iter()) } } @@ -43,11 +44,8 @@ impl ResourceDiscovery for CommandDiscovery { return Ok(()); } - let path_env = match env::var_os("PATH") { - Some(path_env) => path_env, - None => { - return Err(DscError::Operation("Failed to get PATH environment variable".to_string())); - } + let Some(path_env) = env::var_os("PATH") else { + return Err(DscError::Operation("Failed to get PATH environment variable".to_string())); }; for path in env::split_paths(&path_env) { @@ -85,7 +83,7 @@ fn import_manifest(path: &Path) -> Result { implemented_as: ImplementedAs::Command, path: path.to_str().unwrap().to_string(), parent_path: path.parent().unwrap().to_str().unwrap().to_string(), - manifest: Some(manifest.clone()), + manifest: Some(manifest), ..Default::default() }; diff --git a/dsc_lib/src/discovery/mod.rs b/dsc_lib/src/discovery/mod.rs index adb0f07b6..958f55f17 100644 --- a/dsc_lib/src/discovery/mod.rs +++ b/dsc_lib/src/discovery/mod.rs @@ -16,6 +16,11 @@ pub struct Discovery { } impl Discovery { + /// Create a new `Discovery` instance. + /// + /// # Errors + /// + /// This function will return an error if the underlying discovery fails. pub fn new() -> Result { Ok(Self { resources: Vec::new(), @@ -23,6 +28,11 @@ impl Discovery { }) } + /// Initialize the discovery process. + /// + /// # Errors + /// + /// This function will return an error if the underlying discovery fails. pub fn initialize(&mut self) -> Result<(), DscError> { let discovery_types: Vec> = vec![ Box::new(command_discovery::CommandDiscovery::new()), @@ -44,7 +54,13 @@ impl Discovery { Ok(()) } - // TODO: may need more search criteria like version, hash, etc... + // TODO: Need to support version? + /// Find a resource by name. + /// + /// # Arguments + /// + /// * `type_name` - The name of the resource to find, can have wildcards. + #[must_use] pub fn find_resource(&self, type_name: &str) -> ResourceIterator { if !self.initialized { return ResourceIterator::new(vec![]); @@ -52,9 +68,8 @@ impl Discovery { let mut regex_builder = RegexBuilder::new(convert_wildcard_to_regex(type_name).as_str()); regex_builder.case_insensitive(true); - let regex = match regex_builder.build() { - Ok(regex) => regex, - Err(_) => return ResourceIterator::new(vec![]), + let Ok(regex) = regex_builder.build() else { + return ResourceIterator::new(vec![]); }; let mut resources: Vec = Vec::new(); @@ -107,6 +122,7 @@ pub struct ResourceIterator { } impl ResourceIterator { + #[must_use] pub fn new(resources: Vec) -> ResourceIterator { ResourceIterator { resources, diff --git a/dsc_lib/src/discovery/powershell_discovery.rs b/dsc_lib/src/discovery/powershell_discovery.rs index 66534e383..6223e7c70 100644 --- a/dsc_lib/src/discovery/powershell_discovery.rs +++ b/dsc_lib/src/discovery/powershell_discovery.rs @@ -28,7 +28,7 @@ impl ResourceDiscovery for PowerShellDiscovery { // these are just test resources let resources = vec![]; - Box::new(resources.clone().into_iter()) + Box::new(resources.into_iter()) } fn initialize(&mut self) -> Result<(), crate::dscerror::DscError> { diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs index 2cb820672..11e4eeec6 100644 --- a/dsc_lib/src/dscresources/command_resource.rs +++ b/dsc_lib/src/dscresources/command_resource.rs @@ -10,14 +10,24 @@ use super::{dscresource::get_diff,resource_manifest::{ResourceManifest, ReturnKi pub const EXIT_PROCESS_TERMINATED: i32 = 0x102; +/// Invoke the get operation on a resource +/// +/// # Arguments +/// +/// * `resource` - The resource manifest +/// * `filter` - The filter to apply to the resource in JSON +/// +/// # Errors +/// +/// Error returned if the resource does not successfully get the current state pub fn invoke_get(resource: &ResourceManifest, filter: &str) -> Result { - if filter.len() > 0 && resource.get.input.is_some() { + if !filter.is_empty() && resource.get.input.is_some() { verify_json(resource, filter)?; } let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, resource.get.args.clone().unwrap_or_default(), Some(filter))?; if exit_code != 0 { - return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr.to_string())); + return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); } let result: Value = serde_json::from_str(&stdout)?; @@ -26,14 +36,22 @@ pub fn invoke_get(resource: &ResourceManifest, filter: &str) -> Result Result { - if resource.set.is_none() { + let Some(set) = &resource.set else { return Err(DscError::NotImplemented("set".to_string())); - } + }; verify_json(resource, desired)?; - - let set = resource.set.as_ref().unwrap(); // if resource doesn't implement a pre-test, we execute test first to see if a set is needed if !set.pre_test.unwrap_or_default() { let test_result = invoke_test(resource, desired)?; @@ -46,18 +64,17 @@ pub fn invoke_set(resource: &ResourceManifest, desired: &str) -> Result Result { // 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 actual_value: Value = serde_json::from_str(lines.next().unwrap())?; + let Some(actual_line) = lines.next() else { + return Err(DscError::Command(resource.resource_type.clone(), exit_code, "Command did not return expected actual output".to_string())); + }; + let actual_value: Value = serde_json::from_str(actual_line)?; // TODO: need schema for diff_properties to validate against - let diff_properties: Vec = serde_json::from_str(lines.next().unwrap())?; - return Ok(SetResult { + let Some(diff_line) = lines.next() else { + return Err(DscError::Command(resource.resource_type.clone(), exit_code, "Command did not return expected diff output".to_string())); + }; + let diff_properties: Vec = serde_json::from_str(diff_line)?; + Ok(SetResult { before_state: pre_state, 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, desired)?; // for changed_properties, we compare post state to pre state let diff_properties = get_diff( &get_result.actual_state, &pre_state); - return Ok(SetResult { + Ok(SetResult { before_state: pre_state, after_state: get_result.actual_state, changed_properties: Some(diff_properties), - }); + }) }, } } +/// Invoke the test operation against a command resource. +/// +/// # Arguments +/// +/// * `resource` - The resource manifest for the command resource. +/// * `expected` - The expected state of the resource in JSON. +/// +/// # Errors +/// +/// Error is returned if the underlying command returns a non-zero exit code. pub fn invoke_test(resource: &ResourceManifest, expected: &str) -> Result { - if resource.test.is_none() { + let Some(test) = resource.test.as_ref() else { return Err(DscError::NotImplemented("test".to_string())); - } + }; verify_json(resource, expected)?; - - let test = resource.test.as_ref().unwrap(); let (exit_code, stdout, stderr) = invoke_command(&test.executable, test.args.clone().unwrap_or_default(), Some(expected))?; if exit_code != 0 { - return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr.to_string())); + return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); } let expected_value: Value = serde_json::from_str(expected)?; @@ -115,46 +146,61 @@ pub fn invoke_test(resource: &ResourceManifest, expected: &str) -> Result { let actual_value: Value = serde_json::from_str(&stdout)?; let diff_properties = get_diff(&expected_value, &actual_value); - return Ok(TestResult { + Ok(TestResult { expected_state: expected_value, actual_state: actual_value, diff_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 actual_value: Value = serde_json::from_str(lines.next().unwrap())?; - let diff_properties: Vec = serde_json::from_str(lines.next().unwrap())?; - return Ok(TestResult { + let Some(actual_value) = lines.next() else { + return Err(DscError::Command(resource.resource_type.clone(), exit_code, "No actual state returned".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, "No diff properties returned".to_string())); + }; + let diff_properties: Vec = serde_json::from_str(diff_properties)?; + Ok(TestResult { expected_state: expected_value, actual_state: actual_value, diff_properties: Some(diff_properties), - }); + }) }, None => { // perform a get and compare the result to the expected state let get_result = invoke_get(resource, expected)?; let diff_properties = get_diff(&expected_value, &get_result.actual_state); - return Ok(TestResult { + Ok(TestResult { expected_state: expected_value, actual_state: get_result.actual_state, diff_properties: Some(diff_properties), - }); + }) }, } } +/// Get the JSON schema for a resource +/// +/// # Arguments +/// +/// * `resource` - The resource manifest +/// +/// # Errors +/// +/// Error if schema is not available or if there is an error getting the schema pub fn get_schema(resource: &ResourceManifest) -> Result { - if resource.schema.is_none() { + let Some(schema_kind) = resource.schema.as_ref() else { return Err(DscError::SchemaNotAvailable(resource.resource_type.clone())); - } + }; - match resource.schema.as_ref().unwrap() { + match schema_kind { SchemaKind::Command(ref command) => { let (exit_code, stdout, stderr) = invoke_command(&command.executable, command.args.clone().unwrap_or_default(), None)?; if exit_code != 0 { - return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr.to_string())); + return Err(DscError::Command(resource.resource_type.clone(), exit_code, stderr)); } Ok(stdout) }, @@ -202,7 +248,7 @@ fn invoke_command(executable: &str, args: Vec, input: Option<&str>) -> R let mut stderr_buf = Vec::new(); child_stderr.read_to_end(&mut stderr_buf)?; - let exit_code = exit_status.code().unwrap_or(EXIT_PROCESS_TERMINATED) as i32; + let exit_code = exit_status.code().unwrap_or(EXIT_PROCESS_TERMINATED); let stdout = String::from_utf8_lossy(&stdout_buf).to_string(); let stderr = String::from_utf8_lossy(&stderr_buf).to_string(); Ok((exit_code, stdout, stderr)) @@ -218,14 +264,16 @@ fn verify_json(resource: &ResourceManifest, json: &str) -> Result<(), DscError> }, }; let json: Value = serde_json::from_str(json)?; - match compiled_schema.validate(&json) { - Ok(_) => return Ok(()), + let result = match compiled_schema.validate(&json) { + Ok(_) => Ok(()), Err(err) => { let mut error = String::new(); for e in err { - error.push_str(&format!("{} ", e)); + error.push_str(&format!("{e} ")); } - return Err(DscError::Schema(error)); + + Err(DscError::Schema(error)) }, }; + result } diff --git a/dsc_lib/src/dscresources/dscresource.rs b/dsc_lib/src/dscresources/dscresource.rs index 6b04269aa..4012c93e5 100644 --- a/dsc_lib/src/dscresources/dscresource.rs +++ b/dsc_lib/src/dscresources/dscresource.rs @@ -6,7 +6,7 @@ use resource_manifest::ResourceManifest; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; -use super::{*, invoke_result::{GetResult, SetResult, TestResult}}; +use super::{command_resource, dscerror, resource_manifest, invoke_result::{GetResult, SetResult, TestResult}}; // TODO: this should be redesigned to match our new ARM based syntax // example is `name` should now be `type` @@ -48,6 +48,7 @@ pub enum ImplementedAs { } impl DscResource { + #[must_use] pub fn new() -> Self { Self { type_name: String::new(), @@ -71,11 +72,46 @@ impl Default for DscResource { } } +/// The interface for a DSC resource. pub trait Invoke { - // the strings are expected to be json + /// Invoke the get operation on the resource. + /// + /// # Arguments + /// + /// * `filter` - The filter as JSON to apply to the resource. + /// + /// # Errors + /// + /// This function will return an error if the underlying resource fails. fn get(&self, filter: &str) -> Result; + + /// Invoke the set operation on the resource. + /// + /// # Arguments + /// + /// * `desired` - The desired state as JSON to apply to the resource. + /// + /// # Errors + /// + /// This function will return an error if the underlying resource fails. fn set(&self, desired: &str) -> Result; + + /// Invoke the test operation on the resource. + /// + /// # Arguments + /// + /// * `expected` - The expected state as JSON to apply to the resource. + /// + /// # Errors + /// + /// This function will return an error if the underlying resource fails. fn test(&self, expected: &str) -> Result; + + /// Get the schema for the resource. + /// + /// # Errors + /// + /// This function will return an error if the underlying resource fails. fn schema(&self) -> Result; } @@ -89,11 +125,8 @@ impl Invoke for DscResource { Err(DscError::NotImplemented("get PowerShellScript resources".to_string())) }, ImplementedAs::Command => { - let manifest = match &self.manifest { - None => { - return Err(DscError::MissingManifest(self.type_name.clone())); - }, - Some(manifest) => manifest, + let Some(manifest) = &self.manifest else { + return Err(DscError::MissingManifest(self.type_name.clone())); }; command_resource::invoke_get(manifest, filter) }, @@ -109,11 +142,8 @@ impl Invoke for DscResource { Err(DscError::NotImplemented("set PowerShellScript resources".to_string())) }, ImplementedAs::Command => { - let manifest = match &self.manifest { - None => { - return Err(DscError::MissingManifest(self.type_name.clone())); - }, - Some(manifest) => manifest, + let Some(manifest) = &self.manifest else { + return Err(DscError::MissingManifest(self.type_name.clone())); }; command_resource::invoke_set(manifest, desired) }, @@ -129,11 +159,8 @@ impl Invoke for DscResource { Err(DscError::NotImplemented("test PowerShellScript resources".to_string())) }, ImplementedAs::Command => { - let manifest = match &self.manifest { - None => { - return Err(DscError::MissingManifest(self.type_name.clone())); - }, - Some(manifest) => manifest, + let Some(manifest) = &self.manifest else { + return Err(DscError::MissingManifest(self.type_name.clone())); }; // if test is not directly implemented, then we need to handle it here @@ -146,7 +173,7 @@ impl Invoke for DscResource { actual_state: get_result.actual_state, diff_properties: Some(diff_properties), }; - return Ok(test_result); + Ok(test_result) } else { command_resource::invoke_test(manifest, expected) @@ -164,11 +191,8 @@ impl Invoke for DscResource { Err(DscError::NotImplemented("schema PowerShellScript resources".to_string())) }, ImplementedAs::Command => { - let manifest = match &self.manifest { - None => { - return Err(DscError::MissingManifest(self.type_name.clone())); - }, - Some(manifest) => manifest, + let Some(manifest) = &self.manifest else { + return Err(DscError::MissingManifest(self.type_name.clone())); }; command_resource::get_schema(manifest) }, @@ -176,41 +200,45 @@ impl Invoke for DscResource { } } +#[must_use] pub fn get_diff(expected: &Value, actual: &Value) -> Vec { let mut diff_properties: Vec = Vec::new(); if expected.is_null() { return diff_properties; } - for (key, value) in expected.as_object().unwrap() { - // skip meta properties - if key.starts_with("_") || key.starts_with("$") { - continue; - } + if let Some(map) = expected.as_object() { + for (key, value) in map { + // skip meta properties + if key.starts_with('_') || key.starts_with('$') { + continue; + } - if value.is_object() { - let sub_diff = get_diff(value, &actual[key]); - if sub_diff.len() > 0 { - diff_properties.push(key.to_string()); + if value.is_object() { + let sub_diff = get_diff(value, &actual[key]); + if !sub_diff.is_empty() { + diff_properties.push(key.to_string()); + } } - } - else { - match actual.as_object() { - Some(actual_object) => { - if !actual_object.contains_key(key) { - diff_properties.push(key.to_string()); - } - else { - if value != &actual[key] { + else { + match actual.as_object() { + Some(actual_object) => { + if actual_object.contains_key(key) { + if value != &actual[key] { + diff_properties.push(key.to_string()); + } + } + else { diff_properties.push(key.to_string()); } - } - }, - None => { - diff_properties.push(key.to_string()); - }, + }, + None => { + diff_properties.push(key.to_string()); + }, + } } } } + diff_properties } diff --git a/dsc_lib/src/dscresources/mod.rs b/dsc_lib/src/dscresources/mod.rs index 8af8d1900..62d9f3e3e 100644 --- a/dsc_lib/src/dscresources/mod.rs +++ b/dsc_lib/src/dscresources/mod.rs @@ -7,4 +7,4 @@ pub mod invoke_result; pub mod powershell_resource; pub mod resource_manifest; -use super::*; +use super::dscerror; diff --git a/dsc_lib/src/lib.rs b/dsc_lib/src/lib.rs index bfe6ecf4b..dd67fd1a6 100644 --- a/dsc_lib/src/lib.rs +++ b/dsc_lib/src/lib.rs @@ -15,28 +15,80 @@ pub struct DscManager { } impl DscManager { + /// Create a new `DscManager` instance. + /// + /// # Errors + /// + /// This function will return an error if the underlying discovery fails. + /// pub fn new() -> Result { Ok(Self { discovery: discovery::Discovery::new()?, }) } + /// Initialize the discovery process. + /// + /// # Errors + /// + /// This function will return an error if the underlying discovery fails. + /// pub fn initialize_discovery(&mut self) -> Result<(), DscError> { self.discovery.initialize() } + /// Find a resource by name. + /// + /// # Arguments + /// + /// * `name` - The name of the resource to find, can have wildcards. + /// + #[must_use] pub fn find_resource(&self, name: &str) -> ResourceIterator { self.discovery.find_resource(name) } + /// Invoke the get operation on a resource. + /// + /// # Arguments + /// + /// * `resource` - The resource to invoke the operation on. + /// * `input` - The input to the operation. + /// + /// # Errors + /// + /// This function will return an error if the underlying resource fails. + /// pub fn resource_get(&self, resource: &DscResource, input: &str) -> Result { resource.get(input) } + /// Invoke the set operation on a resource. + /// + /// # Arguments + /// + /// * `resource` - The resource to invoke the operation on. + /// * `input` - The input to the operation. + /// + /// # Errors + /// + /// This function will return an error if the underlying resource fails. + /// pub fn resource_set(&self, resource: &DscResource, input: &str) -> Result { resource.set(input) } + /// Invoke the test operation on a resource. + /// + /// # Arguments + /// + /// * `resource` - The resource to invoke the operation on. + /// * `input` - The input to the operation. + /// + /// # Errors + /// + /// This function will return an error if the underlying resource fails. + /// pub fn resource_test(&self, resource: &DscResource, input: &str) -> Result { resource.test(input) } diff --git a/ntreg/src/lib.rs b/ntreg/src/lib.rs index 6d00ff262..5aed8a090 100644 --- a/ntreg/src/lib.rs +++ b/ntreg/src/lib.rs @@ -6,31 +6,38 @@ use core::mem::size_of; use ntapi::winapi::shared::ntdef::{HANDLE, NTSTATUS, OBJECT_ATTRIBUTES, OBJ_CASE_INSENSITIVE, UNICODE_STRING}; -use std::ptr::null_mut; +use std::ptr::{null_mut, addr_of_mut}; pub mod registry_key; pub mod registry_value; const ERROR_NO_MORE_ITEMS: NTSTATUS = 259; -/// Represents the buffer for a UNICODE_STRING. +/// Represents the buffer for a `UNICODE_STRING`. pub struct UnicodeString { buffer: Vec, } impl UnicodeString { - /// Create a new UnicodeString. + /// Create a new `UnicodeString`. /// /// # Arguments /// - /// * `string` - The string to create the UnicodeString from. - pub fn new(string: String) -> UnicodeString { + /// * `string` - The string to create the `UnicodeString` from. + #[must_use] + pub fn new(string: &str) -> UnicodeString { let mut buffer: Vec = string.encode_utf16().collect(); buffer.push(0); UnicodeString { buffer } } - /// Get the UNICODE_STRING representation of the buffer. + /// Get the `UNICODE_STRING` representation of the buffer. + /// + /// # Panics + /// + /// Will panic if the size of `UNICODE_STRING` cannot be converted to `USHORT`. + /// + #[must_use] pub fn as_struct(&self) -> UNICODE_STRING { UNICODE_STRING { Length: ((self.buffer.len() - 1) * 2) as u16, @@ -46,7 +53,7 @@ trait AsUnicodeString { impl AsUnicodeString for String { fn as_unicode_string(&self) -> UnicodeString { - UnicodeString::new(self.to_string()) + UnicodeString::new(self.as_str()) } } @@ -56,7 +63,7 @@ pub struct ObjectAttributes { } impl ObjectAttributes { - /// Create a new ObjectAttributes. + /// Create a new `ObjectAttributes`. /// /// # Arguments /// @@ -69,12 +76,25 @@ impl ObjectAttributes { } } - /// Get the OBJECT_ATTRIBUTES representation of the struct. + /// Get the `OBJECT_ATTRIBUTES` representation of the struct. + /// + /// # Panics + /// + /// Will panic if the size of `UNICODE_STRING` cannot be converted to `ULONG`. + /// + /// # Safety + /// + /// The returned `OBJECT_ATTRIBUTES` struct is only valid as long as the + /// `UnicodeString` struct is valid. + /// + #[must_use] pub fn as_struct(&self) -> OBJECT_ATTRIBUTES { + let mut unicode_string = self.unicode_string.as_struct(); OBJECT_ATTRIBUTES { Length: size_of::() as u32, RootDirectory: self.root_directory, - ObjectName: &mut self.unicode_string.as_struct() as *mut UNICODE_STRING, + ObjectName: addr_of_mut!(unicode_string), + //ObjectName: &mut self.unicode_string.as_struct() as *mut UNICODE_STRING, Attributes: OBJ_CASE_INSENSITIVE, SecurityDescriptor: null_mut(), SecurityQualityOfService: null_mut(), diff --git a/ntreg/src/registry_value.rs b/ntreg/src/registry_value.rs index 0478f3d1f..2249d8577 100644 --- a/ntreg/src/registry_value.rs +++ b/ntreg/src/registry_value.rs @@ -131,16 +131,16 @@ impl fmt::Display for RegistryValueData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let data : String = match self { RegistryValueData::None => "None".to_string(), - RegistryValueData::String(ref data) => format!("String: {}", data), - RegistryValueData::ExpandString(ref data) => format!("ExpandString: {}", data), + RegistryValueData::String(ref data) => format!("String: {data}"), + RegistryValueData::ExpandString(ref data) => format!("ExpandString: {data}"), RegistryValueData::Binary(ref data) => format!("Binary: {}", convert_vec_to_string(&data.to_vec())), - RegistryValueData::DWord(ref data) => format!("Dword: {}", data), - RegistryValueData::Link(ref data) => format!("Link: {}", data), - RegistryValueData::MultiString(ref data) => format!("MultiString: {:?}", data), + RegistryValueData::DWord(ref data) => format!("Dword: {data}"), + RegistryValueData::Link(ref data) => format!("Link: {data}"), + RegistryValueData::MultiString(ref data) => format!("MultiString: {data:?}"), RegistryValueData::ResourceList(ref data) => format!("ResourceList: {:?}", convert_vec_to_string(&data.to_vec())), RegistryValueData::FullResourceDescriptor(ref data) => format!("FullResourceDescriptor: {:?}", convert_vec_to_string(&data.to_vec())), RegistryValueData::ResourceRequirementsList(ref data) => format!("ResourceRequirementsList: {:?}", convert_vec_to_string(&data.to_vec())), - RegistryValueData::QWord(ref data) => format!("Qword: {}", data), + RegistryValueData::QWord(ref data) => format!("Qword: {data}"), }; write!(f, "{}", data) @@ -161,7 +161,6 @@ fn get_data_value(value_information: PKEY_VALUE_FULL_INFORMATION) -> RegistryVal }; match unsafe { (*value_information).Type } { - 0 => RegistryValueData::None, 1 => RegistryValueData::String(String::from_utf16_lossy(unsafe { // remove null terminator std::slice::from_raw_parts(data_ptr as *const u16, (data_length / size_of::()) - 1) @@ -215,7 +214,7 @@ fn get_data_value(value_information: PKEY_VALUE_FULL_INFORMATION) -> RegistryVal fn convert_vec_to_string(data: &Vec) -> String { let mut result = String::new(); for byte in data { - write!(result, "{:02x}", byte).unwrap(); + write!(result, "{byte:02x}").unwrap(); } result } diff --git a/ntstatuserror/src/lib.rs b/ntstatuserror/src/lib.rs index 12a093d1f..1f76e70b1 100644 --- a/ntstatuserror/src/lib.rs +++ b/ntstatuserror/src/lib.rs @@ -14,8 +14,8 @@ pub struct NtStatusError { } impl NtStatusError { - /// Create a new NtStatusError - /// + /// Create a new `NtStatusError` from an NTSTATUS error code and a message. + /// /// # Arguments /// /// * `status` - The NTSTATUS error code @@ -30,6 +30,7 @@ impl NtStatusError { /// assert_eq!(error.status, ntstatuserror::NtStatusErrorKind::ObjectNameNotFound); /// assert_eq!(error.message, "Could not find object".to_string()); /// ``` + #[must_use] pub fn new(status: NTSTATUS, message: &str) -> Self { NtStatusError { status: NtStatusErrorKind::from(status), @@ -62,7 +63,7 @@ pub enum NtStatusErrorKind { CannotDelete, /// Unknown error. #[error("Unknown error: {0:#x}")] - Unknown(u32), + Unknown(i32), } impl From for NtStatusErrorKind { @@ -74,7 +75,7 @@ impl From for NtStatusErrorKind { STATUS_ACCESS_DENIED => NtStatusErrorKind::AccessDenied, STATUS_KEY_DELETED => NtStatusErrorKind::KeyDeleted, STATUS_CANNOT_DELETE => NtStatusErrorKind::CannotDelete, - _ => NtStatusErrorKind::Unknown(status as u32), + _ => NtStatusErrorKind::Unknown(status), } } } diff --git a/ntuserinfo/src/lib.rs b/ntuserinfo/src/lib.rs index 1865a51cd..4122a51f0 100644 --- a/ntuserinfo/src/lib.rs +++ b/ntuserinfo/src/lib.rs @@ -5,7 +5,7 @@ use ntapi::ntpsapi::NtCurrentProcess; use ntapi::ntrtl::{RtlConvertSidToUnicodeString}; use ntapi::ntseapi::{self}; use ntapi::winapi::ctypes::c_void; -use ntapi::winapi::shared::ntdef::{HANDLE, NT_SUCCESS, NTSTATUS, UNICODE_STRING}; +use ntapi::winapi::shared::ntdef::{HANDLE, NT_SUCCESS, NTSTATUS, UNICODE_STRING, ULONG}; use ntapi::winapi::shared::ntstatus::{STATUS_BUFFER_TOO_SMALL}; use ntapi::winapi::um::winnt::{SID, TOKEN_QUERY, TOKEN_USER, TOKEN_QUERY_SOURCE, TokenUser}; use std::ptr::null_mut; @@ -21,8 +21,8 @@ pub struct NtCurrentUserInfo { } impl NtCurrentUserInfo { - /// Create a new NtCurrentUserInfo. - /// + /// Create a new `NtCurrentUserInfo`. + /// /// # Example /// /// ``` @@ -32,6 +32,15 @@ impl NtCurrentUserInfo { /// let user_info = user_info.unwrap(); /// assert!(user_info.sid.len() > 0); /// ``` + /// + /// # Errors + /// + /// Will return an error if the process token cannot be queried. + /// + /// # Panics + /// + /// Will panic if the current user cannot converted from UTF-16. + /// pub fn new() -> Result { let mut token: HANDLE = null_mut(); let mut status: NTSTATUS = unsafe { @@ -48,12 +57,19 @@ impl NtCurrentUserInfo { let mut token_information: Vec = vec![0; 0]; let mut result_length: u32 = 0; + let Ok(token_length) = ULONG::try_from(token_information.len()) else { + return Err(NtStatusError::new( + STATUS_BUFFER_TOO_SMALL, + "Failed to query token information for size" + )); + }; + status = unsafe { ntseapi::NtQueryInformationToken( token, TokenUser, - token_information.as_mut_ptr() as *mut c_void, - token_information.len() as u32, + token_information.as_mut_ptr().cast::(), + token_length, &mut result_length ) }; @@ -63,12 +79,18 @@ impl NtCurrentUserInfo { } token_information.resize(result_length as usize, 0); + let Ok(token_length) = ULONG::try_from(token_information.len()) else { + return Err(NtStatusError::new( + STATUS_BUFFER_TOO_SMALL, + "Failed to query token information for size" + )); + }; status = unsafe { ntseapi::NtQueryInformationToken( token, TokenUser, - token_information.as_mut_ptr() as *mut c_void, - token_information.len() as u32, + token_information.as_mut_ptr().cast::(), + token_length, &mut result_length ) }; @@ -77,7 +99,7 @@ impl NtCurrentUserInfo { return Err(NtStatusError::new(status, "Failed to query token information")); } - let token_user: *const TOKEN_USER = token_information.as_ptr() as *const TOKEN_USER; + let token_user = unsafe { token_information.as_ptr().cast::().read_unaligned() }; let mut sid_string_buffer: Vec = vec![0; MAX_SID_LENGTH as usize]; let mut sid_string: UNICODE_STRING = UNICODE_STRING { Length: 0, @@ -88,7 +110,7 @@ impl NtCurrentUserInfo { status = unsafe { RtlConvertSidToUnicodeString( &mut sid_string, - (*token_user).User.Sid as *const SID as *mut c_void, + token_user.User.Sid as *const SID as *mut c_void, 0 ) }; diff --git a/osinfo/src/config.rs b/osinfo/src/config.rs index 72baa7fbd..019000312 100644 --- a/osinfo/src/config.rs +++ b/osinfo/src/config.rs @@ -2,6 +2,7 @@ // Licensed under the MIT License. use serde::Serialize; +use std::string::ToString; #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(deny_unknown_fields)] @@ -41,9 +42,9 @@ const ID: &str = "https://developer.microsoft.com/json-schemas/dsc/os_info/20230 impl OsInfo { pub fn new() -> Self { let os_info = os_info::get(); - let edition = os_info.edition().map(|edition| edition.to_string()); - let codename = os_info.codename().map(|codename| codename.to_string()); - let architecture = os_info.architecture().map(|architecture| architecture.to_string()); + let edition = os_info.edition().map(ToString::to_string); + let codename = os_info.codename().map(ToString::to_string); + let architecture = os_info.architecture().map(ToString::to_string); let family = match os_info.os_type() { os_info::Type::Macos => Family::MacOS, os_info::Type::Windows => Family::Windows, diff --git a/osinfo/src/main.rs b/osinfo/src/main.rs index f2fc8ca04..6375171d4 100644 --- a/osinfo/src/main.rs +++ b/osinfo/src/main.rs @@ -5,5 +5,5 @@ mod config; fn main() { let json = serde_json::to_string(&config::OsInfo::new()).unwrap(); - println!("{}", json); + println!("{json}"); } diff --git a/pal/build.rs b/pal/build.rs index cd90e28c4..dfc619928 100644 --- a/pal/build.rs +++ b/pal/build.rs @@ -12,28 +12,25 @@ fn main() { // Make import libs for API sets that are not in the SDK. println!("cargo:rustc-link-search={}", env::var("OUT_DIR").unwrap()); - for lib in [ - "ext-ms-win-cng-rng-l1-1-0", - ] { - make_import_lib(lib); - println!("cargo:rustc-link-lib={}", lib); - } + let lib = "ext-ms-win-cng-rng-l1-1-0"; + make_import_lib(lib); + println!("cargo:rustc-link-lib={lib}"); } // Gets the path to the tool for building '.lib' file from the environment variable, if it's set. fn get_tool_var(name: &str) -> Option { let target = env::var("TARGET").unwrap().replace('-', "_"); - let var = format!("{}_{}", name, target); - println!("cargo:rerun-if-env-changed={}", var); + let var = format!("{name}_{target}"); + println!("cargo:rerun-if-env-changed={var}"); env::var(var) .or_else(|_| { - println!("cargo:rerun-if-env-changed={}", name); + println!("cargo:rerun-if-env-changed={name}"); env::var(name) }).ok() } fn make_import_lib(name: &str) { - println!("cargo:rerun-if-changed={}.def", name); + println!("cargo:rerun-if-changed={name}.def"); if let Some(dlltool) = get_tool_var("DLLTOOL") { let mut dlltool = Command::new(dlltool); @@ -42,18 +39,16 @@ fn make_import_lib(name: &str) { "x86" => "i386", "x86_64" => "i386:x86-64", "aarch64" => "arm64", - a => panic!("unsupported architecture {}", a), + a => panic!("unsupported architecture {a}"), }; - dlltool.args(["-d", &format!("{}.def", name)]); + dlltool.args(["-d", &format!("{name}.def")]); dlltool.args(["-m", arch]); dlltool.args([ "-l", &format!("{}/{}.lib", env::var("OUT_DIR").unwrap(), name), ]); - if !dlltool.spawn().unwrap().wait().unwrap().success() { - panic!("dlltool failed"); - } + 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") @@ -63,18 +58,16 @@ fn make_import_lib(name: &str) { "x86" => "X86", "x86_64" => "X64", "aarch64" => "ARM64", - a => panic!("unsupported architecture {}", a), + a => panic!("unsupported architecture {a}"), }; - lib.arg(format!("/machine:{}", arch)); - lib.arg(format!("/def:{}.def", name)); + lib.arg(format!("/machine:{arch}")); + lib.arg(format!("/def:{name}.def")); lib.arg(format!( "/out:{}/{}.lib", env::var("OUT_DIR").unwrap(), name )); - if !lib.spawn().unwrap().wait().unwrap().success() { - panic!("lib.exe failed"); - } + assert!(lib.spawn().unwrap().wait().unwrap().success(), "lib.exe failed"); } } diff --git a/pal/src/windows.rs b/pal/src/windows.rs index 2948db8a0..4483d7c8a 100644 --- a/pal/src/windows.rs +++ b/pal/src/windows.rs @@ -9,10 +9,12 @@ extern "C" { } /// # Safety +/// +/// This function is unsafe because it dereferences a raw pointer. +/// +/// # Panics /// -/// TODO +/// Will panic if the api returns 0 pub unsafe fn getrandom(data: *mut u8, len: usize) { - if ProcessPrng(data, len) == 0 { - panic!("ProcessPrng failed"); - } + assert!((ProcessPrng(data, len) != 0), "ProcessPrng failed"); } diff --git a/y2j/src/main.rs b/y2j/src/main.rs index 52b9ba906..7b0c840c8 100644 --- a/y2j/src/main.rs +++ b/y2j/src/main.rs @@ -21,39 +21,38 @@ fn main() { match String::from_utf8(buffer) { Ok(input) => input, Err(e) => { - eprintln!("Invalid UTF-8 sequence: {}", e); + eprintln!("Invalid UTF-8 sequence: {e}"); exit(EXIT_INVALID_INPUT); } } }; let mut is_json = true; - let input: serde_json::Value = match serde_json::from_str(&input) { - Ok(json) => json, - Err(_) => { - is_json = false; - match serde_yaml::from_str(&input) { - Ok(yaml) => yaml, - Err(err) => { - eprintln!("Error: Input is not valid JSON or YAML: {}", err); - exit(EXIT_INVALID_INPUT); - } + 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, + Err(err) => { + eprintln!("Error: Input is not valid JSON or YAML: {err}"); + exit(EXIT_INVALID_INPUT); } } }; - let output = match is_json { - true => serde_yaml::to_string(&input).unwrap(), - false => serde_json::to_string_pretty(&input).unwrap(), + let output = if is_json { + serde_yaml::to_string(&input).unwrap() + } else { + serde_json::to_string_pretty(&input).unwrap() }; // if stdout is not redirected, print with syntax highlighting if atty::is(Stream::Stdout) { let ps = SyntaxSet::load_defaults_newlines(); let ts = ThemeSet::load_defaults(); - let syntax = match is_json { - true => ps.find_syntax_by_extension("json").unwrap(), - false => ps.find_syntax_by_extension("yaml").unwrap(), + let syntax = if is_json { + ps.find_syntax_by_extension("json").unwrap() + } else { + ps.find_syntax_by_extension("yaml").unwrap() }; let mut h = HighlightLines::new(syntax, &ts.themes["base16-ocean.dark"]); @@ -61,10 +60,10 @@ fn main() { for line in LinesWithEndings::from(output.as_str()) { let ranges: Vec<(Style, &str)> = h.highlight_line(line, &ps).unwrap(); let escaped = as_24_bit_terminal_escaped(&ranges[..], false); - print!("{}", escaped); + print!("{escaped}"); } } else { - println!("{}", output); + println!("{output}"); } exit(EXIT_SUCCESS);