96 lines
5.1 KiB
C#
96 lines
5.1 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
|
||
|
||
/// <summary>
|
||
/// Which authoring surface a proposed effective name comes from. Used to word the collision
|
||
/// error and to exclude the correct same-kind self-row on an edit (rename / override change).
|
||
/// </summary>
|
||
public enum EffectiveNameSourceKind
|
||
{
|
||
/// <summary>A <see cref="Configuration.Entities.UnsTagReference"/> (effective name =
|
||
/// <c>DisplayNameOverride</c> else the backing raw tag's <c>Name</c>).</summary>
|
||
Reference,
|
||
|
||
/// <summary>An equipment-bound <see cref="Configuration.Entities.VirtualTag"/> (effective name = <c>Name</c>).</summary>
|
||
VirtualTag,
|
||
|
||
/// <summary>An equipment-bound <see cref="Configuration.Entities.ScriptedAlarm"/> (effective name = <c>Name</c>).</summary>
|
||
ScriptedAlarm,
|
||
}
|
||
|
||
/// <summary>
|
||
/// v3 Batch 3 authoring-time guard for the UNS effective-name uniqueness rule. Within an
|
||
/// equipment the EFFECTIVE leaf name must be unique across its <c>UnsTagReferences</c>
|
||
/// (<c>DisplayNameOverride</c> else the backing raw tag's <c>Name</c>), its <c>VirtualTags</c>
|
||
/// (<c>Name</c>), and its <c>ScriptedAlarms</c> (<c>Name</c>) — the three share the equipment's
|
||
/// UNS NodeId space (<c>{EquipmentId}/{EffectiveName}</c>). This is the authoring mirror of the
|
||
/// deploy-time <c>DraftValidator</c> <c>UnsEffectiveNameCollision</c> rule; comparison is
|
||
/// <see cref="StringComparer.Ordinal"/> to match NodeId semantics and the validator.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Injectable (registered <c>Scoped</c>, its own pooled context per call — the same pattern
|
||
/// <c>UnsTreeService</c> uses). Consumed by <c>UnsTreeService</c>'s reference / virtual-tag /
|
||
/// scripted-alarm mutations, which call <see cref="CheckAsync"/> before persisting and surface a
|
||
/// non-null result as the mutation's readable failure. The deploy gate remains the backstop for
|
||
/// rename-induced collisions the authoring check never saw.
|
||
/// </remarks>
|
||
public interface IEffectiveNameGuard
|
||
{
|
||
/// <summary>
|
||
/// Tests whether <paramref name="proposedEffectiveName"/> would collide with an existing
|
||
/// effective name within <paramref name="equipmentId"/>.
|
||
/// </summary>
|
||
/// <param name="equipmentId">The owning equipment's logical id.</param>
|
||
/// <param name="proposedEffectiveName">The effective name to be authored (override else raw name for a reference; Name for a VT/alarm).</param>
|
||
/// <param name="kind">Which surface the proposed name is for (words the error; identifies the self-row kind to exclude).</param>
|
||
/// <param name="excludeSourceId">
|
||
/// On an edit, the logical id of the row being changed (its <c>UnsTagReferenceId</c> /
|
||
/// <c>VirtualTagId</c> / <c>ScriptedAlarmId</c>) so it is excluded from the collision set;
|
||
/// <see langword="null"/> on a create.
|
||
/// </param>
|
||
/// <param name="ct">Cancellation token.</param>
|
||
/// <returns>
|
||
/// <see langword="null"/> when the name is free; otherwise a readable error naming the
|
||
/// colliding existing source and the equipment.
|
||
/// </returns>
|
||
Task<string?> CheckAsync(
|
||
string equipmentId,
|
||
string proposedEffectiveName,
|
||
EffectiveNameSourceKind kind,
|
||
string? excludeSourceId,
|
||
CancellationToken ct = default);
|
||
|
||
/// <summary>
|
||
/// Checks many proposed names against ONE load of the equipment's name space. Returns the first
|
||
/// collision message (in input order), or <see langword="null"/> when all are free.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// The bulk-authoring entry point (a select-all "add references" over N raw tags), where the
|
||
/// per-name <see cref="CheckAsync"/> would cost N contexts × 4 queries. There is no
|
||
/// <c>excludeSourceId</c>: a batch is a CREATE of N new rows, so no self-row exists to exclude.
|
||
/// The default implementation simply loops <see cref="CheckAsync"/> — correct but not fast — so
|
||
/// a stub / no-op guard needs no change; <c>EffectiveNameGuard</c> overrides it with the
|
||
/// single-load version.
|
||
/// </remarks>
|
||
/// <param name="equipmentId">The owning equipment's logical id.</param>
|
||
/// <param name="proposedEffectiveNames">The effective names to be authored, in the order the caller wants them reported.</param>
|
||
/// <param name="kind">Which surface the proposed names are for (words the error; identifies the self-row kind).</param>
|
||
/// <param name="ct">Cancellation token.</param>
|
||
/// <returns><see langword="null"/> when every name is free; otherwise the first name's readable error.</returns>
|
||
async Task<string?> CheckManyAsync(
|
||
string equipmentId,
|
||
IReadOnlyList<string> proposedEffectiveNames,
|
||
EffectiveNameSourceKind kind,
|
||
CancellationToken ct = default)
|
||
{
|
||
ArgumentNullException.ThrowIfNull(proposedEffectiveNames);
|
||
|
||
foreach (var proposed in proposedEffectiveNames)
|
||
{
|
||
var collision = await CheckAsync(equipmentId, proposed, kind, excludeSourceId: null, ct);
|
||
if (collision is not null) return collision;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
}
|