Files
git.stella-ops.org/src/Notifier/StellaOps.Notifier/StellaOps.Notifier.Worker/Channels/IChannelAdapter.cs
StellaOps Bot ef6e4b2067
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
Merge branch 'main' of https://git.stella-ops.org/stella-ops.org/git.stella-ops.org
2025-11-27 21:45:32 +02:00

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
};
}