Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
161 lines
4.8 KiB
C#
161 lines
4.8 KiB
C#
using StellaOps.Notify.Models;
|
|
|
|
namespace StellaOps.Notifier.Worker.Channels;
|
|
|
|
/// <summary>
|
|
/// Contract implemented by channel adapters to dispatch notifications.
|
|
/// </summary>
|
|
public interface IChannelAdapter
|
|
{
|
|
/// <summary>
|
|
/// Channel type handled by this adapter.
|
|
/// </summary>
|
|
NotifyChannelType ChannelType { get; }
|
|
|
|
/// <summary>
|
|
/// Dispatches a notification delivery through the channel.
|
|
/// </summary>
|
|
Task<ChannelDispatchResult> DispatchAsync(
|
|
ChannelDispatchContext context,
|
|
CancellationToken cancellationToken);
|
|
|
|
/// <summary>
|
|
/// Checks channel health/connectivity.
|
|
/// </summary>
|
|
Task<ChannelHealthCheckResult> CheckHealthAsync(
|
|
NotifyChannel channel,
|
|
CancellationToken cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Context for dispatching a notification through a channel.
|
|
/// </summary>
|
|
public sealed record ChannelDispatchContext(
|
|
string DeliveryId,
|
|
string TenantId,
|
|
NotifyChannel Channel,
|
|
NotifyDelivery Delivery,
|
|
string RenderedBody,
|
|
string? Subject,
|
|
IReadOnlyDictionary<string, string> Metadata,
|
|
DateTimeOffset Timestamp,
|
|
string TraceId);
|
|
|
|
/// <summary>
|
|
/// Result of a channel dispatch attempt.
|
|
/// </summary>
|
|
public sealed record ChannelDispatchResult
|
|
{
|
|
public required bool Success { get; init; }
|
|
public required ChannelDispatchStatus Status { get; init; }
|
|
public string? Message { get; init; }
|
|
public string? ExternalId { get; init; }
|
|
public int? HttpStatusCode { get; init; }
|
|
public TimeSpan? Duration { get; init; }
|
|
public IReadOnlyDictionary<string, string> Metadata { get; init; } = new Dictionary<string, string>();
|
|
public Exception? Exception { get; init; }
|
|
|
|
public static ChannelDispatchResult Succeeded(
|
|
string? externalId = null,
|
|
string? message = null,
|
|
TimeSpan? duration = null,
|
|
IReadOnlyDictionary<string, string>? metadata = null) => new()
|
|
{
|
|
Success = true,
|
|
Status = ChannelDispatchStatus.Sent,
|
|
ExternalId = externalId,
|
|
Message = message ?? "Delivery dispatched successfully.",
|
|
Duration = duration,
|
|
Metadata = metadata ?? new Dictionary<string, string>()
|
|
};
|
|
|
|
public static ChannelDispatchResult Failed(
|
|
string message,
|
|
ChannelDispatchStatus status = ChannelDispatchStatus.Failed,
|
|
int? httpStatusCode = null,
|
|
Exception? exception = null,
|
|
TimeSpan? duration = null,
|
|
IReadOnlyDictionary<string, string>? metadata = null) => new()
|
|
{
|
|
Success = false,
|
|
Status = status,
|
|
Message = message,
|
|
HttpStatusCode = httpStatusCode,
|
|
Exception = exception,
|
|
Duration = duration,
|
|
Metadata = metadata ?? new Dictionary<string, string>()
|
|
};
|
|
|
|
public static ChannelDispatchResult Throttled(
|
|
string message,
|
|
TimeSpan? retryAfter = null,
|
|
IReadOnlyDictionary<string, string>? metadata = null)
|
|
{
|
|
var meta = metadata is not null
|
|
? new Dictionary<string, string>(metadata)
|
|
: new Dictionary<string, string>();
|
|
|
|
if (retryAfter.HasValue)
|
|
{
|
|
meta["retryAfterSeconds"] = retryAfter.Value.TotalSeconds.ToString("F0");
|
|
}
|
|
|
|
return new()
|
|
{
|
|
Success = false,
|
|
Status = ChannelDispatchStatus.Throttled,
|
|
Message = message,
|
|
HttpStatusCode = 429,
|
|
Metadata = meta
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispatch attempt status.
|
|
/// </summary>
|
|
public enum ChannelDispatchStatus
|
|
{
|
|
Sent,
|
|
Failed,
|
|
Throttled,
|
|
InvalidConfiguration,
|
|
Timeout,
|
|
NetworkError
|
|
}
|
|
|
|
/// <summary>
|
|
/// Result of a channel health check.
|
|
/// </summary>
|
|
public sealed record ChannelHealthCheckResult
|
|
{
|
|
public required bool Healthy { get; init; }
|
|
public required string Status { get; init; }
|
|
public string? Message { get; init; }
|
|
public TimeSpan? Latency { get; init; }
|
|
public IReadOnlyDictionary<string, string> Metadata { get; init; } = new Dictionary<string, string>();
|
|
|
|
public static ChannelHealthCheckResult Ok(string? message = null, TimeSpan? latency = null) => new()
|
|
{
|
|
Healthy = true,
|
|
Status = "healthy",
|
|
Message = message ?? "Channel is operational.",
|
|
Latency = latency
|
|
};
|
|
|
|
public static ChannelHealthCheckResult Degraded(string message, TimeSpan? latency = null) => new()
|
|
{
|
|
Healthy = true,
|
|
Status = "degraded",
|
|
Message = message,
|
|
Latency = latency
|
|
};
|
|
|
|
public static ChannelHealthCheckResult Unhealthy(string message) => new()
|
|
{
|
|
Healthy = false,
|
|
Status = "unhealthy",
|
|
Message = message
|
|
};
|
|
}
|