private void ConvertItem(AModuleItem parent, int index, ModuleItemNode node, IEnumerable <string> expectedTypes) { var type = DeterminNodeType("item", index, node, ModuleItemNode.FieldCombinations, expectedTypes); switch (type) { case "Parameter": AtLocation(node.Parameter, () => { // validation if (node.Properties != null) { Validate(node.Type != null, "'Type' attribute is required"); } Validate((node.Allow == null) || (node.Type == "AWS") || ResourceMapping.IsCloudFormationType(node.Type), "'Allow' attribute can only be used with AWS resource types"); Validate(parent == null, "'Parameter' cannot be nested"); // create input parameter item _builder.AddParameter( name: node.Parameter, section: node.Section, label: node.Label, description: node.Description, type: node.Type ?? "String", scope: ConvertScope(node.Scope), noEcho: node.NoEcho, defaultValue: node.Default, constraintDescription: node.ConstraintDescription, allowedPattern: node.AllowedPattern, allowedValues: node.AllowedValues, maxLength: node.MaxLength, maxValue: node.MaxValue, minLength: node.MinLength, minValue: node.MinValue, allow: node.Allow, properties: ParseToDictionary("Properties", node.Properties), arnAttribute: node.DefaultAttribute, encryptionContext: node.EncryptionContext, pragmas: node.Pragmas ); }); break; case "Import": AtLocation(node.Import, () => { // validation Validate((node.Allow == null) || (node.Type == "AWS") || ResourceMapping.IsCloudFormationType(node.Type), "'Allow' attribute can only be used with AWS resource types"); Validate(node.Module != null, "missing 'Module' attribute"); // create input parameter item _builder.AddImport( parent: parent, name: node.Import, description: node.Description, type: node.Type ?? "String", scope: ConvertScope(node.Scope), allow: node.Allow, module: node.Module ?? "Bad.Module", encryptionContext: node.EncryptionContext ); }); break; case "Variable": AtLocation(node.Variable, () => { // validation Validate(node.Value != null, "missing 'Value' attribute"); Validate((node.EncryptionContext == null) || (node.Type == "Secret"), "item must have Type 'Secret' to use 'EncryptionContext' section"); Validate((node.Type != "Secret") || !(node.Value is IList <object>), "item with type 'Secret' cannot have a list of values"); // create variable item _builder.AddVariable( parent: parent, name: node.Variable, description: node.Description, type: node.Type ?? "String", scope: ConvertScope(node.Scope), value: node.Value ?? "", allow: null, encryptionContext: node.EncryptionContext ); }); break; case "Group": AtLocation(node.Group, () => { // create namespace item var result = _builder.AddVariable( parent: parent, name: node.Group, description: node.Description, type: "String", scope: null, value: "", allow: null, encryptionContext: null ); // recurse ConvertItems(result, expectedTypes); }); break; case "Resource": AtLocation(node.Resource, () => { if (node.Value != null) { // validation Validate((node.Allow == null) || (node.Type == null) || ResourceMapping.IsCloudFormationType(node.Type), "'Allow' attribute can only be used with AWS resource types"); Validate(node.If == null, "'If' attribute cannot be used with a referenced resource"); Validate(node.Properties == null, "'Properties' section cannot be used with a referenced resource"); if (node.Value is IList <object> values) { foreach (var arn in values) { ValidateARN(arn); } } else { ValidateARN(node.Value); } // create variable item _builder.AddVariable( parent: parent, name: node.Resource, description: node.Description, type: node.Type ?? "String", scope: ConvertScope(node.Scope), value: node.Value, allow: node.Allow, encryptionContext: node.EncryptionContext ); } else { // validation Validate(node.Type != null, "missing 'Type' attribute"); Validate((node.Allow == null) || ResourceMapping.IsCloudFormationType(node.Type ?? ""), "'Allow' attribute can only be used with AWS resource types"); // create resource item _builder.AddResource( parent: parent, name: node.Resource, description: node.Description, type: node.Type ?? "AWS", scope: ConvertScope(node.Scope), allow: node.Allow, properties: ParseToDictionary("Properties", node.Properties), dependsOn: ConvertToStringList(node.DependsOn), arnAttribute: node.DefaultAttribute, condition: node.If, pragmas: node.Pragmas ); } }); break; case "Nested": AtLocation(node.Nested, () => { // validation if (node.Module == null) { LogError("missing 'Module' attribute"); } else if (!ModuleInfo.TryParse(node.Module, out var moduleInfo)) { LogError("invalid value for 'Module' attribute"); } else { // create nested module item _builder.AddNestedModule( parent: parent, name: node.Nested, description: node.Description, moduleInfo: moduleInfo, scope: ConvertScope(node.Scope), dependsOn: node.DependsOn, parameters: node.Parameters ); } }); break; case "Package": // package resource AtLocation(node.Package, () => { // discover files to package var files = new List <KeyValuePair <string, string> >(); if (node.Files != null) { string folder; string filePattern; SearchOption searchOption; var packageFiles = Path.Combine(Settings.WorkingDirectory, node.Files); if ((packageFiles.EndsWith("/", StringComparison.Ordinal) || Directory.Exists(packageFiles))) { folder = Path.GetFullPath(packageFiles); filePattern = "*"; searchOption = SearchOption.AllDirectories; } else { folder = Path.GetDirectoryName(packageFiles); filePattern = Path.GetFileName(packageFiles); searchOption = SearchOption.TopDirectoryOnly; } if (Directory.Exists(folder)) { foreach (var filePath in Directory.GetFiles(folder, filePattern, searchOption)) { var relativeFilePathName = Path.GetRelativePath(folder, filePath); files.Add(new KeyValuePair <string, string>(relativeFilePathName, filePath)); } files = files.OrderBy(file => file.Key).ToList(); } else { LogError($"cannot find folder '{Path.GetRelativePath(Settings.WorkingDirectory, folder)}'"); } } else { LogError("missing 'Files' attribute"); } // create package resource item _builder.AddPackage( parent: parent, name: node.Package, description: node.Description, scope: ConvertScope(node.Scope), files: files ); }); break; case "Function": AtLocation(node.Function, () => { // validation Validate(node.Memory != null, "missing 'Memory' attribute"); Validate(int.TryParse(node.Memory, out _), "invalid 'Memory' value"); Validate(node.Timeout != null, "missing 'Timeout' attribute"); Validate(int.TryParse(node.Timeout, out _), "invalid 'Timeout' value"); ValidateFunctionSource(node.Sources ?? new FunctionSourceNode[0]); // determine function type var project = node.Project; var language = node.Language; var runtime = node.Runtime; var handler = node.Handler; DetermineFunctionType(node.Function, ref project, ref language, ref runtime, ref handler); // create function item var sources = AtLocation("Sources", () => node.Sources ?.Select((source, eventIndex) => ConvertFunctionSource(node, eventIndex, source)) .Where(evt => evt != null) .ToList() ); _builder.AddFunction( parent: parent, name: node.Function, description: node.Description, scope: ConvertScope(node.Scope), project: project, language: language, environment: node.Environment, sources: sources, condition: node.If, pragmas: node.Pragmas, timeout: node.Timeout, runtime: runtime, memory: node.Memory, handler: handler, properties: ParseToDictionary("Properties", node.Properties) ); }); break; case "Condition": AtLocation(node.Condition, () => { AtLocation("Value", () => { Validate(node.Value != null, "missing 'Value' attribute"); }); _builder.AddCondition( parent: parent, name: node.Condition, description: node.Description, value: node.Value ); }); break; case "Mapping": AtLocation(node.Mapping, () => { IDictionary <string, IDictionary <string, string> > topLevelResults = new Dictionary <string, IDictionary <string, string> >(); if (node.Value is IDictionary topLevelDictionary) { AtLocation("Value", () => { Validate(topLevelDictionary.Count > 0, "missing top-level mappings"); // iterate over top-level dictionary foreach (DictionaryEntry topLevel in topLevelDictionary) { AtLocation((string)topLevel.Key, () => { var secondLevelResults = new Dictionary <string, string>(); topLevelResults[(string)topLevel.Key] = secondLevelResults; // convert top-level entry if (topLevel.Value is IDictionary secondLevelDictionary) { Validate(secondLevelDictionary.Count > 0, "missing second-level mappings"); // iterate over second-level dictionary foreach (DictionaryEntry secondLevel in secondLevelDictionary) { AtLocation((string)secondLevel.Key, () => { // convert second-level entry if (secondLevel.Value is string secondLevelValue) { secondLevelResults[(string)secondLevel.Key] = secondLevelValue; } else { LogError("invalid value"); } }); } }