- AuditEventDto field 22, SiteCallOperationalDto field 12. Both follow the existing empty-string-means-null convention. - Mappers carry SourceNode end-to-end; round-trip tests cover both populated and null cases.
72 lines
3.3 KiB
C#
72 lines
3.3 KiB
C#
using ScadaLink.Commons.Entities.Audit;
|
|
using ScadaLink.Commons.Types;
|
|
|
|
namespace ScadaLink.Communication.Grpc;
|
|
|
|
/// <summary>
|
|
/// Canonical bridge for Site Call Audit (#22) operational rows between the
|
|
/// wire-format <see cref="SiteCallOperationalDto"/> exchanged on the
|
|
/// <c>CachedCallTelemetry</c> packet and the in-process <see cref="SiteCall"/>
|
|
/// persistence entity central writes into the <c>SiteCalls</c> table.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// This mapper lives in <c>ScadaLink.Communication</c> (which owns the generated
|
|
/// <see cref="SiteCallOperationalDto"/> and references <c>Commons</c> for
|
|
/// <see cref="SiteCall"/>) so both <c>SiteStreamGrpcServer</c> and
|
|
/// <c>ScadaLink.AuditLog</c> can share one implementation without the
|
|
/// project-reference cycle that would result from hosting it in
|
|
/// <c>ScadaLink.AuditLog</c> (AuditLog → Communication, never the reverse).
|
|
/// Mirrors the sibling <see cref="AuditEventDtoMapper"/>.
|
|
/// </para>
|
|
/// <para>
|
|
/// Only the DTO→entity direction is provided: nothing in the system maps a
|
|
/// <see cref="SiteCall"/> back onto the wire (sites emit the operational state
|
|
/// from <c>SiteCallOperational</c>, never from the central <see cref="SiteCall"/>
|
|
/// entity), so an entity→DTO method would be dead code.
|
|
/// </para>
|
|
/// <para>
|
|
/// String nullability convention: proto3 scalar strings cannot be absent, so the
|
|
/// optional <see cref="SiteCall.LastError"/> rehydrates from an empty string back
|
|
/// to null. The optional <c>HttpStatus</c> and <c>TerminalAtUtc</c> use proto
|
|
/// wrappers so they preserve true null semantics.
|
|
/// </para>
|
|
/// </remarks>
|
|
public static class SiteCallDtoMapper
|
|
{
|
|
/// <summary>
|
|
/// Reconstructs a <see cref="SiteCall"/> persistence entity from its
|
|
/// wire-format DTO. An empty <c>LastError</c> rehydrates as null; absent
|
|
/// <c>HttpStatus</c>/<c>TerminalAtUtc</c> wrappers stay null.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <see cref="SiteCall.IngestedAtUtc"/> is stamped here as a placeholder
|
|
/// (<see cref="DateTime.UtcNow"/>); the central ingest actor overwrites it
|
|
/// inside the dual-write transaction so the AuditLog and SiteCalls rows
|
|
/// share one instant. The value sent on the wire is informational only.
|
|
/// </remarks>
|
|
public static SiteCall FromDto(SiteCallOperationalDto dto)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(dto);
|
|
|
|
return new SiteCall
|
|
{
|
|
TrackedOperationId = TrackedOperationId.Parse(dto.TrackedOperationId),
|
|
Channel = dto.Channel,
|
|
Target = dto.Target,
|
|
SourceSite = dto.SourceSite,
|
|
SourceNode = string.IsNullOrEmpty(dto.SourceNode) ? null : dto.SourceNode,
|
|
Status = dto.Status,
|
|
RetryCount = dto.RetryCount,
|
|
LastError = string.IsNullOrEmpty(dto.LastError) ? null : dto.LastError,
|
|
HttpStatus = dto.HttpStatus,
|
|
CreatedAtUtc = DateTime.SpecifyKind(dto.CreatedAtUtc.ToDateTime(), DateTimeKind.Utc),
|
|
UpdatedAtUtc = DateTime.SpecifyKind(dto.UpdatedAtUtc.ToDateTime(), DateTimeKind.Utc),
|
|
TerminalAtUtc = dto.TerminalAtUtc is null
|
|
? null
|
|
: DateTime.SpecifyKind(dto.TerminalAtUtc.ToDateTime(), DateTimeKind.Utc),
|
|
IngestedAtUtc = DateTime.UtcNow, // overwritten by AuditLogIngestActor
|
|
};
|
|
}
|
|
}
|