From 064172e4cf14c2deb32e11f33ba516da823ed9bc Mon Sep 17 00:00:00 2001 From: Alfred Neequaye Date: Thu, 16 Jul 2020 01:45:31 +0000 Subject: [PATCH 1/2] Added JSON Arrays Support --- .../ConfigurationbuilderExtensions.cs | 32 ++++ .../Extensions/KVPairQueryResultExtensions.cs | 1 - .../JsonArrayStreamConfigurationProvider.cs | 27 +++ .../JsonArrayStreamConfigurationSource.cs | 22 +++ .../Internals/JsonConfigurationFileParser.cs | 156 ++++++++++++++++++ .../Parsers/JsonConfigurationParser.cs | 3 +- 6 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 src/Winton.Extensions.Configuration.Consul/Extensions/ConfigurationbuilderExtensions.cs create mode 100644 src/Winton.Extensions.Configuration.Consul/Internals/JsonArrayStreamConfigurationProvider.cs create mode 100644 src/Winton.Extensions.Configuration.Consul/Internals/JsonArrayStreamConfigurationSource.cs create mode 100644 src/Winton.Extensions.Configuration.Consul/Internals/JsonConfigurationFileParser.cs diff --git a/src/Winton.Extensions.Configuration.Consul/Extensions/ConfigurationbuilderExtensions.cs b/src/Winton.Extensions.Configuration.Consul/Extensions/ConfigurationbuilderExtensions.cs new file mode 100644 index 0000000..231701c --- /dev/null +++ b/src/Winton.Extensions.Configuration.Consul/Extensions/ConfigurationbuilderExtensions.cs @@ -0,0 +1,32 @@ +// Copyright (c) Winton. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using System; +using System.IO; +using Microsoft.Extensions.Configuration; +using Winton.Extensions.Configuration.Consul.Internals; + +namespace Winton.Extensions.Configuration.Consul.Extensions +{ + /// + /// Extra Extesntions for Json Array Parsing. + /// + public static class ConfigurationbuilderExtensions + { + /// + /// Adds a JSON Array configuration source to . + /// + /// The to add to. + /// The to read the json configuration data from. + /// The . + public static IConfigurationBuilder AddJsonArrayStream(this IConfigurationBuilder builder, Stream stream) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.Add(s => s.Stream = stream); + } + } +} \ No newline at end of file diff --git a/src/Winton.Extensions.Configuration.Consul/Extensions/KVPairQueryResultExtensions.cs b/src/Winton.Extensions.Configuration.Consul/Extensions/KVPairQueryResultExtensions.cs index c95d69a..7e0e955 100644 --- a/src/Winton.Extensions.Configuration.Consul/Extensions/KVPairQueryResultExtensions.cs +++ b/src/Winton.Extensions.Configuration.Consul/Extensions/KVPairQueryResultExtensions.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Net; using Consul; -using Winton.Extensions.Configuration.Consul.Parsers; namespace Winton.Extensions.Configuration.Consul.Extensions { diff --git a/src/Winton.Extensions.Configuration.Consul/Internals/JsonArrayStreamConfigurationProvider.cs b/src/Winton.Extensions.Configuration.Consul/Internals/JsonArrayStreamConfigurationProvider.cs new file mode 100644 index 0000000..b634667 --- /dev/null +++ b/src/Winton.Extensions.Configuration.Consul/Internals/JsonArrayStreamConfigurationProvider.cs @@ -0,0 +1,27 @@ +// Copyright (c) Winton. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; + +namespace Winton.Extensions.Configuration.Consul.Internals +{ + internal sealed class JsonArrayStreamConfigurationProvider : StreamConfigurationProvider + { + public JsonArrayStreamConfigurationProvider(JsonArrayStreamConfigurationSource source) + : base(source) + { + } + + /// + /// + /// Loads json configuration key/values from a stream into a provider. + /// + /// The json to load configuration data from. + public override void Load(Stream stream) + { + Data = JsonConfigurationFileParser.Parse(stream); + } + } +} \ No newline at end of file diff --git a/src/Winton.Extensions.Configuration.Consul/Internals/JsonArrayStreamConfigurationSource.cs b/src/Winton.Extensions.Configuration.Consul/Internals/JsonArrayStreamConfigurationSource.cs new file mode 100644 index 0000000..6a4872f --- /dev/null +++ b/src/Winton.Extensions.Configuration.Consul/Internals/JsonArrayStreamConfigurationSource.cs @@ -0,0 +1,22 @@ +// Copyright (c) Winton. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; + +namespace Winton.Extensions.Configuration.Consul.Internals +{ + /// + /// Json Array Variant of Config Provider. + /// + public class JsonArrayStreamConfigurationSource : StreamConfigurationSource + { + /// + /// Builds the for this source. + /// + /// The . + /// An . + public override IConfigurationProvider Build(IConfigurationBuilder builder) + => new JsonArrayStreamConfigurationProvider(this); + } +} \ No newline at end of file diff --git a/src/Winton.Extensions.Configuration.Consul/Internals/JsonConfigurationFileParser.cs b/src/Winton.Extensions.Configuration.Consul/Internals/JsonConfigurationFileParser.cs new file mode 100644 index 0000000..4b119c9 --- /dev/null +++ b/src/Winton.Extensions.Configuration.Consul/Internals/JsonConfigurationFileParser.cs @@ -0,0 +1,156 @@ +// Copyright (c) Winton. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using Microsoft.Extensions.Configuration; + +namespace Winton.Extensions.Configuration.Consul.Internals +{ + internal class JsonConfigurationFileParser + { + private readonly IDictionary _data = new SortedDictionary(StringComparer.OrdinalIgnoreCase); + private readonly Stack _context = new Stack(); + private string? _currentPath; + private string? _dataPrefix; + + private JsonConfigurationFileParser(string? prefix = null) + { + _dataPrefix = prefix; + } + + public static IDictionary Parse(Stream input) + => new JsonConfigurationFileParser().ParseStream(input); + + private IDictionary ParseStream(Stream input) + { + _data.Clear(); + + var jsonDocumentOptions = new JsonDocumentOptions + { + CommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true, + }; + + using (var reader = new StreamReader(input)) + using (JsonDocument doc = JsonDocument.Parse(reader.ReadToEnd(), jsonDocumentOptions)) + { + if (doc.RootElement.ValueKind != JsonValueKind.Object && doc.RootElement.ValueKind != JsonValueKind.Array) + { + throw new FormatException(/*Resources.FormatError_UnsupportedJSONToken(doc.RootElement.ValueKind)*/); + } + + switch (doc.RootElement.ValueKind) + { + case JsonValueKind.Object: + VisitElement(doc.RootElement); + break; + case JsonValueKind.Array: + VisitArrayElement(doc.RootElement); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + return _data; + } + + private void VisitElement(JsonElement element) + { + foreach (var property in element.EnumerateObject()) + { + EnterContext(property.Name); + VisitValue(property.Value); + ExitContext(); + } + } + + private void VisitArrayElement(JsonElement element) + { + var prefixIdx = 0; + foreach (var arrayElement in element.EnumerateArray()) + { + switch (arrayElement.ValueKind) + { + case JsonValueKind.Number: + case JsonValueKind.String: + case JsonValueKind.True: + case JsonValueKind.False: + case JsonValueKind.Null: + EnterContext($"{(_dataPrefix is null ? $"{prefixIdx}" : $"{_dataPrefix}:{prefixIdx}")}"); + VisitValue(arrayElement); + ExitContext(); + break; + case JsonValueKind.Object: + foreach (var property in arrayElement.EnumerateObject()) + { + EnterContext($"{(_dataPrefix is null ? $"{prefixIdx}:" : $"{_dataPrefix}:{prefixIdx}:")}{property.Name}"); + VisitValue(property.Value); + ExitContext(); + } + + break; + default: + throw new NotSupportedException(); + } + + prefixIdx++; + } + } + + private void VisitValue(JsonElement value) + { + switch (value.ValueKind) + { + case JsonValueKind.Object: + VisitElement(value); + break; + + case JsonValueKind.Array: + var index = 0; + foreach (var arrayElement in value.EnumerateArray()) + { + EnterContext(index.ToString()); + VisitValue(arrayElement); + ExitContext(); + index++; + } + + break; + + case JsonValueKind.Number: + case JsonValueKind.String: + case JsonValueKind.True: + case JsonValueKind.False: + case JsonValueKind.Null: + var key = _currentPath; + if (_data.ContainsKey(key ?? string.Empty)) + { + throw new FormatException(/*Resources.FormatError_KeyIsDuplicated(key)*/); + } + + _data[key ?? string.Empty] = value.ToString(); + break; + + default: + throw new FormatException(/*Resources.FormatError_UnsupportedJSONToken(value.ValueKind)*/); + } + } + + private void EnterContext(string context) + { + _context.Push(context); + _currentPath = ConfigurationPath.Combine(_context.Reverse()); + } + + private void ExitContext() + { + _context.Pop(); + _currentPath = ConfigurationPath.Combine(_context.Reverse()); + } + } +} \ No newline at end of file diff --git a/src/Winton.Extensions.Configuration.Consul/Parsers/JsonConfigurationParser.cs b/src/Winton.Extensions.Configuration.Consul/Parsers/JsonConfigurationParser.cs index 4a4cea0..4f15b19 100644 --- a/src/Winton.Extensions.Configuration.Consul/Parsers/JsonConfigurationParser.cs +++ b/src/Winton.Extensions.Configuration.Consul/Parsers/JsonConfigurationParser.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using Microsoft.Extensions.Configuration; +using Winton.Extensions.Configuration.Consul.Extensions; namespace Winton.Extensions.Configuration.Consul.Parsers { @@ -18,7 +19,7 @@ public sealed class JsonConfigurationParser : IConfigurationParser public IDictionary Parse(Stream stream) { return new ConfigurationBuilder() - .AddJsonStream(stream) + .AddJsonArrayStream(stream) .Build() .AsEnumerable() .ToDictionary(pair => pair.Key, pair => pair.Value); From 9e19b9f49c88f4a9e61cdf8f79c4fb858f517b17 Mon Sep 17 00:00:00 2001 From: Alfred Neequaye Date: Fri, 18 Sep 2020 13:19:00 +0000 Subject: [PATCH 2/2] Consul Updated --- .../Winton.Extensions.Configuration.Consul.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Winton.Extensions.Configuration.Consul/Winton.Extensions.Configuration.Consul.csproj b/src/Winton.Extensions.Configuration.Consul/Winton.Extensions.Configuration.Consul.csproj index 0f60720..17d2f42 100644 --- a/src/Winton.Extensions.Configuration.Consul/Winton.Extensions.Configuration.Consul.csproj +++ b/src/Winton.Extensions.Configuration.Consul/Winton.Extensions.Configuration.Consul.csproj @@ -27,9 +27,9 @@ - - - + + +