From f1d02d17021c89744e037528aebfef6824c99aa2 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Sat, 18 Jul 2026 07:49:03 -0700 Subject: [PATCH 1/6] Add JSON schema caching --- dsc/tests/dsc_config_get.tests.ps1 | 50 +++++++++++++++++++ lib/dsc-lib-jsonschema/.versions.json | 3 +- lib/dsc-lib/locales/en-us.toml | 2 + lib/dsc-lib/src/configure/mod.rs | 2 + lib/dsc-lib/src/configure/schema_cache.rs | 19 +++++++ .../src/dscresources/command_resource.rs | 22 +++++--- lib/dsc-lib/src/dscresources/dscresource.rs | 13 ++++- 7 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 lib/dsc-lib/src/configure/schema_cache.rs diff --git a/dsc/tests/dsc_config_get.tests.ps1 b/dsc/tests/dsc_config_get.tests.ps1 index 04f0d15d0..3374473b1 100644 --- a/dsc/tests/dsc_config_get.tests.ps1 +++ b/dsc/tests/dsc_config_get.tests.ps1 @@ -182,4 +182,54 @@ Describe 'dsc config get tests' { $result.results[0].result.actualState.family | Should -BeIn @('Windows', 'Linux', 'macOS') $LASTEXITCODE | Should -Be 0 } + + It 'embedded schema should only be retrieved once for multiple instances of the same resource' { + $config_yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Echo 1 + type: Microsoft.DSC.Debug/Echo + properties: + output: hello + - name: Echo 2 + type: Microsoft.DSC.Debug/Echo + properties: + output: world +"@ + $result = dsc -l debug config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json + $errorLog = Get-Content $TestDrive/error.log -Raw + $LASTEXITCODE | Should -Be 0 -Because $errorLog + $result.hadErrors | Should -BeFalse + $result.results.Count | Should -Be 2 + $errorLog | Should -BeLike "*Retrieved schema for resource 'Microsoft.DSC.Debug/Echo' with version '1.0.0' from cache*" + } + + It 'command resource schema should only be retrieved once for multiple instances of the same resource' { + $config_yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: one + type: Test/Version + requireVersion: =1.1.0 + properties: + version: 1.1.0 + - name: two + type: Test/Version + requireVersion: =1.1.0 + properties: + version: 1.1.0 + - name: three + type: Test/Version + requireVersion: =2.0.0 + properties: + version: 2.0.0 +"@ + $result = dsc -l debug config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json + $errorLog = Get-Content $TestDrive/error.log -Raw + $LASTEXITCODE | Should -Be 0 -Because $errorLog + $result.hadErrors | Should -BeFalse + $result.results.Count | Should -Be 3 + $errorLog | Should -BeLike "*Retrieved schema for resource 'Test/Version' with version '1.1.0' from cache*" + $errorLog | Should -BeLike "*Retrieved schema for resource 'Test/Version' with version '2.0.0' from cache*" + } } diff --git a/lib/dsc-lib-jsonschema/.versions.json b/lib/dsc-lib-jsonschema/.versions.json index 15f6ca427..d729d4716 100644 --- a/lib/dsc-lib-jsonschema/.versions.json +++ b/lib/dsc-lib-jsonschema/.versions.json @@ -1,10 +1,11 @@ { "latestMajor": "V3", "latestMinor": "V3_2", - "latestPatch": "V3_2_2", + "latestPatch": "V3_2_3", "all": [ "V3", "V3_2", + "V3_2_3", "V3_2_2", "V3_2_1", "V3_2_0", diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 257e7b2d5..2f77358e0 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -201,6 +201,7 @@ securityContextRequired = "Operation '%{operation}' for resource '%{resource}' r noAdaptedContent = "No adapted content available for resource '%{resource}'" invalidAdaptedContent = "Invalid adapted content for resource '%{resource}': %{error}" exportFilteringNotSupported = "Resource '%{resource}' does not support export filtering" +retrievedSchemaFromCache = "Retrieved schema for resource '%{resource}' with version '%{version}' from cache" [dscresources.dscresource] invokeGet = "Invoking get for '%{resource}'" @@ -240,6 +241,7 @@ adapterManifestNotFound = "Adapter manifest for '%{adapter}' not found" adapterDoesNotSupportDelete = "Adapter '%{adapter}' does not support delete operation" validatingAgainstSchema = "Validating against resource schema" deprecationMessage = "Resource '%{resource}' is deprecated: %{message}" +retrievedSchemaFromCache = "Retrieved schema for resource '%{resource}' with version '%{version}' from cache" [dscresources.resource_manifest] resourceManifestSchemaTitle = "Resource manifest schema URI" diff --git a/lib/dsc-lib/src/configure/mod.rs b/lib/dsc-lib/src/configure/mod.rs index 80d80513f..1611372d6 100644 --- a/lib/dsc-lib/src/configure/mod.rs +++ b/lib/dsc-lib/src/configure/mod.rs @@ -28,12 +28,14 @@ use serde_json::{Map, Value}; use std::path::PathBuf; use std::collections::HashMap; use tracing::{debug, info, trace, warn}; + pub mod context; pub mod config_doc; pub mod config_result; pub mod constraints; pub mod depends_on; pub mod parameters; +pub mod schema_cache; pub struct Configurator { json: String, diff --git a/lib/dsc-lib/src/configure/schema_cache.rs b/lib/dsc-lib/src/configure/schema_cache.rs new file mode 100644 index 000000000..e2a065f54 --- /dev/null +++ b/lib/dsc-lib/src/configure/schema_cache.rs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::{collections::{BTreeMap, HashMap}, sync::RwLock, sync::LazyLock}; +use serde_json::Value; + +use crate::types::{FullyQualifiedTypeName, ResourceVersion}; + +pub type SchemaCache = BTreeMap>; + +pub static RESOURCE_SCHEMAS: LazyLock> = LazyLock::new(|| RwLock::new(SchemaCache::new())); + +pub fn get_resource_schemas(type_name: &FullyQualifiedTypeName, version: &ResourceVersion) -> Option { + let cache = RESOURCE_SCHEMAS.read().unwrap(); + if let Some(schemas) = cache.get(type_name) && let Some(schema) = schemas.get(version) { + return Some(schema.clone()); + } + None +} diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index 206069cfe..30715c142 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -8,8 +8,9 @@ use rust_i18n::t; use serde::Deserialize; use serde_json::{Map, Value}; use std::{collections::HashMap, env, path::Path, process::Stdio}; -use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}}, dscresources::resource_manifest::{ExportSchemaKind, ExportSchemaOrFiltering, SchemaArgKind}, types::{ExitCodesMap}, util::canonicalize_which}; +use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}, schema_cache::{get_resource_schemas, SchemaCache, RESOURCE_SCHEMAS}}, dscresources::resource_manifest::{ExportSchemaKind, ExportSchemaOrFiltering, SchemaArgKind}, types::ExitCodesMap, util::canonicalize_which}; use crate::dscerror::DscError; +use crate::locked_extend; use super::{ dscresource::{get_diff, redact, DscResource}, invoke_result::{ @@ -556,6 +557,11 @@ pub fn invoke_validate(resource: &DscResource, config: &str, target_resource: Op /// /// Error if schema is not available or if there is an error getting the schema pub fn get_schema(resource: &DscResource, target_resource: Option<&DscResource>) -> Result { + if let Some(schema) = get_resource_schemas(&resource.type_name, &resource.version) { + debug!("{}", t!("dscresources.commandResource.retrievedSchemaFromCache", resource = &resource.type_name, version = &resource.version)); + return Ok(serde_json::to_string(&schema)?); + } + let Some(manifest) = &resource.manifest else { return Err(DscError::MissingManifest(resource.type_name.to_string())); }; @@ -573,17 +579,21 @@ pub fn get_schema(resource: &DscResource, target_resource: Option<&DscResource>) return Err(DscError::SchemaNotAvailable(target_resource.type_name.to_string())); }; - match schema_kind { + let schema = match schema_kind { SchemaKind::Command(command) => { let args = process_schema_args(command.args.as_ref(), target_resource); let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, args, None, Some(&resource.directory), None, manifest.exit_codes.as_ref())?; - Ok(stdout) + stdout }, SchemaKind::Embedded(schema) => { - let json = serde_json::to_string(&schema)?; - Ok(json) + serde_json::to_string(&schema)? }, - } + }; + + let mut schema_cache = SchemaCache::new(); + schema_cache.insert(resource.type_name.clone(), HashMap::from([(resource.version.clone(), serde_json::from_str(&schema)?)])); + locked_extend!(RESOURCE_SCHEMAS, schema_cache); + Ok(schema) } fn verify_with_export_schema(input: &str, resource: &DscResource, target_resource: Option<&DscResource>) -> Result<(), DscError> { diff --git a/lib/dsc-lib/src/dscresources/dscresource.rs b/lib/dsc-lib/src/dscresources/dscresource.rs index f3f57c0b9..75ff3d7c9 100644 --- a/lib/dsc-lib/src/dscresources/dscresource.rs +++ b/lib/dsc-lib/src/dscresources/dscresource.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::{configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}, context::ProcessMode, parameters::{SECURE_VALUE_REDACTED, is_secure_value}}, dscresources::resource_manifest::{AdapterInputKind, Kind}, types::{FullyQualifiedTypeName, ResourceVersion}}; +use crate::{configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}, context::ProcessMode, parameters::{SECURE_VALUE_REDACTED, is_secure_value}, schema_cache::{RESOURCE_SCHEMAS, SchemaCache, get_resource_schemas}}, dscresources::resource_manifest::{AdapterInputKind, Kind}, locked_extend, types::{FullyQualifiedTypeName, ResourceVersion}}; use crate::discovery::discovery_trait::DiscoveryFilter; use crate::dscresources::invoke_result::{ResourceGetResponse, ResourceSetResponse}; use crate::schemas::transforms::idiomaticize_string_enum; @@ -529,6 +529,11 @@ impl Invoke for DscResource { } fn schema(&self) -> Result { + if let Some(schema) = get_resource_schemas(&self.type_name, &self.version) { + debug!("{}", t!("dscresources.dscresource.retrievedSchemaFromCache", resource = self.type_name, version = self.version)); + return Ok(serde_json::to_string(&schema)?); + } + debug!("{}", t!("dscresources.dscresource.invokeSchema", resource = self.type_name)); if let Some(deprecation_message) = self.deprecation_message.as_ref() { warn!("{}", t!("dscresources.dscresource.deprecationMessage", resource = self.type_name, message = deprecation_message)); @@ -542,7 +547,11 @@ impl Invoke for DscResource { match &self.implemented_as { Some(ImplementedAs::Command) => { - command_resource::get_schema(self, self.target_resource.as_deref()) + let schema = command_resource::get_schema(self, self.target_resource.as_deref())?; + let mut schema_cache = SchemaCache::new(); + schema_cache.insert(self.type_name.clone(), HashMap::from([(self.version.clone(), serde_json::from_str(&schema)?)])); + locked_extend!(RESOURCE_SCHEMAS, schema_cache); + Ok(schema) }, _ => { Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) From cc20a3f0ea1afc8fc32693175ba62c0f8b0d5d75 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 21 Jul 2026 13:07:56 -0700 Subject: [PATCH 2/6] address copilot feedback --- dsc/tests/dsc_config_get.tests.ps1 | 2 +- lib/dsc-lib/src/dscresources/command_resource.rs | 15 ++++++++------- lib/dsc-lib/src/dscresources/dscresource.rs | 13 +++++-------- lib/dsc-lib/src/util.rs | 8 ++++++++ 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/dsc/tests/dsc_config_get.tests.ps1 b/dsc/tests/dsc_config_get.tests.ps1 index 3374473b1..cfc297c7c 100644 --- a/dsc/tests/dsc_config_get.tests.ps1 +++ b/dsc/tests/dsc_config_get.tests.ps1 @@ -204,7 +204,7 @@ Describe 'dsc config get tests' { $errorLog | Should -BeLike "*Retrieved schema for resource 'Microsoft.DSC.Debug/Echo' with version '1.0.0' from cache*" } - It 'command resource schema should only be retrieved once for multiple instances of the same resource' { + It 'retrieves command resource schema from cache for multiple instances of the same resource' { $config_yaml = @" `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json resources: diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index 30715c142..b765924da 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -8,9 +8,9 @@ use rust_i18n::t; use serde::Deserialize; use serde_json::{Map, Value}; use std::{collections::HashMap, env, path::Path, process::Stdio}; -use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}, schema_cache::{get_resource_schemas, SchemaCache, RESOURCE_SCHEMAS}}, dscresources::resource_manifest::{ExportSchemaKind, ExportSchemaOrFiltering, SchemaArgKind}, types::ExitCodesMap, util::canonicalize_which}; +use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}, schema_cache::{get_resource_schemas, RESOURCE_SCHEMAS}}, dscresources::resource_manifest::{ExportSchemaKind, ExportSchemaOrFiltering, SchemaArgKind}, types::ExitCodesMap, util::canonicalize_which}; use crate::dscerror::DscError; -use crate::locked_extend; +use crate::locked_insert; use super::{ dscresource::{get_diff, redact, DscResource}, invoke_result::{ @@ -557,8 +557,9 @@ pub fn invoke_validate(resource: &DscResource, config: &str, target_resource: Op /// /// Error if schema is not available or if there is an error getting the schema pub fn get_schema(resource: &DscResource, target_resource: Option<&DscResource>) -> Result { - if let Some(schema) = get_resource_schemas(&resource.type_name, &resource.version) { - debug!("{}", t!("dscresources.commandResource.retrievedSchemaFromCache", resource = &resource.type_name, version = &resource.version)); + let cached_resource = target_resource.unwrap_or(resource); + if let Some(schema) = get_resource_schemas(&cached_resource.type_name, &cached_resource.version) { + debug!("{}", t!("dscresources.commandResource.retrievedSchemaFromCache", resource = &cached_resource.type_name, version = &cached_resource.version)); return Ok(serde_json::to_string(&schema)?); } @@ -590,9 +591,9 @@ pub fn get_schema(resource: &DscResource, target_resource: Option<&DscResource>) }, }; - let mut schema_cache = SchemaCache::new(); - schema_cache.insert(resource.type_name.clone(), HashMap::from([(resource.version.clone(), serde_json::from_str(&schema)?)])); - locked_extend!(RESOURCE_SCHEMAS, schema_cache); + let schema_value: Value = serde_json::from_str(&schema)?; + locked_insert!(RESOURCE_SCHEMAS, cached_resource.type_name.clone(), cached_resource.version.clone(), schema_value); + Ok(schema) } diff --git a/lib/dsc-lib/src/dscresources/dscresource.rs b/lib/dsc-lib/src/dscresources/dscresource.rs index 75ff3d7c9..3ef397f03 100644 --- a/lib/dsc-lib/src/dscresources/dscresource.rs +++ b/lib/dsc-lib/src/dscresources/dscresource.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::{configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}, context::ProcessMode, parameters::{SECURE_VALUE_REDACTED, is_secure_value}, schema_cache::{RESOURCE_SCHEMAS, SchemaCache, get_resource_schemas}}, dscresources::resource_manifest::{AdapterInputKind, Kind}, locked_extend, types::{FullyQualifiedTypeName, ResourceVersion}}; +use crate::{configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}, context::ProcessMode, parameters::{SECURE_VALUE_REDACTED, is_secure_value}, schema_cache::get_resource_schemas}, dscresources::resource_manifest::{AdapterInputKind, Kind}, types::{FullyQualifiedTypeName, ResourceVersion}}; use crate::discovery::discovery_trait::DiscoveryFilter; use crate::dscresources::invoke_result::{ResourceGetResponse, ResourceSetResponse}; use crate::schemas::transforms::idiomaticize_string_enum; @@ -529,8 +529,9 @@ impl Invoke for DscResource { } fn schema(&self) -> Result { - if let Some(schema) = get_resource_schemas(&self.type_name, &self.version) { - debug!("{}", t!("dscresources.dscresource.retrievedSchemaFromCache", resource = self.type_name, version = self.version)); + let target_resource = self.target_resource.as_deref().unwrap_or(self); + if let Some(schema) = get_resource_schemas(&target_resource.type_name, &target_resource.version) { + debug!("{}", t!("dscresources.dscresource.retrievedSchemaFromCache", resource = target_resource.type_name, version = target_resource.version)); return Ok(serde_json::to_string(&schema)?); } @@ -547,11 +548,7 @@ impl Invoke for DscResource { match &self.implemented_as { Some(ImplementedAs::Command) => { - let schema = command_resource::get_schema(self, self.target_resource.as_deref())?; - let mut schema_cache = SchemaCache::new(); - schema_cache.insert(self.type_name.clone(), HashMap::from([(self.version.clone(), serde_json::from_str(&schema)?)])); - locked_extend!(RESOURCE_SCHEMAS, schema_cache); - Ok(schema) + command_resource::get_schema(self, self.target_resource.as_deref()) }, _ => { Err(DscError::NotImplemented(t!("dscresources.dscresource.customResourceNotSupported").to_string())) diff --git a/lib/dsc-lib/src/util.rs b/lib/dsc-lib/src/util.rs index ee1c49b20..a291f8d55 100644 --- a/lib/dsc-lib/src/util.rs +++ b/lib/dsc-lib/src/util.rs @@ -515,3 +515,11 @@ macro_rules! locked_get { } }}; } + +#[macro_export] +macro_rules! locked_insert { + ($lockable:expr, $key:expr, $subkey:expr, $value:expr) => {{ + let mut btree = $lockable.write().unwrap(); + btree.entry($key).or_default().insert($subkey, $value); + }}; +} From 5d25b6757486bd4131c5cf9b81819a2800bf167b Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 21 Jul 2026 13:31:06 -0700 Subject: [PATCH 3/6] fix copilot feedback to not deserializing the json --- lib/dsc-lib/src/dscresources/command_resource.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index b765924da..1260df39f 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -580,18 +580,17 @@ pub fn get_schema(resource: &DscResource, target_resource: Option<&DscResource>) return Err(DscError::SchemaNotAvailable(target_resource.type_name.to_string())); }; - let schema = match schema_kind { + let (schema, schema_value) = match schema_kind { SchemaKind::Command(command) => { let args = process_schema_args(command.args.as_ref(), target_resource); let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, args, None, Some(&resource.directory), None, manifest.exit_codes.as_ref())?; - stdout + (stdout.clone(), serde_json::from_str(&stdout)?) }, SchemaKind::Embedded(schema) => { - serde_json::to_string(&schema)? + (serde_json::to_string(&schema)?, schema.clone()) }, }; - let schema_value: Value = serde_json::from_str(&schema)?; locked_insert!(RESOURCE_SCHEMAS, cached_resource.type_name.clone(), cached_resource.version.clone(), schema_value); Ok(schema) From 79635eac093115a8f361f73c5cfd74145b10320d Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 21 Jul 2026 18:44:51 -0700 Subject: [PATCH 4/6] rename function to be singular --- lib/dsc-lib/src/configure/schema_cache.rs | 2 +- lib/dsc-lib/src/dscresources/command_resource.rs | 4 ++-- lib/dsc-lib/src/dscresources/dscresource.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/dsc-lib/src/configure/schema_cache.rs b/lib/dsc-lib/src/configure/schema_cache.rs index e2a065f54..1c1a6b3b0 100644 --- a/lib/dsc-lib/src/configure/schema_cache.rs +++ b/lib/dsc-lib/src/configure/schema_cache.rs @@ -10,7 +10,7 @@ pub type SchemaCache = BTreeMap> = LazyLock::new(|| RwLock::new(SchemaCache::new())); -pub fn get_resource_schemas(type_name: &FullyQualifiedTypeName, version: &ResourceVersion) -> Option { +pub fn get_resource_schema(type_name: &FullyQualifiedTypeName, version: &ResourceVersion) -> Option { let cache = RESOURCE_SCHEMAS.read().unwrap(); if let Some(schemas) = cache.get(type_name) && let Some(schema) = schemas.get(version) { return Some(schema.clone()); diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index 1260df39f..f0cb32f0c 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -8,7 +8,7 @@ use rust_i18n::t; use serde::Deserialize; use serde_json::{Map, Value}; use std::{collections::HashMap, env, path::Path, process::Stdio}; -use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}, schema_cache::{get_resource_schemas, RESOURCE_SCHEMAS}}, dscresources::resource_manifest::{ExportSchemaKind, ExportSchemaOrFiltering, SchemaArgKind}, types::ExitCodesMap, util::canonicalize_which}; +use crate::{configure::{config_doc::{ExecutionKind, SecurityContextKind}, config_result::{ResourceGetResult, ResourceTestResult}, schema_cache::{get_resource_schema, RESOURCE_SCHEMAS}}, dscresources::resource_manifest::{ExportSchemaKind, ExportSchemaOrFiltering, SchemaArgKind}, types::ExitCodesMap, util::canonicalize_which}; use crate::dscerror::DscError; use crate::locked_insert; use super::{ @@ -558,7 +558,7 @@ pub fn invoke_validate(resource: &DscResource, config: &str, target_resource: Op /// Error if schema is not available or if there is an error getting the schema pub fn get_schema(resource: &DscResource, target_resource: Option<&DscResource>) -> Result { let cached_resource = target_resource.unwrap_or(resource); - if let Some(schema) = get_resource_schemas(&cached_resource.type_name, &cached_resource.version) { + if let Some(schema) = get_resource_schema(&cached_resource.type_name, &cached_resource.version) { debug!("{}", t!("dscresources.commandResource.retrievedSchemaFromCache", resource = &cached_resource.type_name, version = &cached_resource.version)); return Ok(serde_json::to_string(&schema)?); } diff --git a/lib/dsc-lib/src/dscresources/dscresource.rs b/lib/dsc-lib/src/dscresources/dscresource.rs index 3ef397f03..8c2566610 100644 --- a/lib/dsc-lib/src/dscresources/dscresource.rs +++ b/lib/dsc-lib/src/dscresources/dscresource.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::{configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}, context::ProcessMode, parameters::{SECURE_VALUE_REDACTED, is_secure_value}, schema_cache::get_resource_schemas}, dscresources::resource_manifest::{AdapterInputKind, Kind}, types::{FullyQualifiedTypeName, ResourceVersion}}; +use crate::{configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}, context::ProcessMode, parameters::{SECURE_VALUE_REDACTED, is_secure_value}, schema_cache::get_resource_schema}, dscresources::resource_manifest::{AdapterInputKind, Kind}, types::{FullyQualifiedTypeName, ResourceVersion}}; use crate::discovery::discovery_trait::DiscoveryFilter; use crate::dscresources::invoke_result::{ResourceGetResponse, ResourceSetResponse}; use crate::schemas::transforms::idiomaticize_string_enum; @@ -530,7 +530,7 @@ impl Invoke for DscResource { fn schema(&self) -> Result { let target_resource = self.target_resource.as_deref().unwrap_or(self); - if let Some(schema) = get_resource_schemas(&target_resource.type_name, &target_resource.version) { + if let Some(schema) = get_resource_schema(&target_resource.type_name, &target_resource.version) { debug!("{}", t!("dscresources.dscresource.retrievedSchemaFromCache", resource = target_resource.type_name, version = target_resource.version)); return Ok(serde_json::to_string(&schema)?); } From 994cffbd39aafb8194c17d603234bb77d29c2170 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 21 Jul 2026 19:36:16 -0700 Subject: [PATCH 5/6] make helpers scoped to crate --- lib/dsc-lib/src/configure/schema_cache.rs | 6 +++--- lib/dsc-lib/src/dscresources/command_resource.rs | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/dsc-lib/src/configure/schema_cache.rs b/lib/dsc-lib/src/configure/schema_cache.rs index 1c1a6b3b0..dedbb8472 100644 --- a/lib/dsc-lib/src/configure/schema_cache.rs +++ b/lib/dsc-lib/src/configure/schema_cache.rs @@ -6,11 +6,11 @@ use serde_json::Value; use crate::types::{FullyQualifiedTypeName, ResourceVersion}; -pub type SchemaCache = BTreeMap>; +pub(crate) type SchemaCache = BTreeMap>; -pub static RESOURCE_SCHEMAS: LazyLock> = LazyLock::new(|| RwLock::new(SchemaCache::new())); +pub(crate) static RESOURCE_SCHEMAS: LazyLock> = LazyLock::new(|| RwLock::new(SchemaCache::new())); -pub fn get_resource_schema(type_name: &FullyQualifiedTypeName, version: &ResourceVersion) -> Option { +pub(crate) fn get_resource_schema(type_name: &FullyQualifiedTypeName, version: &ResourceVersion) -> Option { let cache = RESOURCE_SCHEMAS.read().unwrap(); if let Some(schemas) = cache.get(type_name) && let Some(schema) = schemas.get(version) { return Some(schema.clone()); diff --git a/lib/dsc-lib/src/dscresources/command_resource.rs b/lib/dsc-lib/src/dscresources/command_resource.rs index f0cb32f0c..b29acffa3 100644 --- a/lib/dsc-lib/src/dscresources/command_resource.rs +++ b/lib/dsc-lib/src/dscresources/command_resource.rs @@ -584,7 +584,8 @@ pub fn get_schema(resource: &DscResource, target_resource: Option<&DscResource>) SchemaKind::Command(command) => { let args = process_schema_args(command.args.as_ref(), target_resource); let (_exit_code, stdout, _stderr) = invoke_command(&command.executable, args, None, Some(&resource.directory), None, manifest.exit_codes.as_ref())?; - (stdout.clone(), serde_json::from_str(&stdout)?) + let schema_value: Value = serde_json::from_str(&stdout)?; + (stdout, schema_value) }, SchemaKind::Embedded(schema) => { (serde_json::to_string(&schema)?, schema.clone()) From 99e22f9b0d75c788b0778b2caa4fd42e8f692bea Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 21 Jul 2026 21:15:56 -0700 Subject: [PATCH 6/6] make cache crate only --- lib/dsc-lib/src/configure/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dsc-lib/src/configure/mod.rs b/lib/dsc-lib/src/configure/mod.rs index 1611372d6..0cac6ee56 100644 --- a/lib/dsc-lib/src/configure/mod.rs +++ b/lib/dsc-lib/src/configure/mod.rs @@ -35,7 +35,7 @@ pub mod config_result; pub mod constraints; pub mod depends_on; pub mod parameters; -pub mod schema_cache; +pub(crate) mod schema_cache; pub struct Configurator { json: String,