@@ -8,7 +8,7 @@ use dsc_lib_jsonschema::transforms::{
88} ;
99use rust_i18n:: t;
1010use schemars:: { JsonSchema , json_schema} ;
11- use serde:: { Deserialize , Serialize } ;
11+ use serde:: { Deserialize , Deserializer , Serialize } ;
1212use serde_json:: { Map , Value } ;
1313use 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) ]
189219pub 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