diff --git a/dsc/Cargo.lock b/dsc/Cargo.lock index 1e6d65e37..f152cdc77 100644 --- a/dsc/Cargo.lock +++ b/dsc/Cargo.lock @@ -214,9 +214,9 @@ checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32" [[package]] name = "bstr" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" dependencies = [ "memchr", "serde", @@ -543,6 +543,7 @@ dependencies = [ "linked-hash-map", "num-traits", "regex", + "rust-i18n", "schemars", "security_context_lib", "semver", diff --git a/dsc/examples/brew_uninstall.dsc.yaml b/dsc/examples/brew_uninstall.dsc.yaml index d37e6b1c5..801176241 100644 --- a/dsc/examples/brew_uninstall.dsc.yaml +++ b/dsc/examples/brew_uninstall.dsc.yaml @@ -9,7 +9,7 @@ resources: - name: os_check type: Microsoft/OSInfo properties: - family: MacOS + family: macOS - name: brew type: DSC.PackageManagement/Brew properties: diff --git a/dsc/locales/en-us.toml b/dsc/locales/en-us.toml index 2dcd576bd..baa687b6a 100644 --- a/dsc/locales/en-us.toml +++ b/dsc/locales/en-us.toml @@ -42,7 +42,7 @@ foundProcesses = "Found processes" failedToGetPid = "Could not get current process id" currentPid = "Current process id" failedToGetProcess = "Could not get current process" -terminatingSubprocess ="Terminating subprocesses of process" +terminatingSubprocess = "Terminating subprocesses of process" terminatingProcess = "Terminating process" failedTerminatingProcess = "Failed to terminate process" storeMessage = """DSC.exe is a command-line tool and cannot be run directly from the Windows Store or Explorer. @@ -58,11 +58,11 @@ failedToOpenFile = "Failed to open included file" invalidFileContent = "Invalid UTF-8 sequence in included file" invalidFile = "Failed to read the configuration file as YAML or JSON" resolvingParameters = "Resolving parameters from file" -failedParseParametersFile = "Failed to parse parameters file to JSON" -failedResolveParametersFile = "Failed to resolve parameters file" -noParametersFile = "No parameters file found" +failedParseParametersFile = "Failed to parse parameters file or conetnt to JSON" +couldNotReadParametersFile = "Could not read parameters file" invalidPath = "Include path must not contain '..'" failedGetCurrentDirectory = "Failed to get current directory" +noParameters = "No parameters specified" [resource_command] implementedAs = "implemented as" diff --git a/dsc/src/main.rs b/dsc/src/main.rs index 530abebf2..81b739623 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -42,7 +42,7 @@ fn main() { debug!("{}: {}", t!("main.usingDscVersion"), env!("CARGO_PKG_VERSION")); let progress_format = args.progress_format.unwrap_or( ProgressFormat::Default ); - + match args.subcommand { SubCommand::Completer { shell } => { info!("{} {:?}", t!("main.generatingCompleter"), shell); @@ -55,7 +55,7 @@ fn main() { match std::fs::read_to_string(&file_name) { Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), system_root.as_ref(), &as_group, &as_include, progress_format), Err(err) => { - error!("{} '{file_name}': {err}", t!("main.failedToReadParametersFile")); + error!("{} '{file_name}': {err}", t!("main.failedReadingParametersFile")); exit(util::EXIT_INVALID_INPUT); } } @@ -111,7 +111,7 @@ fn terminate_subprocesses(sys: &System, process: &Process) { info!("{}: {:?} {}", t!("main.terminatingProcess"), process.name(), process.pid()); if !process.kill() { - error!("{}: {:?} {}", t!("main.failedTerminateProcess"), process.name(), process.pid()); + error!("{}: {:?} {}", t!("main.failedTerminatingProcess"), process.name(), process.pid()); } } diff --git a/dsc/src/resolve.rs b/dsc/src/resolve.rs index fad893df4..f9f6ea738 100644 --- a/dsc/src/resolve.rs +++ b/dsc/src/resolve.rs @@ -126,7 +126,7 @@ pub fn get_contents(input: &str) -> Result<(Option, String), String> { Some(parameters_json) }, Err(err) => { - return Err(format!("{} '{parameters_file:?}': {err}", t!("resolve.failedResolveParametersFile"))); + return Err(format!("{} '{parameters_file:?}': {err}", t!("resolve.couldNotReadParametersFile"))); } } }, @@ -134,7 +134,7 @@ pub fn get_contents(input: &str) -> Result<(Option, String), String> { let parameters_json = match parse_input_to_json(&text) { Ok(json) => json, Err(err) => { - return Err(format!("{}: {err}", t!("resolve.invalidParametersContent"))); + return Err(format!("{}: {err}", t!("resolve.failedParseParametersFile"))); } }; Some(parameters_json) diff --git a/dsc/tests/dsc.exit_code.tests.ps1 b/dsc/tests/dsc.exit_code.tests.ps1 index 1e7cd37ff..16c27905b 100644 --- a/dsc/tests/dsc.exit_code.tests.ps1 +++ b/dsc/tests/dsc.exit_code.tests.ps1 @@ -8,7 +8,7 @@ Describe 'exit code tests' { } It 'non-zero exit code not in manifest has generic message' { dsc resource get -r Test/ExitCode --input "{ exitCode: 1 }" 2> $TestDrive/tracing.txt - "$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Exit code 1' + "$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'exit code 1' } It 'success exit code executes without error' { $result = dsc resource get -r Test/ExitCode --input "{ exitCode: 0 }" | ConvertFrom-Json diff --git a/dsc/tests/dsc_i18n.tests.ps1 b/dsc/tests/dsc_i18n.tests.ps1 new file mode 100644 index 000000000..1962d3aef --- /dev/null +++ b/dsc/tests/dsc_i18n.tests.ps1 @@ -0,0 +1,56 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +BeforeDiscovery { + $tomls = Get-ChildItem $PSScriptRoot/../../en-us.toml -Recurse -File + $projects = @() + $tomls | ForEach-Object { + $projectName = (Split-Path $_ -Parent | Split-Path -Parent) + $projects += @{ project = $projectName; toml = $_ } + } +} + +Describe 'Internationalization tests' { + It 'Project uses i18n strings from ' -TestCases $projects { + param($project, $toml) + + $i18n = [System.Collections.Hashtable]::new([System.StringComparer]::Ordinal) + $prefix = '' + Get-Content -Path $toml | ForEach-Object { + if ($_ -match '\[(?.*?)\]') { + $prefix = $Matches['prefix'] + } + elseif ($_ -match '^(?\w+)\s?=\s?"(?.*?)"') { + $key = $prefix + '.' + $Matches['key'] + $i18n[$key] = 0 + } + } + + $missing = @() + Get-ChildItem -Recurse -Path $project -Include *.rs -File | ForEach-Object { + # Write-Verbose -Verbose "File: $_" + $line = 0 + Get-Content -Path $_ | ForEach-Object { + $line++ + ($_ | Select-String -Pattern '[^\w]t\!\("(?.*?)".*?\)' -AllMatches).Matches | ForEach-Object { + # write-verbose -verbose "Line: $_" + if ($null -ne $_) { + $key = $_.Groups['key'].Value + if ($i18n.ContainsKey($key)) { + $i18n[$key] = 1 + # write-verbose -verbose "Found on line $line : $key" + } + else { + $missing += $key + # write-verbose -verbose "Missing: $key" + } + } + } + } + } + + $missing | Should -BeNullOrEmpty -Because "The following i18n keys are missing from $toml :`n$($missing | Out-String)" + $unused = $i18n.GetEnumerator() | Where-Object { $_.Value -eq 0 } | ForEach-Object { $_.Key } + $unused | Should -BeNullOrEmpty -Because "The following i18n keys are unused in the project:`n$($unused | Out-String)" + } +} diff --git a/dsc/tests/dsc_resource_list.tests.ps1 b/dsc/tests/dsc_resource_list.tests.ps1 index bd61ba413..b8fd4ff40 100644 --- a/dsc/tests/dsc_resource_list.tests.ps1 +++ b/dsc/tests/dsc_resource_list.tests.ps1 @@ -84,6 +84,6 @@ Describe 'Tests for listing resources' { It 'Invalid adapter returns an error' { $out = dsc resource list --adapter 'foo*' 2>&1 | Out-String $LASTEXITCODE | Should -Be 0 - $out | Should -BeLike "*ERROR*Adapter 'foo`*' not found*" + $out | Should -BeLike "*ERROR*Adapter not found: foo`*" } } diff --git a/dsc/tests/dsc_settings.tests.ps1 b/dsc/tests/dsc_settings.tests.ps1 index d78d87fb8..c4f6b7c56 100644 --- a/dsc/tests/dsc_settings.tests.ps1 +++ b/dsc/tests/dsc_settings.tests.ps1 @@ -48,7 +48,7 @@ Describe 'tests for dsc settings' { } It 'ensure a new tracing value in settings has effect' { - + $script:dscDefaultv1Settings."tracing"."level" = "TRACE" $script:dscDefaultv1Settings | ConvertTo-Json -Depth 90 | Set-Content -Force -Path $script:dscSettingsFilePath @@ -57,11 +57,11 @@ Describe 'tests for dsc settings' { } It 'ensure a new resource_path value in settings has effect' { - + $script:dscDefaultv1Settings."resourcePath"."directories" = @("TestDir1","TestDir2") $script:dscDefaultv1Settings | ConvertTo-Json -Depth 90 | Set-Content -Force -Path $script:dscSettingsFilePath dsc -l debug resource list 2> $TestDrive/tracing.txt - $expectedString = 'Using Resource Path: "TestDir1'+[System.IO.Path]::PathSeparator+'TestDir2' + $expectedString = 'Using Resource Path: TestDir1'+[System.IO.Path]::PathSeparator+'TestDir2' "$TestDrive/tracing.txt" | Should -FileContentMatchExactly $expectedString } @@ -71,7 +71,7 @@ Describe 'tests for dsc settings' { Set-ItResult -Skip -Because "Setting policy requires sudo" return } - + $script:dscDefaultv1Settings."tracing"."level" = "TRACE" $script:dscDefaultv1Settings."resourcePath"."directories" = @("PolicyDir") $script:dscDefaultv1Settings | ConvertTo-Json -Depth 90 | Set-Content -Force -Path $script:policyFilePath @@ -87,23 +87,23 @@ Describe 'tests for dsc settings' { # ensure policy overrides everything dsc -l debug resource list 2> $TestDrive/tracing.txt "$TestDrive/tracing.txt" | Should -FileContentMatchExactly "Trace-level is Trace" - "$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Using Resource Path: "PolicyDir' + "$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Using Resource Path: PolicyDir' # without policy, command-line args have priority Remove-Item -Path $script:policyFilePath dsc -l debug resource list 2> $TestDrive/tracing.txt "$TestDrive/tracing.txt" | Should -FileContentMatchExactly "Trace-level is Debug" - "$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Using Resource Path: "SettingsDir' + "$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Using Resource Path: SettingsDir' # without policy and command-line args, settings file is used dsc resource list 2> $TestDrive/tracing.txt "$TestDrive/tracing.txt" | Should -FileContentMatchExactly "Trace-level is Trace" - "$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Using Resource Path: "SettingsDir' + "$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Using Resource Path: SettingsDir' # without policy and command-line args and settings file, the default settings file is used Remove-Item -Path $script:dscSettingsFilePath dsc resource list 2> $TestDrive/tracing.txt "$TestDrive/tracing.txt" | Should -FileContentMatchExactly "Trace-level is Trace" - "$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Using Resource Path: "Defaultv1SettingsDir' + "$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Using Resource Path: Defaultv1SettingsDir' } } diff --git a/dsc_lib/Cargo.lock b/dsc_lib/Cargo.lock index d78785ae1..b08eb8f46 100644 --- a/dsc_lib/Cargo.lock +++ b/dsc_lib/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -91,7 +91,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -101,7 +101,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -110,6 +110,12 @@ version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arrayvec" version = "0.7.6" @@ -137,6 +143,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base62" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fa474cf7492f9a299ba6019fb99ec673e1739556d48e8a90eabaea282ef0e4" + [[package]] name = "base64" version = "0.22.1" @@ -158,6 +170,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -170,6 +188,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32" +[[package]] +name = "bstr" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -279,7 +307,7 @@ dependencies = [ "lazy_static", "libc", "unicode-width", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -288,6 +316,31 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "darling" version = "0.20.10" @@ -379,6 +432,7 @@ dependencies = [ "linked-hash-map", "num-traits", "regex", + "rust-i18n", "schemars", "security_context_lib", "semver", @@ -400,6 +454,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "email_address" version = "0.2.9" @@ -421,6 +481,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "fancy-regex" version = "0.13.0" @@ -432,6 +502,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fluent-uri" version = "0.3.1" @@ -485,6 +561,36 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -678,6 +784,22 @@ dependencies = [ "utf8_iter", ] +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -737,6 +859,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -786,9 +917,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.168" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" + +[[package]] +name = "libyml" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "64804cc6a5042d4f05379909ba25b503ec04e2c082151d62122d5dcaa274b961" [[package]] name = "linked-hash-map" @@ -796,6 +933,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "litemap" version = "0.7.3" @@ -842,7 +985,7 @@ dependencies = [ "hermit-abi", "libc", "wasi", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -851,12 +994,21 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "cfg_aliases", "libc", ] +[[package]] +name = "normpath" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1044,7 +1196,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -1109,18 +1261,94 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rust-i18n" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039f57d22229db401af3458ca939300178e99e88b938573cea12b7c2b0f09724" +dependencies = [ + "globwalk", + "once_cell", + "regex", + "rust-i18n-macro", + "rust-i18n-support", + "smallvec", +] + +[[package]] +name = "rust-i18n-macro" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde5c022360a2e54477882843d56b6f9bcb4bc62f504b651a2f497f0028d174f" +dependencies = [ + "glob", + "once_cell", + "proc-macro2", + "quote", + "rust-i18n-support", + "serde", + "serde_json", + "serde_yml", + "syn", +] + +[[package]] +name = "rust-i18n-support" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75d2844d36f62b5d6b66f9cf8f8cbdbbbdcdb5fd37a473a9cc2fb45fdcf485d2" +dependencies = [ + "arc-swap", + "base62", + "globwalk", + "itertools", + "lazy_static", + "normpath", + "once_cell", + "proc-macro2", + "regex", + "serde", + "serde_json", + "serde_yml", + "siphasher", + "toml", + "triomphe", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schemars" version = "0.8.21" @@ -1210,6 +1438,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -1223,6 +1460,23 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serde_yml" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e76bab63c3fd98d27c17f9cbce177f64a91f5e69ac04cafe04e1bb25d1dc3c" +dependencies = [ + "indexmap 2.6.0", + "itoa", + "libyml", + "log", + "memchr", + "ryu", + "serde", + "serde_json", + "tempfile", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1247,6 +1501,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "smallvec" version = "1.13.2" @@ -1260,7 +1520,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1303,6 +1563,19 @@ dependencies = [ "syn", ] +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "thiserror" version = "1.0.64" @@ -1373,7 +1646,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1387,6 +1660,40 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.40" @@ -1495,6 +1802,17 @@ dependencies = [ "tree-sitter-language", ] +[[package]] +name = "triomphe" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" +dependencies = [ + "arc-swap", + "serde", + "stable_deref_trait", +] + [[package]] name = "unicode-bidi" version = "0.3.17" @@ -1625,6 +1943,16 @@ dependencies = [ "quote", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1702,6 +2030,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1726,6 +2063,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1790,6 +2136,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/dsc_lib/Cargo.toml b/dsc_lib/Cargo.toml index 049dab8b8..6e847b940 100644 --- a/dsc_lib/Cargo.toml +++ b/dsc_lib/Cargo.toml @@ -20,6 +20,7 @@ jsonschema = { version = "0.23", default-features = false } linked-hash-map = "0.5" num-traits = "0.2" regex = "1.11" +rust-i18n = { version = "3.0" } # reqwest = { version = "0.12.8", features = ["native-tls"], default-features = false } schemars = { version = "0.8", features = ["preserve_order"] } serde = { version = "1.0", features = ["derive"] } diff --git a/dsc_lib/locales/en-us.toml b/dsc_lib/locales/en-us.toml new file mode 100644 index 000000000..cb8cd2905 --- /dev/null +++ b/dsc_lib/locales/en-us.toml @@ -0,0 +1,321 @@ +_version = 1 + +[configure.constraints] +minLengthIsNull = "Parameter '%{name}' has minimum length constraint but is null" +notMinLength = "Parameter '%{name}' has minimum length constraint of %{min_length} but is %{length}" +minLengthNotStringOrArray = "Parameter '%{name}' has minimum length constraint but is not a string or array" + +maxLengthIsNull = "Parameter '%{name}' has maximum length constraint but is null" +maxLengthExceeded = "Parameter '%{name}' has maximum length constraint of %{max_length} but is %{length}" +maxLengthNotStringOrArray = "Parameter '%{name}' has maximum length constraint but is not a string or array" + +minValueIsNull = "Parameter '%{name}' has minimum value constraint but is null" +notMinValue = "Parameter '%{name}' has minimum value constraint of %{min_value} but is %{value}" +minValueNotInteger = "Parameter '%{name}' has minimum value constraint but is not an integer" +maxValueIsNull = "Parameter '%{name}' has maximum value constraint but is null" +notMaxValue = "Parameter '%{name}' has maximum value constraint of %{max_value} but is %{value}" +maxValueNotInteger = "Parameter '%{name}' has maximum value constraint but is not an integer" + +allowedValuesIsNull = "Parameter '%{name}' has allowed values constraint but is null" +notAllowedValue = "Parameter '%{name}' has allowed values constraint but is not in the list of allowed values" +allowedValuesNotStringOrInteger = "Parameter '%{name}' has allowed values constraint but is not a string or integer" + +[configure.dependsOn] +duplicateResource = "Resource named '%{name}' is specified more than once in the configuration" +syntaxIncorrect = "'dependsOn' syntax is incorrect: %{dependency}" +dependencyNotFound = "'dependsOn' resource name '%{dependency_name}' does not exist for resource named '%{resource_name}'" +dependencyTypeMismatch = "'dependsOn' resource type '%{resource_type}' does not match resource type '%{dependency_type}' for resource named '%{resource_name}'" +resourceNotInOrder = "Resource not found in order" +dependencyNotInOrder = "Dependency not found in order" +circularDependency = "Circular dependency detected for resource named '%{resource}'" +invocationOrder = "Resource invocation order" + +[configure.mod] +escapePropertyValues = "Escape returned property values" +nestedArraysNotSupported = "Nested arrays not supported" +arrayElementCouldNotTransformAsString = "Array element could not be transformed as string" +valueCouldNotBeTransformedAsString = "Property value '%{value}' could not be transformed as string" +elevationRequired = "Elevated security context required" +restrictedRequired = "Restricted security context required" +desired = "Desired state: %{state}" +handlesExist = "Resource handles _exist or _exist is true" +whatIfNotSupportedForDelete = "What-if execution not supported for delete" +implementsDelete = "Resource implements delete and _exist is false" +groupNotSupportedForDelete = "Group resources not supported for delete" +deleteNotSupported = "Resource '%{resource}' does not support `delete` and does not handle `_exist` as false" +expectedState = "Expected state: %{state}" +exportInput = "Export input: %{input}" +noParameters = "No parameters defined in configuration and no parameters input" +noParametersDefined = "No parameters defined in configuration" +processingParameter = "Processing parameter '%{name}'" +setDefaultParameter = "Set default parameter '%{name}'" +defaultStringNotDefined = "Default value as string is not defined" +noParametersInput = "No parameters input" +setSecureParameter = "Set secure parameter '%{name}'" +setParameter = "Set parameter '%{name}' to '%{value}'" +parameterNotDefined = "Parameter '%{name}' is not defined in configuration" +noVariables = "No variables defined in configuration" +setVariable = "Set variable '%{name}' to '%{value}'" +parameterNotString = "Parameter '%{name}' is not a string" +parameterNotInteger = "Parameter '%{name}' is not an integer" +parameterNotBoolean = "Parameter '%{name}' is not a boolean" +parameterNotArray = "Parameter '%{name}' is not an array" +parameterNotObject = "Parameter '%{name}' is not an object" +invokePropertyExpressions = "Invoke property expressions" +invokeExpression = "Invoke property expression for %{name}: %{value}" + +[discovery.commandDiscovery] +couldNotReadSetting = "Could not read 'resourcePath' setting" +appendingEnvPath = "Appending PATH to resourcePath" +originalPath = "Original PATH: %{path}" +failedGetEnvPath = "Failed to get PATH environment variable" +exeHomeAlreadyInPath = "Exe home is already in path: %{path}" +addExeHomeToPath = "Adding exe home to path: %{path}" +usingResourcePath = "Using Resource Path: %{path}" +discoverResources = "Discovering resources using filter: %{filter}" +invalidAdapterFilter = "Could not build Regex filter for adapter name" +progressSearching = "Searching for resources" +foundResourceManifest = "Found resource manifest: %{path}" +adapterFound = "Resource adapter '%{adapter}' found" +resourceFound = "Resource '%{resource}' found" + +[dscresources.commandResource] +invokeGet = "Invoking get for '%{resource}'" +invokeGetUsing = "Invoking get '%{resource}' using '%{executable}'" +verifyOutputUsing = "Verifying output of get '%{resource}' using '%{executable}'" +groupGetResponse = "Group get response: %{response}" +failedParseJson = "Failed to parse JSON from get %{executable}|%{stdout}|%{stderr} -> %{err}" +invokeSet = "Invoking set for '%{resource}'" +noPretest = "No pretest, invoking test on '%{resource}'" +syntheticWhatIf = "cannot process what-if execution type, as resource implements pre-test and does not support what-if" +setGetCurrent = "Getting current state for set by invoking get on '%{resource}' using '%{executable}'" +setVerifyGet = "Verifying output of get on '%{resource}' using '%{executable}'" +setVerifyOutput = "Verifying output of %{operation} '%{resource}' using '%{executable}'" +setUnexpectedOutput = "Command did not return expected actual output" +setUnexpectedDiff = "Command did not return expected diff output" +invokeTest = "Invoking test for '%{resource}'" +testSyntheticTest = "Resource '%{resource}' does not implement test, performing synthetic test" +invokeTestUsing = "Invoking test on '%{resource}' using '{executable}'" +testVerifyOutput = "Verifying output of test on '%{resource}' using '%{executable}'" +testGroupTestResponse = "Import resource kind, returning group test response" +testNoActualState = "No actual state returned" +testNoDiff = "No diff properties returned" +invokeDeleteUsing = "Invoking delete on '%{resource}' using '%{executable}'" +invokeValidateConfig = "Invoking validate on '%{resource}' using '%{config}'" +invokeValidateUsing = "Invoking validate on '%{resource}' using '%{executable}'" +exportNotSupported = "Export is not supported by resource '%{resource}'" +exportVerifyOutput = "Verifying output of export on '%{resource}' using '%{executable}'" +resolveNotSupported = "Resolve is not supported by resource '%{resource}'" +invokeResolveUsing = "Invoking resolve on '%{resource}' using '%{executable}'" +processChildStdout = "child process did not have a handle to stdout" +processChildStderr = "child process did not have a handle to stderr" +processChildStdin = "child process did not have a handle to stdin" +processWriteStdin = "could not write to stdin" +processChildId = "Can't get child process id" +processChildExit = "Process '%{executable}' id %{id} exited with code %{code}" +processChildTerminated = "Process '%{executable}' id %{id} terminated by signal" +processTerminated = "Process terminated by signal" +commandInvoke = "Invoking command '%{executable}' with args %{args}" +noArgs = "No args to process" +parseAsEnvVars = "Parsing input as environment variables" +parseAsStdin = "Parsing input as stdin" +noInput = "No input kind specified" +verifyJson = "Verify JSON for '%{resource}'" +validateJson = "Validating against JSON: %{json}" +resourceInvalidJson = "Resource reported input JSON is not valid" +invalidArrayKey = "Unsupported array value for key '%{key}'. Only string and number is supported." +invalidKey = "Unsupported value for key '%{key}'. Only string, bool, number, and array is supported." + +[dscresources.dscresource] +invokeGet = "Invoking get for '%{resource}'" +customResourceNotSupported = "Custom resource not supported" +invokeSet = "Invoking set for '%{resource}'" +invokeTest = "Invoking test for '%{resource}'" +invokeDelete = "Invoking delete for '%{resource}'" +invokeValidate = "Invoking validate for '%{resource}'" +invokeSchema = "Invoking schema for '%{resource}'" +invokeExport = "Invoking export for '%{resource}'" +invokeResolve = "Invoking resolve for '%{resource}'" +subDiff = "diff: sub diff for '%{key}'" +diffArray = "diff: arrays differ for '%{key}'" +diffNotArray = "diff: '%{key}' is not an array" +diffKeyMissing = "diff: key '%{key}' missing" +diffKeyNotObject = "diff: key '%{key}' is not an object" +diffArraySize = "diff: arrays have different lengths" +diffMissingItem = "diff: actual array missing expected item" + +[functions] +invalidArgType = "Invalid argument type" +invalidArguments = "Invalid argument(s)" +unknownFunction = "Unknown function '%{name}'" +noArgsAccepted = "Function '%{name}' does not accept arguments" +invalidArgCount = "Function '%{name}' requires exactly %{count} arguments" +minArgsRequired = "Function '%{name}' requires at least %{count} arguments" +argCountRequired = "Function '%{name}' requires between %{min_args} and %{max_args} arguments" +noArrayArgs = "Function '%{name}' does not accept array arguments, accepted types are: %{accepted_args_string}" +noBooleanArgs = "Function '%{name}' does not accept boolean arguments, accepted types are: %{accepted_args_string}" +noNumberArgs = "Function '%{name}' does not accept number arguments, accepted types are: %{accepted_args_string}" +noObjectArgs = "Function '%{name}' does not accept object arguments, accepted types are: %{accepted_args_string}" +noStringArgs = "Function '%{name}' does not accept string arguments, accepted types are: %{accepted_args_string}" + +[functions.add] +invoked = "add function" + +[functions.concat] +invoked = "concat function" +argsMustBeStrings = "Arguments must all be strings" +argsMustBeArrays = "Arguments must all be arrays" +onlyArraysOfStrings = "Arguments must all be arrays of strings" + +[functions.createArray] +invoked = "createArray function" +argsMustAllBeArrays = "Arguments must all be arrays" +argsMustAllBeIntegers = "Arguments must all be integers" +argsMustAllBeObjects = "Arguments must all be objects" +argsMustAllBeStrings = "Arguments must all be strings" + +[functions.div] +invoked = "div function" +divideByZero = "Cannot divide by zero" + +[functions.envvar] +notFound = "Environment variable not found" + +[functions.int] +invalidInput = "invalid input string" +parseStringError = "unable to parse string to int" +castError = "unable to cast to int" +parseNumError = "unable to parse number to int" + +[functions.max] +emptyArray = "Array cannot be empty" +integersOnly = "Array must contain only integers" +noMax = "Unable to find max value" + +[functions.min] +invoked = "min function" +emptyArray = "Array cannot be empty" +integersOnly = "Input must only contain integers" +noMin = "Unable to find min value" + +[functions.mod] +divideByZero = "Cannot divide by zero" + +[functions.mul] +invoked = "mul function" + +[functions.parameters] +invoked = "parameters function" +traceKey = "parameters key: %{key}" +keyNotString = "Parameter '%{key}' is not a string" +keyNotFound = "Parameter '%{key}' not found in context" + +[functions.path] +traceArgs = "Executing path function with args: %{args}" +argsMustBeStrings = "Arguments must all be strings" + +[functions.reference] +invoked = "reference function" +keyNotFound = "Invalid resourceId or resource has not executed yet: %{key}" + +[functions.resourceId] +incorrectTypeFormat = "Type argument must contain exactly one slash" +invalidFirstArgType = "Invalid argument type for first parameter" +incorrectNameFormat = "Name argument cannot contain a slash" +invalidSecondArgType = "Invalid argument type for second parameter" + +[functions.sub] +invoked = "sub function" + +[functions.systemRoot] +invoked = "systemRoot function" + +[functions.variables] +invoked = "variables function" +keyNotFound = "Variable '%{key}' does not exist or has not been initialized yet" + +[parser.expression] +functionNodeNotFound = "Function node not found" +parsingFunction = "Parsing function '%{name}'" +parsingAccessor = "Parsing accessor '%{name}'" +accessorParsingError = "Error parsing accessor" +parsingMemberAccessor = "Parsing member accessor '%{name}'" +memberNotFound = "Member name not found" +parsingIndexAccessor = "Parsing index accessor '%{index}'" +indexNotFound = "Index value not found" +invalidAccessorKind = "Invalid accessor kind: '%{kind}'" +functionResult = "Function results: %{results}" +evalAccessors = "Evaluating accessors" +memberNameNotFound = "Member '%{member}' not found" +accessOnNonObject = "Member access on non-object value" +expressionResult = "Expression result: %{result}" +indexNotValid = "Index is not a valid number" +indexOutOfBounds = "Index is out of bounds" +indexOnNonArray = "Index access on non-array value" +invalidIndexType = "Invalid index type" + +[parser.functions] +foundErrorNode = "Found error node parsing function" +nameNodeNotFound = "Function name node not found" +functionName = "Function name: '%{name}'" +argIsExpression = "Argument is an expression" +argIsValue = "Argument is a value: '%{value}'" +unknownArgType = "Unknown argument type '%{kind}'" + +[parser] +parsingStatement = "Parsing statement: %{statement}" +failedToParse = "Unable to parse: %{statement}" +failedToParseRoot = "Unable to parse statement root: %{statement}" +invalidStatement = "Invalid statement: %{statement}" +failedToParseStringLiteral = "Unable to parse string literal" +parsingStringLiteral = "Parsing string literal: %{value}" +failedToParseEscapedStringLiteral = "Unable to parse escaped string literal" +parsingEscapedStringLiteral = "Parsing escaped string literal: %{value}" +parsingExpression = "Parsing expression" +unknownExpressionType = "Unknown expression type: %{kind}" + +[dscerror] +adapterNotFound = "Adapter not found" +booleanConversion = "Function boolean argument conversion" +exitCode = "exit code" +commandResource = "Command: Resource" +commandExecutable = "Command: Executable" +manifestDescription = "manifest description" +commandOperation = "Command: Operation" +forExecutable = "for executable" +function = "Function" +error = "error" +integerConversion = "Function integer argument conversion" +invalidConfiguration = "Invalid configuration" +unsupportedManifestVersion = "Unsupported manifest version" +mustBe = "Must be" +invalidFunctionParameterCount = "Invalid function parameter count for" +expected = "expected" +got = "got" +language = "Language" +manifest = "Manifest" +missingManifest = "Missing manifest" +adapterBasedResource = "Adapter based resource" +missingRequires = "missing 'requires' property for resource" +schemaMissing = "Schema missing from manifest" +notImplemented = "Not implemented" +notSupported = "Not supported" +numberConversion = "Number conversion" +operation = "Operation" +parser = "Parser" +progress = "Progress" +resourceNotFound = "Resource not found" +resourceManifestNotFound = "Resource manifest not found" +schema = "Schema" +schemaNotAvailable = "No Schema found and `validate` is not supported" +securityContext = "Security context" +utf8Conversion = "UTF-8 conversion" +unknown = "Unknown" +validation = "Validation" +setting = "Setting" + +[util] +foundSetting = "Found setting '%{name}' in %{path}" +notFoundSetting = "Setting '%{name}' not found in %{path}" +failedToGetExePath = "Can't get 'dsc' executable path" +settingNotFound = "Setting '%{name}' not found" diff --git a/dsc_lib/src/configure/contraints.rs b/dsc_lib/src/configure/constraints.rs similarity index 58% rename from dsc_lib/src/configure/contraints.rs rename to dsc_lib/src/configure/constraints.rs index 486b6cc97..dccd64840 100644 --- a/dsc_lib/src/configure/contraints.rs +++ b/dsc_lib/src/configure/constraints.rs @@ -3,6 +3,7 @@ use crate::configure::config_doc::Parameter; use crate::DscError; +use rust_i18n::t; use serde_json::Value; /// Checks that the given value matches the given parameter length constraints. @@ -25,48 +26,48 @@ pub fn check_length(name: &str, value: &Value, constraint: &Parameter) -> Result if let Some(min_length) = constraint.min_length { if value.is_string() { let Some(value) = value.as_str() else { - return Err(DscError::Validation(format!("Parameter '{name}' has minimum length constraint but is null"))); + return Err(DscError::Validation(t!("configure.constraints.minLengthIsNull", name = name).to_string())); }; if value.len() < usize::try_from(min_length)? { - return Err(DscError::Validation(format!("Parameter '{name}' has minimum length constraint of {min_length} but is {0}", value.len()))); + return Err(DscError::Validation(t!("configure.constraints.notMinLength", name = name, min_length = min_length, length = value.len()).to_string())); } } else if value.is_array() { let Some(value) = value.as_array() else { - return Err(DscError::Validation(format!("Parameter '{name}' has minimum length constraint but is null"))); + return Err(DscError::Validation(t!("configure.constraints.minLengthIsNull", name = name).to_string())); }; if value.len() < usize::try_from(min_length)? { - return Err(DscError::Validation(format!("Parameter '{name}' has minimum length constraint of {min_length} but is {0}", value.len()))); + return Err(DscError::Validation(t!("configure.constraints.notMinLength", name = name, min_length = min_length, length = value.len()).to_string())); } } else { - return Err(DscError::Validation(format!("Parameter '{name}' has minimum length constraint but is not a string or array"))); + return Err(DscError::Validation(t!("configure.constraints.minLengthNotStringOrArray", name = name).to_string())); } } if let Some(max_length) = constraint.max_length { if value.is_string() { let Some(value) = value.as_str() else { - return Err(DscError::Validation(format!("Parameter '{name}' has maximum length constraint but is null"))); + return Err(DscError::Validation(t!("configure.constraints.maxLengthIsNull", name = name).to_string())); }; if value.len() > usize::try_from(max_length)? { - return Err(DscError::Validation(format!("Parameter '{name}' has maximum length constraint of {max_length} but is {0}", value.len()))); + return Err(DscError::Validation(t!("configure.constraints.maxLengthExceeded", name = name, max_length = max_length, length = value.len()).to_string())); } } else if value.is_array() { let Some(value) = value.as_array() else { - return Err(DscError::Validation(format!("Parameter '{name}' has maximum length constraint but is null"))); + return Err(DscError::Validation(t!("configure.constraints.maxLengthIsNull", name = name).to_string())); }; if value.len() > usize::try_from(max_length)? { - return Err(DscError::Validation(format!("Parameter '{name}' has maximum length constraint of {max_length} but is {0}", value.len()))); + return Err(DscError::Validation(t!("configure.constraints.maxLengthExceeded", name = name, max_length = max_length, length = value.len()).to_string())); } } else { - return Err(DscError::Validation(format!("Parameter '{name}' has maximum length constraint but is not a string or array"))); + return Err(DscError::Validation(t!("configure.constraints.maxLengthNotStringOrArray", name = name).to_string())); } } @@ -93,30 +94,30 @@ pub fn check_number_limits(name: &str, value: &Value, constraint: &Parameter) -> if let Some(min_value) = constraint.min_value { if value.is_i64() && value.as_i64().is_some() { let Some(value) = value.as_i64() else { - return Err(DscError::Validation(format!("Parameter '{name}' has minimum value constraint but is null"))); + return Err(DscError::Validation(t!("configure.constraints.minValueIsNull", name = name).to_string())); }; if value < min_value { - return Err(DscError::Validation(format!("Parameter '{name}' has minimum value constraint of {min_value} but is {value}"))); + return Err(DscError::Validation(t!("configure.constraints.notMinValue", name = name, min_value = min_value, value = value).to_string())); } } else { - return Err(DscError::Validation(format!("Parameter '{name}' has minimum value constraint but is not an integer"))); + return Err(DscError::Validation(t!("configure.constraints.minValueNotInteger", name = name).to_string())); } } if let Some(max_value) = constraint.max_value { if value.is_i64() && value.as_i64().is_some() { let Some(value) = value.as_i64() else { - return Err(DscError::Validation(format!("Parameter '{name}' has maximum value constraint but is null"))); + return Err(DscError::Validation(t!("configure.constraints.maxValueIsNull", name = name).to_string())); }; if value > max_value { - return Err(DscError::Validation(format!("Parameter '{name}' has maximum value constraint of {max_value} but is {value}"))); + return Err(DscError::Validation(t!("configure.constraints.notMaxValue", name = name, max_value = max_value, value = value).to_string())); } } else { - return Err(DscError::Validation(format!("Parameter '{name}' has maximum value constraint but is not an integer"))); + return Err(DscError::Validation(t!("configure.constraints.maxValueNotInteger", name = name).to_string())); } } @@ -143,24 +144,24 @@ pub fn check_allowed_values(name: &str, value: &Value, constraint: &Parameter) - if let Some(allowed_values) = &constraint.allowed_values { if value.is_string() && value.as_str().is_some(){ let Some(value) = value.as_str() else { - return Err(DscError::Validation(format!("Parameter '{name}' has allowed values constraint but is null"))); + return Err(DscError::Validation(t!("configure.constraints.allowedValuesIsNull", name = name).to_string())); }; if !allowed_values.contains(&Value::String(value.to_string())) { - return Err(DscError::Validation(format!("Parameter '{name}' has value not in the allowed values list"))); + return Err(DscError::Validation(t!("configure.constraints.notAllowedValue", name = name).to_string())); } } else if value.is_i64() && value.as_i64().is_some() { let Some(value) = value.as_i64() else { - return Err(DscError::Validation(format!("Parameter '{name}' has allowed values constraint but is null"))); + return Err(DscError::Validation(t!("configure.constraints.allowedValuesIsNull", name = name).to_string())); }; if !allowed_values.contains(&Value::Number(value.into())) { - return Err(DscError::Validation(format!("Parameter '{name}' has value not in the allowed values list"))); + return Err(DscError::Validation(t!("configure.constraints.notAllowedValue", name = name).to_string())); } } else { - return Err(DscError::Validation(format!("Parameter '{name}' has allowed values constraint but is not a string or integer"))); + return Err(DscError::Validation(t!("configure.constraints.allowedValuesNotStringOrInteger", name = name).to_string())); } } diff --git a/dsc_lib/src/configure/depends_on.rs b/dsc_lib/src/configure/depends_on.rs index 46a0f0d77..10b653af6 100644 --- a/dsc_lib/src/configure/depends_on.rs +++ b/dsc_lib/src/configure/depends_on.rs @@ -6,9 +6,9 @@ use crate::configure::Configuration; use crate::DscError; use crate::parser::Statement; +use rust_i18n::t; use super::context::Context; - -use tracing::{debug, trace}; +use tracing::debug; /// Gets the invocation order of resources based on their dependencies /// @@ -29,7 +29,7 @@ pub fn get_resource_invocation_order(config: &Configuration, parser: &mut Statem for resource in &config.resources { // validate that the resource isn't specified more than once in the config if config.resources.iter().filter(|r| r.name == resource.name && r.resource_type == resource.resource_type).count() > 1 { - return Err(DscError::Validation(format!("Resource named '{0}' is specified more than once in the configuration", resource.name))); + return Err(DscError::Validation(t!("configure.dependsOn.duplicateResource", resource_name = resource.name).to_string())); } let mut dependency_already_in_order = true; @@ -37,17 +37,17 @@ pub fn get_resource_invocation_order(config: &Configuration, parser: &mut Statem for dependency in depends_on { let statement = parser.parse_and_execute(&dependency, context)?; let Some(string_result) = statement.as_str() else { - return Err(DscError::Validation(format!("'dependsOn' syntax is incorrect: {dependency}"))); + return Err(DscError::Validation(t!("configure.dependsOn.syntaxIncorrect", dependency = dependency).to_string())); }; let (resource_type, resource_name) = get_type_and_name(string_result)?; // find the resource by name let Some(dependency_resource) = config.resources.iter().find(|r| r.name.eq(resource_name)) else { - return Err(DscError::Validation(format!("'dependsOn' resource name '{resource_name}' does not exist for resource named '{0}'", resource.name))); + return Err(DscError::Validation(t!("configure.dependsOn.dependencyNotFound", dependency_name = resource_name, resource_name = resource.name).to_string())); }; // validate the type matches if dependency_resource.resource_type != resource_type { - return Err(DscError::Validation(format!("'dependsOn' resource type '{resource_type}' does not match resource type '{0}' for resource named '{1}'", dependency_resource.resource_type, dependency_resource.name))); + return Err(DscError::Validation(t!("configure.dependsOn.dependencyTypeMismatch", resource_type = resource_type, dependency_type = dependency_resource.resource_type, resource_name = resource.name).to_string())); } // see if the dependency is already in the order if order.iter().any(|r| r.name == resource_name && r.resource_type == resource_type) { @@ -67,16 +67,16 @@ pub fn get_resource_invocation_order(config: &Configuration, parser: &mut Statem continue; }; // check if the order has resource before its dependencies - let resource_index = order.iter().position(|r| r.name == resource.name && r.resource_type == resource.resource_type).ok_or(DscError::Validation("Resource not found in order".to_string()))?; + let resource_index = order.iter().position(|r| r.name == resource.name && r.resource_type == resource.resource_type).ok_or(DscError::Validation(t!("configure.dependsOn.resourceNotInOrder").to_string()))?; for dependency in depends_on { let statement = parser.parse_and_execute(dependency, context)?; let Some(string_result) = statement.as_str() else { - return Err(DscError::Validation(format!("'dependsOn' syntax is incorrect: {dependency}"))); + return Err(DscError::Validation(t!("configure.dependsOn.syntaxIncorrect", dependency = dependency).to_string())); }; let (resource_type, resource_name) = get_type_and_name(string_result)?; - let dependency_index = order.iter().position(|r| r.name == resource_name && r.resource_type == resource_type).ok_or(DscError::Validation("Dependency not found in order".to_string()))?; + let dependency_index = order.iter().position(|r| r.name == resource_name && r.resource_type == resource_type).ok_or(DscError::Validation(t!("configure.dependsOn.dependencyNotInOrder").to_string()))?; if resource_index < dependency_index { - return Err(DscError::Validation(format!("Circular dependency detected for resource named '{0}'", resource.name))); + return Err(DscError::Validation(t!("configure.dependsOn.circularDependency", resource_name = resource.name).to_string())); } } } @@ -87,14 +87,14 @@ pub fn get_resource_invocation_order(config: &Configuration, parser: &mut Statem order.push(resource.clone()); } - trace!("Resource invocation order: {0:?}", order); + debug!("{}: {order:?}", t!("configure.dependsOn.invocationOrder")); Ok(order) } fn get_type_and_name(statement: &str) -> Result<(&str, &str), DscError> { let parts: Vec<&str> = statement.split(':').collect(); if parts.len() != 2 { - return Err(DscError::Validation(format!("'dependsOn' syntax is incorrect: {statement}"))); + return Err(DscError::Validation(t!("configure.dependsOn.syntaxIncorrect", dependency = statement).to_string())); } Ok((parts[0], parts[1])) } diff --git a/dsc_lib/src/configure/mod.rs b/dsc_lib/src/configure/mod.rs index 7b885b63e..809a2b625 100644 --- a/dsc_lib/src/configure/mod.rs +++ b/dsc_lib/src/configure/mod.rs @@ -18,8 +18,9 @@ use self::context::Context; use self::config_doc::{Configuration, DataType, MicrosoftDscMetadata, Operation, SecurityContextKind}; use self::depends_on::get_resource_invocation_order; use self::config_result::{ConfigurationExportResult, ConfigurationGetResult, ConfigurationSetResult, ConfigurationTestResult}; -use self::contraints::{check_length, check_number_limits, check_allowed_values}; +use self::constraints::{check_length, check_number_limits, check_allowed_values}; use indicatif::ProgressStyle; +use rust_i18n::t; use security_context_lib::{SecurityContext, get_security_context}; use serde_json::{Map, Value}; use std::path::PathBuf; @@ -28,7 +29,7 @@ use tracing::{debug, info, trace}; pub mod context; pub mod config_doc; pub mod config_result; -pub mod contraints; +pub mod constraints; pub mod depends_on; pub mod parameters; @@ -78,7 +79,7 @@ pub fn add_resource_export_results_to_configuration(resource: &DscResource, adap // for values returned by resources, they may look like expressions, so we make sure to escape them in case // they are re-used to apply configuration fn escape_property_values(properties: &Map) -> Result>, DscError> { - debug!("Escape returned property values"); + debug!("{}", t!("configure.mod.escapePropertyValues")); let mut result: Map = Map::new(); for (name, value) in properties { match value { @@ -97,12 +98,12 @@ fn escape_property_values(properties: &Map) -> Result { - return Err(DscError::Parser("Nested arrays not supported".to_string())); + return Err(DscError::Parser(t!("configure.mod.nestedArraysNotSupported").to_string())); }, Value::String(_) => { // use as_str() so that the enclosing quotes are not included for strings let Some(statement) = element.as_str() else { - return Err(DscError::Parser("Array element could not be transformed as string".to_string())); + return Err(DscError::Parser(t!("configure.mod.arrayElementCouldNotTransformAsString").to_string())); }; if statement.starts_with('[') && statement.ends_with(']') { result_array.push(Value::String(format!("[{statement}"))); @@ -120,7 +121,7 @@ fn escape_property_values(properties: &Map) -> Result { // use as_str() so that the enclosing quotes are not included for strings let Some(statement) = value.as_str() else { - return Err(DscError::Parser(format!("Property value '{value}' could not be transformed as string"))); + return Err(DscError::Parser(t!("configure.mod.valueCouldNotBeTransformedAsString", value = value).to_string())); }; if statement.starts_with('[') && statement.ends_with(']') { result.insert(name.clone(), Value::String(format!("[{statement}"))); @@ -138,7 +139,7 @@ fn escape_property_values(properties: &Map) -> Result Result { let mut pb_span = ProgressBar::new(progress_format == ProgressFormat::Json); - + pb_span.pb_set_style(&ProgressStyle::with_template( "{spinner:.green} [{elapsed_precise:.cyan}] [{bar:40.cyan/blue}] {pos:>7}/{len:7} {msg:.yellow}" )?); @@ -178,12 +179,12 @@ fn check_security_context(metadata: Option<&Metadata>) -> Result<(), DscError> { }, SecurityContextKind::Elevated => { if get_security_context() != SecurityContext::Admin { - return Err(DscError::SecurityContext("Elevated security context required".to_string())); + return Err(DscError::SecurityContext(t!("configure.mod.elevationRequired").to_string())); } }, SecurityContextKind::Restricted => { if get_security_context() != SecurityContext::User { - return Err(DscError::SecurityContext("Restricted security context required".to_string())); + return Err(DscError::SecurityContext(t!("configure.mod.restrictedRequired").to_string())); } }, } @@ -328,22 +329,22 @@ impl Configurator { }; let desired = add_metadata(&dsc_resource.kind, properties)?; - trace!("desired: {desired}"); + trace!("{}", t!("configure.mod.desired", state = desired)); let start_datetime; let end_datetime; let set_result; if exist || dsc_resource.capabilities.contains(&Capability::SetHandlesExist) { - debug!("Resource handles _exist or _exist is true"); + debug!("{}", t!("configure.mod.handlesExist")); start_datetime = chrono::Local::now(); set_result = dsc_resource.set(&desired, skip_test, &self.context.execution_type)?; end_datetime = chrono::Local::now(); } else if dsc_resource.capabilities.contains(&Capability::Delete) { if self.context.execution_type == ExecutionKind::WhatIf { // TODO: add delete what-if support - return Err(DscError::NotSupported("What-if execution not supported for delete".to_string())); + return Err(DscError::NotSupported(t!("configure.mod.whatIfNotSupportedForDelete").to_string())); } - debug!("Resource implements delete and _exist is false"); + debug!("{}", t!("configure.mod.implementsDelete")); let before_result = dsc_resource.get(&desired)?; start_datetime = chrono::Local::now(); dsc_resource.delete(&desired)?; @@ -352,7 +353,7 @@ impl Configurator { set_result = match before_result { GetResult::Resource(before_response) => { let GetResult::Resource(after_result) = after_result else { - return Err(DscError::NotSupported("Group resources not supported for delete".to_string())) + return Err(DscError::NotSupported(t!("configure.mod.groupNotSupportedForDelete").to_string())) }; let before_value = serde_json::to_value(&before_response.actual_state)?; let after_value = serde_json::to_value(&after_result.actual_state)?; @@ -363,12 +364,12 @@ impl Configurator { }) }, GetResult::Group(_) => { - return Err(DscError::NotSupported("Group resources not supported for delete".to_string())); + return Err(DscError::NotSupported(t!("configure.mod.groupNotSupportedForDelete").to_string())) }, }; end_datetime = chrono::Local::now(); } else { - return Err(DscError::NotImplemented(format!("Resource '{}' does not support `delete` and does not handle `_exist` as false", resource.resource_type))); + return Err(DscError::NotImplemented(t!("configure.mod.deleteNotSupported", resource = resource.resource_type).to_string())); } self.context.outputs.insert(format!("{}:{}", resource.resource_type, resource.name), serde_json::to_value(&set_result)?); @@ -420,7 +421,7 @@ impl Configurator { }; debug!("resource_type {}", &resource.resource_type); let expected = add_metadata(&dsc_resource.kind, properties)?; - trace!("expected: {expected}"); + trace!("{}", t!("configure.mod.expectedState", state = expected)); let start_datetime = chrono::Local::now(); let test_result = dsc_resource.test(&expected)?; let end_datetime = chrono::Local::now(); @@ -474,7 +475,7 @@ impl Configurator { return Err(DscError::ResourceNotFound(resource.resource_type.clone())); }; let input = add_metadata(&dsc_resource.kind, properties)?; - trace!("input: {input}"); + trace!("{}", t!("configure.mod.exportInput", input = input)); add_resource_export_results_to_configuration(dsc_resource, Some(dsc_resource), &mut conf, input.as_str())?; } @@ -513,22 +514,22 @@ impl Configurator { // set default parameters first let Some(parameters) = &config.parameters else { if parameters_input.is_none() { - info!("No parameters defined in configuration and no parameters input"); + info!("{}", t!("configure.mod.noParameters")); return Ok(()); } - return Err(DscError::Validation("No parameters defined in configuration".to_string())); + return Err(DscError::Validation(t!("configure.mod.noParametersDefined").to_string())); }; for (name, parameter) in parameters { - debug!("Processing parameter '{name}'"); + debug!("{}", t!("configure.mod.processingParameter", name = name)); if let Some(default_value) = ¶meter.default_value { - debug!("Set default parameter '{name}'"); + debug!("{}", t!("configure.mod.setDefaultParameter", name = name)); // default values can be expressions let value = if default_value.is_string() { if let Some(value) = default_value.as_str() { self.statement_parser.parse_and_execute(value, &self.context)? } else { - return Err(DscError::Parser("Default value as string is not defined".to_string())); + return Err(DscError::Parser(t!("configure.mod.defaultStringNotDefined").to_string())); } } else { default_value.clone() @@ -539,14 +540,14 @@ impl Configurator { } let Some(parameters_input) = parameters_input else { - debug!("No parameters input"); + debug!("{}", t!("configure.mod.noParametersInput")); return Ok(()); }; trace!("parameters_input: {parameters_input}"); let parameters: HashMap = serde_json::from_value::(parameters_input.clone())?.parameters; let Some(parameters_constraints) = &config.parameters else { - return Err(DscError::Validation("No parameters defined in configuration".to_string())); + return Err(DscError::Validation(t!("configure.mod.noParametersDefined").to_string())); }; for (name, value) in parameters { if let Some(constraint) = parameters_constraints.get(&name) { @@ -559,9 +560,9 @@ impl Configurator { Configurator::validate_parameter_type(&name, &value, &constraint.parameter_type)?; if constraint.parameter_type == DataType::SecureString || constraint.parameter_type == DataType::SecureObject { - info!("Set secure parameter '{name}'"); + info!("{}", t!("configure.mod.setSecureParameter", name = name)); } else { - info!("Set parameter '{name}' to '{value}'"); + info!("{}", t!("configure.mod.setParameter", name = name, value = value)); } self.context.parameters.insert(name.clone(), (value.clone(), constraint.parameter_type.clone())); @@ -573,7 +574,7 @@ impl Configurator { } } else { - return Err(DscError::Validation(format!("Parameter '{name}' not defined in configuration"))); + return Err(DscError::Validation(t!("configure.mod.parameterNotDefined", name = name).to_string())); } } Ok(()) @@ -581,7 +582,7 @@ impl Configurator { fn set_variables(&mut self, config: &Configuration) -> Result<(), DscError> { let Some(variables) = &config.variables else { - debug!("No variables defined in configuration"); + debug!("{}", t!("configure.mod.noVariables")); return Ok(()); }; @@ -592,7 +593,7 @@ impl Configurator { else { value.clone() }; - info!("Set variable '{name}' to '{new_value}'"); + info!("{}", t!("configure.mod.setVariable", name = name, value = new_value)); self.context.variables.insert(name.to_string(), new_value); } Ok(()) @@ -620,27 +621,27 @@ impl Configurator { match parameter_type { DataType::String | DataType::SecureString => { if !value.is_string() { - return Err(DscError::Validation(format!("Parameter '{name}' is not a string"))); + return Err(DscError::Validation(t!("configure.mod.parameterNotString", name = name).to_string())); } }, DataType::Int => { if !value.is_i64() { - return Err(DscError::Validation(format!("Parameter '{name}' is not an integer"))); + return Err(DscError::Validation(t!("configure.mod.parameterNotInteger", name = name).to_string())); } }, DataType::Bool => { if !value.is_boolean() { - return Err(DscError::Validation(format!("Parameter '{name}' is not a boolean"))); + return Err(DscError::Validation(t!("configure.mod.parameterNotBoolean", name = name).to_string())); } }, DataType::Array => { if !value.is_array() { - return Err(DscError::Validation(format!("Parameter '{name}' is not an array"))); + return Err(DscError::Validation(t!("configure.mod.parameterNotArray", name = name).to_string())); } }, DataType::Object | DataType::SecureObject => { if !value.is_object() { - return Err(DscError::Validation(format!("Parameter '{name}' is not an object"))); + return Err(DscError::Validation(t!("configure.mod.parameterNotObject", name = name).to_string())); } }, } @@ -660,7 +661,7 @@ impl Configurator { } fn invoke_property_expressions(&mut self, properties: Option<&Map>) -> Result>, DscError> { - debug!("Invoke property expressions"); + debug!("{}", t!("configure.mod.invokePropertyExpressions")); if properties.is_none() { return Ok(None); } @@ -668,7 +669,7 @@ impl Configurator { let mut result: Map = Map::new(); if let Some(properties) = properties { for (name, value) in properties { - trace!("Invoke property expression for {name}: {value}"); + trace!("{}", t!("configure.mod.invokeExpression", name = name, value = value)); match value { Value::Object(object) => { let value = self.invoke_property_expressions(Some(object))?; @@ -685,16 +686,16 @@ impl Configurator { continue; }, Value::Array(_) => { - return Err(DscError::Parser("Nested arrays not supported".to_string())); + return Err(DscError::Parser(t!("configure.mod.nestedArraysNotSupported").to_string())); }, Value::String(_) => { // use as_str() so that the enclosing quotes are not included for strings let Some(statement) = element.as_str() else { - return Err(DscError::Parser("Array element could not be transformed as string".to_string())); + return Err(DscError::Parser(t!("configure.mod.arrayElementCouldNotTransformAsString").to_string())); }; let statement_result = self.statement_parser.parse_and_execute(statement, &self.context)?; let Some(string_result) = statement_result.as_str() else { - return Err(DscError::Parser("Array element could not be transformed as string".to_string())); + return Err(DscError::Parser(t!("configure.mod.arrayElementCouldNotTransformAsString").to_string())); }; result_array.push(Value::String(string_result.to_string())); } @@ -708,7 +709,7 @@ impl Configurator { Value::String(_) => { // use as_str() so that the enclosing quotes are not included for strings let Some(statement) = value.as_str() else { - return Err(DscError::Parser(format!("Property value '{value}' could not be transformed as string"))); + return Err(DscError::Parser(t!("configure.mod.valueCouldNotBeTransformedAsString", value = value).to_string())); }; let statement_result = self.statement_parser.parse_and_execute(statement, &self.context)?; if let Some(string_result) = statement_result.as_str() { diff --git a/dsc_lib/src/discovery/command_discovery.rs b/dsc_lib/src/discovery/command_discovery.rs index 7162bedbe..f763e1246 100644 --- a/dsc_lib/src/discovery/command_discovery.rs +++ b/dsc_lib/src/discovery/command_discovery.rs @@ -11,6 +11,7 @@ use crate::util::ProgressFormat; use indicatif::ProgressStyle; use linked_hash_map::LinkedHashMap; use regex::RegexBuilder; +use rust_i18n::t; use semver::Version; use serde::Deserialize; use std::collections::{BTreeMap, HashSet, HashMap}; @@ -86,7 +87,7 @@ impl CommandDiscovery { } } - Err(DscError::Setting("Could not read 'resourcePath' setting".to_string())) + Err(DscError::Setting(t!("discovery.commandDiscovery.couldNotReadSetting").to_string())) } fn get_resource_paths() -> Result, DscError> @@ -108,7 +109,7 @@ impl CommandDiscovery { let dsc_resource_path = env::var_os("DSC_RESOURCE_PATH"); if resource_path_setting.allow_env_override && dsc_resource_path.is_some(){ let value = dsc_resource_path.unwrap(); - debug!("Using DSC_RESOURCE_PATH: {:?}", value.to_string_lossy()); + debug!("DSC_RESOURCE_PATH: {:?}", value.to_string_lossy()); using_custom_path = true; paths.append(&mut env::split_paths(&value).collect::>()); } else { @@ -118,14 +119,14 @@ impl CommandDiscovery { } if resource_path_setting.append_env_path { - debug!("Appending PATH to resourcePath"); + debug!("{}", t!("discovery.commandDiscovery.appendingEnvPath")); match env::var_os("PATH") { Some(value) => { - trace!("Original PATH: {:?}", value.to_string_lossy()); + trace!("{}", t!("discovery.commandDiscovery.originalPath", path = value.to_string_lossy())); paths.append(&mut env::split_paths(&value).collect::>()); }, None => { - return Err(DscError::Operation("Failed to get PATH environment variable".to_string())); + return Err(DscError::Operation(t!("discovery.commandDiscovery.failedGetEnvPath").to_string())); } } } @@ -140,9 +141,9 @@ impl CommandDiscovery { if let Some(exe_home) = get_exe_path()?.parent() { let exe_home_pb = exe_home.to_path_buf(); if paths.contains(&exe_home_pb) { - trace!("Exe home is already in path: {}", exe_home.to_string_lossy()); + trace!("{}", t!("discovery.commandDiscovery.exeHomeAlreadyInPath", path = exe_home.to_string_lossy())); } else { - trace!("Adding exe home to path: {}", exe_home.to_string_lossy()); + trace!("{}", t!("discovery.commandDiscovery.addExeHomeToPath", path = exe_home.to_string_lossy())); paths.push(exe_home_pb); if let Ok(new_path) = env::join_paths(paths.clone()) { @@ -153,7 +154,7 @@ impl CommandDiscovery { }; if let Ok(final_resource_path) = env::join_paths(paths.clone()) { - debug!("Using Resource Path: {:?}", final_resource_path.to_string_lossy()); + debug!("{}", t!("discovery.commandDiscovery.usingResourcePath", path = final_resource_path.to_string_lossy())); } Ok(paths) @@ -169,21 +170,21 @@ impl Default for CommandDiscovery { impl ResourceDiscovery for CommandDiscovery { fn discover_resources(&mut self, filter: &str) -> Result<(), DscError> { - info!("Discovering resources using filter: {filter}"); + info!("{}", t!("discovery.commandDiscovery.discoverResources", filter = filter)); let regex_str = convert_wildcard_to_regex(filter); debug!("Using regex {regex_str} as filter for adapter name"); let mut regex_builder = RegexBuilder::new(®ex_str); regex_builder.case_insensitive(true); let Ok(regex) = regex_builder.build() else { - return Err(DscError::Operation("Could not build Regex filter for adapter name".to_string())); + return Err(DscError::Operation(t!("discovery.commandDiscovery.invalidAdapterFilter").to_string())); }; let pb_span = warn_span!(""); pb_span.pb_set_style(&ProgressStyle::with_template( "{spinner:.green} [{elapsed_precise:.cyan}] [{bar:40.cyan/blue}] {pos:>7}/{len:7} {msg:.yellow}" )?); - pb_span.pb_set_message("Searching for resources"); + pb_span.pb_set_message(t!("discovery.commandDiscovery.progressSearching").to_string().as_str()); let _ = pb_span.enter(); let mut resources = BTreeMap::>::new(); @@ -208,7 +209,7 @@ impl ResourceDiscovery for CommandDiscovery { if file_name_lowercase.ends_with(".dsc.resource.json") || file_name_lowercase.ends_with(".dsc.resource.yaml") || file_name_lowercase.ends_with(".dsc.resource.yml") { - trace!("Found resource manifest: {path:?}"); + trace!("{}", t!("discovery.commandDiscovery.foundResourceManifest", path = path.to_string_lossy())); let resource = match load_manifest(&path) { Ok(r) => r, @@ -226,10 +227,10 @@ impl ResourceDiscovery for CommandDiscovery { if let Some(ref manifest) = resource.manifest { let manifest = import_manifest(manifest.clone())?; if manifest.kind == Some(Kind::Adapter) { - trace!("Resource adapter '{}' found", resource.type_name); + trace!("{}", t!("discovery.commandDiscovery.adapterFound", adapter = resource.type_name)); insert_resource(&mut adapters, &resource, true); } else { - trace!("Resource '{}' found", resource.type_name); + trace!("{}", t!("discovery.commandDiscovery.resourceFound", resource = resource.type_name)); insert_resource(&mut resources, &resource, true); } } diff --git a/dsc_lib/src/dscerror.rs b/dsc_lib/src/dscerror.rs index e2c3b631a..c11ebffd9 100644 --- a/dsc_lib/src/dscerror.rs +++ b/dsc_lib/src/dscerror.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use rust_i18n::t; use std::str::Utf8Error; use indicatif::style::TemplateError; @@ -10,40 +11,40 @@ use tree_sitter::LanguageError; #[derive(Error, Debug)] pub enum DscError { - #[error("Adapter '{0}' not found")] + #[error("{t}: {0}", t = t!("dscerror.adapterNotFound"))] AdapterNotFound(String), - #[error("Function boolean argument conversion error: {0}")] + #[error("{t}: {0}", t = t!("dscerror.booleanConversion"))] BooleanConversion(#[from] std::str::ParseBoolError), - #[error("Command: Resource '{0}' [Exit code {1}] {2}")] + #[error("{t} '{0}' [{t2} {1}] {2}", t = t!("dscerror.commandResource"), t2 = t!("dscerror.exitCode"))] Command(String, i32, String), - #[error("Command: Executable '{0}' [Exit code {1}] {2}")] + #[error("{t} '{0}' [{t2} {1}] {2}", t = t!("dscerror.commandExecutable"), t2 = t!("dscerror.exitCode"))] CommandExit(String, i32, String), - #[error("Command: Resource '{0}' [Exit code {1}] manifest description: {2}")] + #[error("{t} '{0}' [{t2} {1}] {t3}: {2}", t = t!("dscerror.commandResource"), t2 = t!("dscerror.exitCode"), t3 = t!("dscerror.manifestDescription"))] CommandExitFromManifest(String, i32, String), - #[error("CommandOperation: {0} for executable '{1}'")] + #[error("{t} {0} {t2} '{1}'", t = t!("dscerror.commandOperation"), t2 = t!("dscerror.forExecutable"))] CommandOperation(String, String), - #[error("Function '{0}' error: {1}")] + #[error("{t} '{0}' {t2}: {1}", t = t!("dscerror.function"), t2 = t!("dscerror.error"))] Function(String, String), - #[error("Function '{0}' error: {1}")] + #[error("{t} '{0}' {t2}: {1}", t = t!("dscerror.function"), t2 = t!("dscerror.error"))] FunctionArg(String, String), - #[error("Function integer argument conversion error: {0}")] + #[error("{t}: {0}", t = t!("dscerror.integerConversion"))] IntegerConversion(#[from] std::num::ParseIntError), - #[error("Invalid configuration:\n{0}")] + #[error("{t}:\n{0}", t = t!("dscerror.invalidConfiguration"))] InvalidConfiguration(String), - #[error("Unsupported manifest version: {0}. Must be: {1}")] + #[error("{t}: {0}. {t2}: {1}", t = t!("dscerror.unsupportedManifestVersion"), t2 = t!("dscerror.mustBe"))] InvalidManifestSchemaVersion(String, String), - #[error("Invalid function parameter count for '{0}', expected {1}, got {2}")] + #[error("{t} '{0}', {t2} {1}, {t3} {2}", t = t!("dscerror.invalidFunctionParameterCount"), t2 = t!("dscerror.expected"), t3 = t!("dscerror.got"))] InvalidFunctionParameterCount(String, usize, usize), #[error("IO: {0}")] @@ -52,72 +53,72 @@ pub enum DscError { #[error("JSON: {0}")] Json(#[from] serde_json::Error), - #[error("Language: {0}")] + #[error("{t}: {0}", t = t!("dscerror.language"))] Language(#[from] LanguageError), - #[error("Manifest: {0}\nJSON: {1}")] + #[error("{t}: {0}\nJSON: {1}", t = t!("dscerror.manifest"))] Manifest(String, serde_json::Error), - #[error("Manifest: {0}\nYAML: {1}")] + #[error("{t}: {0}\nYAML: {1}", t = t!("dscerror.manifest"))] ManifestYaml(String, serde_yaml::Error), - #[error("Missing manifest: {0}")] + #[error("{t}: {0}", t = t!("dscerror.missingManifest"))] MissingManifest(String), - #[error("Adapter-based resource '{0}' missing 'requires' property for resource '{1}'")] + #[error("{t} '{0}' {t2} '{1}'", t = t!("dscerror.adapterBasedResource"), t2 = t!("dscerror.missingRequires"))] MissingRequires(String, String), - #[error("Schema missing from manifest: {0}")] + #[error("{t}: {0}", t = t!("dscerror.schemaMissing"))] MissingSchema(String), - #[error("Not implemented: {0}")] + #[error("{t}: {0}", t = t!("dscerror.notImplemented"))] NotImplemented(String), - #[error("Not supported: {0}")] + #[error("{t}: {0}", t = t!("dscerror.notSupported"))] NotSupported(String), - #[error("Number conversion error: {0}")] + #[error("{t}: {0}", t = t!("dscerror.numberConversion"))] NumberConversion(#[from] std::num::TryFromIntError), - #[error("Operation: {0}")] + #[error("{t}: {0}", t = t!("dscerror.operation"))] Operation(String), - #[error("Parser: {0}")] + #[error("{t}: {0}", t = t!("dscerror.parser"))] Parser(String), - #[error("Progress: {0}")] + #[error("{t}: {0}", t = t!("dscerror.progress"))] Progress(#[from] TemplateError), - #[error("Resource not found: {0}")] + #[error("{t}: {0}", t = t!("dscerror.resourceNotFound"))] ResourceNotFound(String), - #[error("Resource manifest not found: {0}")] + #[error("{t}: {0}", t = t!("dscerror.resourceManifestNotFound"))] ResourceManifestNotFound(String), - #[error("Schema: {0}")] + #[error("{t}: {0}", t = t!("dscerror.schema"))] Schema(String), - #[error("No Schema found and `validate` is not supported: {0}")] + #[error("{t}: {0}", t = t!("dscerror.schemaNotAvailable"))] SchemaNotAvailable(String), - #[error("Security context: {0}")] + #[error("{t}: {0}", t = t!("dscerror.securityContext"))] SecurityContext(String), - #[error("Utf-8 conversion error: {0}")] + #[error("{t}: {0}", t = t!("dscerror.utf8Conversion"))] Utf8Conversion(#[from] Utf8Error), - #[error("Unknown: {code:?} {message:?}")] + #[error("{t}: {code:?} {message:?}", t = t!("dscerror.unknown"))] Unknown { code: i32, message: String, }, - #[error("Validation: {0}")] + #[error("{t}: {0}", t = t!("dscerror.validation"))] Validation(String), #[error("YAML: {0}")] Yaml(#[from] serde_yaml::Error), - #[error("Setting: {0}")] + #[error("{t}: {0}", t = t!("dscerror.setting"))] Setting(String), } diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs index 556cb73ee..aea941764 100644 --- a/dsc_lib/src/dscresources/command_resource.rs +++ b/dsc_lib/src/dscresources/command_resource.rs @@ -3,6 +3,7 @@ use clap::ValueEnum; use jsonschema::Validator; +use rust_i18n::t; use serde::Deserialize; use serde_json::Value; use std::{collections::HashMap, env, process::Stdio}; @@ -25,7 +26,7 @@ pub const EXIT_PROCESS_TERMINATED: i32 = 0x102; /// /// Error returned if the resource does not successfully get the current state pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Result { - debug!("Invoking get for '{}'", &resource.resource_type); + debug!("{}", t!("dscresources.commandResource.invokeGet", resource = &resource.resource_type)); let mut command_input = CommandInput { env: None, stdin: None }; let Some(get) = &resource.get else { return Err(DscError::NotImplemented("get".to_string())); @@ -36,21 +37,21 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul command_input = get_command_input(get.input.as_ref(), filter)?; } - info!("Invoking get '{}' using '{}'", &resource.resource_type, &get.executable); + info!("{}", t!("dscresources.commandResource.invokeGetUsing", resource = &resource.resource_type, executable = &get.executable)); let (_exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?; if resource.kind == Some(Kind::Resource) { - debug!("Verifying output of get '{}' using '{}'", &resource.resource_type, &get.executable); + debug!("{}", t!("dscresources.commandResource.verifyOutputUsing", resource = &resource.resource_type, executable = &get.executable)); verify_json(resource, cwd, &stdout)?; } let result: GetResult = if let Ok(group_response) = serde_json::from_str::>(&stdout) { - trace!("Group get response: {:?}", &group_response); + trace!("{}", t!("dscresources.commandResource.groupGetResponse", response = &group_response : {:?})); GetResult::Group(group_response) } else { let result: Value = match serde_json::from_str(&stdout) { Ok(r) => {r}, Err(err) => { - return Err(DscError::Operation(format!("Failed to parse JSON from get {}|{}|{} -> {err}", &get.executable, stdout, stderr))) + return Err(DscError::Operation(t!("dscresources.commandResource.failedParseJson", executable = &get.executable, stdout = stdout, stderr = stderr, err = err).to_string())) } }; GetResult::Resource(ResourceGetResponse{ @@ -74,7 +75,7 @@ pub fn invoke_get(resource: &ResourceManifest, cwd: &str, filter: &str) -> Resul /// Error returned if the resource does not successfully set the desired state #[allow(clippy::too_many_lines)] pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result { - debug!("Invoking set for '{}'", &resource.resource_type); + debug!("{}", t!("dscresources.commandResource.invokeSet", resource = &resource.resource_type)); let operation_type: String; let mut is_synthetic_what_if = false; let set_method = match execution_type { @@ -99,7 +100,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te // if resource doesn't implement a pre-test, we execute test first to see if a set is needed if !skip_test && set.pre_test != Some(true) { - info!("No pretest, invoking test {}", &resource.resource_type); + info!("{}", t!("dscresources.commandResource.noPretest", resource = &resource.resource_type)); let test_result = invoke_test(resource, cwd, desired)?; if is_synthetic_what_if { return Ok(test_result.into()); @@ -128,7 +129,7 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te } if is_synthetic_what_if { - return Err(DscError::NotImplemented("cannot process what-if execution type, as resource implements pre-test and does not support what-if".to_string())); + return Err(DscError::NotImplemented(t!("dscresources.commandResource.syntheticWhatIf").to_string())); } let Some(get) = &resource.get else { @@ -137,11 +138,11 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te let args = process_args(get.args.as_ref(), desired); let command_input = get_command_input(get.input.as_ref(), desired)?; - info!("Getting current state for set by invoking get '{}' using '{}'", &resource.resource_type, &get.executable); + info!("{}", t!("dscresources.commandResource.setGetCurrent", resource = &resource.resource_type, executable = &get.executable)); let (exit_code, stdout, stderr) = invoke_command(&get.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?; if resource.kind == Some(Kind::Resource) { - debug!("Verifying output of get '{}' using '{}'", &resource.resource_type, &get.executable); + debug!("{}", t!("dscresources.commandResource.setVerifyGet", resource = &resource.resource_type, executable = &get.executable)); verify_json(resource, cwd, &stdout)?; } @@ -174,14 +175,14 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te Some(ReturnKind::State) => { if resource.kind == Some(Kind::Resource) { - debug!("Verifying output of {} '{}' using '{}'", operation_type, &resource.resource_type, &set.executable); + debug!("{}", t!("dscresources.commandResource.setVerifyOutput", operation = operation_type, resource = &resource.resource_type, executable = &set.executable)); verify_json(resource, cwd, &stdout)?; } let actual_value: Value = match serde_json::from_str(&stdout){ Result::Ok(r) => {r}, Result::Err(err) => { - return Err(DscError::Operation(format!("Failed to parse json from {} '{}'|'{}'|'{}' -> {err}", operation_type, &set.executable, stdout, stderr))) + return Err(DscError::Operation(t!("dscresources.commandResource.failedParseJson", executable = &get.executable, stdout = stdout, stderr = stderr, err = err).to_string())) } }; @@ -197,12 +198,12 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te // command should be returning actual state as a JSON line and a list of properties that differ as separate JSON line let mut lines = stdout.lines(); let Some(actual_line) = lines.next() else { - return Err(DscError::Command(resource.resource_type.clone(), exit_code, "Command did not return expected actual output".to_string())); + return Err(DscError::Command(resource.resource_type.clone(), exit_code, t!("dscresources.commandResource.setUnexpectedOutput").to_string())); }; let actual_value: Value = serde_json::from_str(actual_line)?; // TODO: need schema for diff_properties to validate against let Some(diff_line) = lines.next() else { - return Err(DscError::Command(resource.resource_type.clone(), exit_code, "Command did not return expected diff output".to_string())); + return Err(DscError::Command(resource.resource_type.clone(), exit_code, t!("dscresources.commandResource.setUnexpectedDiff").to_string())); }; let diff_properties: Vec = serde_json::from_str(diff_line)?; Ok(SetResult::Resource(ResourceSetResponse { @@ -248,9 +249,9 @@ pub fn invoke_set(resource: &ResourceManifest, cwd: &str, desired: &str, skip_te /// /// Error is returned if the underlying command returns a non-zero exit code. pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Result { - debug!("Invoking test for '{}'", &resource.resource_type); + debug!("{}", t!("dscresources.commandResource.invokeTest", resource = &resource.resource_type)); let Some(test) = &resource.test else { - info!("Resource '{}' does not implement test, performing synthetic test", &resource.resource_type); + info!("{}", t!("dscresources.commandResource.testSyntheticTest", resource = &resource.resource_type)); return invoke_synthetic_test(resource, cwd, expected); }; @@ -259,16 +260,16 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re let args = process_args(test.args.as_ref(), expected); let command_input = get_command_input(test.input.as_ref(), expected)?; - info!("Invoking test '{}' using '{}'", &resource.resource_type, &test.executable); + info!("{}", t!("dscresources.commandResource.invokeTestUsing", resource = &resource.resource_type, executable = &test.executable)); let (exit_code, stdout, stderr) = invoke_command(&test.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?; if resource.kind == Some(Kind::Resource) { - debug!("Verifying output of test '{}' using '{}'", &resource.resource_type, &test.executable); + debug!("{}", t!("dscresources.commandResource.testVerifyOutput", resource = &resource.resource_type, executable = &test.executable)); verify_json(resource, cwd, &stdout)?; } if resource.kind == Some(Kind::Importer) { - debug!("Import resource kind, returning group test response"); + debug!("{}", t!("dscresources.commandResource.testGroupTestResponse")); let group_test_response: Vec = serde_json::from_str(&stdout)?; return Ok(TestResult::Group(group_test_response)); } @@ -279,7 +280,7 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re let actual_value: Value = match serde_json::from_str(&stdout){ Result::Ok(r) => {r}, Result::Err(err) => { - return Err(DscError::Operation(format!("Failed to parse json from test {}|{}|{} -> {err}", &test.executable, stdout, stderr))) + return Err(DscError::Operation(t!("dscresources.commandResource.failedParseJson", executable = &test.executable, stdout = stdout, stderr = stderr, err = err).to_string())) } }; let diff_properties = get_diff(&expected_value, &actual_value); @@ -294,11 +295,11 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re // command should be returning actual state as a JSON line and a list of properties that differ as separate JSON line let mut lines = stdout.lines(); let Some(actual_value) = lines.next() else { - return Err(DscError::Command(resource.resource_type.clone(), exit_code, "No actual state returned".to_string())); + return Err(DscError::Command(resource.resource_type.clone(), exit_code, t!("dscresources.commandResource.testNoActualState").to_string())); }; let actual_value: Value = serde_json::from_str(actual_value)?; let Some(diff_properties) = lines.next() else { - return Err(DscError::Command(resource.resource_type.clone(), exit_code, "No diff properties returned".to_string())); + return Err(DscError::Command(resource.resource_type.clone(), exit_code, t!("dscresources.commandResource.testNoDiff").to_string())); }; let diff_properties: Vec = serde_json::from_str(diff_properties)?; Ok(TestResult::Resource(ResourceTestResponse { @@ -379,7 +380,7 @@ pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Re let args = process_args(delete.args.as_ref(), filter); let command_input = get_command_input(delete.input.as_ref(), filter)?; - info!("Invoking delete '{}' using '{}'", &resource.resource_type, &delete.executable); + info!("{}", t!("dscresources.commandResource.invokeDeleteUsing", resource = &resource.resource_type, executable = &delete.executable)); let (_exit_code, _stdout, _stderr) = invoke_command(&delete.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?; Ok(()) @@ -401,7 +402,7 @@ pub fn invoke_delete(resource: &ResourceManifest, cwd: &str, filter: &str) -> Re /// /// Error is returned if the underlying command returns a non-zero exit code. pub fn invoke_validate(resource: &ResourceManifest, cwd: &str, config: &str) -> Result { - trace!("Invoking validate '{}' using: {}", &resource.resource_type, &config); + trace!("{}", t!("dscresources.commandResource.invokeValidateConfig", resource = &resource.resource_type, config = &config)); // TODO: use schema to validate config if validate is not implemented let Some(validate) = resource.validate.as_ref() else { return Err(DscError::NotImplemented("validate".to_string())); @@ -410,7 +411,7 @@ pub fn invoke_validate(resource: &ResourceManifest, cwd: &str, config: &str) -> let args = process_args(validate.args.as_ref(), config); let command_input = get_command_input(validate.input.as_ref(), config)?; - info!("Invoking validate '{}' using '{}'", &resource.resource_type, &validate.executable); + info!("{}", t!("dscresources.commandResource.invokeValidateUsing", resource = &resource.resource_type, executable = &validate.executable)); let (_exit_code, stdout, _stderr) = invoke_command(&validate.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?; let result: ValidateResult = serde_json::from_str(&stdout)?; Ok(result) @@ -459,7 +460,7 @@ pub fn get_schema(resource: &ResourceManifest, cwd: &str) -> Result) -> Result { let Some(export) = resource.export.as_ref() else { - return Err(DscError::Operation(format!("Export is not supported by resource {}", &resource.resource_type))) + return Err(DscError::Operation(t!("dscresources.commandResource.exportNotSupported", resource = &resource.resource_type).to_string())) }; let mut command_input: CommandInput = CommandInput { env: None, stdin: None }; @@ -483,11 +484,11 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str> let instance: Value = match serde_json::from_str(line){ Result::Ok(r) => {r}, Result::Err(err) => { - return Err(DscError::Operation(format!("Failed to parse json from export {}|{}|{} -> {err}", &export.executable, stdout, stderr))) + return Err(DscError::Operation(t!("dscresources.commandResource.failedParseJson", executable = &export.executable, stdout = stdout, stderr = stderr, err = err).to_string())) } }; if resource.kind == Some(Kind::Resource) { - debug!("Verifying output of export '{}' using '{}'", &resource.resource_type, &export.executable); + debug!("{}", t!("dscresources.commandResource.exportVerifyOutput", resource = &resource.resource_type, executable = &export.executable)); verify_json(resource, cwd, line)?; } instances.push(instance); @@ -515,13 +516,13 @@ pub fn invoke_export(resource: &ResourceManifest, cwd: &str, input: Option<&str> /// Error returned if the resource does not successfully resolve the input pub fn invoke_resolve(resource: &ResourceManifest, cwd: &str, input: &str) -> Result { let Some(resolve) = &resource.resolve else { - return Err(DscError::Operation(format!("Resolve is not supported by resource {}", &resource.resource_type))); + return Err(DscError::Operation(t!("dscresources.commandResource.resolveNotSupported", resource = &resource.resource_type).to_string())); }; let args = process_args(resolve.args.as_ref(), input); let command_input = get_command_input(resolve.input.as_ref(), input)?; - info!("Invoking resolve '{}' using '{}'", &resource.resource_type, &resolve.executable); + info!("{}", t!("dscresources.commandResource.invokeResolveUsing", resource = &resource.resource_type, executable = &resolve.executable)); let (_exit_code, stdout, _stderr) = invoke_command(&resolve.executable, args, command_input.stdin.as_deref(), Some(cwd), command_input.env, resource.exit_codes.as_ref())?; let result: ResolveResult = serde_json::from_str(&stdout)?; Ok(result) @@ -575,20 +576,28 @@ async fn run_process_async(executable: &str, args: Option>, input: O } }; - let stdout = child.stdout.take().expect("child did not have a handle to stdout"); - let stderr = child.stderr.take().expect("child did not have a handle to stderr"); + let Some(stdout) = child.stdout.take() else { + return Err(DscError::CommandOperation(t!("dscresources.commandResource.processChildStdout").to_string(), executable.to_string())); + }; + let Some(stderr) = child.stderr.take() else { + return Err(DscError::CommandOperation(t!("dscresources.commandResource.processChildStderr").to_string(), executable.to_string())); + }; let mut stdout_reader = BufReader::new(stdout).lines(); let mut stderr_reader = BufReader::new(stderr).lines(); if let Some(input) = input { trace!("Writing to command STDIN: {input}"); - let mut stdin = child.stdin.take().expect("child did not have a handle to stdin"); - stdin.write_all(input.as_bytes()).await.expect("could not write to stdin"); + let Some(mut stdin) = child.stdin.take() else { + return Err(DscError::CommandOperation(t!("dscresources.commandResource.processChildStdin").to_string(), executable.to_string())); + }; + if stdin.write_all(input.as_bytes()).await.is_err() { + return Err(DscError::CommandOperation(t!("dscresources.commandResource.processWriteStdin").to_string(), executable.to_string())); + } drop(stdin); } let Some(child_id) = child.id() else { - return Err(DscError::CommandOperation("Can't get child process id".to_string(), executable.to_string())); + return Err(DscError::CommandOperation(t!("dscresources.commandResource.processChildId").to_string(), executable.to_string())); }; let child_task = tokio::spawn(async move { @@ -621,7 +630,7 @@ async fn run_process_async(executable: &str, args: Option>, input: O let stderr_result = stderr_task.await.unwrap(); if let Some(code) = exit_code { - debug!("Process '{executable}' id {child_id} exited with code {code}"); + debug!("{}", t!("dscresources.commandResource.processChildExit", executable = executable, id = child_id, code = code)); if code != 0 { if let Some(exit_codes) = exit_codes { @@ -634,8 +643,8 @@ async fn run_process_async(executable: &str, args: Option>, input: O Ok((code, stdout_result, stderr_result)) } else { - debug!("Process '{executable}' id {child_id} terminated by signal"); - Err(DscError::CommandOperation("Process terminated by signal".to_string(), executable.to_string())) + debug!("{}", t!("dscresources.commandResource.processChildTerminated", executable = executable, id = child_id)); + Err(DscError::CommandOperation(t!("dscresources.commandResource.processTerminated").to_string(), executable.to_string())) } } @@ -660,7 +669,7 @@ async fn run_process_async(executable: &str, args: Option>, input: O /// #[allow(clippy::implicit_hasher)] pub fn invoke_command(executable: &str, args: Option>, input: Option<&str>, cwd: Option<&str>, env: Option>, exit_codes: Option<&HashMap>) -> Result<(i32, String, String), DscError> { - debug!("Invoking command '{}' with args {:?}", executable, args); + debug!("{}", t!("dscresources.commandResource.commandInvoke", executable = executable, args = args : {:?})); tokio::runtime::Builder::new_multi_thread() .enable_all() @@ -671,7 +680,7 @@ pub fn invoke_command(executable: &str, args: Option>, input: Option fn process_args(args: Option<&Vec>, value: &str) -> Option> { let Some(arg_values) = args else { - debug!("No args to process"); + debug!("{}", t!("dscresources.commandResource.noArgs")); return None; }; @@ -705,15 +714,15 @@ fn get_command_input(input_kind: Option<&InputKind>, input: &str) -> Result = None; match input_kind { Some(InputKind::Env) => { - debug!("Parsing input as environment variables"); + debug!("{}", t!("dscresources.commandResource.parseAsEnvVars")); env = Some(json_to_hashmap(input)?); }, Some(InputKind::Stdin) => { - debug!("Parsing input as stdin"); + debug!("{}", t!("dscresources.commandResource.parseAsStdin")); stdin = Some(input.to_string()); }, None => { - debug!("No input kind specified"); + debug!("{}", t!("dscresources.commandResource.noInput")); // leave input as none }, } @@ -726,17 +735,17 @@ fn get_command_input(input_kind: Option<&InputKind>, input: &str) -> Result Result<(), DscError> { - debug!("Verify JSON for '{}'", resource.resource_type); + debug!("{}", t!("dscresources.commandResource.verifyJson", resource = resource.resource_type)); // see if resource implements validate if resource.validate.is_some() { - trace!("Validating against JSON: {json}"); + trace!("{}", t!("dscresources.commandResource.validateJson", json = json)); let result = invoke_validate(resource, cwd, json)?; if result.valid { return Ok(()); } - return Err(DscError::Validation("Resource reported input JSON is not valid".to_string())); + return Err(DscError::Validation(t!("dscresources.commandResource.resourceInvalidJson").to_string())); } // otherwise, use schema validation @@ -788,7 +797,7 @@ fn json_to_hashmap(json: &str) -> Result, DscError> { array.push(n.to_string()); }, _ => { - return Err(DscError::Operation(format!("Unsupported array value for key {key}. Only string and number is supported."))); + return Err(DscError::Operation(t!("dscresources.commandResource.invalidArrayKey", key = key).to_string())); }, } } @@ -798,7 +807,7 @@ fn json_to_hashmap(json: &str) -> Result, DscError> { continue; } Value::Object(_) => { - return Err(DscError::Operation(format!("Unsupported value for key {key}. Only string, bool, number, and array is supported."))); + return Err(DscError::Operation(t!("dscresources.commandResource.invalidKey", key = key).to_string())); }, } } diff --git a/dsc_lib/src/dscresources/dscresource.rs b/dsc_lib/src/dscresources/dscresource.rs index 7c62bda15..868a9098d 100644 --- a/dsc_lib/src/dscresources/dscresource.rs +++ b/dsc_lib/src/dscresources/dscresource.rs @@ -3,6 +3,7 @@ use crate::{configure::config_doc::ExecutionKind, dscresources::resource_manifest::Kind}; use dscerror::DscError; +use rust_i18n::t; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -188,10 +189,10 @@ pub trait Invoke { impl Invoke for DscResource { fn get(&self, filter: &str) -> Result { - debug!("Invoking get for resource: {}", self.type_name); + debug!("{}", t!("dscresources.dscresource.invokeGet", resource = self.type_name)); match &self.implemented_as { ImplementedAs::Custom(_custom) => { - Err(DscError::NotImplemented("get custom resources".to_string())) + Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) }, ImplementedAs::Command => { let Some(manifest) = &self.manifest else { @@ -204,10 +205,10 @@ impl Invoke for DscResource { } fn set(&self, desired: &str, skip_test: bool, execution_type: &ExecutionKind) -> Result { - debug!("Invoking set for resource: {}", self.type_name); + debug!("{}", t!("dscresources.dscresource.invokeSet", resource = self.type_name)); match &self.implemented_as { ImplementedAs::Custom(_custom) => { - Err(DscError::NotImplemented("set custom resources".to_string())) + Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) }, ImplementedAs::Command => { let Some(manifest) = &self.manifest else { @@ -220,10 +221,10 @@ impl Invoke for DscResource { } fn test(&self, expected: &str) -> Result { - debug!("Invoking test for resource: {}", self.type_name); + debug!("{}", t!("dscresources.dscresource.invokeTest", resource = self.type_name)); match &self.implemented_as { ImplementedAs::Custom(_custom) => { - Err(DscError::NotImplemented("test custom resources".to_string())) + Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) }, ImplementedAs::Command => { let Some(manifest) = &self.manifest else { @@ -264,10 +265,10 @@ impl Invoke for DscResource { } fn delete(&self, filter: &str) -> Result<(), DscError> { - debug!("Invoking delete for resource: {}", self.type_name); + debug!("{}", t!("dscresources.dscresource.invokeDelete", resource = self.type_name)); match &self.implemented_as { ImplementedAs::Custom(_custom) => { - Err(DscError::NotImplemented("set custom resources".to_string())) + Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) }, ImplementedAs::Command => { let Some(manifest) = &self.manifest else { @@ -280,10 +281,10 @@ impl Invoke for DscResource { } fn validate(&self, config: &str) -> Result { - debug!("Invoking validate for resource: {}", self.type_name); + debug!("{}", t!("dscresources.dscresource.invokeValidate", resource = self.type_name)); match &self.implemented_as { ImplementedAs::Custom(_custom) => { - Err(DscError::NotImplemented("validate custom resources".to_string())) + Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) }, ImplementedAs::Command => { let Some(manifest) = &self.manifest else { @@ -296,10 +297,10 @@ impl Invoke for DscResource { } fn schema(&self) -> Result { - debug!("Invoking schema for resource: {}", self.type_name); + debug!("{}", t!("dscresources.dscresource.invokeSchema", resource = self.type_name)); match &self.implemented_as { ImplementedAs::Custom(_custom) => { - Err(DscError::NotImplemented("schema custom resources".to_string())) + Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) }, ImplementedAs::Command => { let Some(manifest) = &self.manifest else { @@ -312,7 +313,7 @@ impl Invoke for DscResource { } fn export(&self, input: &str) -> Result { - debug!("Invoking export for resource: {}", self.type_name); + debug!("{}", t!("dscresources.dscresource.invokeExport", resource = self.type_name)); let Some(manifest) = &self.manifest else { return Err(DscError::MissingManifest(self.type_name.clone())); }; @@ -321,7 +322,7 @@ impl Invoke for DscResource { } fn resolve(&self, input: &str) -> Result { - debug!("Invoking resolve for resource: {}", self.type_name); + debug!("{}", t!("dscresources.dscresource.invokeResolve", resource = self.type_name)); let Some(manifest) = &self.manifest else { return Err(DscError::MissingManifest(self.type_name.clone())); }; @@ -373,7 +374,7 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec { if value.is_object() { let sub_diff = get_diff(value, &actual[key]); if !sub_diff.is_empty() { - debug!("diff: sub diff for {key}"); + debug!("{}", t!("dscresources.dscresource.subDiff", key = key)); diff_properties.push(key.to_string()); } } @@ -388,22 +389,22 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec { if let Some(value_array) = value.as_array() { if let Some(actual_array) = actual[key].as_array() { if !is_same_array(value_array, actual_array) { - info!("diff: arrays differ for {key}"); + info!("{}", t!("dscresources.dscresource.diffArray", key = key)); diff_properties.push(key.to_string()); } } else { - info!("diff: {} is not an array", actual[key]); + info!("{}", t!("dscresources.dscresource.diffNotArray", key = actual[key])); diff_properties.push(key.to_string()); } } else if value != &actual[key] { diff_properties.push(key.to_string()); } } else { - info!("diff: {key} missing"); + info!("{}", t!("dscresources.dscresource.diffKeyMissing", key = key)); diff_properties.push(key.to_string()); } } else { - info!("diff: {key} not object"); + info!("{}", t!("dscresources.dscresource.diffKeyNotObject", key = key)); diff_properties.push(key.to_string()); } } @@ -416,13 +417,13 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec { /// Compares two arrays independent of order fn is_same_array(expected: &Vec, actual: &Vec) -> bool { if expected.len() != actual.len() { - info!("diff: arrays are different lengths"); + info!("{}", t!("dscresources.dscresource.diffArraySize")); return false; } for item in expected { if !array_contains(actual, item) { - info!("diff: actual array missing expected element"); + info!("{}", t!("dscresources.dscresource.diffMissingItem")); return false; } } diff --git a/dsc_lib/src/functions/add.rs b/dsc_lib/src/functions/add.rs index 71ce22e7f..9058f48be 100644 --- a/dsc_lib/src/functions/add.rs +++ b/dsc_lib/src/functions/add.rs @@ -4,6 +4,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -24,11 +25,11 @@ impl Function for Add { } fn invoke(&self, args: &[Value], _context: &Context) -> Result { - debug!("add function"); + debug!("{}", t!("functions.add.invoked")); if let (Some(arg1), Some(arg2)) = (args[0].as_i64(), args[1].as_i64()) { Ok(Value::Number((arg1 + arg2).into())) } else { - Err(DscError::Parser("Invalid argument(s)".to_string())) + Err(DscError::Parser(t!("functions.invalidArguments").to_string())) } } } diff --git a/dsc_lib/src/functions/concat.rs b/dsc_lib/src/functions/concat.rs index 6c475bb2d..3f694fdbe 100644 --- a/dsc_lib/src/functions/concat.rs +++ b/dsc_lib/src/functions/concat.rs @@ -4,6 +4,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -24,7 +25,7 @@ impl Function for Concat { } fn invoke(&self, args: &[Value], _context: &Context) -> Result { - debug!("concat function"); + debug!("{}", t!("functions.concat.invoked")); let mut string_result = String::new(); let mut array_result: Vec = Vec::new(); let mut input_type : Option = None; @@ -33,7 +34,7 @@ impl Function for Concat { if input_type.is_none() { input_type = Some(AcceptedArgKind::String); } else if input_type != Some(AcceptedArgKind::String) { - return Err(DscError::Parser("Arguments must all be strings".to_string())); + return Err(DscError::Parser(t!("functions.concat.argsMustBeStrings").to_string())); } string_result.push_str(value.as_str().unwrap_or_default()); @@ -41,7 +42,7 @@ impl Function for Concat { if input_type.is_none() { input_type = Some(AcceptedArgKind::Array); } else if input_type != Some(AcceptedArgKind::Array) { - return Err(DscError::Parser("Arguments must all be arrays".to_string())); + return Err(DscError::Parser(t!("functions.concat.argsMustBeArrays").to_string())); } if let Some(array) = value.as_array() { @@ -53,12 +54,12 @@ impl Function for Concat { array_result.push(String::new()); } } else { - return Err(DscError::Parser("Only arrays of strings are valid".to_string())); + return Err(DscError::Parser(t!("functions.concat.onlyArraysOfStrings").to_string())); } } } } else { - return Err(DscError::Parser("Invalid argument type".to_string())); + return Err(DscError::Parser(t!("functions.invalidArgType").to_string())); } } @@ -70,7 +71,7 @@ impl Function for Concat { Ok(Value::Array(array_result.into_iter().map(Value::String).collect())) }, _ => { - Err(DscError::Parser("Invalid argument type".to_string())) + Err(DscError::Parser(t!("functions.invalidArgType").to_string())) } } } diff --git a/dsc_lib/src/functions/create_array.rs b/dsc_lib/src/functions/create_array.rs index 595baf3ac..f15939909 100644 --- a/dsc_lib/src/functions/create_array.rs +++ b/dsc_lib/src/functions/create_array.rs @@ -4,6 +4,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -24,7 +25,7 @@ impl Function for CreateArray { } fn invoke(&self, args: &[Value], _context: &Context) -> Result { - debug!("createArray function"); + debug!("{}", t!("functions.createArray.invoked")); let mut array_result = Vec::::new(); let mut input_type : Option = None; for value in args { @@ -32,28 +33,28 @@ impl Function for CreateArray { if input_type.is_none() { input_type = Some(AcceptedArgKind::Array); } else if input_type != Some(AcceptedArgKind::Array) { - return Err(DscError::Parser("Arguments must all be arrays".to_string())); + return Err(DscError::Parser(t!("functions.createArray.argsMustAllBeArrays").to_string())); } } else if value.is_number() { if input_type.is_none() { input_type = Some(AcceptedArgKind::Number); } else if input_type != Some(AcceptedArgKind::Number) { - return Err(DscError::Parser("Arguments must all be integers".to_string())); + return Err(DscError::Parser(t!("functions.createArray.argsMustAllBeIntegers").to_string())); } } else if value.is_object() { if input_type.is_none() { input_type = Some(AcceptedArgKind::Object); } else if input_type != Some(AcceptedArgKind::Object) { - return Err(DscError::Parser("Arguments must all be objects".to_string())); + return Err(DscError::Parser(t!("functions.createArray.argsMustAllBeObjects").to_string())); } } else if value.is_string() { if input_type.is_none() { input_type = Some(AcceptedArgKind::String); } else if input_type != Some(AcceptedArgKind::String) { - return Err(DscError::Parser("Arguments must all be strings".to_string())); + return Err(DscError::Parser(t!("functions.createArray.argsMustAllBeStrings").to_string())); } } else { - return Err(DscError::Parser("Invalid argument type".to_string())); + return Err(DscError::Parser(t!("functions.invalidArgType").to_string())); } array_result.push(value.clone()); } diff --git a/dsc_lib/src/functions/div.rs b/dsc_lib/src/functions/div.rs index 04516809a..b4c4c234d 100644 --- a/dsc_lib/src/functions/div.rs +++ b/dsc_lib/src/functions/div.rs @@ -4,6 +4,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -24,15 +25,15 @@ impl Function for Div { } fn invoke(&self, args: &[Value], _context: &Context) -> Result { - debug!("div function"); + debug!("{}", t!("functions.div.invoked")); if let (Some(arg1), Some(arg2)) = (args[0].as_i64(), args[1].as_i64()) { if let Some(value) = arg1.checked_div(arg2) { Ok(Value::Number(value.into())) } else { - Err(DscError::Parser("Cannot divide by zero".to_string())) + Err(DscError::Parser(t!("functions.div.divideByZero").to_string())) } } else { - Err(DscError::Parser("Invalid argument(s)".to_string())) + Err(DscError::Parser(t!("functions.invalidArguments").to_string())) } } } diff --git a/dsc_lib/src/functions/envvar.rs b/dsc_lib/src/functions/envvar.rs index 372360c8d..e4d4dab70 100644 --- a/dsc_lib/src/functions/envvar.rs +++ b/dsc_lib/src/functions/envvar.rs @@ -5,6 +5,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::AcceptedArgKind; use super::Function; +use rust_i18n::t; use serde_json::Value; use std::env; @@ -29,7 +30,7 @@ impl Function for Envvar { return Ok(Value::String(val)); } - Err(DscError::Function("envvar".to_string(), "Environment variable not found".to_string())) + Err(DscError::Function("envvar".to_string(), t!("functions.envvar.notFound").to_string())) } } diff --git a/dsc_lib/src/functions/int.rs b/dsc_lib/src/functions/int.rs index f1e497b45..3875537a2 100644 --- a/dsc_lib/src/functions/int.rs +++ b/dsc_lib/src/functions/int.rs @@ -5,6 +5,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::AcceptedArgKind; use num_traits::cast::NumCast; +use rust_i18n::t; use serde_json::Value; use super::Function; @@ -28,13 +29,13 @@ impl Function for Int { let arg = &args[0]; let value: i64; if arg.is_string() { - let input = arg.as_str().ok_or(DscError::FunctionArg("int".to_string(), "invalid input string".to_string()))?; - let result = input.parse::().map_err(|_| DscError::FunctionArg("int".to_string(), "unable to parse string to int".to_string()))?; - value = NumCast::from(result).ok_or(DscError::FunctionArg("int".to_string(), "unable to cast to int".to_string()))?; + let input = arg.as_str().ok_or(DscError::FunctionArg("int".to_string(), t!("functions.int.invalidInput").to_string()))?; + let result = input.parse::().map_err(|_| DscError::FunctionArg("int".to_string(), t!("functions.int.parseStringError").to_string()))?; + value = NumCast::from(result).ok_or(DscError::FunctionArg("int".to_string(), t!("functions.int.castError").to_string()))?; } else if arg.is_number() { - value = arg.as_i64().ok_or(DscError::FunctionArg("int".to_string(), "unable to parse number to int".to_string()))?; + value = arg.as_i64().ok_or(DscError::FunctionArg("int".to_string(), t!("functions.int.parseNumError").to_string()))?; } else { - return Err(DscError::FunctionArg("int".to_string(), "Invalid argument type".to_string())); + return Err(DscError::FunctionArg("int".to_string(), t!("functions.invalidArgType").to_string())); } Ok(Value::Number(value.into())) } diff --git a/dsc_lib/src/functions/max.rs b/dsc_lib/src/functions/max.rs index f9a5177be..eb569df94 100644 --- a/dsc_lib/src/functions/max.rs +++ b/dsc_lib/src/functions/max.rs @@ -4,6 +4,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -30,7 +31,7 @@ impl Function for Max { find_max(array) } else { - Err(DscError::Parser("Array cannot be empty".to_string())) + Err(DscError::Parser(t!("functions.max.emptyArray").to_string())) } } else { @@ -40,8 +41,8 @@ impl Function for Max { } fn find_max(args: &[Value]) -> Result { - let array = args.iter().map(|v| v.as_i64().ok_or(DscError::Parser("Input must only contain integers".to_string()))).collect::, DscError>>()?; - let value = array.iter().max().ok_or(DscError::Parser("Unable to find max value".to_string()))?; + let array = args.iter().map(|v| v.as_i64().ok_or(DscError::Parser(t!("functions.max.integersOnly").to_string()))).collect::, DscError>>()?; + let value = array.iter().max().ok_or(DscError::Parser(t!("functions.max.noMax").to_string()))?; Ok(Value::Number((*value).into())) } diff --git a/dsc_lib/src/functions/min.rs b/dsc_lib/src/functions/min.rs index 8fe9444c2..9fae6bbc7 100644 --- a/dsc_lib/src/functions/min.rs +++ b/dsc_lib/src/functions/min.rs @@ -4,6 +4,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -24,13 +25,13 @@ impl Function for Min { } fn invoke(&self, args: &[Value], _context: &Context) -> Result { - debug!("min function"); + debug!("{}", t!("functions.min.invoked")); if args.len() == 1 { if let Some(array) = args[0].as_array() { find_min(array) } else { - Err(DscError::Parser("Array cannot be empty".to_string())) + Err(DscError::Parser(t!("functions.min.emptyArray").to_string())) } } else { @@ -40,8 +41,8 @@ impl Function for Min { } fn find_min(args: &[Value]) -> Result { - let array = args.iter().map(|v| v.as_i64().ok_or(DscError::Parser("Input must only contain integers".to_string()))).collect::, DscError>>()?; - let value = array.iter().min().ok_or(DscError::Parser("Unable to find min value".to_string()))?; + let array = args.iter().map(|v| v.as_i64().ok_or(DscError::Parser(t!("functions.min.integersOnly").to_string()))).collect::, DscError>>()?; + let value = array.iter().min().ok_or(DscError::Parser(t!("functions.min.noMin").to_string()))?; Ok(Value::Number((*value).into())) } diff --git a/dsc_lib/src/functions/mod.rs b/dsc_lib/src/functions/mod.rs index 909d73f93..fd30ae19b 100644 --- a/dsc_lib/src/functions/mod.rs +++ b/dsc_lib/src/functions/mod.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use crate::DscError; use crate::configure::context::Context; +use rust_i18n::t; use serde_json::Value; pub mod add; @@ -101,7 +102,7 @@ impl FunctionDispatcher { /// This function will return an error if the function fails to execute. pub fn invoke(&self, name: &str, args: &Vec, context: &Context) -> Result { let Some(function) = self.functions.get(name) else { - return Err(DscError::Parser(format!("Unknown function '{name}'"))); + return Err(DscError::Parser(t!("functions.unknownFunction", name = name).to_string())); }; // check if arg number are valid @@ -109,31 +110,31 @@ impl FunctionDispatcher { let max_args = function.max_args(); if args.len() < min_args || args.len() > max_args { if max_args == 0 { - return Err(DscError::Parser(format!("Function '{name}' does not accept arguments"))); + return Err(DscError::Parser(t!("functions.noArgsAccepted", name = name).to_string())); } else if min_args == max_args { - return Err(DscError::Parser(format!("Function '{name}' requires exactly {min_args} arguments"))); + return Err(DscError::Parser(t!("functions.invalidArgCount", name = name, count = min_args).to_string())); } else if max_args == usize::MAX { - return Err(DscError::Parser(format!("Function '{name}' requires at least {min_args} arguments"))); + return Err(DscError::Parser(t!("functions.minArgsRequired", name = name, count = min_args).to_string())); } - return Err(DscError::Parser(format!("Function '{name}' requires between {min_args} and {max_args} arguments"))); + return Err(DscError::Parser(t!("functions.argCountRequired", name = name, min = min_args, max = max_args).to_string())); } // check if arg types are valid let accepted_arg_types = function.accepted_arg_types(); let accepted_args_string = accepted_arg_types.iter().map(|x| format!("{x:?}")).collect::>().join(", "); for value in args { if value.is_array() && !accepted_arg_types.contains(&AcceptedArgKind::Array) { - return Err(DscError::Parser(format!("Function '{name}' does not accept array arguments, accepted types are: {accepted_args_string}"))); + return Err(DscError::Parser(t!("functions.noArrayArgs", name = name, accepted_args_string = accepted_args_string).to_string())); } else if value.is_boolean() && !accepted_arg_types.contains(&AcceptedArgKind::Boolean) { - return Err(DscError::Parser(format!("Function '{name}' does not accept boolean arguments, accepted types are: {accepted_args_string}"))); + return Err(DscError::Parser(t!("functions.noBooleanArgs", name = name, accepted_args_string = accepted_args_string).to_string())); } else if value.is_number() && !accepted_arg_types.contains(&AcceptedArgKind::Number) { - return Err(DscError::Parser(format!("Function '{name}' does not accept number arguments, accepted types are: {accepted_args_string}"))); + return Err(DscError::Parser(t!("functions.noNumberArgs", name = name, accepted_args_string = accepted_args_string).to_string())); } else if value.is_object() && !accepted_arg_types.contains(&AcceptedArgKind::Object) { - return Err(DscError::Parser(format!("Function '{name}' does not accept object arguments, accepted types are: {accepted_args_string}"))); + return Err(DscError::Parser(t!("functions.noObjectArgs", name = name, accepted_args_string = accepted_args_string).to_string())); } else if value.is_string() && !accepted_arg_types.contains(&AcceptedArgKind::String) { - return Err(DscError::Parser(format!("Function '{name}' does not accept string argument, accepted types are: {accepted_args_string}"))); + return Err(DscError::Parser(t!("functions.noStringArgs", name = name, accepted_args_string = accepted_args_string).to_string())); } } diff --git a/dsc_lib/src/functions/mod_function.rs b/dsc_lib/src/functions/mod_function.rs index 50e63b4ac..c6464d94c 100644 --- a/dsc_lib/src/functions/mod_function.rs +++ b/dsc_lib/src/functions/mod_function.rs @@ -4,6 +4,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -27,11 +28,11 @@ impl Function for Mod { debug!("mod function"); if let (Some(arg1), Some(arg2)) = (args[0].as_i64(), args[1].as_i64()) { if arg2 == 0 { - return Err(DscError::Parser("Cannot divide by zero".to_string())); + return Err(DscError::Parser(t!("functions.mod.divideByZero").to_string())); } Ok(Value::Number((arg1 % arg2).into())) } else { - Err(DscError::Parser("Invalid argument(s)".to_string())) + Err(DscError::Parser(t!("functions.invalidArguments").to_string())) } } } diff --git a/dsc_lib/src/functions/mul.rs b/dsc_lib/src/functions/mul.rs index 888a463d0..1e5f332dd 100644 --- a/dsc_lib/src/functions/mul.rs +++ b/dsc_lib/src/functions/mul.rs @@ -4,6 +4,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -24,11 +25,11 @@ impl Function for Mul { } fn invoke(&self, args: &[Value], _context: &Context) -> Result { - debug!("mul function"); + debug!("{}", t!("functions.mul.invoked")); if let (Some(arg1), Some(arg2)) = (args[0].as_i64(), args[1].as_i64()) { Ok(Value::Number((arg1 * arg2).into())) } else { - Err(DscError::Parser("Invalid argument(s)".to_string())) + Err(DscError::Parser(t!("functions.invalidArguments").to_string())) } } } diff --git a/dsc_lib/src/functions/parameters.rs b/dsc_lib/src/functions/parameters.rs index 51e4533af..0cbe4bbbe 100644 --- a/dsc_lib/src/functions/parameters.rs +++ b/dsc_lib/src/functions/parameters.rs @@ -6,6 +6,7 @@ use crate::configure::parameters::SecureKind; use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use tracing::{debug, trace}; @@ -26,9 +27,9 @@ impl Function for Parameters { } fn invoke(&self, args: &[Value], context: &Context) -> Result { - debug!("Invoke parameters function"); + debug!("{}", t!("functions.parameters.invoked")); if let Some(key) = args[0].as_str() { - trace!("parameters key: {key}"); + trace!("{}", t!("functions.parameters.traceKey", key = key)); if context.parameters.contains_key(key) { let (value, data_type) = &context.parameters[key]; @@ -36,7 +37,7 @@ impl Function for Parameters { match data_type { DataType::SecureString => { let Some(value) = value.as_str() else { - return Err(DscError::Parser(format!("Parameter '{key}' is not a string"))); + return Err(DscError::Parser(t!("functions.parameters.keyNotString", key = key).to_string())); }; let secure_string = SecureKind::SecureString(value.to_string()); Ok(serde_json::to_value(secure_string)?) @@ -51,10 +52,10 @@ impl Function for Parameters { } } else { - Err(DscError::Parser(format!("Parameter '{key}' not found in context"))) + Err(DscError::Parser(t!("functions.parameters.keyNotFound", key = key).to_string())) } } else { - Err(DscError::Parser("Invalid argument type".to_string())) + Err(DscError::Parser(t!("functions.invalidArgType").to_string())) } } } diff --git a/dsc_lib/src/functions/path.rs b/dsc_lib/src/functions/path.rs index 37ab14f28..215a676c0 100644 --- a/dsc_lib/src/functions/path.rs +++ b/dsc_lib/src/functions/path.rs @@ -4,9 +4,10 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use std::path::PathBuf; -use tracing::debug; +use tracing::trace; #[derive(Debug, Default)] pub struct Path {} @@ -28,14 +29,14 @@ impl Function for Path { } fn invoke(&self, args: &[Value], _context: &Context) -> Result { - debug!("Executing path function with args: {:?}", args); + trace!("{}", t!("functions.path.traceArgs", args = args : {:?})); let mut path = PathBuf::new(); for arg in args { if let Value::String(s) = arg { path.push(s); } else { - return Err(DscError::Parser("Arguments must all be strings".to_string())); + return Err(DscError::Parser(t!("functions.path.argsMustBeStrings").to_string())); } } diff --git a/dsc_lib/src/functions/reference.rs b/dsc_lib/src/functions/reference.rs index 4f3f8b08d..95aa867e7 100644 --- a/dsc_lib/src/functions/reference.rs +++ b/dsc_lib/src/functions/reference.rs @@ -4,6 +4,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -24,15 +25,15 @@ impl Function for Reference { } fn invoke(&self, args: &[Value], context: &Context) -> Result { - debug!("reference function"); + debug!("{}", t!("functions.reference.invoked")); if let Some(key) = args[0].as_str() { if context.outputs.contains_key(key) { Ok(context.outputs[key].clone()) } else { - Err(DscError::Parser(format!("Invalid resourceId or resource has not executed yet: {key}"))) + Err(DscError::Parser(t!("functions.reference.keyNotFound", key = key).to_string())) } } else { - Err(DscError::Parser("Invalid argument".to_string())) + Err(DscError::Parser(t!("functions.invalidArguments").to_string())) } } } diff --git a/dsc_lib/src/functions/resource_id.rs b/dsc_lib/src/functions/resource_id.rs index f893f1272..0ffb57000 100644 --- a/dsc_lib/src/functions/resource_id.rs +++ b/dsc_lib/src/functions/resource_id.rs @@ -4,6 +4,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; #[derive(Debug, Default)] @@ -29,11 +30,11 @@ impl Function for ResourceId { if let Some(value) = resource_type.as_str() { let slash_count = value.chars().filter(|c| *c == '/').count(); if slash_count != 1 { - return Err(DscError::Function("resourceId".to_string(), "Type argument must contain exactly one slash".to_string())); + return Err(DscError::Function("resourceId".to_string(), t!("functions.resourceId.incorrectTypeFormat").to_string())); } result.push_str(value); } else { - return Err(DscError::Parser("Invalid argument type for first parameter".to_string())); + return Err(DscError::Parser(t!("functions.resourceId.invalidFirstArgType").to_string())); } // ARM uses a slash separator, but here we use a colon which is not allowed for the type nor name result.push(':'); @@ -41,12 +42,12 @@ impl Function for ResourceId { let resource_name = &args[1]; if let Some(value) = resource_name.as_str() { if value.contains('/') { - return Err(DscError::Function("resourceId".to_string(), "Name argument cannot contain a slash".to_string())); + return Err(DscError::Function("resourceId".to_string(), t!("functions.resourceId.incorrectNameFormat").to_string())); } result.push_str(value); } else { - return Err(DscError::Parser("Invalid argument type for second parameter".to_string())); + return Err(DscError::Parser(t!("functions.resourceId.invalidSecondArgType").to_string())); } Ok(Value::String(result)) diff --git a/dsc_lib/src/functions/sub.rs b/dsc_lib/src/functions/sub.rs index 8f584ea10..02aedeed9 100644 --- a/dsc_lib/src/functions/sub.rs +++ b/dsc_lib/src/functions/sub.rs @@ -4,6 +4,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -24,11 +25,11 @@ impl Function for Sub { } fn invoke(&self, args: &[Value], _context: &Context) -> Result { - debug!("sub function"); + debug!("{}", t!("functions.sub.invoked")); if let (Some(arg1), Some(arg2)) = (args[0].as_i64(), args[1].as_i64()) { Ok(Value::Number((arg1 - arg2).into())) } else { - Err(DscError::Parser("Invalid argument(s)".to_string())) + Err(DscError::Parser(t!("functions.invalidArguments").to_string())) } } } diff --git a/dsc_lib/src/functions/system_root.rs b/dsc_lib/src/functions/system_root.rs index 8e2ce017a..1398ced51 100644 --- a/dsc_lib/src/functions/system_root.rs +++ b/dsc_lib/src/functions/system_root.rs @@ -4,6 +4,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -26,7 +27,7 @@ impl Function for SystemRoot { } fn invoke(&self, _args: &[Value], context: &Context) -> Result { - debug!("Executing targetPath function"); + debug!("{}", t!("functions.systemRoot.invoked")); Ok(Value::String(context.system_root.to_string_lossy().to_string())) } diff --git a/dsc_lib/src/functions/variables.rs b/dsc_lib/src/functions/variables.rs index 9aac9e541..61392ed6a 100644 --- a/dsc_lib/src/functions/variables.rs +++ b/dsc_lib/src/functions/variables.rs @@ -4,6 +4,7 @@ use crate::DscError; use crate::configure::context::Context; use crate::functions::{AcceptedArgKind, Function}; +use rust_i18n::t; use serde_json::Value; use tracing::debug; @@ -24,15 +25,15 @@ impl Function for Variables { } fn invoke(&self, args: &[Value], context: &Context) -> Result { - debug!("variables function"); + debug!("{}", t!("functions.variables.invoked")); if let Some(key) = args[0].as_str() { if context.variables.contains_key(key) { Ok(context.variables[key].clone()) } else { - Err(DscError::Parser(format!("Variable '{key}' does not exist or has not been initialized yet"))) + Err(DscError::Parser(t!("functions.variables.keyNotFound", key = key).to_string())) } } else { - Err(DscError::Parser("Invalid argument".to_string())) + Err(DscError::Parser(t!("functions.invalidArguments").to_string())) } } } diff --git a/dsc_lib/src/lib.rs b/dsc_lib/src/lib.rs index 21b22874d..bb61d5578 100644 --- a/dsc_lib/src/lib.rs +++ b/dsc_lib/src/lib.rs @@ -5,6 +5,7 @@ use crate::util::ProgressFormat; use configure::config_doc::ExecutionKind; use dscerror::DscError; use dscresources::{dscresource::{DscResource, Invoke}, invoke_result::{GetResult, SetResult, TestResult}}; +use rust_i18n::i18n; pub mod configure; pub mod discovery; @@ -14,6 +15,8 @@ pub mod functions; pub mod parser; pub mod util; +i18n!("locales", fallback = "en-us"); + pub struct DscManager { discovery: discovery::Discovery, } diff --git a/dsc_lib/src/parser/expressions.rs b/dsc_lib/src/parser/expressions.rs index b37140551..817c187ff 100644 --- a/dsc_lib/src/parser/expressions.rs +++ b/dsc_lib/src/parser/expressions.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use rust_i18n::t; use serde_json::Value; use tracing::{debug, trace}; use tree_sitter::Node; @@ -37,35 +38,35 @@ impl Expression { /// This function will return an error if the expression node is not valid. pub fn new(statement_bytes: &[u8], expression: &Node) -> Result { let Some(function) = expression.child_by_field_name("function") else { - return Err(DscError::Parser("Function node not found".to_string())); + return Err(DscError::Parser(t!("parser.expression.functionNodeNotFound").to_string())); }; - debug!("Parsing function '{:?}'", function); + debug!("{}", t!("parser.expression.parsingFunction", name = function : {:?})); let function = Function::new(statement_bytes, &function)?; let mut accessors = Vec::::new(); if let Some(accessor) = expression.child_by_field_name("accessor") { - debug!("Parsing accessor '{:?}'", accessor); + debug!("{}", t!("parser.expression.parsingAccessor", name = accessor : {:?})); if accessor.is_error() { - return Err(DscError::Parser("Error parsing accessor".to_string())); + return Err(DscError::Parser(t!("parser.expression.accessorParsingError").to_string())); } let mut cursor = accessor.walk(); for accessor in accessor.named_children(&mut cursor) { if accessor.is_error() { - return Err(DscError::Parser("Error parsing accessor".to_string())); + return Err(DscError::Parser(t!("parser.expression.accessorParsingError").to_string())); } let accessor_kind = accessor.kind(); let value = match accessor_kind { "memberAccess" => { - debug!("Parsing member accessor '{:?}'", accessor); + debug!("{}", t!("parser.expression.parsingMemberAccessor", name = accessor : {:?})); let Some(member_name) = accessor.child_by_field_name("name") else { - return Err(DscError::Parser("Member name not found".to_string())); + return Err(DscError::Parser(t!("parser.expression.memberNotFound").to_string())); }; let member = member_name.utf8_text(statement_bytes)?; Accessor::Member(member.to_string()) }, "index" => { - debug!("Parsing index accessor '{:?}'", accessor); + debug!("{}", t!("parser.expression.parsingIndexAccessor", index = accessor : {:?})); let Some(index_value) = accessor.child_by_field_name("indexValue") else { - return Err(DscError::Parser("Index value not found".to_string())); + return Err(DscError::Parser(t!("parser.expression.indexNotFound").to_string())); }; match index_value.kind() { "number" => { @@ -78,12 +79,12 @@ impl Expression { Accessor::IndexExpression(expression) }, _ => { - return Err(DscError::Parser(format!("Invalid accessor kind: '{accessor_kind}'"))); + return Err(DscError::Parser(t!("parser.expression.invalidAccessorKind", kind = accessor_kind).to_string())); }, } }, _ => { - return Err(DscError::Parser(format!("Invalid accessor kind: '{accessor_kind}'"))); + return Err(DscError::Parser(t!("parser.expression.invalidAccessorKind", kind = accessor_kind).to_string())); }, }; accessors.push(value); @@ -112,12 +113,12 @@ impl Expression { /// This function will return an error if the expression fails to execute. pub fn invoke(&self, function_dispatcher: &FunctionDispatcher, context: &Context) -> Result { let result = self.function.invoke(function_dispatcher, context)?; - trace!("Function result: '{:?}'", result); + trace!("{}", t!("parser.expression.functionResult", result = result : {:?})); if self.accessors.is_empty() { Ok(result) } else { - debug!("Evaluating accessors"); + debug!("{}", t!("parser.expression.evalAccessors")); let mut value = result; for accessor in &self.accessors { let mut index = Value::Null; @@ -125,11 +126,11 @@ impl Expression { Accessor::Member(member) => { if let Some(object) = value.as_object() { if !object.contains_key(member) { - return Err(DscError::Parser(format!("Member '{member}' not found"))); + return Err(DscError::Parser(t!("parser.expression.memberNameNotFound", member = member).to_string())); } value = object[member].clone(); } else { - return Err(DscError::Parser("Member access on non-object value".to_string())); + return Err(DscError::Parser(t!("parser.expression.accessOnNonObject").to_string())); } }, Accessor::Index(index_value) => { @@ -137,26 +138,26 @@ impl Expression { }, Accessor::IndexExpression(expression) => { index = expression.invoke(function_dispatcher, context)?; - trace!("Expression result: '{:?}'", index); + trace!("{}", t!("parser.expression.expressionResult", index = index : {:?})); }, } if index.is_number() { if let Some(array) = value.as_array() { let Some(index) = index.as_u64() else { - return Err(DscError::Parser("Index is not a valid number".to_string())); + return Err(DscError::Parser(t!("parser.expression.indexNotValid").to_string())); }; let index = usize::try_from(index)?; if index >= array.len() { - return Err(DscError::Parser("Index out of bounds".to_string())); + return Err(DscError::Parser(t!("parser.expression.indexOutOfBounds").to_string())); } value = array[index].clone(); } else { - return Err(DscError::Parser("Index access on non-array value".to_string())); + return Err(DscError::Parser(t!("parser.expression.indexOnNonArray").to_string())); } } else if !index.is_null() { - return Err(DscError::Parser("Invalid index type".to_string())); + return Err(DscError::Parser(t!("parser.expression.invalidIndexType").to_string())); } } diff --git a/dsc_lib/src/parser/functions.rs b/dsc_lib/src/parser/functions.rs index f03b49efb..15e470ffc 100644 --- a/dsc_lib/src/parser/functions.rs +++ b/dsc_lib/src/parser/functions.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use rust_i18n::t; use serde_json::{Number, Value}; use tracing::debug; use tree_sitter::Node; @@ -44,16 +45,16 @@ impl Function { match member.kind() { "arguments" => function_args = Some(member), "functionName" => function_name = Some(member), - "ERROR" => return Err(DscError::Parser("Found error node parsing function".to_string())), + "ERROR" => return Err(DscError::Parser(t!("parser.functions.foundErrorNode").to_string())), _ => {} } } let Some(name) = function_name else { - return Err(DscError::Parser("Function name node not found".to_string())); + return Err(DscError::Parser(t!("parser.functions.nameNodeNotFound").to_string())); }; let args = convert_args_node(statement_bytes, function_args.as_ref())?; let name = name.utf8_text(statement_bytes)?; - debug!("Function name: {0}", name); + debug!("{}", t!("parser.functions.functionName", name = name)); Ok(Function{ name: name.to_string(), args}) @@ -71,12 +72,12 @@ impl Function { for arg in args { match arg { FunctionArg::Expression(expression) => { - debug!("Arg is expression"); + debug!("{}", t!("parser.functions.argIsExpression")); let value = expression.invoke(function_dispatcher, context)?; resolved_args.push(value.clone()); }, FunctionArg::Value(value) => { - debug!("Arg is value: '{:?}'", value); + debug!("{}", t!("parser.functions.argIsValue", value = value : {:?})); resolved_args.push(value.clone()); } } @@ -114,7 +115,7 @@ fn convert_args_node(statement_bytes: &[u8], args: Option<&Node>) -> Result { - return Err(DscError::Parser(format!("Unknown argument type '{0}'", arg.kind()))); + return Err(DscError::Parser(t!("parser.functions.unknownArgType", kind = arg.kind()).to_string())); } } } diff --git a/dsc_lib/src/parser/mod.rs b/dsc_lib/src/parser/mod.rs index b0440b2fe..5310ff45a 100644 --- a/dsc_lib/src/parser/mod.rs +++ b/dsc_lib/src/parser/mod.rs @@ -2,6 +2,7 @@ // Licensed under the MIT License. use expressions::Expression; +use rust_i18n::t; use serde_json::Value; use tracing::debug; use tree_sitter::Parser; @@ -44,48 +45,48 @@ impl Statement { /// /// This function will return an error if the statement fails to parse or execute. pub fn parse_and_execute(&mut self, statement: &str, context: &Context) -> Result { - debug!("Parsing statement: {0}", statement); + debug!("{}", t!("parser.parsingStatement", statement = statement)); let Some(tree) = &mut self.parser.parse(statement, None) else { - return Err(DscError::Parser(format!("Error parsing statement: {statement}"))); + return Err(DscError::Parser(t!("parser.failedToParse", statement = statement).to_string())); }; let root_node = tree.root_node(); if root_node.is_error() { - return Err(DscError::Parser(format!("Error parsing statement root: {statement}"))); + return Err(DscError::Parser(t!("parser.failedToParseRoot", statement = statement).to_string())); } if root_node.kind() != "statement" { - return Err(DscError::Parser(format!("Invalid statement: {statement}"))); + return Err(DscError::Parser(t!("parser.invalidStatement", statement = statement).to_string())); } let statement_bytes = statement.as_bytes(); let mut cursor = root_node.walk(); let mut return_value = Value::Null; for child_node in root_node.named_children(&mut cursor) { if child_node.is_error() { - return Err(DscError::Parser(format!("Error parsing statement: {statement}"))); + return Err(DscError::Parser(t!("parser.failedToParse", statement = statement).to_string())); } match child_node.kind() { "stringLiteral" => { let Ok(value) = child_node.utf8_text(statement_bytes) else { - return Err(DscError::Parser("Error parsing string literal".to_string())); + return Err(DscError::Parser(t!("parser.failedToParseStringLiteral").to_string())); }; - debug!("Parsing string literal: {0}", value.to_string()); + debug!("{}", t!("parser.parsingStringLiteral", value = value.to_string())); return_value = Value::String(value.to_string()); }, "escapedStringLiteral" => { // need to remove the first character: [[ => [ let Ok(value) = child_node.utf8_text(statement_bytes) else { - return Err(DscError::Parser("Error parsing escaped string literal".to_string())); + return Err(DscError::Parser(t!("parser.failedToParseEscapedStringLiteral").to_string())); }; - debug!("Parsing escaped string literal: {0}", value[1..].to_string()); + debug!("{}", t!("parser.parsingEscapedStringLiteral", value = value[1..].to_string())); return_value = Value::String(value[1..].to_string()); }, "expression" => { - debug!("Parsing expression"); + debug!("{}", t!("parser.parsingExpression")); let expression = Expression::new(statement_bytes, &child_node)?; return_value = expression.invoke(&self.function_dispatcher, context)?; }, _ => { - return Err(DscError::Parser(format!("Unknown expression type {0}", child_node.kind()))); + return Err(DscError::Parser(t!("parser.unknownExpressionType", kind = child_node.kind()).to_string())); } } } diff --git a/dsc_lib/src/util.rs b/dsc_lib/src/util.rs index 1f70504f5..e82504a41 100644 --- a/dsc_lib/src/util.rs +++ b/dsc_lib/src/util.rs @@ -3,6 +3,7 @@ use crate::dscerror::DscError; use clap::ValueEnum; +use rust_i18n::t; use serde_json::Value; use serde::Serialize; use std::fs; @@ -184,36 +185,35 @@ pub fn get_setting(value_name: &str) -> Result { if let Ok(v) = load_value_from_json(&settings_file_path, DEFAULT_SETTINGS_SCHEMA_VERSION) { if let Some(n) = v.get(value_name) { result.setting = n.clone(); - debug!("Found setting '{}' in {}", &value_name, settings_file_path.to_string_lossy()); + debug!("{}", t!("util.foundSetting", name = value_name, path = settings_file_path.to_string_lossy())); } } else { - debug!("Did not find setting '{}' in {}", &value_name, settings_file_path.to_string_lossy()); + debug!("{}", t!("util.notFoundSetting", name = value_name, path = settings_file_path.to_string_lossy())); } // Second, get setting from the active settings file overwriting previous value settings_file_path = exe_home.join(SETTINGS_FILE_NAME); if let Ok(v) = load_value_from_json(&settings_file_path, value_name) { result.setting = v; - debug!("Found setting '{}' in {}", &value_name, settings_file_path.to_string_lossy()); + debug!("{}", t!("util.foundSetting", name = value_name, path = settings_file_path.to_string_lossy())); } else { - debug!("Did not find setting '{}' in {}", &value_name, settings_file_path.to_string_lossy()); + debug!("{}", t!("util.notFoundSetting", name = value_name, path = settings_file_path.to_string_lossy())); } } else { - debug!("Can't get dsc executable path"); + debug!("{}", t!("util.failedToGetExePath")); } // Third, get setting from the policy settings_file_path = PathBuf::from(get_settings_policy_file_path()); if let Ok(v) = load_value_from_json(&settings_file_path, value_name) { result.policy = v; - debug!("Found setting '{}' in {}", &value_name, settings_file_path.to_string_lossy()); + debug!("{}", t!("util.foundSetting", name = value_name, path = settings_file_path.to_string_lossy())); } else { - debug!("Did not find setting '{}' in {}", &value_name, settings_file_path.to_string_lossy()); + debug!("{}", t!("util.notFoundSetting", name = value_name, path = settings_file_path.to_string_lossy())); } - if (result.setting == serde_json::Value::Null) && - (result.policy == serde_json::Value::Null) { - return Err(DscError::NotSupported(format!("Could not find '{value_name}' in settings").to_string())); + if (result.setting == serde_json::Value::Null) && (result.policy == serde_json::Value::Null) { + return Err(DscError::NotSupported(t!("util.settingNotFound", name = value_name).to_string())); } Ok(result) @@ -255,7 +255,7 @@ pub fn get_exe_path() -> Result { return Ok(exe); } - Err(DscError::NotSupported("Can't get the path to dsc executable".to_string())) + Err(DscError::NotSupported(t!("util.failedToGetExePath").to_string())) } #[cfg(target_os = "windows")] diff --git a/tools/test_group_resource/Cargo.lock b/tools/test_group_resource/Cargo.lock index 9775a5e8b..1963ab64b 100644 --- a/tools/test_group_resource/Cargo.lock +++ b/tools/test_group_resource/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -91,7 +91,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -101,7 +101,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -110,6 +110,12 @@ version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arrayvec" version = "0.7.6" @@ -137,6 +143,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base62" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fa474cf7492f9a299ba6019fb99ec673e1739556d48e8a90eabaea282ef0e4" + [[package]] name = "base64" version = "0.22.1" @@ -158,6 +170,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -170,6 +188,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32" +[[package]] +name = "bstr" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -279,7 +307,7 @@ dependencies = [ "lazy_static", "libc", "unicode-width", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -288,6 +316,31 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "darling" version = "0.20.10" @@ -379,6 +432,7 @@ dependencies = [ "linked-hash-map", "num-traits", "regex", + "rust-i18n", "schemars", "security_context_lib", "semver", @@ -400,6 +454,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "email_address" version = "0.2.9" @@ -421,6 +481,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "fancy-regex" version = "0.13.0" @@ -432,6 +502,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fluent-uri" version = "0.3.1" @@ -485,6 +561,36 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -678,6 +784,22 @@ dependencies = [ "utf8_iter", ] +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -737,6 +859,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -786,9 +917,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.168" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" + +[[package]] +name = "libyml" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "64804cc6a5042d4f05379909ba25b503ec04e2c082151d62122d5dcaa274b961" [[package]] name = "linked-hash-map" @@ -796,6 +933,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "litemap" version = "0.7.3" @@ -842,7 +985,7 @@ dependencies = [ "hermit-abi", "libc", "wasi", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -851,12 +994,21 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "cfg_aliases", "libc", ] +[[package]] +name = "normpath" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1044,7 +1196,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -1109,18 +1261,94 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rust-i18n" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039f57d22229db401af3458ca939300178e99e88b938573cea12b7c2b0f09724" +dependencies = [ + "globwalk", + "once_cell", + "regex", + "rust-i18n-macro", + "rust-i18n-support", + "smallvec", +] + +[[package]] +name = "rust-i18n-macro" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde5c022360a2e54477882843d56b6f9bcb4bc62f504b651a2f497f0028d174f" +dependencies = [ + "glob", + "once_cell", + "proc-macro2", + "quote", + "rust-i18n-support", + "serde", + "serde_json", + "serde_yml", + "syn", +] + +[[package]] +name = "rust-i18n-support" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75d2844d36f62b5d6b66f9cf8f8cbdbbbdcdb5fd37a473a9cc2fb45fdcf485d2" +dependencies = [ + "arc-swap", + "base62", + "globwalk", + "itertools", + "lazy_static", + "normpath", + "once_cell", + "proc-macro2", + "regex", + "serde", + "serde_json", + "serde_yml", + "siphasher", + "toml", + "triomphe", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schemars" version = "0.8.21" @@ -1210,6 +1438,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -1223,6 +1460,23 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serde_yml" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e76bab63c3fd98d27c17f9cbce177f64a91f5e69ac04cafe04e1bb25d1dc3c" +dependencies = [ + "indexmap 2.6.0", + "itoa", + "libyml", + "log", + "memchr", + "ryu", + "serde", + "serde_json", + "tempfile", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1247,6 +1501,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "smallvec" version = "1.13.2" @@ -1260,7 +1520,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1303,6 +1563,19 @@ dependencies = [ "syn", ] +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "test_group_resource" version = "0.1.0" @@ -1384,7 +1657,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1398,6 +1671,40 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.40" @@ -1506,6 +1813,17 @@ dependencies = [ "tree-sitter-language", ] +[[package]] +name = "triomphe" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" +dependencies = [ + "arc-swap", + "serde", + "stable_deref_trait", +] + [[package]] name = "unicode-bidi" version = "0.3.17" @@ -1636,6 +1954,16 @@ dependencies = [ "quote", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1713,6 +2041,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1737,6 +2074,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1801,6 +2147,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "write16" version = "1.0.0"