Restructure solution layout by module
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Concelier.Storage.Mongo.Aliases;
|
||||
|
||||
namespace StellaOps.Concelier.Merge.Services;
|
||||
|
||||
public sealed class AliasGraphResolver
|
||||
{
|
||||
private readonly IAliasStore _aliasStore;
|
||||
|
||||
public AliasGraphResolver(IAliasStore aliasStore)
|
||||
{
|
||||
_aliasStore = aliasStore ?? throw new ArgumentNullException(nameof(aliasStore));
|
||||
}
|
||||
|
||||
public async Task<AliasIdentityResult> ResolveAsync(string advisoryKey, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(advisoryKey);
|
||||
var aliases = await _aliasStore.GetByAdvisoryAsync(advisoryKey, cancellationToken).ConfigureAwait(false);
|
||||
var collisions = new List<AliasCollision>();
|
||||
|
||||
foreach (var alias in aliases)
|
||||
{
|
||||
var candidates = await _aliasStore.GetByAliasAsync(alias.Scheme, alias.Value, cancellationToken).ConfigureAwait(false);
|
||||
var advisoryKeys = candidates
|
||||
.Select(static candidate => candidate.AdvisoryKey)
|
||||
.Where(static key => !string.IsNullOrWhiteSpace(key))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
if (advisoryKeys.Length <= 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
collisions.Add(new AliasCollision(alias.Scheme, alias.Value, advisoryKeys));
|
||||
}
|
||||
|
||||
var unique = new Dictionary<string, AliasCollision>(StringComparer.Ordinal);
|
||||
foreach (var collision in collisions)
|
||||
{
|
||||
var key = $"{collision.Scheme}\u0001{collision.Value}";
|
||||
if (!unique.ContainsKey(key))
|
||||
{
|
||||
unique[key] = collision;
|
||||
}
|
||||
}
|
||||
|
||||
var distinctCollisions = unique.Values.ToArray();
|
||||
|
||||
return new AliasIdentityResult(advisoryKey, aliases, distinctCollisions);
|
||||
}
|
||||
|
||||
public async Task<AliasComponent> BuildComponentAsync(string advisoryKey, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(advisoryKey);
|
||||
|
||||
var visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var queue = new Queue<string>();
|
||||
var collisionMap = new Dictionary<string, AliasCollision>(StringComparer.Ordinal);
|
||||
|
||||
var aliasCache = new Dictionary<string, IReadOnlyList<AliasRecord>>(StringComparer.OrdinalIgnoreCase);
|
||||
queue.Enqueue(advisoryKey);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var current = queue.Dequeue();
|
||||
if (!visited.Add(current))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var aliases = await GetAliasesAsync(current, cancellationToken, aliasCache).ConfigureAwait(false);
|
||||
aliasCache[current] = aliases;
|
||||
foreach (var alias in aliases)
|
||||
{
|
||||
var aliasRecords = await GetAdvisoriesForAliasAsync(alias.Scheme, alias.Value, cancellationToken).ConfigureAwait(false);
|
||||
var advisoryKeys = aliasRecords
|
||||
.Select(static record => record.AdvisoryKey)
|
||||
.Where(static key => !string.IsNullOrWhiteSpace(key))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
if (advisoryKeys.Length <= 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var candidate in advisoryKeys)
|
||||
{
|
||||
if (!visited.Contains(candidate))
|
||||
{
|
||||
queue.Enqueue(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
var collision = new AliasCollision(alias.Scheme, alias.Value, advisoryKeys);
|
||||
var key = $"{collision.Scheme}\u0001{collision.Value}";
|
||||
collisionMap.TryAdd(key, collision);
|
||||
}
|
||||
}
|
||||
|
||||
var aliasMap = new Dictionary<string, IReadOnlyList<AliasRecord>>(aliasCache, StringComparer.OrdinalIgnoreCase);
|
||||
return new AliasComponent(advisoryKey, visited.ToArray(), collisionMap.Values.ToArray(), aliasMap);
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyList<AliasRecord>> GetAliasesAsync(
|
||||
string advisoryKey,
|
||||
CancellationToken cancellationToken,
|
||||
IDictionary<string, IReadOnlyList<AliasRecord>> cache)
|
||||
{
|
||||
if (cache.TryGetValue(advisoryKey, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var aliases = await _aliasStore.GetByAdvisoryAsync(advisoryKey, cancellationToken).ConfigureAwait(false);
|
||||
cache[advisoryKey] = aliases;
|
||||
return aliases;
|
||||
}
|
||||
|
||||
private Task<IReadOnlyList<AliasRecord>> GetAdvisoriesForAliasAsync(
|
||||
string scheme,
|
||||
string value,
|
||||
CancellationToken cancellationToken)
|
||||
=> _aliasStore.GetByAliasAsync(scheme, value, cancellationToken);
|
||||
}
|
||||
|
||||
public sealed record AliasIdentityResult(string AdvisoryKey, IReadOnlyList<AliasRecord> Aliases, IReadOnlyList<AliasCollision> Collisions);
|
||||
|
||||
public sealed record AliasComponent(
|
||||
string SeedAdvisoryKey,
|
||||
IReadOnlyList<string> AdvisoryKeys,
|
||||
IReadOnlyList<AliasCollision> Collisions,
|
||||
IReadOnlyDictionary<string, IReadOnlyList<AliasRecord>> AliasMap);
|
||||
Reference in New Issue
Block a user