Skip to content

Commit 7984ca1

Browse files
committed
Implement custom deserializer for resources
1 parent 7b038c1 commit 7984ca1

File tree

1 file changed

+108
-1
lines changed

1 file changed

+108
-1
lines changed

lib/dsc-lib/src/configure/config_doc.rs

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use dsc_lib_jsonschema::transforms::{
88
};
99
use rust_i18n::t;
1010
use schemars::{JsonSchema, json_schema};
11-
use serde::{Deserialize, Serialize};
11+
use serde::{Deserialize, Deserializer, Serialize};
1212
use serde_json::{Map, Value};
1313
use std::{collections::HashMap, fmt::Display};
1414

@@ -174,6 +174,7 @@ pub struct Configuration {
174174
pub outputs: Option<HashMap<String, Output>>,
175175
#[serde(skip_serializing_if = "Option::is_none")]
176176
pub parameters: Option<HashMap<String, Parameter>>,
177+
#[serde(deserialize_with = "deserialize_resources")]
177178
pub resources: Vec<Resource>,
178179
#[serde(skip_serializing_if = "Option::is_none")]
179180
pub variables: Option<Map<String, Value>>,
@@ -184,6 +185,35 @@ pub struct Configuration {
184185
pub extensions: Option<Map<String, Value>>,
185186
}
186187

188+
/// Simplest implementation of a custom deserializer that will map a JSON object
189+
/// of resources (where the keys are symbolic names) as found in ARMv2 back to a
190+
/// vector, so the rest of this codebase can remain untouched.
191+
fn deserialize_resources<'de, D>(deserializer: D) -> Result<Vec<Resource>, D::Error>
192+
where
193+
D: Deserializer<'de>,
194+
{
195+
let value = Value::deserialize(deserializer)?;
196+
197+
match value {
198+
Value::Array(resources) => {
199+
resources.into_iter()
200+
.map(|resource| serde_json::from_value(resource).map_err(serde::de::Error::custom))
201+
.collect()
202+
}
203+
Value::Object(resources) => {
204+
resources.into_iter()
205+
.map(|(name, resource)| {
206+
let mut resource: Resource = serde_json::from_value(resource).map_err(serde::de::Error::custom)?;
207+
// TODO: Decide what to do if resource.name is already set.
208+
resource.name = name;
209+
Ok(resource)
210+
})
211+
.collect()
212+
}
213+
other => Err(serde::de::Error::custom(format!("Expected resources to be either an array or an object, but was {:?}", other))),
214+
}
215+
}
216+
187217
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
188218
#[serde(deny_unknown_fields)]
189219
pub struct Parameter {
@@ -326,6 +356,7 @@ pub struct Resource {
326356
#[serde(skip_serializing_if = "Option::is_none", rename = "apiVersion")]
327357
pub api_version: Option<String>,
328358
/// A friendly name for the resource instance
359+
#[serde(default)]
329360
pub name: String, // friendly unique instance name
330361
#[serde(skip_serializing_if = "Option::is_none")]
331362
pub comments: Option<String>,
@@ -486,4 +517,80 @@ mod test {
486517

487518
assert!(result.is_ok());
488519
}
520+
521+
#[test]
522+
fn test_resources_as_array() {
523+
let config_json = r#"{
524+
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/config/document.json",
525+
"resources": [
526+
{
527+
"type": "Microsoft.DSC.Debug/Echo",
528+
"name": "echoResource",
529+
"apiVersion": "1.0.0"
530+
},
531+
{
532+
"type": "Microsoft/Process",
533+
"name": "processResource",
534+
"apiVersion": "0.1.0"
535+
}
536+
]
537+
}"#;
538+
539+
let config: Configuration = serde_json::from_str(config_json).unwrap();
540+
541+
assert_eq!(config.resources.len(), 2);
542+
assert_eq!(config.resources[0].name, "echoResource");
543+
assert_eq!(config.resources[0].resource_type, "Microsoft.DSC.Debug/Echo");
544+
assert_eq!(config.resources[0].api_version.as_deref(), Some("1.0.0"));
545+
546+
assert_eq!(config.resources[1].name, "processResource");
547+
assert_eq!(config.resources[1].resource_type, "Microsoft/Process");
548+
assert_eq!(config.resources[1].api_version.as_deref(), Some("0.1.0"));
549+
}
550+
551+
#[test]
552+
fn test_resources_with_symbolic_names() {
553+
let config_json = r#"{
554+
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/config/document.json",
555+
"languageVersion": "2.2-experimental",
556+
"extensions": {
557+
"dsc": {
558+
"name": "DesiredStateConfiguration",
559+
"version": "0.1.0"
560+
}
561+
},
562+
"resources": {
563+
"echoResource": {
564+
"extension": "dsc",
565+
"type": "Microsoft.DSC.Debug/Echo",
566+
"apiVersion": "1.0.0",
567+
"properties": {
568+
"output": "Hello World"
569+
}
570+
},
571+
"processResource": {
572+
"extension": "dsc",
573+
"type": "Microsoft/Process",
574+
"apiVersion": "0.1.0",
575+
"properties": {
576+
"name": "pwsh",
577+
"pid": 1234
578+
}
579+
}
580+
}
581+
}"#;
582+
583+
let config: Configuration = serde_json::from_str(config_json).unwrap();
584+
assert_eq!(config.resources.len(), 2);
585+
586+
// Find resources by name (order may vary in HashMap)
587+
let echo_resource = config.resources.iter().find(|r| r.name == "echoResource").unwrap();
588+
let process_resource = config.resources.iter().find(|r| r.name == "processResource").unwrap();
589+
590+
assert_eq!(echo_resource.resource_type, "Microsoft.DSC.Debug/Echo");
591+
assert_eq!(echo_resource.api_version.as_deref(), Some("1.0.0"));
592+
593+
assert_eq!(process_resource.resource_type, "Microsoft/Process");
594+
assert_eq!(process_resource.api_version.as_deref(), Some("0.1.0"));
595+
}
489596
}

0 commit comments

Comments
 (0)