diff --git a/.vscode/schemas/definitions.json b/.vscode/schemas/definitions.json index e7ce77e2f..0838b90b5 100644 --- a/.vscode/schemas/definitions.json +++ b/.vscode/schemas/definitions.json @@ -23,6 +23,7 @@ "Kind": { "$ref": "#/$defs/ProjectKind" }, "SupportedPlatformOS": { "$ref": "#/$defs/ProjectSupportedPlatformOS" }, "IsRust": { "$ref": "#/$defs/ProjectIsRust" }, + "RustPackageName": { "$ref": "#/$defs/ProjectRustPackageName" }, "TestOnly": { "$ref": "#/$defs/ProjectTestOnly" }, "SkipTest": { "$ref": "#/$defs/ProjectSkipTest" }, "ClippyUnclean": { "$ref": "#/$defs/ProjectClippyUnclean" }, @@ -70,6 +71,7 @@ "Kind": { "$ref": "#/$defs/ProjectKind" }, "SupportedPlatformOS": { "$ref": "#/$defs/ProjectSupportedPlatformOS" }, "IsRust": { "$ref": "#/$defs/ProjectIsRust" }, + "RustPackageName": { "$ref": "#/$defs/ProjectRustPackageName" }, "TestOnly": { "$ref": "#/$defs/ProjectTestOnly" }, "SkipTest": { "$ref": "#/$defs/ProjectSkipTest" }, "ClippyUnclean": { "$ref": "#/$defs/ProjectClippyUnclean" }, @@ -190,6 +192,11 @@ "title": "Is Rust Crate", "markdownDescription": "Indicates whether the project is a Rust crate." }, + "ProjectRustPackageName": { + "type": "string", + "title": "Rust package name", + "markdownDescription": "Defines the name of the rust project for the `-p` option on cargo commands." + }, "ProjectTestOnly": { "type": "boolean", "default": false, diff --git a/Cargo.lock b/Cargo.lock index 739adf4f8..e710a7276 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -723,7 +723,7 @@ dependencies = [ [[package]] name = "dsc" -version = "3.2.0-preview.14" +version = "3.2.0-rc.1" dependencies = [ "clap", "clap_complete", @@ -1852,14 +1852,14 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2988,9 +2988,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", @@ -3032,12 +3032,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3273,9 +3273,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.50.0" +version = "1.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" dependencies = [ "bytes", "libc", @@ -3289,9 +3289,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -3417,9 +3417,9 @@ dependencies = [ [[package]] name = "tonic-prost-build" -version = "0.14.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a16cba4043dc3ff43fcb3f96b4c5c154c64cbd18ca8dce2ab2c6a451d058a2" +checksum = "f3144df636917574672e93d0f56d7edec49f90305749c668df5101751bb8f95a" dependencies = [ "prettyplease", "proc-macro2", @@ -4200,15 +4200,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" diff --git a/Cargo.toml b/Cargo.toml index 7cd0e980f..7e0b7a343 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -223,7 +223,7 @@ tempfile = { version = "3.27" } # dsc, dsc-lib, registry, dsc-lib-registry, sshdconfig thiserror = { version = "2.0" } # dsc, dsc-lib, dsc-bicep-ext -tokio = { version = "1.50" } +tokio = { version = "1.51" } # dsc-bicep-ext tokio-stream = { version = "0.1" } # dsc diff --git a/build.ps1 b/build.ps1 index 0fd55e674..7f79e29f3 100755 --- a/build.ps1 +++ b/build.ps1 @@ -200,6 +200,11 @@ process { Install-Clippy -UseCFS:$UseCFS -Architecture $Architecture @VerboseParam } + if (!$SkipBuild -and !$SkipLinkCheck -and $IsWindows) { + Write-BuildProgress @progressParams -Status "Ensuring Windows C++ build tools are available" + Install-WindowsCPlusPlusBuildTools @VerboseParam + } + if (-not ($SkipBuild -and $Test -and $ExcludeRustTests)) { Write-BuildProgress @progressParams -Status 'Ensuring Protobuf is available' Install-Protobuf @VerboseParam @@ -212,10 +217,6 @@ process { } } - if (!$SkipBuild -and !$SkipLinkCheck -and $IsWindows) { - Write-BuildProgress @progressParams -Status "Ensuring Windows C++ build tools are available" - Install-WindowsCPlusPlusBuildTools @VerboseParam - } #endregion Setup if (!$SkipBuild) { diff --git a/dsc-bicep-ext/src/main.rs b/dsc-bicep-ext/src/main.rs index dd51fa571..ffbe56a5f 100644 --- a/dsc-bicep-ext/src/main.rs +++ b/dsc-bicep-ext/src/main.rs @@ -213,7 +213,7 @@ impl BicepExtension for BicepExtensionService { resource: Some(proto::Resource { r#type: resource_type, api_version: version, - identifiers: identifiers, + identifiers, properties: result.actual_state.to_string(), status: None, }), @@ -270,7 +270,7 @@ impl BicepExtension for BicepExtensionService { resource: Some(proto::Resource { r#type: resource_type, api_version: version, - identifiers: identifiers, + identifiers, properties: "{}".to_string(), status: None, }), @@ -367,9 +367,7 @@ async fn run_server( impl Connected for NamedPipeConnection { type ConnectInfo = (); - fn connect_info(&self) -> Self::ConnectInfo { - () - } + fn connect_info(&self) -> Self::ConnectInfo {} } impl AsyncRead for NamedPipeConnection { diff --git a/dsc/Cargo.toml b/dsc/Cargo.toml index 58de39f86..8160a0a9c 100644 --- a/dsc/Cargo.toml +++ b/dsc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dsc" -version = "3.2.0-preview.14" +version = "3.2.0-rc.1" edition = "2024" [dependencies] diff --git a/dsc/src/resource_command.rs b/dsc/src/resource_command.rs index 735335bb7..2d678434f 100644 --- a/dsc/src/resource_command.rs +++ b/dsc/src/resource_command.rs @@ -34,8 +34,8 @@ pub fn get(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version match resource.get(input) { Ok(result) => { - if let GetResult::Resource(response) = &result { - if format == Some(&GetOutputFormat::PassThrough) { + if let GetResult::Resource(response) = &result + && format == Some(&GetOutputFormat::PassThrough) { let json = match serde_json::to_string(&response.actual_state) { Ok(json) => json, Err(err) => { @@ -46,7 +46,6 @@ pub fn get(dsc: &mut DscManager, resource_type: &FullyQualifiedTypeName, version write_object(&json, Some(&OutputFormat::Json), false); return; } - } // convert to json let json = match serde_json::to_string(&result) { diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index 30b116957..25a725b32 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -322,10 +322,8 @@ pub fn config(subcommand: &ConfigSubCommand, parameters: &Option, mounte configurator.context.dsc_version = Some(env!("CARGO_PKG_VERSION").to_string()); - if let ConfigSubCommand::Set { what_if , .. } = subcommand { - if *what_if { - configurator.context.execution_type = ExecutionKind::WhatIf; - } + if let ConfigSubCommand::Set { what_if , .. } = subcommand && *what_if { + configurator.context.execution_type = ExecutionKind::WhatIf; } let parameters: Option = match if new_parameters.is_some() { @@ -499,7 +497,7 @@ pub fn validate_config(config: &Configuration, progress_format: ProgressFormat) let type_name = &FullyQualifiedTypeName::parse(type_name)?; let require_version = resource_block["requireVersion"] .as_str() - .map(|r| ResourceVersionReq::parse(r)) + .map(ResourceVersionReq::parse) .transpose()?; resource_types.push(DiscoveryFilter::new(type_name, require_version, None)); } @@ -512,7 +510,7 @@ pub fn validate_config(config: &Configuration, progress_format: ProgressFormat) let type_name = &FullyQualifiedTypeName::parse(type_name)?; let require_version = resource_block["requireVersion"] .as_str() - .map(|r| ResourceVersionReq::parse(r)) + .map(ResourceVersionReq::parse) .transpose()?; trace!("{} '{}'", t!("subcommand.validatingResource"), resource_block["name"].as_str().unwrap_or_default()); diff --git a/dsc/src/util.rs b/dsc/src/util.rs index d691e8935..1b3c3d81a 100644 --- a/dsc/src/util.rs +++ b/dsc/src/util.rs @@ -350,18 +350,16 @@ pub fn enable_tracing(trace_level_arg: Option<&TraceLevel>, trace_format_arg: Op } // override with DSC_TRACE_LEVEL env var if permitted - if tracing_setting.allow_override { - if let Ok(level) = env::var(DSC_TRACE_LEVEL) { - tracing_setting.level = match level.to_ascii_uppercase().as_str() { - "ERROR" => TraceLevel::Error, - "WARN" => TraceLevel::Warn, - "INFO" => TraceLevel::Info, - "DEBUG" => TraceLevel::Debug, - "TRACE" => TraceLevel::Trace, - _ => { - warn!("{}: '{level}'", t!("util.invalidTraceLevel")); - TraceLevel::Warn - } + if tracing_setting.allow_override && let Ok(level) = env::var(DSC_TRACE_LEVEL) { + tracing_setting.level = match level.to_ascii_uppercase().as_str() { + "ERROR" => TraceLevel::Error, + "WARN" => TraceLevel::Warn, + "INFO" => TraceLevel::Info, + "DEBUG" => TraceLevel::Debug, + "TRACE" => TraceLevel::Trace, + _ => { + warn!("{}: '{level}'", t!("util.invalidTraceLevel")); + TraceLevel::Warn } } } diff --git a/helpers.build.psm1 b/helpers.build.psm1 index 430a2373b..571a6cc8b 100644 --- a/helpers.build.psm1 +++ b/helpers.build.psm1 @@ -28,6 +28,7 @@ class DscProjectDefinition { [string] $RelativePath [string] $Kind [bool] $IsRust + [string] $RustPackageName [bool] $ClippyUnclean [bool] $ClippyPedanticUnclean [bool] $SkipTestProject @@ -38,6 +39,17 @@ class DscProjectDefinition { [DscProjectCopyFiles] $CopyFiles [DscProjectSkipTest] $SkipTest + [string[]] ToPackageFlags() { + if (-not $this.IsRust) { + return $null + } + + return @( + '-p' + $this.RustPackageName ?? $this.Name + ) + } + [string] ToJson([bool]$forBuild) { $json = $this.ToData($forBuild) | ConvertTo-Json -Depth 5 -EnumsAsStrings return ($json -replace "`r`n", "`n") @@ -68,6 +80,9 @@ class DscProjectDefinition { } if ($this.IsRust) { $data.IsRust = $true + if (-not [string]::IsNullOrEmpty($this.RustPackageName)) { + $data.RustPackageName = $this.RustPackageName + } if ($this.ClippyPedanticUnclean) { $data.ClippyPedanticUnclean = $true } @@ -1416,6 +1431,113 @@ function Export-GrammarBinding { } } } + +function Test-Clippy { + [CmdletBinding()] + param( + [DscProjectDefinition[]]$Project, + [ValidateSet('current','aarch64-pc-windows-msvc','x86_64-pc-windows-msvc','aarch64-apple-darwin','x86_64-apple-darwin','aarch64-unknown-linux-gnu','aarch64-unknown-linux-musl','x86_64-unknown-linux-gnu','x86_64-unknown-linux-musl')] + $Architecture = 'current', + [switch]$Release, + [switch]$NoModifyWorkspace + ) + + begin { + $flags = @($Release ? '-r' : $null) + if ($Architecture -ne 'current') { + $flags += '--target' + $flags += $Architecture + $memberGroup = if ($Architecture -match 'linux') { + 'Linux' + } elseif ($Architecture -match 'darwin') { + 'macOS' + } elseif ($Architecture -match 'windows') { + 'Windows' + } else { + throw "Unsupported architecture '$Architecture'" + } + } else { + $memberGroup = if ($IsLinux) { + 'Linux' + } elseif ($IsMacOS) { + 'macOS' + } elseif ($IsWindows) { + 'Windows' + } + } + $workspaceParams = @{ + MemberGroup = $memberGroup + } + if ($VerbosePreference -eq 'Continue') { + $workspaceParams.Verbose = $true + } + if (-not $NoModifyWorkspace) { + Set-DefaultWorkspaceMemberGroup @workspaceParams + } + } + + process { + $clippyFlags = @( + '--%' + '--' + '-Dwarnings' # Treat warnings as errors + '--no-deps' # Only lint DSC projects, not dependencies + ) + # Collect projects to lint into two groups: + # - Normal projects get linting without pedantic checks + # - Pedantic projects get stricter linting + [DscProjectDefinition[]]$normalProjects = @() + [DscProjectDefinition[]]$pedanticProjects = @() + foreach ($p in $Project) { + if ( + -not $p.IsRust -or + $p.ClippyUnclean -or + (Test-ShouldSkipProject -Project $p -Architecture $Architecture) + ) { + continue + } + + if ($p.ClippyPedanticUnclean) { + $pedanticProjects += $p + } else { + $normalProjects += $p + } + } + $normalProjectsExitCode = 0 + $pedanticProjectsExitCode = 0 + if ($normalProjects.count -gt 0) { + [string[]]$projectFlags = $normalProjects.ToPackageFlags() + Write-Verbose "Linting rust projects with clippy: $($normalProjects.Name | ConvertTo-Json)" + Write-Verbose "Invoking clippy: cargo clippy $flags $projectFlags $clippyFlags" + cargo clippy @flags @projectFlags @clippyFlags + + if ($null -ne $LASTEXITCODE -and $LASTEXITCODE -ne 0) { + $normalProjectsExitCode = $LASTEXITCODE + } + } + if ($pedanticProjects.count -gt 0) { + [string[]]$projectFlags = $pedanticProjects.ToPackageFlags() + $clippyFlags += '-Dclippy::pedantic' + Write-Verbose "Linting rust projects with clippy (pedantic): $($pedanticProjects.Name | ConvertTo-Json)" + Write-Verbose "Invoking clippy: cargo clippy $flags $projectFlags $clippyFlags" + cargo clippy @flags @projectFlags @clippyFlags + + if ($null -ne $LASTEXITCODE -and $LASTEXITCODE -ne 0) { + $pedanticProjectsExitCode = $LASTEXITCODE + } + } + if ($normalProjectsExitCode -or $pedanticProjectsExitCode) { + throw "Clippy failed for at least one project" + } + } + + clean { + if (-not $NoModifyWorkspace) { + Reset-DefaultWorkspaceMemberGroup + } + } +} + function Build-RustProject { [CmdletBinding()] param( @@ -1475,18 +1597,16 @@ function Build-RustProject { cargo clean } - if ($Clippy -and !$Project.ClippyUnclean) { - $clippyFlags = @() - if (!$Project.ClippyPedanticUnclean) { - cargo clippy @clippyFlags --% -- -Dclippy::pedantic --no-deps -Dwarnings - } else { - cargo clippy @clippyFlags --% -- -Dwarnings --no-deps - } - - if ($null -ne $LASTEXITCODE -and $LASTEXITCODE -ne 0) { - throw "Last exit code is $LASTEXITCODE, clippy failed for at least one project" + if ($Clippy) { + $clippyParams = @{ + Project = $Project + Architecture = $Architecture + Release = $Release + NoModifyWorkspace = $true } + Test-Clippy @clippyParams } + $members = Get-DefaultWorkspaceMemberGroup Write-Verbose -Verbose "Building rust projects: [$members]" Write-Verbose "Invoking cargo:`n`tcargo build $flags" diff --git a/lib/dsc-lib-jsonschema/build.rs b/lib/dsc-lib-jsonschema/build.rs index 59ac916f6..812a42907 100644 --- a/lib/dsc-lib-jsonschema/build.rs +++ b/lib/dsc-lib-jsonschema/build.rs @@ -519,9 +519,7 @@ fn main() { let version_data = read_to_string(data_path) .expect("Failed to read .versions.json file"); let version_info: VersionInfo = serde_json::from_str(&version_data) - .expect(format!( - "Failed to parse version data from .versions.json:\n---FILE TEXT START---\n{version_data}\n---FILE TEXT END---\n" - ).as_str()); + .unwrap_or_else(|_| panic!("Failed to parse version data from .versions.json:\n---FILE TEXT START---\n{version_data}\n---FILE TEXT END---\n")); let contents = format_file_content(&version_info); fs::write( diff --git a/lib/dsc-lib-jsonschema/src/dsc_repo/dsc_repo_schema.rs b/lib/dsc-lib-jsonschema/src/dsc_repo/dsc_repo_schema.rs index a5ba4f4b9..eb18a111c 100644 --- a/lib/dsc-lib-jsonschema/src/dsc_repo/dsc_repo_schema.rs +++ b/lib/dsc-lib-jsonschema/src/dsc_repo/dsc_repo_schema.rs @@ -79,6 +79,7 @@ pub trait DscRepoSchema : JsonSchema { /// function overrides this default when exporting schemas for various versions and forms. /// /// [`generate_exportable_schema()`]: DscRepoSchema::generate_exportable_schema + #[must_use] fn default_export_schema_id_uri() -> String { Self::get_schema_id_uri( RecognizedSchemaVersion::VNext, @@ -91,12 +92,14 @@ pub trait DscRepoSchema : JsonSchema { /// /// Use this to define the `$schema` keyword when deriving or manually implementing the /// [`schemars::JsonSchema`] trait. + #[must_use] fn default_export_meta_schema_uri() -> String { "https://json-schema.org/draft/2020-12/schema".to_string() } /// Generates the JSON schema for a given version and form. This function is /// useful for exporting the JSON Schema to disk. + #[must_use] fn generate_exportable_schema( schema_version: RecognizedSchemaVersion, schema_form: SchemaForm @@ -105,6 +108,7 @@ pub trait DscRepoSchema : JsonSchema { } /// Generates the JSON Schema for a given version, form, and URI prefix. + #[must_use] fn generate_schema( schema_version: RecognizedSchemaVersion, schema_form: SchemaForm, @@ -167,6 +171,7 @@ pub trait DscRepoSchema : JsonSchema { } /// Returns the path for a schema relative to the `schemas` folder. + #[must_use] fn get_schema_relative_path( schema_version: RecognizedSchemaVersion, schema_form: SchemaForm @@ -176,12 +181,12 @@ pub trait DscRepoSchema : JsonSchema { path.push(schema_version.to_string()); let form_folder = schema_form.to_folder_prefix(); - let form_folder = form_folder.trim_end_matches("/"); + let form_folder = form_folder.trim_end_matches('/'); if !form_folder.is_empty() { path.push(form_folder); } - for segment in Self::SCHEMA_FOLDER_PATH.split("/") { + for segment in Self::SCHEMA_FOLDER_PATH.split('/') { path.push(segment); } @@ -218,7 +223,7 @@ pub trait DscRepoSchema : JsonSchema { fn set_enhanced_schema_id_uri(schema: &mut Schema, schema_version: RecognizedSchemaVersion) { if let Some(id_uri) = Self::get_enhanced_schema_id_uri(schema_version) { schema.set_id(&id_uri); - }; + } } /// Returns the URI for the canonical (non-bundled) form of the schema with the default @@ -262,7 +267,7 @@ pub trait DscRepoSchema : JsonSchema { fn set_bundled_schema_id_uri(schema: &mut Schema, schema_version: RecognizedSchemaVersion) { if let Some(id_uri) = Self::get_bundled_schema_id_uri(schema_version) { schema.set_id(&id_uri); - }; + } } /// Returns the list of recognized schema URIs for the struct or enum. @@ -328,6 +333,7 @@ pub trait DscRepoSchema : JsonSchema { /// /// - If the value is `true`, all schema forms are valid for the type. /// - If the value is `false`, only [`SchemaForm::Canonical`] is valid for the type. + #[must_use] fn get_valid_schema_forms() -> Vec { if Self::SCHEMA_SHOULD_BUNDLE { vec![SchemaForm::VSCode, SchemaForm::Bundled, SchemaForm::Canonical] diff --git a/lib/dsc-lib-jsonschema/src/dsc_repo/mod.rs b/lib/dsc-lib-jsonschema/src/dsc_repo/mod.rs index 303d258a1..eb12d1e30 100644 --- a/lib/dsc-lib-jsonschema/src/dsc_repo/mod.rs +++ b/lib/dsc-lib-jsonschema/src/dsc_repo/mod.rs @@ -193,18 +193,16 @@ pub(crate) fn get_default_schema_form(should_bundle: bool) -> SchemaForm { /// Retrieves the version segment from the `$id` keyword of a DSC repo schema. pub(crate) fn get_schema_id_version(schema: &Schema) -> Option { - let Some(root_id) = schema.get_id() else { - return None; - }; + let root_id = schema.get_id()?; // Remove the URI prefix and leading slash to get the URI relative to the `schemas` folder let schema_folder_relative_id = root_id .trim_start_matches(&SchemaUriPrefix::AkaDotMs.to_string()) .trim_start_matches(&SchemaUriPrefix::Github.to_string()) - .trim_start_matches("/"); + .trim_start_matches('/'); // The version segment is the first segment of the relative URI schema_folder_relative_id - .split("/") + .split('/') .collect::>() .first() .map(std::string::ToString::to_string) diff --git a/lib/dsc-lib-jsonschema/src/schema_utility_extensions.rs b/lib/dsc-lib-jsonschema/src/schema_utility_extensions.rs index 431e4700d..57fd02040 100644 --- a/lib/dsc-lib-jsonschema/src/schema_utility_extensions.rs +++ b/lib/dsc-lib-jsonschema/src/schema_utility_extensions.rs @@ -1153,8 +1153,8 @@ pub trait SchemaUtilityExtensions { /// use dsc_lib_jsonschema::schema_utility_extensions::SchemaUtilityExtensions; /// use schemars::json_schema; /// - /// let foo_id = &"https://contoso.com/schemas/properties/foo.json".to_string(); - /// let bar_id = &"https://contoso.com/schemas/properties/bar.json".to_string(); + /// let foo_id = "https://contoso.com/schemas/properties/foo.json"; + /// let bar_id = "https://contoso.com/schemas/properties/bar.json"; /// let schema = json_schema!({ /// "$id": "https://contoso.com/schemas/example.json", /// "properties": { @@ -1177,7 +1177,7 @@ pub trait SchemaUtilityExtensions { /// None /// ); /// ``` - fn get_bundled_schema_resource_defs_key(&self, id: &String) -> Option<&String>; + fn get_bundled_schema_resource_defs_key(&self, id: &str) -> Option<&String>; /// Inserts a subschema entry into the `$defs` keyword for the [`Schema`]. If an entry for the /// given key already exists, this function returns the old value as a map. /// @@ -1925,21 +1925,18 @@ impl SchemaUtilityExtensions for Schema { None } - fn get_bundled_schema_resource_defs_key(&self, id: &String) -> Option<&String> { - let Some(defs) = self.get_defs() else { - return None; - }; + fn get_bundled_schema_resource_defs_key(&self, id: &str) -> Option<&String> { + let defs = self.get_defs()?; for (def_key, def_value) in defs { let Ok(def_subschema) = Schema::try_from(def_value.clone()) else { continue; }; - if let Some(def_id) = def_subschema.get_id() { - if def_id == id.as_str() { + if let Some(def_id) = def_subschema.get_id() + && def_id == id { return Some(def_key); } - } } None @@ -1973,7 +1970,7 @@ impl SchemaUtilityExtensions for Schema { for bundled_id in lookup_schema.get_bundled_schema_resource_ids(true) { let Some(bundled_key) = lookup_schema.get_bundled_schema_resource_defs_key( - &bundled_id.to_string() + bundled_id ) else { continue; }; @@ -2148,7 +2145,7 @@ impl SchemaUtilityExtensions for Schema { references } fn get_references_to_bundled_schema_resource(&self, resource_id: &str) -> HashSet<&str> { - let Some(def_key) = self.get_bundled_schema_resource_defs_key(&resource_id.to_string()) else { + let Some(def_key) = self.get_bundled_schema_resource_defs_key(resource_id) else { return HashSet::new(); }; @@ -2167,7 +2164,7 @@ impl SchemaUtilityExtensions for Schema { self.get_references() .into_iter() - .filter(|reference| matching_references.contains(&&reference.to_string())) + .filter(|reference| matching_references.contains(&reference.to_string())) .collect() } fn replace_references(&mut self, find_value: &str, new_value: &str) { @@ -2244,7 +2241,7 @@ impl SchemaUtilityExtensions for Schema { let lookup_schema = self.clone(); let bundled_ids = lookup_schema.get_bundled_schema_resource_ids(true); for bundled_id in bundled_ids { - let Some(defs_key) = lookup_schema.get_bundled_schema_resource_defs_key(&bundled_id.to_string()) else { + let Some(defs_key) = lookup_schema.get_bundled_schema_resource_defs_key(bundled_id) else { continue; }; let reference_lookup = format!("#/$defs/{defs_key}"); diff --git a/lib/dsc-lib-jsonschema/src/vscode/transforms/vscodify_refs_and_defs.rs b/lib/dsc-lib-jsonschema/src/vscode/transforms/vscodify_refs_and_defs.rs index b89144ac8..b09d24927 100644 --- a/lib/dsc-lib-jsonschema/src/vscode/transforms/vscodify_refs_and_defs.rs +++ b/lib/dsc-lib-jsonschema/src/vscode/transforms/vscodify_refs_and_defs.rs @@ -94,7 +94,7 @@ pub fn vscodify_refs_and_defs(schema: &mut Schema) { let lookup_schema = schema.clone(); for bundled_resource_id in lookup_schema.get_bundled_schema_resource_ids(true) { let Some(def_key) = lookup_schema - .get_bundled_schema_resource_defs_key(&bundled_resource_id.to_string()) else { + .get_bundled_schema_resource_defs_key(bundled_resource_id) else { continue; }; diff --git a/lib/dsc-lib/src/configure/depends_on.rs b/lib/dsc-lib/src/configure/depends_on.rs index bed2d43ec..90c1dc56f 100644 --- a/lib/dsc-lib/src/configure/depends_on.rs +++ b/lib/dsc-lib/src/configure/depends_on.rs @@ -37,8 +37,8 @@ pub fn get_resource_invocation_order(config: &Configuration, parser: &mut Statem let mut dependency_already_in_order = true; // Skip dependency validation for copy loop resources here - it will be handled in unroll_and_push // where the copy context is properly set up for copyIndex() expressions in dependsOn - if resource.copy.is_none() { - if let Some(depends_on) = resource.depends_on.clone() { + if resource.copy.is_none() + && let Some(depends_on) = resource.depends_on.clone() { for dependency in depends_on { let statement = parser.parse_and_execute(&dependency, context)?; let Some(string_result) = statement.as_str() else { @@ -62,7 +62,6 @@ pub fn get_resource_invocation_order(config: &Configuration, parser: &mut Statem dependency_already_in_order = false; } } - } if order.iter().any(|r| r.name == resource.name && r.resource_type == resource.resource_type) { // if dependencies were already in the order, then this might be a circular dependency diff --git a/lib/dsc-lib/src/configure/mod.rs b/lib/dsc-lib/src/configure/mod.rs index 19af3094f..a00a0c884 100644 --- a/lib/dsc-lib/src/configure/mod.rs +++ b/lib/dsc-lib/src/configure/mod.rs @@ -241,11 +241,11 @@ fn add_metadata(dsc_resource: &DscResource, mut properties: Option = Map::new(); - if let Some(resource_metadata) = resource_metadata { - if !resource_metadata.other.is_empty() { + if let Some(resource_metadata) = resource_metadata + && !resource_metadata.other.is_empty() { metadata.extend(resource_metadata.other); } - } + let mut dsc_value = Map::new(); dsc_value.insert("context".to_string(), Value::String("configuration".to_string())); metadata.insert("Microsoft.DSC".to_string(), Value::Object(dsc_value)); @@ -284,11 +284,11 @@ fn add_metadata(dsc_resource: &DscResource, mut properties: Option) -> Option { - if let Some(directives) = resource_directives { - if let Some(require_adapter) = &directives.require_adapter { + if let Some(directives) = resource_directives + && let Some(require_adapter) = &directives.require_adapter { return Some(require_adapter.clone()); } - } + None } @@ -298,19 +298,22 @@ fn check_security_context(metadata: Option<&Metadata>, directive_security_contex } let mut security_context_required: Option<&SecurityContextKind> = None; - if let Some(metadata) = &metadata { - if let Some(microsoft_dsc) = &metadata.microsoft { - if let Some(required_security_context) = µsoft_dsc.security_context { - warn!("{}", t!("configure.mod.securityContextInMetadataDeprecated")); - security_context_required = Some(required_security_context); - } + if let Some(metadata) = &metadata + && let Some(microsoft_dsc) = &metadata.microsoft + && let Some(required_security_context) = µsoft_dsc.security_context { + warn!("{}", t!("configure.mod.securityContextInMetadataDeprecated")); + security_context_required = Some(required_security_context); } - } - if let Some(directive_security_context) = &directive_security_context { - if security_context_required.is_some() && security_context_required != Some(directive_security_context) { - return Err(DscError::SecurityContext(t!("configure.mod.conflictingSecurityContext", metadata = security_context_required.unwrap(), directive = directive_security_context).to_string())); - } + if let Some(directive_security_context) = directive_security_context { + if let Some(security_context_required) = security_context_required + && security_context_required != directive_security_context { + return Err(DscError::SecurityContext(t!( + "configure.mod.conflictingSecurityContext", + metadata = security_context_required, + directive = directive_security_context + ).to_string())); + } security_context_required = Some(directive_security_context); } @@ -395,7 +398,7 @@ fn get_metadata_from_result(mut context: Option<&mut Context>, properties: &mut // set the current process env vars to the new values for (key, value) in env_vars { unsafe { - std::env::set_var(&key.to_string(), &value); + std::env::set_var(&key, &value); } } } @@ -485,18 +488,16 @@ impl Configurator { fn get_properties(&mut self, resource: &Resource, resource_kind: &Kind) -> Result>, DscError> { // Restore copy loop context from resource metadata under Microsoft.DSC/copyLoops if present - if let Some(metadata) = &resource.metadata { - if let Some(microsoft) = &metadata.microsoft { - if let Some(copy_loops) = µsoft.copy_loops { - for (loop_name, value) in copy_loops { - if let Some(index) = value.as_i64() { - self.context.copy.insert(loop_name.to_string(), index); - self.context.copy_current_loop_name.clone_from(loop_name); - } + if let Some(metadata) = &resource.metadata + && let Some(microsoft) = &metadata.microsoft + && let Some(copy_loops) = µsoft.copy_loops { + for (loop_name, value) in copy_loops { + if let Some(index) = value.as_i64() { + self.context.copy.insert(loop_name.to_string(), index); + self.context.copy_current_loop_name.clone_from(loop_name); } } } - } let result = match resource_kind { Kind::Group => { @@ -544,7 +545,7 @@ impl Configurator { let adapter = get_require_adapter_from_directive(&resource.directives); find_resource_or_error!(dsc_resource, discovery, resource, adapter); let properties = self.get_properties(&resource, &dsc_resource.kind)?; - let filter = add_metadata(&dsc_resource, properties, resource.metadata.clone())?; + let filter = add_metadata(dsc_resource, properties, resource.metadata.clone())?; let start_datetime = chrono::Local::now(); let mut get_result = match dsc_resource.get(&filter) { Ok(result) => result, @@ -650,7 +651,7 @@ impl Configurator { } }; - let desired = add_metadata(&dsc_resource, properties, resource.metadata.clone())?; + let desired = add_metadata(dsc_resource, properties, resource.metadata.clone())?; trace!("{}", t!("configure.mod.desired", state = desired)); let start_datetime; @@ -742,16 +743,13 @@ impl Configurator { // Process metadata - only add whatIf if we have ResourceWhatIf variant let mut execution_information = ExecutionInformation::new_with_duration(&start_datetime, &end_datetime); let mut other_metadata = Map::new(); - if self.context.execution_type == ExecutionKind::WhatIf { - if let Some(delete_res) = delete_what_if_metadata { - if let Some(metadata) = delete_res.metadata { - if let Some(what_if) = metadata.what_if { - execution_information.what_if = Some(what_if.clone()); - other_metadata.insert("whatIf".to_string(), what_if); - } - } + if self.context.execution_type == ExecutionKind::WhatIf + && let Some(delete_res) = delete_what_if_metadata + && let Some(metadata) = delete_res.metadata + && let Some(what_if) = metadata.what_if { + execution_information.what_if = Some(what_if.clone()); + other_metadata.insert("whatIf".to_string(), what_if); } - } let mut metadata = Metadata { microsoft: Some( @@ -827,7 +825,7 @@ impl Configurator { find_resource_or_error!(dsc_resource, discovery, resource, adapter); let properties = self.get_properties(&resource, &dsc_resource.kind)?; debug!("resource_type {}", &resource.resource_type); - let expected = add_metadata(&dsc_resource, properties, resource.metadata.clone())?; + let expected = add_metadata(dsc_resource, properties, resource.metadata.clone())?; trace!("{}", t!("configure.mod.expectedState", state = expected)); let start_datetime = chrono::Local::now(); let mut test_result = match dsc_resource.test(&expected) { @@ -917,9 +915,9 @@ impl Configurator { find_resource_or_error!(dsc_resource, discovery, resource, adapter); let properties = self.get_properties(resource, &dsc_resource.kind)?; debug!("resource_type {}", &resource.resource_type); - let input = add_metadata(&dsc_resource, properties, resource.metadata.clone())?; + let input = add_metadata(dsc_resource, properties, resource.metadata.clone())?; trace!("{}", t!("configure.mod.exportInput", input = input)); - let export_result = match add_resource_export_results_to_configuration(&dsc_resource, &mut conf, input.as_str()) { + let export_result = match add_resource_export_results_to_configuration(dsc_resource, &mut conf, input.as_str()) { Ok(result) => result, Err(e) => { progress.set_failure(get_failure_from_error(&e)); @@ -1064,11 +1062,10 @@ impl Configurator { } self.context.parameters.insert(name.clone(), (value.clone(), constraint.parameter_type.clone())); - if let Some(parameters) = &mut self.config.parameters { - if let Some(parameter) = parameters.get_mut(&name) { + if let Some(parameters) = &mut self.config.parameters + && let Some(parameter) = parameters.get_mut(&name) { parameter.default_value = Some(value); } - } } else { return Err(DscError::Validation(t!("configure.mod.parameterNotDefined", name = name).to_string())); @@ -1205,31 +1202,25 @@ impl Configurator { fn validate_config(&mut self) -> Result<(), DscError> { let config: Configuration = serde_json::from_str(self.json.as_str())?; let config_security_context = if let Some(directives) = &config.directives { - if let Some(security_context) = &directives.security_context { - Some(security_context.clone()) - } else { - None - } + directives.security_context.clone() } else { None }; check_security_context(config.metadata.as_ref(), config_security_context.as_ref())?; - if let Some(directives) = &config.directives { - if let Some(version_req) = &directives.version { + if let Some(directives) = &config.directives + && let Some(version_req) = &directives.version { let dsc_version = SemanticVersion::parse(env!("CARGO_PKG_VERSION"))?; if !version_req.matches(&dsc_version) { return Err(DscError::Validation(t!("configure.mod.versionNotSatisfied", required_version = version_req, current_version = env!("CARGO_PKG_VERSION")).to_string())); } } - } let mut resource_discovery_mode = ResourceDiscoveryMode::PreDeployment; - if let Some(directives) = &config.directives { - if let Some(resource_discovery_directive) = &directives.resource_discovery { + if let Some(directives) = &config.directives + && let Some(resource_discovery_directive) = &directives.resource_discovery { resource_discovery_mode = resource_discovery_directive.clone(); } - } if resource_discovery_mode == ResourceDiscoveryMode::DuringDeployment { debug!("{}", t!("configure.mod.skippingResourceDiscovery")); diff --git a/lib/dsc-lib/src/configure/parameters.rs b/lib/dsc-lib/src/configure/parameters.rs index b9357da13..7bb482d88 100644 --- a/lib/dsc-lib/src/configure/parameters.rs +++ b/lib/dsc-lib/src/configure/parameters.rs @@ -61,11 +61,12 @@ impl Display for SecureObject { /// `true` if the value is a secure value, `false` otherwise. #[must_use] pub fn is_secure_value(value: &Value) -> bool { - if let Some(obj) = value.as_object() { - if obj.len() == 1 && (obj.contains_key("secureString") || obj.contains_key("secureObject")) { + if let Some(obj) = value.as_object() + && obj.len() == 1 + && (obj.contains_key("secureString") || obj.contains_key("secureObject")) { return true; } - } + false } @@ -89,7 +90,7 @@ pub fn import_parameters(parameters: &Value) -> Result, D result }, Err(_) => { - let simple_input = match serde_json::from_value::(parameters.clone()) { + match serde_json::from_value::(parameters.clone()) { Ok(simple_input) => { trace!("{}", t!("configure.parameters.importingParametersFromInput")); simple_input.parameters @@ -97,8 +98,7 @@ pub fn import_parameters(parameters: &Value) -> Result, D Err(e) => { return Err(DscError::Parser(t!("configure.parameters.invalidParamsFormat", error = e).to_string())); } - }; - simple_input + } } }; Ok(parameters) diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index 2efa4925f..181613680 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -45,6 +45,7 @@ pub struct ManifestList { pub extensions: Option>, } +#[allow(clippy::large_enum_variant)] #[derive(Clone, Deserialize, JsonSchema)] #[schemars(transform = idiomaticize_externally_tagged_enum)] pub enum ImportedManifest { @@ -509,7 +510,7 @@ impl ResourceDiscovery for CommandDiscovery { for filter in required_resource_types { if let Some(required_adapter) = filter.require_adapter() { - if !adapters.contains(&required_adapter) { + if !adapters.contains(required_adapter) { return Err(DscError::AdapterNotFound(required_adapter.to_string())); } // otherwise insert at the front of the list @@ -558,12 +559,10 @@ fn filter_resources(found_resources: &mut DiscoveryResourceCache, required_resou debug!("{}", t!("discovery.commandDiscovery.foundResourceWithVersion", resource = resource.type_name, version = resource.version)); break; } - } else { - if matches_adapter_requirement(resource, filter) { - found_resources.entry(filter.resource_type().clone()).or_default().push(resource.clone()); - required_resources.insert(filter.clone(), true); - break; - } + } else if matches_adapter_requirement(resource, filter) { + found_resources.entry(filter.resource_type().clone()).or_default().push(resource.clone()); + required_resources.insert(filter.clone(), true); + break; } if required_resources.values().all(|&v| v) { return; @@ -639,7 +638,7 @@ pub fn load_manifest(path: &Path) -> Result, DscError> { debug!("{}", t!("discovery.commandDiscovery.conditionNotMet", path = path.to_string_lossy(), condition = resource.condition.unwrap_or_default(), resource = resource.type_name)); return Ok(vec![]); } - let resource = load_adapted_resource_manifest(&path, &resource)?; + let resource = load_adapted_resource_manifest(path, &resource)?; return Ok(vec![ImportedManifest::Resource(resource)]); } if DSC_RESOURCE_EXTENSIONS.iter().any(|ext| file_name_lowercase.ends_with(ext)) { @@ -711,7 +710,7 @@ pub fn load_manifest(path: &Path) -> Result, DscError> { debug!("{}", t!("discovery.commandDiscovery.conditionNotMet", path = path.to_string_lossy(), condition = resource.condition.as_ref() : {:?}, resource = resource.type_name)); continue; } - let resource = load_adapted_resource_manifest(&path, resource)?; + let resource = load_adapted_resource_manifest(path, resource)?; resources.push(ImportedManifest::Resource(resource)); } } diff --git a/lib/dsc-lib/src/discovery/discovery_trait.rs b/lib/dsc-lib/src/discovery/discovery_trait.rs index ea501bd42..561e49128 100644 --- a/lib/dsc-lib/src/discovery/discovery_trait.rs +++ b/lib/dsc-lib/src/discovery/discovery_trait.rs @@ -56,7 +56,7 @@ impl DiscoveryFilter { /// /// - `type_name` - The [`FullyQualifiedTypeName`] of the extension. /// - `require_version` - An optional [`SemanticVersionReq`] specifying the semantic version - /// requirement for the extension. + /// requirement for the extension. /// /// # Returns /// diff --git a/lib/dsc-lib/src/discovery/mod.rs b/lib/dsc-lib/src/discovery/mod.rs index 1faf04978..6446d9996 100644 --- a/lib/dsc-lib/src/discovery/mod.rs +++ b/lib/dsc-lib/src/discovery/mod.rs @@ -104,10 +104,9 @@ impl Discovery { .collect() } - #[must_use] pub fn find_resource(&mut self, filter: &DiscoveryFilter) -> Result, DscError> { if self.refresh_cache || self.resources.is_empty() { - self.find_resources(&[filter.clone()], ProgressFormat::None)?; + self.find_resources(std::slice::from_ref(filter), ProgressFormat::None)?; } let type_name = filter.resource_type(); diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index d43f20851..8a3b2e646 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -55,18 +55,14 @@ pub fn invoke_get(resource: &DscResource, filter: &str, target_resource: Option< None => resource.type_name.clone(), }; validate_security_context(&get.require_security_context, &resource_type, "get")?; - let path = if let Some(target_resource) = target_resource { - Some(target_resource.path.clone()) - } else { - None - }; + let path = target_resource.map(|target_resource| target_resource.path.clone()); let command_resource_info = CommandResourceInfo { type_name: resource_type.clone(), path, }; let args = process_get_args(get.args.as_ref(), filter, &command_resource_info); if !filter.is_empty() { - verify_json_from_manifest(&resource, filter, target_resource)?; + verify_json_from_manifest(resource, filter, target_resource)?; command_input = get_command_input(get.input.as_ref(), filter)?; } @@ -74,7 +70,7 @@ pub fn invoke_get(resource: &DscResource, filter: &str, target_resource: Option< let (_exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(&resource.directory), command_input.env, manifest.exit_codes.as_ref())?; if resource.kind == Kind::Resource { debug!("{}", t!("dscresources.commandResource.verifyOutputUsing", resource = &resource.type_name, executable = &get.executable)); - verify_json_from_manifest(&resource, &stdout, target_resource)?; + verify_json_from_manifest(resource, &stdout, target_resource)?; } let result: GetResult = if let Ok(group_response) = serde_json::from_str::>(&stdout) { @@ -118,11 +114,7 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, execut Some(r) => r.type_name.clone(), None => resource.type_name.clone(), }; - let path = if let Some(target_resource) = target_resource { - Some(target_resource.path.clone()) - } else { - None - }; + let path = target_resource.map(|target_resource| target_resource.path.clone()); let command_resource_info = CommandResourceInfo { type_name: resource_type.clone(), path, @@ -136,22 +128,24 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, execut ExecutionKind::WhatIf => { operation_type = "whatif".to_string(); // Check if set supports native what-if - let has_native_whatif = manifest.set.as_ref() - .map_or(false, |set| { - let (_, supports_whatif) = process_set_delete_args(set.args.as_ref(), "", &command_resource_info, execution_type); - supports_whatif - }); + let has_native_whatif = manifest.set.as_ref().is_some_and(|set| { + let (_, supports_whatif) = process_set_delete_args( + set.args.as_ref(), + "", + &command_resource_info, + execution_type + ); + supports_whatif + }); if has_native_whatif { &manifest.set + } else if manifest.what_if.is_some() { + warn!("{}", t!("dscresources.commandResource.whatIfWarning", resource = &resource_type)); + &manifest.what_if } else { - if manifest.what_if.is_some() { - warn!("{}", t!("dscresources.commandResource.whatIfWarning", resource = &resource_type)); - &manifest.what_if - } else { - is_synthetic_what_if = true; - &manifest.set - } + is_synthetic_what_if = true; + &manifest.set } } }; @@ -159,7 +153,7 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, execut return Err(DscError::NotImplemented("set".to_string())); }; validate_security_context(&set.require_security_context, &resource_type, "set")?; - verify_json_from_manifest(&resource, desired, target_resource)?; + verify_json_from_manifest(resource, desired, target_resource)?; // if resource doesn't implement a pre-test, we execute test first to see if a set is needed if !skip_test && set.pre_test != Some(true) { @@ -199,16 +193,12 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, execut let Some(get) = &manifest.get else { return Err(DscError::NotImplemented("get".to_string())); }; - let resource_type = match target_resource.clone() { + let resource_type = match target_resource { Some(r) => r.type_name.clone(), None => resource.type_name.clone(), }; validate_security_context(&get.require_security_context, &resource_type, "get")?; - let path = if let Some(target_resource) = target_resource { - Some(target_resource.path.clone()) - } else { - None - }; + let path = target_resource.map(|target_resource| target_resource.path.clone()); let command_resource_info = CommandResourceInfo { type_name: resource_type.clone(), path, @@ -221,7 +211,7 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, execut if resource.kind == Kind::Resource { debug!("{}", t!("dscresources.commandResource.setVerifyGet", resource = &resource.type_name, executable = &get.executable)); - verify_json_from_manifest(&resource, &stdout, target_resource)?; + verify_json_from_manifest(resource, &stdout, target_resource)?; } let pre_state_value: Value = if exit_code == 0 { @@ -271,7 +261,7 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, execut if resource.kind == Kind::Resource { debug!("{}", t!("dscresources.commandResource.setVerifyOutput", operation = operation_type, resource = &resource.type_name, executable = &set.executable)); - verify_json_from_manifest(&resource, &stdout, target_resource)?; + verify_json_from_manifest(resource, &stdout, target_resource)?; } let actual_value: Value = match serde_json::from_str(&stdout){ @@ -299,7 +289,7 @@ pub fn invoke_set(resource: &DscResource, desired: &str, skip_test: bool, execut if resource.kind == Kind::Resource { debug!("{}", t!("dscresources.commandResource.setVerifyOutput", operation = operation_type, resource = &resource.type_name, executable = &set.executable)); - verify_json_from_manifest(&resource, actual_line, target_resource)?; + verify_json_from_manifest(resource, actual_line, target_resource)?; } let actual_value: Value = serde_json::from_str(actual_line)?; @@ -361,18 +351,14 @@ pub fn invoke_test(resource: &DscResource, expected: &str, target_resource: Opti return invoke_synthetic_test(resource, expected, target_resource); }; - verify_json_from_manifest(&resource, expected, target_resource)?; + verify_json_from_manifest(resource, expected, target_resource)?; - let resource_type = match target_resource.clone() { + let resource_type = match target_resource { Some(r) => r.type_name.clone(), None => resource.type_name.clone(), }; validate_security_context(&test.require_security_context, &resource_type, "test")?; - let path = if let Some(target_resource) = target_resource { - Some(target_resource.path.clone()) - } else { - None - }; + let path = target_resource.map(|target_resource| target_resource.path.clone()); let command_resource_info = CommandResourceInfo { type_name: resource_type.clone(), path, @@ -394,7 +380,7 @@ pub fn invoke_test(resource: &DscResource, expected: &str, target_resource: Opti Some(ReturnKind::State) => { if resource.kind == Kind::Resource { debug!("{}", t!("dscresources.commandResource.testVerifyOutput", resource = &resource.type_name, executable = &test.executable)); - verify_json_from_manifest(&resource, &stdout, target_resource)?; + verify_json_from_manifest(resource, &stdout, target_resource)?; } let actual_value: Value = match serde_json::from_str(&stdout){ @@ -422,7 +408,7 @@ pub fn invoke_test(resource: &DscResource, expected: &str, target_resource: Opti if resource.kind == Kind::Resource { debug!("{}", t!("dscresources.commandResource.testVerifyOutput", resource = &resource.type_name, executable = &test.executable)); - verify_json_from_manifest(&resource, actual_value, target_resource)?; + verify_json_from_manifest(resource, actual_value, target_resource)?; } let actual_value: Value = serde_json::from_str(actual_value)?; @@ -522,18 +508,14 @@ pub fn invoke_delete(resource: &DscResource, filter: &str, target_resource: Opti return Err(DscError::NotImplemented("delete".to_string())); }; - verify_json_from_manifest(&resource, filter, target_resource)?; + verify_json_from_manifest(resource, filter, target_resource)?; let resource_type = match target_resource { Some(r) => r.type_name.clone(), None => resource.type_name.clone(), }; validate_security_context(&delete.require_security_context, &resource_type, "delete")?; - let path = if let Some(target_resource) = target_resource { - Some(target_resource.path.clone()) - } else { - None - }; + let path = target_resource.map(|target_resource| target_resource.path.clone()); let command_resource_info = CommandResourceInfo { type_name: resource_type.clone(), path, @@ -541,7 +523,7 @@ pub fn invoke_delete(resource: &DscResource, filter: &str, target_resource: Opti let (args, supports_whatif) = process_set_delete_args(delete.args.as_ref(), filter, &command_resource_info, execution_type); if execution_type == &ExecutionKind::WhatIf && !supports_whatif { // perform a synthetic what-if by calling test and wrapping the TestResult in DeleteResultKind::SyntheticWhatIf - let test_result = invoke_test(resource, filter, target_resource.clone())?; + let test_result = invoke_test(resource, filter, target_resource)?; return Ok(DeleteResultKind::SyntheticWhatIf(test_result)); } let command_input = get_command_input(delete.input.as_ref(), filter)?; @@ -586,11 +568,7 @@ pub fn invoke_validate(resource: &DscResource, config: &str, target_resource: Op Some(r) => r.type_name.clone(), None => resource.type_name.clone(), }; - let path = if let Some(target_resource) = target_resource { - Some(target_resource.path.clone()) - } else { - None - }; + let path = target_resource.map(|target_resource| target_resource.path.clone()); let command_resource_info = CommandResourceInfo { type_name: resource_type.clone(), path, @@ -688,18 +666,14 @@ pub fn invoke_export(resource: &DscResource, input: Option<&str>, target_resourc None => resource.type_name.clone(), }; validate_security_context(&export.require_security_context, &resource_type, "export")?; - let path = if let Some(target_resource) = target_resource { - Some(target_resource.path.clone()) - } else { - None - }; + let path = target_resource.map(|target_resource| target_resource.path.clone()); let command_resource_info = CommandResourceInfo { type_name: resource_type.clone(), path, }; if let Some(input) = input { if !input.is_empty() { - verify_json_from_manifest(&resource, input, target_resource)?; + verify_json_from_manifest(resource, input, target_resource)?; command_input = get_command_input(export.input.as_ref(), input)?; } @@ -721,7 +695,7 @@ pub fn invoke_export(resource: &DscResource, input: Option<&str>, target_resourc }; if resource.kind == Kind::Resource { debug!("{}", t!("dscresources.commandResource.exportVerifyOutput", resource = &resource.type_name, executable = &export.executable)); - verify_json_from_manifest(&resource, line, target_resource)?; + verify_json_from_manifest(resource, line, target_resource)?; } instances.push(instance); } @@ -876,15 +850,14 @@ async fn run_process_async(executable: &str, args: Option>, input: O if code != 0 { // Only use manifest-provided exit code mappings when the map is not empty/default, // so that default mappings do not suppress stderr-based diagnostics. - if !exit_codes.is_empty_or_default() { - if let Some(error_message) = exit_codes.get_code(code) { + if !exit_codes.is_empty_or_default() + && let Some(error_message) = exit_codes.get_code(code) { return Err(DscError::CommandExitFromManifest( executable.to_string(), code, error_message.clone() )); } - } return Err(DscError::Command(executable.to_string(), code, stderr_result)); } diff --git a/lib/dsc-lib/src/dscresources/dscresource.rs b/lib/dsc-lib/src/dscresources/dscresource.rs index b748090bf..ef2eacc78 100644 --- a/lib/dsc-lib/src/dscresources/dscresource.rs +++ b/lib/dsc-lib/src/dscresources/dscresource.rs @@ -287,7 +287,10 @@ impl DscResource { return adapter.schema(); } - return Err(DscError::NotSupported(t!("dscresources.dscresource.invokeSchemaNotSupported", resource = self.type_name).to_string())); + Err(DscError::NotSupported(t!( + "dscresources.dscresource.invokeSchemaNotSupported", + resource = self.type_name + ).to_string())) } fn get_adapter_resource(configurator: &mut Configurator, adapter: &FullyQualifiedTypeName) -> Result { @@ -399,12 +402,12 @@ impl Invoke for DscResource { warn!("{}", t!("dscresources.dscresource.deprecationMessage", resource = self.type_name, message = deprecation_message)); } if let Some(adapter) = &self.require_adapter { - return self.invoke_get_with_adapter(adapter, &self, filter); + return self.invoke_get_with_adapter(adapter, self, filter); } match &self.implemented_as { Some(ImplementedAs::Command) => { - command_resource::invoke_get(&self, filter, self.target_resource.as_deref()) + command_resource::invoke_get(self, filter, self.target_resource.as_deref()) }, _ => { Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) @@ -418,12 +421,12 @@ impl Invoke for DscResource { warn!("{}", t!("dscresources.dscresource.deprecationMessage", resource = self.type_name, message = deprecation_message)); } if let Some(adapter) = &self.require_adapter { - return self.invoke_set_with_adapter(adapter, &self, desired, skip_test, execution_type); + return self.invoke_set_with_adapter(adapter, self, desired, skip_test, execution_type); } match &self.implemented_as { Some(ImplementedAs::Command) => { - command_resource::invoke_set(&self, desired, skip_test, execution_type, self.target_resource.as_deref()) + command_resource::invoke_set(self, desired, skip_test, execution_type, self.target_resource.as_deref()) }, _ => { Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) @@ -437,7 +440,7 @@ impl Invoke for DscResource { warn!("{}", t!("dscresources.dscresource.deprecationMessage", resource = self.type_name, message = deprecation_message)); } if let Some(adapter) = &self.require_adapter { - return self.invoke_test_with_adapter(adapter, &self, expected); + return self.invoke_test_with_adapter(adapter, self, expected); } match &self.implemented_as { @@ -473,7 +476,7 @@ impl Invoke for DscResource { Ok(test_result) } else { - command_resource::invoke_test(&self, expected, self.target_resource.as_deref()) + command_resource::invoke_test(self, expected, self.target_resource.as_deref()) } }, _ => { @@ -488,12 +491,12 @@ impl Invoke for DscResource { warn!("{}", t!("dscresources.dscresource.deprecationMessage", resource = self.type_name, message = deprecation_message)); } if let Some(adapter) = &self.require_adapter { - return self.invoke_delete_with_adapter(adapter, &self, filter, execution_type); + return self.invoke_delete_with_adapter(adapter, self, filter, execution_type); } match &self.implemented_as { Some(ImplementedAs::Command) => { - command_resource::invoke_delete(&self, filter, self.target_resource.as_deref(), execution_type) + command_resource::invoke_delete(self, filter, self.target_resource.as_deref(), execution_type) }, _ => { Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) @@ -512,7 +515,7 @@ impl Invoke for DscResource { match &self.implemented_as { Some(ImplementedAs::Command) => { - command_resource::invoke_validate(&self, config, self.target_resource.as_deref()) + command_resource::invoke_validate(self, config, self.target_resource.as_deref()) }, _ => { Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) @@ -529,12 +532,12 @@ impl Invoke for DscResource { return Ok(serde_json::to_string(schema)?); } if let Some(adapter) = &self.require_adapter { - return self.invoke_schema_with_adapter(adapter, &self); + return self.invoke_schema_with_adapter(adapter, self); } match &self.implemented_as { Some(ImplementedAs::Command) => { - command_resource::get_schema(&self, self.target_resource.as_deref()) + command_resource::get_schema(self, self.target_resource.as_deref()) }, _ => { Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) @@ -548,10 +551,10 @@ impl Invoke for DscResource { warn!("{}", t!("dscresources.dscresource.deprecationMessage", resource = self.type_name, message = deprecation_message)); } if let Some(adapter) = &self.require_adapter { - return self.invoke_export_with_adapter(adapter, &self, input); + return self.invoke_export_with_adapter(adapter, self, input); } - command_resource::invoke_export(&self, Some(input), self.target_resource.as_deref()) + command_resource::invoke_export(self, Some(input), self.target_resource.as_deref()) } fn resolve(&self, input: &str) -> Result { @@ -563,7 +566,7 @@ impl Invoke for DscResource { return Err(DscError::NotSupported(t!("dscresources.dscresource.invokeResolveNotSupported", resource = self.type_name).to_string())); } - command_resource::invoke_resolve(&self, input) + command_resource::invoke_resolve(self, input) } } @@ -615,10 +618,8 @@ pub fn redact(value: &Value) -> Value { /// # Errors /// * `DscError` - The adapter manifest is not found or invalid pub fn get_adapter_input_kind(adapter: &DscResource) -> Result { - if let Some(manifest) = &adapter.manifest { - if let Some(adapter_operation) = &manifest.adapter { - return Ok(adapter_operation.input_kind.clone()); - } + if let Some(manifest) = &adapter.manifest && let Some(adapter_operation) = &manifest.adapter { + return Ok(adapter_operation.input_kind.clone()); } Err(DscError::Operation(t!("dscresources.dscresource.adapterManifestNotFound", adapter = adapter.type_name).to_string())) } diff --git a/lib/dsc-lib/src/functions/contains.rs b/lib/dsc-lib/src/functions/contains.rs index 4604bd676..df0871f9c 100644 --- a/lib/dsc-lib/src/functions/contains.rs +++ b/lib/dsc-lib/src/functions/contains.rs @@ -44,19 +44,15 @@ impl Function for Contains { if let Some(array) = args[0].as_array() { for item in array { if let Some(item_str) = item.as_str() { - if let Some(string) = &string_to_find { - if item_str == string { - found = true; - break; - } - } - } else if let Some(item_num) = item.as_i64() { - if let Some(number) = number_to_find { - if item_num == number { - found = true; - break; - } + if let Some(string) = &string_to_find && item_str == string { + found = true; + break; } + } else if let Some(item_num) = item.as_i64() + && let Some(number) = number_to_find + && item_num == number { + found = true; + break; } } return Ok(Value::Bool(found)); @@ -71,11 +67,9 @@ impl Function for Contains { found = true; break; } - } else if let Some(number) = number_to_find { - if key == &number.to_string() { - found = true; - break; - } + } else if let Some(number) = number_to_find && key == &number.to_string() { + found = true; + break; } } return Ok(Value::Bool(found)); diff --git a/lib/dsc-lib/src/functions/data_uri_to_string.rs b/lib/dsc-lib/src/functions/data_uri_to_string.rs index 3533bbdf4..7cc480a87 100644 --- a/lib/dsc-lib/src/functions/data_uri_to_string.rs +++ b/lib/dsc-lib/src/functions/data_uri_to_string.rs @@ -75,14 +75,14 @@ impl Function for DataUriToString { for part in &parts[1..] { if *part == "base64" { has_base64 = true; - } else if part.starts_with("charset=") { + } else if let Some(charset_from_part) = part.strip_prefix("charset=") { if charset.is_some() { return Err(DscError::FunctionArg( "dataUriToString".to_string(), t!("functions.dataUriToString.invalidDataUri").to_string(), )); } - charset = Some(&part[8..]); + charset = Some(charset_from_part); } else { return Err(DscError::FunctionArg( "dataUriToString".to_string(), diff --git a/lib/dsc-lib/src/functions/try_which.rs b/lib/dsc-lib/src/functions/try_which.rs index 6644a81a3..93895738c 100644 --- a/lib/dsc-lib/src/functions/try_which.rs +++ b/lib/dsc-lib/src/functions/try_which.rs @@ -55,11 +55,11 @@ mod tests { #[test] fn exe_exists() { let mut parser = Statement::new().unwrap(); - let result = parser.parse_and_execute("[tryWhich('dsc')]", &Context::new()).unwrap(); + let result = parser.parse_and_execute("[tryWhich('cargo')]", &Context::new()).unwrap(); #[cfg(windows)] - assert!(result.as_str().unwrap().to_lowercase().ends_with("\\dsc.exe")); + assert!(result.as_str().unwrap().to_lowercase().ends_with("\\cargo.exe")); #[cfg(not(windows))] - assert!(result.as_str().unwrap().ends_with("/dsc")); + assert!(result.as_str().unwrap().ends_with("/cargo")); } #[test] diff --git a/lib/dsc-lib/src/lib.rs b/lib/dsc-lib/src/lib.rs index 5dce75744..aa7299a15 100644 --- a/lib/dsc-lib/src/lib.rs +++ b/lib/dsc-lib/src/lib.rs @@ -51,7 +51,6 @@ impl DscManager { /// /// * `name` - The name of the resource to find, can have wildcards. /// - #[must_use] pub fn find_resource(&mut self, filter: &DiscoveryFilter) -> Result, DscError> { self.discovery.find_resource(filter) } diff --git a/lib/dsc-lib/src/types/date_version.rs b/lib/dsc-lib/src/types/date_version.rs index 458d76fe0..70652e347 100644 --- a/lib/dsc-lib/src/types/date_version.rs +++ b/lib/dsc-lib/src/types/date_version.rs @@ -6,6 +6,7 @@ use std::{ fmt::Display, + hash::Hash, str::FromStr, sync::OnceLock, }; @@ -42,7 +43,7 @@ use crate::schemas::dsc_repo::DscRepoSchema; /// /// If the date version is for a prerelease, the prerelease segment must be a string of ASCII /// alphabetic characters (`[a-zA-Z]`). -#[derive(Debug, Clone, Hash, Serialize, Deserialize, DscRepoSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, DscRepoSchema)] #[serde(try_from = "String", into = "String")] #[dsc_repo_schema(base_name = "dateVersion", folder_path = "definitions")] pub struct DateVersion(NaiveDate, Option); @@ -451,7 +452,7 @@ impl DateVersion { }; let mut errors: Vec = vec![]; - if year > 9999 || year < 1000 { + if !(1000..=9999).contains(&year) { errors.push(DateVersionError::InvalidYear { year }); } @@ -526,46 +527,39 @@ impl Datelike for DateVersion { self.as_ref().iso_week() } fn with_year(&self, year: i32) -> Option { - match self.as_ref().with_year(year) { - None => None, - Some(new_date) => Some(Self(new_date, self.1.clone())) - } + self.as_ref() + .with_year(year) + .map(|new_date| Self(new_date, self.1.clone())) } fn with_month(&self, month: u32) -> Option { - match self.as_ref().with_month(month) { - None => None, - Some(new_date) => Some(Self(new_date, self.1.clone())) - } + self.as_ref() + .with_month(month) + .map(|new_date| Self(new_date, self.1.clone())) } fn with_month0(&self, month0: u32) -> Option { - match self.as_ref().with_month0(month0) { - None => None, - Some(new_date) => Some(Self(new_date, self.1.clone())) - } + self.as_ref() + .with_month0(month0) + .map(|new_date| Self(new_date, self.1.clone())) } fn with_day(&self, day: u32) -> Option { - match self.as_ref().with_day(day) { - None => None, - Some(new_date) => Some(Self(new_date, self.1.clone())) - } + self.as_ref() + .with_day(day) + .map(|new_date| Self(new_date, self.1.clone())) } fn with_day0(&self, day0: u32) -> Option { - match self.as_ref().with_day0(day0) { - None => None, - Some(new_date) => Some(Self(new_date, self.1.clone())) - } + self.as_ref() + .with_day0(day0) + .map(|new_date| Self(new_date, self.1.clone())) } fn with_ordinal(&self, ordinal: u32) -> Option { - match self.as_ref().with_ordinal(ordinal) { - None => None, - Some(new_date) => Some(Self(new_date, self.1.clone())) - } + self.as_ref() + .with_ordinal(ordinal) + .map(|new_date| Self(new_date, self.1.clone())) } fn with_ordinal0(&self, ordinal0: u32) -> Option { - match self.as_ref().with_ordinal0(ordinal0) { - None => None, - Some(new_date) => Some(Self(new_date, self.1.clone())) - } + self.as_ref() + .with_ordinal0(ordinal0) + .map(|new_date| Self(new_date, self.1.clone())) } } @@ -628,6 +622,13 @@ impl From for NaiveDate { } } +impl Hash for DateVersion { + fn hash(&self, state: &mut H) { + self.0.hash(state); + self.1.hash(state); + } +} + impl Eq for DateVersion {} impl PartialEq for DateVersion { @@ -697,7 +698,7 @@ impl PartialEq for str { impl PartialEq<&str> for DateVersion { fn eq(&self, other: &&str) -> bool { - match Self::parse(*other) { + match Self::parse(other) { Ok(other_version) => self.eq(&other_version), Err(_) => false, } @@ -706,7 +707,7 @@ impl PartialEq<&str> for DateVersion { impl PartialEq for &str { fn eq(&self, other: &DateVersion) -> bool { - match DateVersion::parse(*self) { + match DateVersion::parse(self) { Ok(version) => version.eq(other), Err(_) => false } diff --git a/lib/dsc-lib/src/types/exit_code.rs b/lib/dsc-lib/src/types/exit_code.rs index 6f7b82951..f7c18ff07 100644 --- a/lib/dsc-lib/src/types/exit_code.rs +++ b/lib/dsc-lib/src/types/exit_code.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::{borrow::Borrow, fmt::Display, ops::Deref, str::FromStr}; +use std::{borrow::Borrow, fmt::Display, hash::Hash, ops::Deref, str::FromStr}; use serde::{Deserialize, Serialize}; @@ -12,7 +12,7 @@ use crate::dscerror::DscError; /// DSC uses exit codes to determine whether invoked commands, including resource and extension /// operations, are successful. DSC treats exit code `0` as successful and all other exit codes /// as indicating a failure. -#[derive(Debug, Copy, Clone, Hash, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(try_from = "String", into = "String")] pub struct ExitCode(i32); @@ -119,7 +119,11 @@ impl From for i32 { value.0 } } - +impl Hash for ExitCode { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} impl PartialEq for ExitCode { fn eq(&self, other: &Self) -> bool { self.0.eq(&other.0) diff --git a/lib/dsc-lib/src/types/exit_codes_map.rs b/lib/dsc-lib/src/types/exit_codes_map.rs index 613dee40d..7bc915813 100644 --- a/lib/dsc-lib/src/types/exit_codes_map.rs +++ b/lib/dsc-lib/src/types/exit_codes_map.rs @@ -25,7 +25,7 @@ pub struct ExitCodesMap(HashMap); /// Defines the default map as a private static to use with the `get_code_or_default` method, /// minimizing the performance hit compared to reconstructing the default map on every method /// invocation. -static DEFAULT_MAP: LazyLock = LazyLock::new(|| ExitCodesMap::default()); +static DEFAULT_MAP: LazyLock = LazyLock::new(ExitCodesMap::default); impl ExitCodesMap { /// Defines the regular expression for validating a string as an exit code. @@ -65,15 +65,15 @@ impl ExitCodesMap { match self.0.get(&ExitCode::new(code)) { Some(description) => description.clone(), None => match code { - 0 => (&*DEFAULT_MAP).get_code(0).expect("default always defines exit code 0").clone(), - _ => (&*DEFAULT_MAP).get_code(1).expect("default always defines exit code 1").clone(), + 0 => DEFAULT_MAP.get_code(0).expect("default always defines exit code 0").clone(), + _ => DEFAULT_MAP.get_code(1).expect("default always defines exit code 1").clone(), } } } /// Indicates whether the [`ExitCodesMap`] is identical to the default map. pub fn is_default(&self) -> bool { - self == &*DEFAULT_MAP + *self == *DEFAULT_MAP } /// Indicates whether the [`ExitCodesMap`] is empty or identical to the default map. @@ -132,7 +132,7 @@ impl JsonSchema for ExitCodesMap { impl AsRef for ExitCodesMap { fn as_ref(&self) -> &ExitCodesMap { - &self + self } } diff --git a/lib/dsc-lib/src/types/fully_qualified_type_name.rs b/lib/dsc-lib/src/types/fully_qualified_type_name.rs index 6e73a7f36..4c4829c2d 100644 --- a/lib/dsc-lib/src/types/fully_qualified_type_name.rs +++ b/lib/dsc-lib/src/types/fully_qualified_type_name.rs @@ -117,6 +117,7 @@ use crate::schemas::dsc_repo::DscRepoSchema; #[derive( Clone, Debug, + Default, Eq, Serialize, Deserialize, @@ -409,7 +410,7 @@ impl FullyQualifiedTypeName { let validating_segment_regex = Self::init_validating_segment_regex(); let (_owner, _namespaces, name) = Self::parse_segments( text, - &validating_segment_regex, + validating_segment_regex, errors ); if errors.len() == 1 && errors[0] == FullyQualifiedTypeNameError::EmptyTypeName { @@ -568,14 +569,6 @@ impl FullyQualifiedTypeName { } } -// While it's technically never valid for a _defined_ FQTN to be empty, we need the default -// implementation for creating empty instances of various structs to then populate/modify. -impl Default for FullyQualifiedTypeName { - fn default() -> Self { - Self(String::new()) - } -} - // We implement `PartialEq` by hand for various types because FQTNs should be compared // case insensitively. This obviates the need to `.to_string().to_lowercase()` for comparisons. impl PartialEq for FullyQualifiedTypeName { diff --git a/lib/dsc-lib/src/types/resource_version.rs b/lib/dsc-lib/src/types/resource_version.rs index daa2b8842..5da031ce5 100644 --- a/lib/dsc-lib/src/types/resource_version.rs +++ b/lib/dsc-lib/src/types/resource_version.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::{fmt::Display, str::FromStr, sync::OnceLock}; +use std::{fmt::Display, hash::Hash, str::FromStr, sync::OnceLock}; use miette::Diagnostic; use regex::Regex; @@ -156,7 +156,7 @@ use crate::{ /// /// [01]: https://doc.rust-lang.org/std/cmp/trait.Ord.html#lexicographical-comparison /// [02]: https://www.iso.org/iso-8601-date-and-time-format.html -#[derive(Debug, Clone, Hash, Eq, Serialize, Deserialize, JsonSchema, DscRepoSchema)] +#[derive(Debug, Clone, Eq, Serialize, Deserialize, JsonSchema, DscRepoSchema)] #[dsc_repo_schema(base_name = "resourceVersion", folder_path = "definitions")] #[serde(untagged, try_from = "String", into = "String")] #[schemars(!try_from, !into)] @@ -366,10 +366,7 @@ impl ResourceVersion { /// assert_eq!(date.is_semver(), false); /// ``` pub fn is_semver(&self) -> bool { - match self { - Self::Semantic(_) => true, - _ => false, - } + matches!(self, Self::Semantic(_)) } /// Indicates whether the resource version is a date version. @@ -386,10 +383,7 @@ impl ResourceVersion { /// assert_eq!(date.is_date_version(), true); /// ``` pub fn is_date_version(&self) -> bool { - match self { - Self::Date(_) => true, - _ => false, - } + matches!(self, Self::Date(_)) } /// Returns the version as a reference to the underlying [`SemanticVersion`] if possible. @@ -679,6 +673,15 @@ impl TryFrom for DateVersion { } } +impl Hash for ResourceVersion { + fn hash(&self, state: &mut H) { + match self { + Self::Semantic(v) => v.hash(state), + Self::Date(v) => v.hash(state), + } + } +} + // Implement traits for comparing `ResourceVersion` to strings, semantic versions, and date // versions bi-directionally. impl PartialEq for ResourceVersion { @@ -734,7 +737,7 @@ impl PartialEq for DateVersion { impl PartialEq<&str> for ResourceVersion { fn eq(&self, other: &&str) -> bool { - if let Ok(other_version) = Self::parse(*other) { + if let Ok(other_version) = Self::parse(other) { self == &other_version } else { false @@ -744,7 +747,7 @@ impl PartialEq<&str> for ResourceVersion { impl PartialEq for &str { fn eq(&self, other: &ResourceVersion) -> bool { - if let Ok(version) = ResourceVersion::parse(*self) { + if let Ok(version) = ResourceVersion::parse(self) { &version == other } else { false @@ -869,7 +872,7 @@ impl PartialOrd for String { impl PartialOrd<&str> for ResourceVersion { fn partial_cmp(&self, other: &&str) -> Option { - match ResourceVersion::parse(*other) { + match ResourceVersion::parse(other) { Ok(other_version) => self.partial_cmp(&other_version), Err(_) => None, } @@ -887,7 +890,7 @@ impl PartialOrd for ResourceVersion { impl PartialOrd for &str { fn partial_cmp(&self, other: &ResourceVersion) -> Option { - match ResourceVersion::parse(*self) { + match ResourceVersion::parse(self) { Ok(version) => version.partial_cmp(other), Err(_) => None, } diff --git a/lib/dsc-lib/src/types/resource_version_req.rs b/lib/dsc-lib/src/types/resource_version_req.rs index 5041e6b29..f6f6c89df 100644 --- a/lib/dsc-lib/src/types/resource_version_req.rs +++ b/lib/dsc-lib/src/types/resource_version_req.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::{fmt::Display, str::FromStr, sync::OnceLock}; +use std::{fmt::Display, hash::Hash, str::FromStr, sync::OnceLock}; use miette::Diagnostic; use regex::Regex; @@ -78,7 +78,7 @@ use crate::{ /// ``` /// /// [01]: SemanticVersionReq -#[derive(Debug, Clone, Hash, Eq, Serialize, Deserialize, JsonSchema, DscRepoSchema)] +#[derive(Debug, Clone, Eq, Serialize, Deserialize, JsonSchema, DscRepoSchema)] #[dsc_repo_schema(base_name = "resourceVersionReq", folder_path = "definitions")] #[serde(untagged, try_from = "String", into = "String")] #[schemars(!try_from, !into)] @@ -297,10 +297,7 @@ impl ResourceVersionReq { /// assert_eq!(date.is_semver(), false); /// ``` pub fn is_semver(&self) -> bool { - match self { - Self::Semantic(_) => true, - _ => false, - } + matches!(self, Self::Semantic(_)) } /// Indicates whether the resource version requirement is for a specific [`DateVersion`]. @@ -317,10 +314,7 @@ impl ResourceVersionReq { /// assert_eq!(semantic.is_date_version(), false); /// ``` pub fn is_date_version(&self) -> bool { - match self { - Self::Date(_) => true, - _ => false, - } + matches!(self, Self::Date(_)) } /// Returns the requirement as a reference to the underlying [`SemanticVersionReq`] if possible. @@ -583,6 +577,15 @@ impl TryFrom for DateVersion { } } +impl Hash for ResourceVersionReq { + fn hash(&self, state: &mut H) { + match self { + Self::Semantic(req) => req.hash(state), + Self::Date(req) => req.hash(state), + } + } +} + // Implement traits for comparing `ResourceVersionReq` to strings and semantic version requirements // bi-directionally. impl PartialEq for ResourceVersionReq { @@ -638,7 +641,7 @@ impl PartialEq for DateVersion { impl PartialEq<&str> for ResourceVersionReq { fn eq(&self, other: &&str) -> bool { - match Self::parse(*other) { + match Self::parse(other) { Ok(other_req) => self == &other_req, Err(_) => false, } @@ -647,7 +650,7 @@ impl PartialEq<&str> for ResourceVersionReq { impl PartialEq for &str { fn eq(&self, other: &ResourceVersionReq) -> bool { - match ResourceVersionReq::parse(*self) { + match ResourceVersionReq::parse(self) { Ok(req) => &req == other, Err(_) => false, } diff --git a/lib/dsc-lib/src/types/semantic_version.rs b/lib/dsc-lib/src/types/semantic_version.rs index dfee27fbc..709ade8fc 100644 --- a/lib/dsc-lib/src/types/semantic_version.rs +++ b/lib/dsc-lib/src/types/semantic_version.rs @@ -292,7 +292,7 @@ use crate::schemas::dsc_repo::DscRepoSchema; /// /// [01]: https://semver.org /// [`SemanticVersionReq`]: crate::types::SemanticVersionReq -#[derive(Debug, Clone, Hash, Eq, Serialize, Deserialize, DscRepoSchema)] +#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, DscRepoSchema)] #[dsc_repo_schema(base_name = "semver", folder_path = "definitions")] pub struct SemanticVersion(semver::Version); @@ -549,11 +549,11 @@ impl Deref for SemanticVersion { } // Comparison traits -impl PartialEq for SemanticVersion { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} +// impl PartialEq for SemanticVersion { +// fn eq(&self, other: &Self) -> bool { +// self.0 == other.0 +// } +// } impl PartialEq for SemanticVersion { fn eq(&self, other: &semver::Version) -> bool { @@ -605,7 +605,7 @@ impl PartialEq for str { impl PartialEq<&str> for SemanticVersion { fn eq(&self, other: &&str) -> bool { - match Self::parse(*other) { + match Self::parse(other) { Ok(other_version) => self.eq(&other_version), Err(_) => false, } @@ -614,7 +614,7 @@ impl PartialEq<&str> for SemanticVersion { impl PartialEq for &str { fn eq(&self, other: &SemanticVersion) -> bool { - match SemanticVersion::parse(*self) { + match SemanticVersion::parse(self) { Ok(version) => version.eq(other), Err(_) => false, } @@ -623,7 +623,7 @@ impl PartialEq for &str { impl PartialOrd for SemanticVersion { fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) + Some(self.cmp(other)) } } diff --git a/lib/dsc-lib/src/types/semantic_version_req.rs b/lib/dsc-lib/src/types/semantic_version_req.rs index c249b2e20..d1ce0bec8 100644 --- a/lib/dsc-lib/src/types/semantic_version_req.rs +++ b/lib/dsc-lib/src/types/semantic_version_req.rs @@ -400,7 +400,7 @@ use crate::{schemas::dsc_repo::DscRepoSchema, types::SemanticVersion}; /// /// [01]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#version-requirement-syntax /// [`ComparatorIncludesForbiddenBuildMetadata`]: SemanticVersionReqError::ComparatorIncludesForbiddenBuildMetadata -#[derive(Debug, Clone, Hash, Eq, Serialize, Deserialize, DscRepoSchema)] +#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize, DscRepoSchema)] #[dsc_repo_schema(base_name = "semverRequirement", folder_path = "definitions")] pub struct SemanticVersionReq(semver::VersionReq); @@ -983,12 +983,6 @@ impl JsonSchema for SemanticVersionReq { } } -impl Default for SemanticVersionReq { - fn default() -> Self { - Self(semver::VersionReq::default()) - } -} - impl Display for SemanticVersionReq { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) @@ -1063,11 +1057,11 @@ impl Deref for SemanticVersionReq { } // Comparison traits -impl PartialEq for SemanticVersionReq { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} +// impl PartialEq for SemanticVersionReq { +// fn eq(&self, other: &Self) -> bool { +// self.0 == other.0 +// } +// } impl PartialEq for SemanticVersionReq { fn eq(&self, other: &semver::VersionReq) -> bool { @@ -1137,7 +1131,7 @@ impl PartialEq for str { impl PartialEq<&str> for SemanticVersionReq { fn eq(&self, other: &&str) -> bool { - match Self::parse(*other) { + match Self::parse(other) { Ok(o) => self == &o, Err(_) => false } @@ -1146,7 +1140,7 @@ impl PartialEq<&str> for SemanticVersionReq { impl PartialEq for &str { fn eq(&self, other: &SemanticVersionReq) -> bool { - match SemanticVersionReq::parse(*self) { + match SemanticVersionReq::parse(self) { Ok(s) => &s == other, Err(_) => false } diff --git a/lib/dsc-lib/src/types/wildcard_type_name.rs b/lib/dsc-lib/src/types/wildcard_type_name.rs index 98ce261dd..d1e5ed85c 100644 --- a/lib/dsc-lib/src/types/wildcard_type_name.rs +++ b/lib/dsc-lib/src/types/wildcard_type_name.rs @@ -472,10 +472,10 @@ impl WildcardTypeName { if errors.is_empty() { Ok(Self { text: text.to_string(), regex }) } else { - return Err(WildcardTypeNameError::InvalidTypeName { + Err(WildcardTypeNameError::InvalidTypeName { text: text.to_string(), errors: errors.clone(), - }); + }) } } @@ -513,7 +513,7 @@ impl WildcardTypeName { fqtn_errors ); // Convert the FQTN errors and add into the WCTN error collection - errors.extend(fqtn_errors.into_iter().map(|e| { + errors.extend(fqtn_errors.iter_mut().map(|e| { match TryInto::::try_into(e.clone()) { Ok(wtne) => wtne, Err(e) => e, diff --git a/resources/WindowsUpdate/.project.data.json b/resources/WindowsUpdate/.project.data.json index c447c1c56..05b2dbfd0 100644 --- a/resources/WindowsUpdate/.project.data.json +++ b/resources/WindowsUpdate/.project.data.json @@ -2,6 +2,7 @@ "Name": "windowsupdate", "Kind": "Resource", "IsRust": true, + "RustPackageName": "dsc-resource-windows-update", "SupportedPlatformOS": "Windows", "Binaries": [ "wu_dsc" diff --git a/resources/WindowsUpdate/src/windows_update/export.rs b/resources/WindowsUpdate/src/windows_update/export.rs index 66930d3da..81ab9b6ba 100644 --- a/resources/WindowsUpdate/src/windows_update/export.rs +++ b/resources/WindowsUpdate/src/windows_update/export.rs @@ -33,7 +33,7 @@ pub fn handle_export(input: &str) -> Result { } } else { serde_json::from_str(input) - .map_err(|e| Error::new(E_INVALIDARG.into(), t!("export.failedParseInput", err = e.to_string()).to_string()))? + .map_err(|e| Error::new(E_INVALIDARG, t!("export.failedParseInput", err = e.to_string())))? }; let filters = &update_list.updates; @@ -120,16 +120,14 @@ pub fn handle_export(input: &str) -> Result { } // Filter by KB article IDs (match if any KB ID in the filter is present) - if let Some(kb_filter) = &filter.kb_article_ids { - if !kb_filter.is_empty() { - if let Some(ref kb_article_ids) = update_info.kb_article_ids { - let kb_matches = kb_filter.iter().any(|filter_kb| { - kb_article_ids.iter().any(|update_kb| update_kb.eq_ignore_ascii_case(filter_kb)) - }); - matches = matches && kb_matches; - } else { - matches = false; - } + if let Some(kb_filter) = &filter.kb_article_ids && !kb_filter.is_empty() { + if let Some(ref kb_article_ids) = update_info.kb_article_ids { + let kb_matches = kb_filter.iter().any(|filter_kb| { + kb_article_ids.iter().any(|update_kb| update_kb.eq_ignore_ascii_case(filter_kb)) + }); + matches = matches && kb_matches; + } else { + matches = false; } } @@ -148,8 +146,8 @@ pub fn handle_export(input: &str) -> Result { } // Filter by security bulletin IDs (match if any bulletin ID in the filter is present) - if let Some(bulletin_filter) = &filter.security_bulletin_ids { - if !bulletin_filter.is_empty() { + if let Some(bulletin_filter) = &filter.security_bulletin_ids + && !bulletin_filter.is_empty() { if let Some(ref security_bulletin_ids) = update_info.security_bulletin_ids { let bulletin_matches = bulletin_filter.iter().any(|filter_bulletin| { security_bulletin_ids.iter().any(|update_bulletin| update_bulletin.eq_ignore_ascii_case(filter_bulletin)) @@ -159,7 +157,6 @@ pub fn handle_export(input: &str) -> Result { matches = false; } } - } // Filter by update type if let Some(type_filter) = &filter.update_type { @@ -230,7 +227,7 @@ pub fn handle_export(input: &str) -> Result { // Emit JSON error to stderr eprintln!("{{\"error\":\"{}\"}}", error_msg); - return Err(Error::new(E_FAIL.into(), error_msg)); + return Err(Error::new(E_FAIL, error_msg)); } } } @@ -252,7 +249,7 @@ pub fn handle_export(input: &str) -> Result { updates }; serde_json::to_string(&result) - .map_err(|e| Error::new(E_FAIL.into(), t!("export.failedSerializeOutput", err = e.to_string()).to_string())) + .map_err(|e| Error::new(E_FAIL, t!("export.failedSerializeOutput", err = e.to_string()))) } Err(e) => Err(e), } @@ -303,15 +300,13 @@ fn matches_wildcard(text: &str, pattern: &str) -> bool { } } } - + // For the last part, check if it should be at the end - if !ends_with_wildcard && !parts.is_empty() { - if let Some(last_part) = parts.last() { - if !last_part.is_empty() && !text_lower.ends_with(last_part) { - return false; - } + if !ends_with_wildcard && !parts.is_empty() + && let Some(last_part) = parts.last() + && !last_part.is_empty() && !text_lower.ends_with(last_part) { + return false; } - } - + true } diff --git a/resources/WindowsUpdate/src/windows_update/get.rs b/resources/WindowsUpdate/src/windows_update/get.rs index e8aab7db9..9a846ef40 100644 --- a/resources/WindowsUpdate/src/windows_update/get.rs +++ b/resources/WindowsUpdate/src/windows_update/get.rs @@ -14,10 +14,10 @@ use crate::windows_update::types::{UpdateList, extract_update_info}; pub fn handle_get(input: &str) -> Result { // Parse input as UpdateList let update_list: UpdateList = serde_json::from_str(input) - .map_err(|e| Error::new(E_INVALIDARG.into(), t!("get.failedParseInput", err = e.to_string()).to_string()))?; + .map_err(|e| Error::new(E_INVALIDARG, t!("get.failedParseInput", err = e.to_string())))?; if update_list.updates.is_empty() { - return Err(Error::new(E_INVALIDARG.into(), t!("get.updatesArrayEmpty").to_string())); + return Err(Error::new(E_INVALIDARG, t!("get.updatesArrayEmpty"))); } // Initialize COM @@ -54,7 +54,7 @@ pub fn handle_get(input: &str) -> Result { && update_input.is_installed.is_none() && update_input.update_type.is_none() && update_input.msrc_severity.is_none() { - return Err(Error::new(E_INVALIDARG.into(), t!("get.atLeastOneCriterionRequired").to_string())); + return Err(Error::new(E_INVALIDARG, t!("get.atLeastOneCriterionRequired"))); } // Find the update matching ALL provided criteria (logical AND) @@ -168,7 +168,7 @@ pub fn handle_get(input: &str) -> Result { }; eprintln!("{{\"error\":\"{}\"}}", error_msg); - return Err(Error::new(E_INVALIDARG.into(), error_msg)); + return Err(Error::new(E_INVALIDARG, error_msg)); } // Get the first (and should be only) match @@ -206,7 +206,7 @@ pub fn handle_get(input: &str) -> Result { // Emit JSON error to stderr eprintln!("{{\"error\":\"{}\"}}", error_msg); - return Err(Error::new(E_FAIL.into(), error_msg)); + return Err(Error::new(E_FAIL, error_msg)); } } @@ -227,7 +227,7 @@ pub fn handle_get(input: &str) -> Result { updates }; serde_json::to_string(&result) - .map_err(|e| Error::new(E_FAIL.into(), t!("get.failedSerializeOutput", err = e.to_string()).to_string())) + .map_err(|e| Error::new(E_FAIL, t!("get.failedSerializeOutput", err = e.to_string()))) } Err(e) => Err(e), } diff --git a/resources/WindowsUpdate/src/windows_update/set.rs b/resources/WindowsUpdate/src/windows_update/set.rs index 1381a1016..5528b826c 100644 --- a/resources/WindowsUpdate/src/windows_update/set.rs +++ b/resources/WindowsUpdate/src/windows_update/set.rs @@ -20,10 +20,10 @@ fn get_computer_name() -> String { pub fn handle_set(input: &str) -> Result { // Parse input as UpdateList let update_list: UpdateList = serde_json::from_str(input) - .map_err(|e| Error::new(E_INVALIDARG.into(), t!("set.failedParseInput", err = e.to_string()).to_string()))?; + .map_err(|e| Error::new(E_INVALIDARG, t!("set.failedParseInput", err = e.to_string())))?; if update_list.updates.is_empty() { - return Err(Error::new(E_INVALIDARG.into(), t!("set.updatesArrayEmpty").to_string())); + return Err(Error::new(E_INVALIDARG, t!("set.updatesArrayEmpty"))); } // Initialize COM @@ -60,7 +60,7 @@ pub fn handle_set(input: &str) -> Result { && update_input.is_installed.is_none() && update_input.update_type.is_none() && update_input.msrc_severity.is_none() { - return Err(Error::new(E_INVALIDARG.into(), t!("set.atLeastOneCriterionRequired").to_string())); + return Err(Error::new(E_INVALIDARG, t!("set.atLeastOneCriterionRequired"))); } // Find the update matching ALL provided criteria (logical AND) @@ -167,7 +167,7 @@ pub fn handle_set(input: &str) -> Result { t!("set.criteriaMatchedMultipleUpdates", count = matching_updates.len()).to_string() }; eprintln!("{{\"error\":\"{}\"}}", error_msg); - return Err(Error::new(E_INVALIDARG.into(), error_msg)); + return Err(Error::new(E_INVALIDARG, error_msg)); } // Get the first (and should be only) match @@ -205,7 +205,7 @@ pub fn handle_set(input: &str) -> Result { // Emit JSON error to stderr eprintln!("{{\"error\":\"{}\"}}", error_msg); - return Err(Error::new(E_FAIL.into(), error_msg)); + return Err(Error::new(E_FAIL, error_msg)); } } @@ -238,7 +238,7 @@ pub fn handle_set(input: &str) -> Result { // Check if download was successful (orcSucceeded = 2) if result_code != OperationResultCode(2) { let hresult = download_result.HResult()?; - return Err(Error::new(HRESULT(hresult).into(), t!("set.failedDownloadUpdate", code = result_code.0).to_string())); + return Err(Error::new(HRESULT(hresult), t!("set.failedDownloadUpdate", code = result_code.0))); } } @@ -252,17 +252,15 @@ pub fn handle_set(input: &str) -> Result { // Check if installation was successful (orcSucceeded = 2) if result_code != OperationResultCode(2) { let hresult = install_result.HResult()?; - return Err(Error::new(HRESULT(hresult).into(), t!("set.failedInstallUpdate", code = result_code.0).to_string())); + return Err(Error::new(HRESULT(hresult), t!("set.failedInstallUpdate", code = result_code.0))); } // Check if installation result indicates a reboot is required - if !reboot_required { - if let Ok(reboot_req) = install_result.RebootRequired() { - if reboot_req.as_bool() { - reboot_required = true; - } + if !reboot_required + && let Ok(reboot_req) = install_result.RebootRequired() + && reboot_req.as_bool() { + reboot_required = true; } - } // Get full details now that it's installed extract_update_info(&update)? @@ -298,7 +296,7 @@ pub fn handle_set(input: &str) -> Result { updates }; serde_json::to_string(&results) - .map_err(|e| Error::new(E_FAIL.into(), t!("set.failedSerializeOutput", err = e.to_string()).to_string())) + .map_err(|e| Error::new(E_FAIL, t!("set.failedSerializeOutput", err = e.to_string()))) } Err(e) => Err(e), } diff --git a/resources/dism_dsc/src/dism.rs b/resources/dism_dsc/src/dism.rs index 70415fe20..167b20597 100644 --- a/resources/dism_dsc/src/dism.rs +++ b/resources/dism_dsc/src/dism.rs @@ -25,13 +25,13 @@ unsafe extern "system" { ) -> *mut c_void; } -#[repr(C, packed(4))] +#[repr(C, packed)] struct DismFeature { feature_name: *const u16, state: i32, } -#[repr(C, packed(4))] +#[repr(C, packed)] struct DismFeatureInfo { feature_name: *const u16, state: i32, @@ -42,13 +42,13 @@ struct DismFeatureInfo { custom_property_count: u32, } -#[repr(C, packed(4))] +#[repr(C, packed)] struct DismCapability { name: *const u16, state: i32, } -#[repr(C, packed(4))] +#[repr(C, packed)] struct DismCapabilityDetail { name: *const u16, state: i32, diff --git a/resources/dism_dsc/src/feature_on_demand/export.rs b/resources/dism_dsc/src/feature_on_demand/export.rs index 15f1b289c..6709a6a7e 100644 --- a/resources/dism_dsc/src/feature_on_demand/export.rs +++ b/resources/dism_dsc/src/feature_on_demand/export.rs @@ -41,12 +41,11 @@ pub fn handle_export(input: &str) -> Result { let mut should_get_full = !filters_without_identity.is_empty(); if !should_get_full { for f in &filters_with_identity { - if let Some(ref filter_identity) = f.identity { - if matches_wildcard(name, filter_identity) { + if let Some(ref filter_identity) = f.identity + && matches_wildcard(name, filter_identity) { should_get_full = true; break; } - } } } if !should_get_full { diff --git a/resources/dism_dsc/src/optional_feature/export.rs b/resources/dism_dsc/src/optional_feature/export.rs index b05d7e0bb..5b19f95e3 100644 --- a/resources/dism_dsc/src/optional_feature/export.rs +++ b/resources/dism_dsc/src/optional_feature/export.rs @@ -45,12 +45,11 @@ pub fn handle_export(input: &str) -> Result { let mut should_get_full = !filters_without_name.is_empty(); if !should_get_full { for f in &filters_with_name { - if let Some(ref filter_name) = f.feature_name { - if matches_wildcard(name, filter_name) { + if let Some(ref filter_name) = f.feature_name + && matches_wildcard(name, filter_name) { should_get_full = true; break; } - } } } if !should_get_full { diff --git a/resources/dscecho/.project.data.json b/resources/dscecho/.project.data.json index 429167b4a..2e86b4470 100644 --- a/resources/dscecho/.project.data.json +++ b/resources/dscecho/.project.data.json @@ -2,6 +2,7 @@ "Name": "dscecho", "Kind": "Resource", "IsRust": true, + "RustPackageName": "dsc-resource-echo", "Binaries": [ "dscecho" ], diff --git a/resources/dscecho/src/main.rs b/resources/dscecho/src/main.rs index 8b1ef5dd4..ed040dc73 100644 --- a/resources/dscecho/src/main.rs +++ b/resources/dscecho/src/main.rs @@ -57,11 +57,12 @@ fn main() { } fn is_secure_value(value: &Value) -> bool { - if let Some(obj) = value.as_object() { - if obj.len() == 1 && (obj.contains_key("secureString") || obj.contains_key("secureObject")) { + if let Some(obj) = value.as_object() + && obj.len() == 1 + && (obj.contains_key("secureString") || obj.contains_key("secureObject")) { return true; } - } + false } diff --git a/resources/osinfo/.project.data.json b/resources/osinfo/.project.data.json index ad1292e2f..f0765c163 100644 --- a/resources/osinfo/.project.data.json +++ b/resources/osinfo/.project.data.json @@ -2,6 +2,7 @@ "Name": "osinfo", "Kind": "Resource", "IsRust": true, + "RustPackageName": "dsc-resource-osinfo", "Binaries": [ "osinfo" ], diff --git a/resources/process/.project.data.json b/resources/process/.project.data.json index 0f47a6fb7..53986b109 100644 --- a/resources/process/.project.data.json +++ b/resources/process/.project.data.json @@ -2,6 +2,7 @@ "Name": "process", "Kind": "Resource", "IsRust": true, + "RustPackageName": "dsc-resource-process", "Binaries": [ "process" ], diff --git a/resources/registry/.project.data.json b/resources/registry/.project.data.json index bdeaa0726..2ff010f4f 100644 --- a/resources/registry/.project.data.json +++ b/resources/registry/.project.data.json @@ -2,6 +2,7 @@ "Name": "registry", "Kind": "Resource", "IsRust": true, + "RustPackageName": "dsc-resource-registry", "SupportedPlatformOS": "Windows", "Binaries": [ "registry" diff --git a/resources/registry/src/main.rs b/resources/registry/src/main.rs index cc6346444..8511dcdc5 100644 --- a/resources/registry/src/main.rs +++ b/resources/registry/src/main.rs @@ -78,24 +78,23 @@ fn main() { if what_if { reg_helper.enable_what_if(); } // In what-if, if the desired state is _exist: false, route to delete - if what_if { - if let Ok(desired) = serde_json::from_str::(&input) { - if matches!(desired.exist, Some(false)) { - match reg_helper.remove() { - Ok(Some(reg_config)) => { - let json = serde_json::to_string(®_config).unwrap(); - println!("{json}"); - }, - Ok(None) => {}, - Err(err) => { - error!("{err}"); - exit(EXIT_REGISTRY_ERROR); - } + if what_if + && let Ok(desired) = serde_json::from_str::(&input) + && matches!(desired.exist, Some(false)) { + match reg_helper.remove() { + Ok(Some(reg_config)) => { + let json = serde_json::to_string(®_config).unwrap(); + println!("{json}"); + }, + Ok(None) => {}, + Err(err) => { + error!("{err}"); + exit(EXIT_REGISTRY_ERROR); } - return; } + return; } - } + match reg_helper.set() { Ok(reg_config) => { if let Some(config) = reg_config { diff --git a/resources/runcommandonset/.project.data.json b/resources/runcommandonset/.project.data.json index 15a44ce14..8509dd2c1 100644 --- a/resources/runcommandonset/.project.data.json +++ b/resources/runcommandonset/.project.data.json @@ -2,6 +2,7 @@ "Name": "runcommandonset", "Kind": "Resource", "IsRust": true, + "RustPackageName": "dsc-resource-run_command_on_set", "Binaries": [ "runcommandonset" ], diff --git a/resources/sshdconfig/.project.data.json b/resources/sshdconfig/.project.data.json index 33510799e..ab5d83ec1 100644 --- a/resources/sshdconfig/.project.data.json +++ b/resources/sshdconfig/.project.data.json @@ -2,6 +2,7 @@ "Name": "sshdconfig", "Kind": "Resource", "IsRust": true, + "RustPackageName": "dsc-resource-sshdconfig", "Binaries": [ "sshdconfig" ], diff --git a/resources/sshdconfig/src/export.rs b/resources/sshdconfig/src/export.rs index 752978118..e133194d5 100644 --- a/resources/sshdconfig/src/export.rs +++ b/resources/sshdconfig/src/export.rs @@ -19,8 +19,8 @@ pub fn invoke_export(input: Option<&String>, compare: bool) -> Result(input_value.clone()) { @@ -36,7 +36,6 @@ pub fn invoke_export(input: Option<&String>, compare: bool) -> Result SshdConfigValue<'a> { )); } - if let Value::Array(arr) = value { - if arr.is_empty() { - return Err(SshdConfigError::ParserError( - t!("formatter.invalidValue", key = key).to_string() - )); - } + if let Value::Array(arr) = value && arr.is_empty() { + return Err(SshdConfigError::ParserError( + t!("formatter.invalidValue", key = key).to_string() + )); } let mut keyword_info = KeywordInfo::from_keyword(key); diff --git a/resources/sshdconfig/src/get.rs b/resources/sshdconfig/src/get.rs index 263443a82..a162b57e2 100644 --- a/resources/sshdconfig/src/get.rs +++ b/resources/sshdconfig/src/get.rs @@ -144,10 +144,8 @@ pub fn get_sshd_settings(cmd_info: &CommandInfo, is_get: bool) -> Result, } +fn default_true() -> bool { true } + /// Input for name-value keyword single-entry operations (e.g., subsystem). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct RepeatInput { /// Whether the entry should exist (true) or be removed (false) - #[serde(rename = "_exist", default)] + #[serde(rename = "_exist", default = "default_true")] pub exist: bool, /// Metadata for the operation #[serde(rename = "_metadata", skip_serializing_if = "Option::is_none")] @@ -244,8 +246,8 @@ pub fn parse_and_validate_entries(entries_array: &[Value]) -> Result) -> Option { keyword_array.iter().position(|item| { - if let Value::Object(obj) = item { - if let Some(Value::String(name)) = obj.get("name") { + if let Value::Object(obj) = item + && let Some(Value::String(name)) = obj.get("name") { if name != entry_name { return false; } @@ -260,7 +262,7 @@ pub fn find_name_value_entry_index(keyword_array: &[Value], entry_name: &str, ma return true; } - } + false }) } @@ -324,9 +326,8 @@ pub fn add_or_update_entry(config: &mut Map, keyword: &str, entry /// * `keyword` - The keyword name (e.g., "subsystem") /// * `entry_name` - The name of the entry to remove pub fn remove_entry(config: &mut Map, keyword: &str, entry_name: &str) { - if let Some(Value::Array(arr)) = config.get_mut(keyword) { - if let Some(index) = find_name_value_entry_index(arr, entry_name, None) { + if let Some(Value::Array(arr)) = config.get_mut(keyword) + && let Some(index) = find_name_value_entry_index(arr, entry_name, None) { arr.remove(index); } - } } diff --git a/resources/sshdconfig/tests/sshdconfigRepeat.tests.ps1 b/resources/sshdconfig/tests/sshdconfigRepeat.tests.ps1 index ac39caf23..8a2a34d7b 100644 --- a/resources/sshdconfig/tests/sshdconfigRepeat.tests.ps1 +++ b/resources/sshdconfig/tests/sshdconfigRepeat.tests.ps1 @@ -212,5 +212,23 @@ PasswordAuthentication yes Remove-Item -Path $stderrFile -Force -ErrorAction SilentlyContinue } + + It 'Should default to _exist=true when not specified explicitly' { + $inputConfig = @{ + _metadata = @{ + filepath = $TestConfigPath + } + subsystem = @{ + name = "testExistDefault" + value = "/path/to/subsystem" + } + } | ConvertTo-Json + + $output = sshdconfig set --input $inputConfig -s sshd-config-repeat 2>$null + $LASTEXITCODE | Should -Be 0 + # verify subsystem was added (defaulting to _exist=true) + $subsystems = Get-Content $TestConfigPath | Where-Object { $_ -match '^\s*subsystem\s+' } + $subsystems | Should -Contain "subsystem testExistDefault /path/to/subsystem" + } } } diff --git a/resources/windows_firewall/src/firewall.rs b/resources/windows_firewall/src/firewall.rs index bf75cd2aa..8c570e550 100644 --- a/resources/windows_firewall/src/firewall.rs +++ b/resources/windows_firewall/src/firewall.rs @@ -8,11 +8,36 @@ use windows::Win32::Foundation::{S_FALSE, VARIANT_BOOL}; use windows::Win32::NetworkManagement::WindowsFirewall::*; use windows::Win32::System::Com::{CLSCTX_INPROC_SERVER, CoCreateInstance, CoInitializeEx, CoUninitialize, IDispatch, COINIT_APARTMENTTHREADED}; use windows::Win32::System::Ole::IEnumVARIANT; -use windows::Win32::System::Variant::VARIANT; +use windows::Win32::System::Variant::{VARIANT, VariantClear}; use crate::types::{FirewallError, FirewallRule, FirewallRuleList, RuleAction, RuleDirection}; use crate::util::matches_any_filter; +/// RAII wrapper for VARIANT that automatically calls VariantClear on drop +struct SafeVariant(VARIANT); + +impl SafeVariant { + fn new() -> Self { + Self(VARIANT::default()) + } + + fn as_mut_ptr(&mut self) -> *mut VARIANT { + &mut self.0 + } + + fn as_ref(&self) -> &VARIANT { + &self.0 + } +} + +impl Drop for SafeVariant { + fn drop(&mut self) { + if let Err(e) = unsafe { VariantClear(&mut self.0) } { + crate::write_error(&format!("Warning: VariantClear failed with HRESULT: {:#010x}", e.code().0)); + } + } +} + struct ComGuard; impl ComGuard { @@ -55,20 +80,23 @@ impl FirewallStore { let mut results = Vec::new(); loop { let mut fetched = 0u32; - let mut variant = [VARIANT::default()]; - let hr = unsafe { enum_variant.Next(&mut variant, &mut fetched) }; + let mut safe_variant = SafeVariant::new(); + let variant_slice = unsafe { std::slice::from_raw_parts_mut(safe_variant.as_mut_ptr(), 1) }; + let hr = unsafe { enum_variant.Next(variant_slice, &mut fetched) }; if hr == S_FALSE || fetched == 0 { break; } hr.ok() .map_err(|error| t!("firewall.ruleEnumerationFailed", error = error.to_string()).to_string())?; - let dispatch = IDispatch::try_from(&variant[0]) + let dispatch = IDispatch::try_from(safe_variant.as_ref()) .map_err(|error: windows::core::Error| t!("firewall.ruleEnumerationFailed", error = error.to_string()).to_string())?; let rule: INetFwRule = dispatch .cast() .map_err(|error| t!("firewall.ruleEnumerationFailed", error = error.to_string()).to_string())?; results.push(rule); + + // SafeVariant will automatically call VariantClear when it goes out of scope } Ok(results) @@ -169,6 +197,10 @@ fn profiles_from_mask(mask: i32) -> Vec { } fn profiles_to_mask(values: &[String]) -> Result { + if values.is_empty() { + return Ok(NET_FW_PROFILE2_ALL.0); + } + let mut mask = 0; for value in values { match value.to_ascii_lowercase().as_str() { @@ -197,6 +229,10 @@ fn join_csv(value: &[String]) -> String { } fn interface_types_to_string(values: &[String]) -> Result { + if values.is_empty() { + return Ok("All".to_string()); + } + let mut normalized = Vec::new(); for value in values { match value.to_ascii_lowercase().as_str() { @@ -269,16 +305,20 @@ fn apply_rule_properties(rule: &INetFwRule, desired: &FirewallRule, existing_pro // the existing rule's protocol (if updating an existing rule). let effective_protocol = desired.protocol.or(existing_protocol); + // If effective_protocol is None, read the current protocol from the rule. + let effective_protocol = match effective_protocol { + Some(protocol) => Some(protocol), + None => Some(unsafe { rule.Protocol() }.map_err(&err)?), + }; + // Reject port specifications for protocols that don't support them (e.g. ICMP). // This must be checked regardless of whether the protocol itself was changed, // because the caller may only be setting local_ports or remote_ports. - if let Some(protocol) = effective_protocol { - if !protocol_supports_ports(protocol) - && (desired.local_ports.is_some() || desired.remote_ports.is_some()) - { + if let Some(protocol) = effective_protocol + && !protocol_supports_ports(protocol) + && (desired.local_ports.is_some() || desired.remote_ports.is_some()) { return Err(t!("firewall.portsNotAllowed", name = name, protocol = protocol).to_string().into()); } - } if let Some(protocol) = desired.protocol { if let Some(current_protocol) = existing_protocol diff --git a/resources/windows_firewall/src/main.rs b/resources/windows_firewall/src/main.rs index c18093a95..3036ed2f1 100644 --- a/resources/windows_firewall/src/main.rs +++ b/resources/windows_firewall/src/main.rs @@ -19,7 +19,7 @@ const EXIT_INVALID_ARGS: i32 = 1; const EXIT_INVALID_INPUT: i32 = 2; const EXIT_FIREWALL_ERROR: i32 = 3; -fn write_error(message: &str) { +pub(crate) fn write_error(message: &str) { eprintln!("{}", serde_json::json!({ "error": message })); } diff --git a/resources/windows_service/src/service.rs b/resources/windows_service/src/service.rs index dbf425b50..c54d5e979 100644 --- a/resources/windows_service/src/service.rs +++ b/resources/windows_service/src/service.rs @@ -92,10 +92,8 @@ unsafe fn read_service_state( let sizing_result = unsafe { QueryServiceConfigW(service_handle, None, 0, &mut bytes_needed) }; - if let Err(e) = sizing_result { - if e.code() != ERROR_INSUFFICIENT_BUFFER.to_hresult() { - return Err(t!("get.queryConfigFailed", error = e.to_string()).to_string().into()); - } + if let Err(e) = sizing_result && e.code() != ERROR_INSUFFICIENT_BUFFER.to_hresult() { + return Err(t!("get.queryConfigFailed", error = e.to_string()).to_string().into()); } if bytes_needed == 0 { return Err(t!("get.queryConfigFailed", error = "buffer size is 0").to_string().into()); @@ -223,8 +221,8 @@ pub fn get_service(input: &WindowsService) -> Result) -> Result) -> Result continue, // skip services we can't query }; - if let Some(f) = filter { - if !matches_filter(&svc, f) { - continue; - } + if let Some(f) = filter && !matches_filter(&svc, f) { + continue; } results.push(svc); @@ -457,13 +451,11 @@ unsafe fn enumerate_services(scm: SC_HANDLE) -> Result bool { } } - if !ends_with_wildcard { - if let Some(last) = parts.last() { - if !last.is_empty() && !text_lower.ends_with(last) { - return false; - } + if !ends_with_wildcard + && let Some(last) = parts.last() + && !last.is_empty() + && !text_lower.ends_with(last) { + return false; } - } true } @@ -616,12 +607,10 @@ pub fn set_service(input: &WindowsService) -> Result Result<(), Strin } else { return Err("Source file not a resource manifest".to_string()); } - let name_part = type_name.split('/').last().unwrap_or(type_name); + let name_part = type_name.split('/').next_back().unwrap_or(type_name); let output_file = format!("{name_part}.dsc.resource.json"); let output_content = serde_json::to_string_pretty(&resource_json) .map_err(|e| format!("Failed to serialize JSON: {e}"))?; diff --git a/tools/dsctest/src/refresh_env.rs b/tools/dsctest/src/refresh_env.rs index bfe797961..96e4580d5 100644 --- a/tools/dsctest/src/refresh_env.rs +++ b/tools/dsctest/src/refresh_env.rs @@ -40,17 +40,12 @@ impl RefreshEnv { { // Get the environment variable from the registry for current user let hkcu = Hive::CurrentUser.open("Environment", Security::Read).unwrap(); - if let Ok(data) = hkcu.value(&self.name) { - match data { - Data::String(value) | Data::ExpandString(value) => { - return RefreshEnv { - metadata: None, - name: self.name.clone(), - value: value.to_string_lossy(), - }; - } - _ => {} - } + if let Ok(Data::String(value) | Data::ExpandString(value)) = hkcu.value(&self.name) { + return RefreshEnv { + metadata: None, + name: self.name.clone(), + value: value.to_string_lossy(), + }; } RefreshEnv {