tools/code/publisher/OverrideDto.cs (111 lines of code) (raw):
using common;
using LanguageExt;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace publisher;
public delegate TDto OverrideDto<TName, TDto>(TName name, TDto dto) where TName : notnull;
public sealed class OverrideDtoFactory(ConfigurationJson configurationJson)
{
private readonly FrozenDictionary<string, FrozenDictionary<string, JsonObject>> configurationDtos = GetConfigurationDtos(configurationJson);
private static readonly FrozenDictionary<Type, string> sectionNames = GetConfigurationSectionNames();
private static FrozenDictionary<string, FrozenDictionary<string, JsonObject>> GetConfigurationDtos(ConfigurationJson configurationJson) =>
configurationJson.Value
// Get sections that are JSON arrays
.ChooseValues(node => node.TryAsJsonArray().ToOption())
// Convert each JSON array a dictionary
.Select(kvp => kvp.MapValue(jsonArray => jsonArray.PickJsonObjects()
// Where the JSON object has a "name" property
.Choose(jsonObject => from name in jsonObject.TryGetStringProperty("name").ToOption()
select (name, jsonObject))
// Return a dictionary where the key is the "name" property and the value is the JSON object
.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase)))
// Return a dictionary where the key is the section name and the value is the dictionary of JSON objects.
// For instance, one item in this dictionary could be "apis" -> { "api1": { ... }, "api2": { ... } }
.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
private static FrozenDictionary<Type, string> GetConfigurationSectionNames() =>
new Dictionary<Type, string>
{
[typeof(NamedValueName)] = "namedValues",
[typeof(TagName)] = "tags",
[typeof(GatewayName)] = "gateways",
[typeof(VersionSetName)] = "versionSets",
[typeof(BackendName)] = "backends",
[typeof(LoggerName)] = "loggers",
[typeof(DiagnosticName)] = "diagnostics",
[typeof(PolicyFragmentName)] = "policyFragments",
[typeof(ServicePolicyName)] = "servicePolicies",
[typeof(ProductName)] = "products",
[typeof(GroupName)] = "groups",
[typeof(SubscriptionName)] = "subscriptions",
[typeof(ApiName)] = "apis",
}
.ToFrozenDictionary();
public static string GetSectionName<T>() =>
sectionNames.Find(typeof(T))
.IfNone(() => throw new InvalidOperationException($"Resource type {typeof(T).Name} is not supported."));
public static string GetNameToFind<T>(T name) where T : notnull =>
sectionNames.ContainsKey(typeof(T))
? name switch
{
ApiName apiName => ApiName.GetRootName(apiName).ToString(),
_ => name.ToString()!
}
: throw new InvalidOperationException($"Resource type {typeof(T).Name} is not supported.");
public OverrideDto<TName, TDto> Create<TName, TDto>() where TName : notnull => Override;
private TDto Override<TName, TDto>(TName name, TDto dto) where TName : notnull
{
var sectionName = GetSectionName<TName>();
var nameToFind = GetNameToFind(name);
return configurationDtos.Find(sectionName)
.Bind(section => section.Find(nameToFind))
.Map(overrideJson => Override(dto, overrideJson))
.IfNone(dto);
}
public static T Override<T>(T current, JsonObject other)
{
var currentJson = BinaryData.FromObjectAsJson(current)
.ToObjectFromJson<JsonObject>();
var overridenJson = Override(currentJson, other);
return overridenJson.Deserialize<T>() ?? throw new JsonException($"Failed to deserialize dto of type {typeof(T).Name}.");
}
private static JsonObject Override(JsonObject current, JsonObject other)
{
var nodeOptions = new JsonNodeOptions { PropertyNameCaseInsensitive = true };
var merged = new JsonObject(nodeOptions);
foreach (var property in current)
{
string propertyName = property.Key;
var currentPropertyValue = property.Value;
merged[propertyName] = other.TryGetPropertyValue(propertyName, out var otherPropertyValue)
? (currentPropertyValue, otherPropertyValue) switch
{
(JsonObject currentObject, JsonObject otherObject) => Override(currentObject, otherObject),
_ => otherPropertyValue?.DeepClone(),
}
: (currentPropertyValue?.DeepClone());
}
foreach (var property in other)
{
string propertyName = property.Key;
if (current.ContainsKey(propertyName) is false)
{
merged[propertyName] = property.Value?.DeepClone();
}
}
return merged;
}
}
internal static class OverrideDtoModule
{
public static void ConfigureOverrideDtoFactory(IHostApplicationBuilder builder)
{
ConfigurationModule.ConfigureConfigurationJson(builder);
builder.Services.TryAddSingleton(GetOverrideDtoFactory);
}
private static OverrideDtoFactory GetOverrideDtoFactory(IServiceProvider provider)
{
var configurationJson = provider.GetRequiredService<ConfigurationJson>();
return new OverrideDtoFactory(configurationJson);
}
}