| | | 1 | | namespace AsyncResponse.Transports.SQS; |
| | | 2 | | |
| | | 3 | | /// <summary>Options for the AWS SQS AsyncResponse transport.</summary> |
| | | 4 | | public sealed class SqsAsyncResponseOptions |
| | | 5 | | { |
| | | 6 | | /// <summary>The transport name reported to reply targets and startup validation.</summary> |
| | | 7 | | public const string TransportName = "AmazonSQS"; |
| | | 8 | | |
| | | 9 | | /// <summary> |
| | | 10 | | /// Custom SQS endpoint, such as a LocalStack edge URL (<c>http://localhost:4566</c>). Leave |
| | | 11 | | /// <c>null</c> to use the standard AWS endpoint resolution for <see cref="Region"/>. Ignored when |
| | | 12 | | /// an <c>Amazon.SQS.IAmazonSQS</c> singleton is already registered in the service container. |
| | | 13 | | /// </summary> |
| | | 14 | | public string? ServiceUrl { get; set; } |
| | | 15 | | |
| | | 16 | | /// <summary> |
| | | 17 | | /// AWS region system name (<c>us-east-1</c>, …). Leave <c>null</c> to use the AWS SDK default |
| | | 18 | | /// region resolution (environment, profile, instance metadata). |
| | | 19 | | /// </summary> |
| | | 20 | | public string? Region { get; set; } |
| | | 21 | | |
| | | 22 | | /// <summary> |
| | | 23 | | /// Explicit AWS access key. Leave <c>null</c> (with <see cref="SecretKey"/>) to use the AWS SDK |
| | | 24 | | /// default credential chain. Both must be set together; useful for emulators such as LocalStack. |
| | | 25 | | /// </summary> |
| | | 26 | | public string? AccessKey { get; set; } |
| | | 27 | | |
| | | 28 | | /// <summary>Explicit AWS secret key paired with <see cref="AccessKey"/>.</summary> |
| | | 29 | | public string? SecretKey { get; set; } |
| | | 30 | | |
| | | 31 | | /// <summary> |
| | | 32 | | /// SQS queue used by <see cref="SqsWorkerTransport"/> to publish worker jobs. Accepts a queue |
| | | 33 | | /// name (resolved once via <c>GetQueueUrl</c>) or a full queue URL. A name or URL ending in |
| | | 34 | | /// <c>.fifo</c> opts the worker path into FIFO publishing: the correlation id becomes the |
| | | 35 | | /// <c>MessageGroupId</c> so one flow's jobs stay ordered. |
| | | 36 | | /// </summary> |
| | 3 | 37 | | public string WorkerQueue { get; set; } = "asyncresponse-worker"; |
| | | 38 | | |
| | | 39 | | /// <summary>Worker queue handling options.</summary> |
| | 3 | 40 | | public SqsSubscriberOptions WorkerSubscriber { get; } = new(); |
| | | 41 | | |
| | | 42 | | /// <summary> |
| | | 43 | | /// SQS queue consumed by the hosted response-ingress subscriber. Accepts a queue name or URL and |
| | | 44 | | /// must be distinct from <see cref="WorkerQueue"/>. |
| | | 45 | | /// </summary> |
| | 3 | 46 | | public string ResponseQueue { get; set; } = "asyncresponse-response"; |
| | | 47 | | |
| | | 48 | | /// <summary>Response queue handling options.</summary> |
| | 3 | 49 | | public SqsSubscriberOptions ResponseSubscriber { get; } = new(); |
| | | 50 | | |
| | | 51 | | /// <summary>The logical reply target name used by <c>WithReplyTarget()</c>. Default: <c>default</c>.</summary> |
| | 3 | 52 | | public string DefaultReplyTargetName { get; set; } = "default"; |
| | | 53 | | |
| | | 54 | | /// <summary> |
| | | 55 | | /// Named reply targets exposed to Core through <see cref="IAsyncResponseReplyTargetProvider"/>. |
| | | 56 | | /// When empty, <see cref="ResponseQueue"/> becomes the default reply target. |
| | | 57 | | /// </summary> |
| | 3 | 58 | | public Dictionary<string, SqsReplyTargetOptions> ReplyTargets { get; } = new(StringComparer.Ordinal); |
| | | 59 | | |
| | | 60 | | /// <summary> |
| | | 61 | | /// SQS message attribute that carries the AsyncResponse correlation id. Default: |
| | | 62 | | /// <c>correlationId</c>. |
| | | 63 | | /// </summary> |
| | 3 | 64 | | public string CorrelationIdAttribute { get; set; } = "correlationId"; |
| | | 65 | | |
| | | 66 | | /// <summary> |
| | | 67 | | /// JSON paths inspected when a response message does not carry the correlation id as a message |
| | | 68 | | /// attribute. Paths are case-insensitive and support nested JSON strings, such as |
| | | 69 | | /// <c>CustomParameters</c> containing serialized JSON. |
| | | 70 | | /// </summary> |
| | | 71 | | public string[] CorrelationIdJsonPaths { get; set; } = |
| | 3 | 72 | | [ |
| | 3 | 73 | | "CorrelationId", |
| | 3 | 74 | | "CustomParameters", |
| | 3 | 75 | | "CustomParameters.CorrelationId", |
| | 3 | 76 | | "PubSubParams.CustomParameters", |
| | 3 | 77 | | "PubSubParams.CustomParameters.CorrelationId", |
| | 3 | 78 | | "DagJsonParameters.CorrelationId" |
| | 3 | 79 | | ]; |
| | | 80 | | |
| | | 81 | | /// <summary> |
| | | 82 | | /// Maximum messages requested from SQS in one <c>ReceiveMessage</c> call. SQS allows 1–10; |
| | | 83 | | /// default: <c>10</c>. |
| | | 84 | | /// </summary> |
| | 3 | 85 | | public int MaxMessagesPerReceive { get; set; } = 10; |
| | | 86 | | |
| | | 87 | | /// <summary> |
| | | 88 | | /// Long-poll wait time per <c>ReceiveMessage</c> call. SQS allows 0–20 seconds; the default of |
| | | 89 | | /// 20 seconds minimizes empty-receive billing and wake-up latency. |
| | | 90 | | /// </summary> |
| | 3 | 91 | | public TimeSpan ReceiveWaitTime { get; set; } = TimeSpan.FromSeconds(20); |
| | | 92 | | |
| | | 93 | | /// <summary> |
| | | 94 | | /// Provision <see cref="WorkerQueue"/> and <see cref="ResponseQueue"/> (plus one dead-letter |
| | | 95 | | /// queue each, wired through a native redrive policy with <see cref="MaxReceiveCount"/>) on |
| | | 96 | | /// startup. Only queues configured by name are provisioned; queue URLs are assumed to exist. |
| | | 97 | | /// Default: <c>false</c> — production queues are usually owned by infrastructure code. |
| | | 98 | | /// </summary> |
| | | 99 | | public bool CreateQueues { get; set; } |
| | | 100 | | |
| | | 101 | | /// <summary> |
| | | 102 | | /// Suffix appended to a provisioned queue's name to derive its dead-letter queue (before the |
| | | 103 | | /// <c>.fifo</c> suffix for FIFO queues). Default: <c>-dlq</c>. |
| | | 104 | | /// </summary> |
| | 3 | 105 | | public string DeadLetterQueueSuffix { get; set; } = "-dlq"; |
| | | 106 | | |
| | | 107 | | /// <summary> |
| | | 108 | | /// <c>maxReceiveCount</c> written into the redrive policy of provisioned queues: how many |
| | | 109 | | /// receives (tracked natively by SQS via <c>ApproximateReceiveCount</c>) a message survives |
| | | 110 | | /// before SQS moves it to the dead-letter queue. Default: <c>5</c>. |
| | | 111 | | /// </summary> |
| | 3 | 112 | | public int MaxReceiveCount { get; set; } = 5; |
| | | 113 | | |
| | | 114 | | /// <summary> |
| | | 115 | | /// <c>MessageGroupId</c> used when publishing to a FIFO worker queue and the job carries no |
| | | 116 | | /// correlation id. Default: <c>asyncresponse</c>. |
| | | 117 | | /// </summary> |
| | 3 | 118 | | public string FifoMessageGroupIdFallback { get; set; } = "asyncresponse"; |
| | | 119 | | |
| | | 120 | | /// <summary>Maximum attempts for SQS send operations. Set to 1 to disable transport-level retries.</summary> |
| | 3 | 121 | | public int PublishMaxAttempts { get; set; } = 3; |
| | | 122 | | |
| | | 123 | | /// <summary>Initial delay before retrying a failed send operation.</summary> |
| | 3 | 124 | | public TimeSpan PublishRetryBaseDelay { get; set; } = TimeSpan.FromMilliseconds(50); |
| | | 125 | | |
| | | 126 | | /// <summary>Maximum delay between send retry attempts.</summary> |
| | 3 | 127 | | public TimeSpan PublishRetryMaxDelay { get; set; } = TimeSpan.FromSeconds(1); |
| | | 128 | | |
| | | 129 | | /// <summary>Initial delay after a subscriber loop failure.</summary> |
| | 3 | 130 | | public TimeSpan SubscriberRetryBaseDelay { get; set; } = TimeSpan.FromMilliseconds(250); |
| | | 131 | | |
| | | 132 | | /// <summary>Maximum delay after repeated subscriber loop failures.</summary> |
| | 3 | 133 | | public TimeSpan SubscriberRetryMaxDelay { get; set; } = TimeSpan.FromSeconds(5); |
| | | 134 | | |
| | | 135 | | /// <summary> |
| | | 136 | | /// The hosting shutdown budget that must contain |
| | | 137 | | /// <see cref="SqsSubscriberOptions.BackgroundDrainTimeout"/> when a subscriber uses |
| | | 138 | | /// <see cref="SqsAckMode.AckAfterEnqueue"/>. Defaults to the Generic Host default of 30 seconds. |
| | | 139 | | /// Set to <c>null</c> only when this budget is validated externally. |
| | | 140 | | /// </summary> |
| | 3 | 141 | | public TimeSpan? HostShutdownTimeout { get; set; } = TimeSpan.FromSeconds(30); |
| | | 142 | | |
| | | 143 | | /// <summary>Adds or replaces a named SQS reply target.</summary> |
| | | 144 | | public SqsAsyncResponseOptions AddReplyTarget(string name, string queue) |
| | | 145 | | { |
| | 2 | 146 | | ArgumentException.ThrowIfNullOrWhiteSpace(name); |
| | 2 | 147 | | ArgumentException.ThrowIfNullOrWhiteSpace(queue); |
| | | 148 | | |
| | 2 | 149 | | ReplyTargets[name] = new SqsReplyTargetOptions { Queue = queue }; |
| | 2 | 150 | | return this; |
| | | 151 | | } |
| | | 152 | | } |
| | | 153 | | |
| | | 154 | | /// <summary>Options for one named SQS async-response reply target.</summary> |
| | | 155 | | public sealed class SqsReplyTargetOptions |
| | | 156 | | { |
| | | 157 | | /// <summary>Queue (name or URL) remote systems should publish responses to.</summary> |
| | | 158 | | public string? Queue { get; set; } |
| | | 159 | | |
| | | 160 | | /// <summary>Additional values copied to the transport-neutral reply target.</summary> |
| | | 161 | | public Dictionary<string, string> Properties { get; } = new(StringComparer.Ordinal); |
| | | 162 | | } |