167 lines
5.9 KiB
C#
167 lines
5.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace FlowScope.Config
|
|
{
|
|
public sealed class JsonConfigProvider : IConfigProvider
|
|
{
|
|
private readonly IReadOnlyList<ConfigMapping> _mappings;
|
|
private readonly Dictionary<Type, object> _rowsByType = new();
|
|
|
|
public JsonConfigProvider(IReadOnlyList<ConfigMapping> mappings)
|
|
{
|
|
_mappings = mappings ?? throw new ArgumentNullException(nameof(mappings));
|
|
}
|
|
|
|
public static ConfigMapping Mapping<T>(string fileName, Func<CancellationToken, Task<string>> loadTextAsync)
|
|
where T : class, IConfigRow
|
|
{
|
|
return Mapping<T>(
|
|
fileName,
|
|
new DelegateConfigSource(fileName, loadTextAsync),
|
|
new JsonConfigParser());
|
|
}
|
|
|
|
public static ConfigMapping Mapping<T>(
|
|
string fileName,
|
|
IConfigSource source,
|
|
IConfigParser parser)
|
|
where T : class, IConfigRow
|
|
{
|
|
return new ConfigMapping(typeof(T), fileName, source, parser);
|
|
}
|
|
|
|
public async Task LoadAllAsync(CancellationToken cancellationToken)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
var loadedRows = new Dictionary<Type, object>();
|
|
|
|
foreach (var mapping in _mappings)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
string json;
|
|
try
|
|
{
|
|
json = await mapping.Source.LoadTextAsync(mapping.FileName, cancellationToken);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Failed to load config file '{mapping.FileName}' from source '{mapping.Source.Name}'.",
|
|
exception);
|
|
}
|
|
|
|
try
|
|
{
|
|
loadedRows[mapping.RowType] = mapping.Parse(json);
|
|
}
|
|
catch (Exception exception) when (exception is not OperationCanceledException)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Failed to parse config file '{mapping.FileName}': {exception.GetBaseException().Message}",
|
|
exception);
|
|
}
|
|
}
|
|
|
|
_rowsByType.Clear();
|
|
foreach (var pair in loadedRows)
|
|
{
|
|
_rowsByType[pair.Key] = pair.Value;
|
|
}
|
|
}
|
|
|
|
public T Get<T>(int id) where T : class, IConfigRow
|
|
{
|
|
return _rowsByType.TryGetValue(typeof(T), out var table) &&
|
|
table is Dictionary<int, T> rows &&
|
|
rows.TryGetValue(id, out var row)
|
|
? row
|
|
: null;
|
|
}
|
|
|
|
public IReadOnlyList<T> GetAll<T>() where T : class, IConfigRow
|
|
{
|
|
if (!_rowsByType.TryGetValue(typeof(T), out var table) || table is not Dictionary<int, T> rows)
|
|
{
|
|
return Array.Empty<T>();
|
|
}
|
|
|
|
return new List<T>(rows.Values);
|
|
}
|
|
|
|
public sealed class ConfigMapping
|
|
{
|
|
private readonly MethodInfo _parseRowsMethod;
|
|
|
|
internal ConfigMapping(
|
|
Type rowType,
|
|
string fileName,
|
|
IConfigSource source,
|
|
IConfigParser parser)
|
|
{
|
|
RowType = rowType ?? throw new ArgumentNullException(nameof(rowType));
|
|
FileName = string.IsNullOrWhiteSpace(fileName)
|
|
? throw new ArgumentException("Config file name is required.", nameof(fileName))
|
|
: fileName;
|
|
Source = source ?? throw new ArgumentNullException(nameof(source));
|
|
Parser = parser ?? throw new ArgumentNullException(nameof(parser));
|
|
_parseRowsMethod = typeof(IConfigParser)
|
|
.GetMethod(nameof(IConfigParser.ParseRows))
|
|
.MakeGenericMethod(rowType);
|
|
}
|
|
|
|
public Type RowType { get; }
|
|
public string FileName { get; }
|
|
public IConfigSource Source { get; }
|
|
public IConfigParser Parser { get; }
|
|
|
|
internal object Parse(string json)
|
|
{
|
|
var rows = (System.Collections.IEnumerable)_parseRowsMethod.Invoke(
|
|
Parser,
|
|
new object[] { FileName, json });
|
|
var byIdType = typeof(Dictionary<,>).MakeGenericType(typeof(int), RowType);
|
|
var byId = (System.Collections.IDictionary)Activator.CreateInstance(byIdType);
|
|
foreach (IConfigRow row in rows)
|
|
{
|
|
if (byId.Contains(row.Id))
|
|
{
|
|
throw new InvalidOperationException($"Duplicate config id {row.Id} for {RowType.Name}.");
|
|
}
|
|
|
|
byId.Add(row.Id, row);
|
|
}
|
|
|
|
return byId;
|
|
}
|
|
}
|
|
|
|
private sealed class DelegateConfigSource : IConfigSource
|
|
{
|
|
private readonly Func<CancellationToken, Task<string>> _loadTextAsync;
|
|
|
|
public DelegateConfigSource(
|
|
string fileName,
|
|
Func<CancellationToken, Task<string>> loadTextAsync)
|
|
{
|
|
Name = fileName;
|
|
_loadTextAsync = loadTextAsync ?? throw new ArgumentNullException(nameof(loadTextAsync));
|
|
}
|
|
|
|
public string Name { get; }
|
|
|
|
public Task<string> LoadTextAsync(string fileName, CancellationToken cancellationToken)
|
|
{
|
|
return _loadTextAsync(cancellationToken);
|
|
}
|
|
}
|
|
}
|
|
}
|