@@ -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,44 @@ 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 > ( 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 = serde_json:: from_value :: < Resource > ( resource) . map_err ( serde:: de:: Error :: custom) ?;
207+ // Note that this is setting the symbolic name as the
208+ // resource's name property only if that isn't already set.
209+ // In the general use case from Bicep, it won't be, but
210+ // we're unsure of the implications in other use cases.
211+ //
212+ // TODO: We will need to update the 'dependsOn' logic to
213+ // accept both the symbolic name as mapped here in addition
214+ // to `resourceId()`, or possibly track both.
215+ if resource. name . is_empty ( ) {
216+ resource. name = name;
217+ }
218+ Ok ( resource)
219+ } )
220+ . collect ( )
221+ }
222+ other => Err ( serde:: de:: Error :: custom ( format ! ( "Expected resources to be either an array or an object, but was {:?}" , other) ) ) ,
223+ }
224+ }
225+
187226#[ derive( Debug , Clone , PartialEq , Deserialize , Serialize , JsonSchema ) ]
188227#[ serde( deny_unknown_fields) ]
189228pub struct Parameter {
@@ -326,6 +365,7 @@ pub struct Resource {
326365 #[ serde( skip_serializing_if = "Option::is_none" , rename = "apiVersion" ) ]
327366 pub api_version : Option < String > ,
328367 /// A friendly name for the resource instance
368+ #[ serde( default ) ]
329369 pub name : String , // friendly unique instance name
330370 #[ serde( skip_serializing_if = "Option::is_none" ) ]
331371 pub comments : Option < String > ,
@@ -486,4 +526,80 @@ mod test {
486526
487527 assert ! ( result. is_ok( ) ) ;
488528 }
529+
530+ #[ test]
531+ fn test_resources_as_array ( ) {
532+ let config_json = r#"{
533+ "$schema": "https://aka.ms/dsc/schemas/v3/bundled/config/document.json",
534+ "resources": [
535+ {
536+ "type": "Microsoft.DSC.Debug/Echo",
537+ "name": "echoResource",
538+ "apiVersion": "1.0.0"
539+ },
540+ {
541+ "type": "Microsoft/Process",
542+ "name": "processResource",
543+ "apiVersion": "0.1.0"
544+ }
545+ ]
546+ }"# ;
547+
548+ let config: Configuration = serde_json:: from_str ( config_json) . unwrap ( ) ;
549+
550+ assert_eq ! ( config. resources. len( ) , 2 ) ;
551+ assert_eq ! ( config. resources[ 0 ] . name, "echoResource" ) ;
552+ assert_eq ! ( config. resources[ 0 ] . resource_type, "Microsoft.DSC.Debug/Echo" ) ;
553+ assert_eq ! ( config. resources[ 0 ] . api_version. as_deref( ) , Some ( "1.0.0" ) ) ;
554+
555+ assert_eq ! ( config. resources[ 1 ] . name, "processResource" ) ;
556+ assert_eq ! ( config. resources[ 1 ] . resource_type, "Microsoft/Process" ) ;
557+ assert_eq ! ( config. resources[ 1 ] . api_version. as_deref( ) , Some ( "0.1.0" ) ) ;
558+ }
559+
560+ #[ test]
561+ fn test_resources_with_symbolic_names ( ) {
562+ let config_json = r#"{
563+ "$schema": "https://aka.ms/dsc/schemas/v3/bundled/config/document.json",
564+ "languageVersion": "2.2-experimental",
565+ "extensions": {
566+ "dsc": {
567+ "name": "DesiredStateConfiguration",
568+ "version": "0.1.0"
569+ }
570+ },
571+ "resources": {
572+ "echoResource": {
573+ "extension": "dsc",
574+ "type": "Microsoft.DSC.Debug/Echo",
575+ "apiVersion": "1.0.0",
576+ "properties": {
577+ "output": "Hello World"
578+ }
579+ },
580+ "processResource": {
581+ "extension": "dsc",
582+ "type": "Microsoft/Process",
583+ "apiVersion": "0.1.0",
584+ "properties": {
585+ "name": "pwsh",
586+ "pid": 1234
587+ }
588+ }
589+ }
590+ }"# ;
591+
592+ let config: Configuration = serde_json:: from_str ( config_json) . unwrap ( ) ;
593+ assert_eq ! ( config. resources. len( ) , 2 ) ;
594+
595+ // Find resources by name (order may vary in HashMap)
596+ let echo_resource = config. resources . iter ( ) . find ( |r| r. name == "echoResource" ) . unwrap ( ) ;
597+ let process_resource = config. resources . iter ( ) . find ( |r| r. name == "processResource" ) . unwrap ( ) ;
598+
599+ assert_eq ! ( echo_resource. resource_type, "Microsoft.DSC.Debug/Echo" ) ;
600+ assert_eq ! ( echo_resource. api_version. as_deref( ) , Some ( "1.0.0" ) ) ;
601+
602+ assert_eq ! ( process_resource. resource_type, "Microsoft/Process" ) ;
603+ assert_eq ! ( process_resource. api_version. as_deref( ) , Some ( "0.1.0" ) ) ;
604+ }
489605}
0 commit comments