From f9e19ad2de2b9a037f939852087a7ed901c86df9 Mon Sep 17 00:00:00 2001 From: "Eric Sibly [chullybun]" Date: Fri, 15 Mar 2024 05:58:27 -0700 Subject: [PATCH] OnConfigurationLoad (#21) --- CHANGELOG.md | 3 ++ src/OnRamp/CodeGenerator.cs | 40 +++++++++++++++++++++++--- src/OnRamp/Console/CodeGenConsole.cs | 3 -- src/OnRamp/OnRamp.csproj | 2 +- src/OnRamp/Utility/StreamExtensions.cs | 35 +++++++++++++++++++++- 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16f4a9e..7700832 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ Represents the **NuGet** versions. +## v2.2.0 +- *Enhancement:* Added `ConeGenerator.OnConfigurationLoad` to enable validation and/or mutation of the configuration (`JsonNode`) before deserialization into the corresponding `ConfigBase` type. + ## v2.1.0 - *Enhancement:* Added `CodeGenerator.LoadConfigAsync` to enable the loading of the configuration without having to execute the code generation. This is useful when needing to either further validate the configuration prior to execution, or be able to query the configuration without initiating the code generation. - *Fixed:* All dependencies updated to the latest version. diff --git a/src/OnRamp/CodeGenerator.cs b/src/OnRamp/CodeGenerator.cs index 2f7fc61..302722a 100644 --- a/src/OnRamp/CodeGenerator.cs +++ b/src/OnRamp/CodeGenerator.cs @@ -10,6 +10,8 @@ using System.IO; using System.Linq; using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; using System.Threading.Tasks; namespace OnRamp @@ -19,6 +21,11 @@ namespace OnRamp /// public class CodeGenerator { + /// + /// Represents the file name when loaded from a stream; i.e. actual file name is unknown. + /// + public const string StreamFileName = ""; + /// /// Create a new instance of the class. /// @@ -152,7 +159,7 @@ public async Task LoadConfigAsync(string? configFileName = null) /// The resultant . /// Thrown when an error is encountered during the code-generation. /// Thrown where the code-generation would result in changes to an underlying artefact. This is managed by setting to true. - public async Task LoadConfigAsync(TextReader configReader, StreamContentType contentType, string configFileName = "") + public async Task LoadConfigAsync(TextReader configReader, StreamContentType contentType, string configFileName = StreamFileName) { ConfigBase? config; IRootConfig rootConfig; @@ -160,14 +167,28 @@ public async Task LoadConfigAsync(TextReader configReader, StreamCon // Load, validate and prepare. try { + JsonNode? jsonNode; + + // Read the YAML/JSON into a JsonNode. try { - config = contentType switch + jsonNode = contentType switch { - StreamContentType.Yaml => (ConfigBase?)configReader.DeserializeYaml(Scripts.GetConfigType()), - StreamContentType.Json => (ConfigBase?)configReader.DeserializeJson(Scripts.GetConfigType()), + StreamContentType.Yaml => configReader.YamlToJsonNode(), + StreamContentType.Json => configReader.JsonToJsonNode(), _ => throw new CodeGenException($"Stream content type is not supported.") } ?? throw new CodeGenException($"Stream is empty."); + + // Verify and mutate the configuration before deserialization. + OnConfigurationLoad(Scripts, configFileName, jsonNode); + } + catch (CodeGenException) { throw; } + catch (Exception ex) { throw new CodeGenException(ex.Message); } + + // Deserialize the JsonNode in to the configured (script) type. + try + { + config = (ConfigBase?)jsonNode.Deserialize(Scripts.GetConfigType(), OnRamp.Utility.JsonSerializer.Options); } catch (CodeGenException) { throw; } catch (Exception ex) { throw new CodeGenException(ex.Message); } @@ -232,6 +253,9 @@ public Task GenerateAsync(ConfigBase config) if (config is not IRootConfig rootConfig) throw new ArgumentException("Configuration must implement IRootConfig.", nameof(config)); + CodeGenArgs.Logger?.LogInformation("{Content}", string.Empty); + CodeGenArgs.Logger?.LogInformation("{Content}", "Scripts:"); + // Generate the scripted artefacts. var overallStopwatch = Stopwatch.StartNew(); var overallStats = new CodeGenStatistics(); @@ -262,6 +286,14 @@ public Task GenerateAsync(ConfigBase config) return Task.FromResult(overallStats); } + /// + /// Provides an opportunity to verify and manipulate the mutable configuration as it is loaded; directly before it is deserialized into the configured as defined by the . + /// + /// The . + /// The configuration file name. + /// The . + protected virtual void OnConfigurationLoad(CodeGenScript script, string fileName, JsonNode json) { } + /// /// Handles the processing before the is executed. /// diff --git a/src/OnRamp/Console/CodeGenConsole.cs b/src/OnRamp/Console/CodeGenConsole.cs index 2c6ca7c..49dc0a9 100644 --- a/src/OnRamp/Console/CodeGenConsole.cs +++ b/src/OnRamp/Console/CodeGenConsole.cs @@ -349,9 +349,6 @@ public static void WriteStandardizedArgs(ICodeGeneratorArgs args) { args.Logger.LogInformation("{Content}", $" {a.FullName}"); } - - args.Logger.LogInformation("{Content}", string.Empty); - args.Logger.LogInformation("{Content}", "Scripts:"); } /// diff --git a/src/OnRamp/OnRamp.csproj b/src/OnRamp/OnRamp.csproj index bf7702b..fd3a2a3 100644 --- a/src/OnRamp/OnRamp.csproj +++ b/src/OnRamp/OnRamp.csproj @@ -4,7 +4,7 @@ Exe netstandard2.1 OnRamp - 2.1.0 + 2.2.0 true Avanade Avanade diff --git a/src/OnRamp/Utility/StreamExtensions.cs b/src/OnRamp/Utility/StreamExtensions.cs index ad8d59a..b5b1dcd 100644 --- a/src/OnRamp/Utility/StreamExtensions.cs +++ b/src/OnRamp/Utility/StreamExtensions.cs @@ -3,6 +3,7 @@ using System; using System.IO; using System.Linq; +using System.Text.Json.Nodes; using YamlDotNet.Core.Events; using YamlDotNet.Serialization; @@ -60,9 +61,41 @@ public static class TextReaderExtensions return DeserializeJson(sr, type); } } -#pragma warning restore IDE0063 +#pragma warning restore IDE0063 } + /// + /// Converts the content into a . + /// + /// The YAML . + /// The . + public static JsonNode? YamlToJsonNode(this TextReader yaml) + { + var yml = new DeserializerBuilder().WithNodeTypeResolver(new YamlNodeTypeResolver()).Build().Deserialize(yaml); + +#pragma warning disable IDE0063 // Use simple 'using' statement; cannot as need to be more explicit with managing the close and dispose. + using (var ms = new MemoryStream()) + { + using (var sw = new StreamWriter(ms)) + { + sw.Write(new SerializerBuilder().JsonCompatible().Build().Serialize(yml!)); + sw.Flush(); + + ms.Position = 0; + using var sr = new StreamReader(ms); + return sr.JsonToJsonNode(); + } + } +#pragma warning restore IDE0063 + } + + /// + /// Converts the content into a . + /// + /// The YAML . + /// The . + public static JsonNode? JsonToJsonNode(this TextReader json) => JsonNode.Parse(json.ReadToEnd()); + private class YamlNodeTypeResolver : INodeTypeResolver { private static readonly string[] boolValues = ["true", "false"];