namespace ZB.MOM.WW.ScadaBridge.DelmiaNotifier; /// Final outcome of the notify operation, mapped 1:1 to the YES/NO + exit-code contract. internal sealed record NotifyResult(bool Ok, string Reason); /// /// Connect-failure-only failover loop: tries each base URL in order. A node that responds at all is /// authoritative — its answer is final (success, business rejection, or HTTP error alike). Only a /// failure to connect rolls over to the next URL; if every URL fails to connect the last error is reported. /// internal static class Notifier { public static async Task RunAsync( string[] baseUrls, RecipeDownload payload, IRecipeSender sender, CancellationToken ct) { var lastError = "no base URLs configured"; foreach (var baseUrl in baseUrls) { var outcome = await sender.SendAsync(baseUrl, payload, ct); if (outcome.Kind == AttemptKind.ConnectFailed) { lastError = outcome.Error ?? "connection failed"; continue; // unreachable node → try the next } // Connected — this node's answer is authoritative; never fail over past it. if (IsSuccessStatus(outcome.StatusCode)) { if (outcome.Body is { Result: true }) { return new NotifyResult(true, outcome.Body.ResultText ?? string.Empty); } var reason = outcome.Body?.ResultText; return new NotifyResult(false, string.IsNullOrEmpty(reason) ? "request rejected" : reason); } return new NotifyResult(false, $"HTTP {outcome.StatusCode}"); } return new NotifyResult(false, $"all URLs unreachable: {lastError}"); } private static bool IsSuccessStatus(int status) => status is >= 200 and < 300; }