Files
lmxopcua/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IEffectiveNameGuard.cs
T

96 lines
5.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}