< Summary - AsyncResponse (Release / net8.0+net10.0 / unit+integration)

Information
Class: AsyncResponse.DurableFlowNotDispatchedException
Assembly: AsyncResponse.Abstractions
File(s): /_/src/AsyncResponse.Abstractions/IDurableFlow.cs
Line coverage
100%
Covered lines: 7
Uncovered lines: 0
Coverable lines: 7
Total lines: 101
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_FlowId()100%11100%

File(s)

/_/src/AsyncResponse.Abstractions/IDurableFlow.cs

#LineLine coverage
 1namespace AsyncResponse;
 2
 3/// <summary>
 4/// A durable multi-step flow: plain sequential C# whose steps are checkpointed, so the flow can be
 5/// killed at any point (crash, redeploy, redelivery) and re-run safely — completed steps are
 6/// skipped, the in-flight awaited step is re-attached, and everything after continues.
 7/// <para>
 8/// Implement the flow as ordinary code: conditionals, loops, and data flow are all allowed. The
 9/// only rules are that each step has a stable unique name (names are persisted in the flow state)
 10/// and that step bodies are safe to re-execute when the library cannot prove they completed
 11/// (at-least-once, like every other delivery guarantee in AsyncResponse).
 12/// </para>
 13/// <para>
 14/// Register the implementation in DI (e.g. <c>services.AddScoped&lt;MyFlow&gt;()</c>) and start it with
 15/// <see cref="IDurableFlows.StartAsync{TFlow,TInput}"/>. The flow class name is persisted in the
 16/// flow state and resolved when the flow is executed or resumed — treat the class name as a wire
 17/// contract (rename with a forwarding type, like recovery-callback names).
 18/// </para>
 19/// </summary>
 20/// <typeparam name="TInput">
 21/// The flow's input, persisted as JSON with the flow state and handed to every (re-)execution.
 22/// Use one serializable record; capture nothing through closures.
 23/// </typeparam>
 24public interface IDurableFlow<in TInput>
 25{
 26    /// <summary>
 27    /// The flow body. Invoked on start and on every resume/redelivery — always from the top, with
 28    /// completed steps skipping via their checkpoints. Must therefore be safe to call repeatedly.
 29    /// </summary>
 30    /// <param name="flow">The step context: checkpointed steps, awaited steps, progress, values.</param>
 31    /// <param name="input">The input the flow was started with, rehydrated from the flow state.</param>
 32    Task ExecuteAsync(IDurableFlowContext flow, TInput input);
 33}
 34
 35/// <summary>
 36/// Terminates a durable flow run as <see cref="FlowRunStatus.Failed"/> without transport
 37/// redelivery. Any other exception thrown from a flow is treated as retriable: it propagates to
 38/// the worker transport, which redelivers the flow run with bounded attempts and dead-letters it
 39/// when they are exhausted.
 40/// </summary>
 41public sealed class DurableFlowFailedException : Exception
 42{
 43    /// <summary>Creates a terminal flow failure with an operator-facing message.</summary>
 44    public DurableFlowFailedException(string message) : base(message)
 45    {
 46    }
 47
 48    /// <summary>Creates a terminal flow failure wrapping the causing exception.</summary>
 49    public DurableFlowFailedException(string message, Exception innerException) : base(message, innerException)
 50    {
 51    }
 52}
 53
 54/// <summary>
 55/// Thrown by <c>IDurableFlows.StartAsync</c> when the requested flow id already exists bound to a
 56/// different flow type or a semantically different input — the idempotent-start contract accepts
 57/// only exact retries. Derives from <see cref="InvalidOperationException"/> for compatibility with
 58/// callers that catch that. The distinct type exists so callers running deterministic-id races
 59/// (the scheduled-flow service, outbox-style starters) can tell <em>this id is taken with other
 60/// data</em> apart from every other <see cref="InvalidOperationException"/> a start can throw —
 61/// treating, say, an input-factory failure as a benign duplicate would report an occurrence as
 62/// having run when nothing ran.
 63/// </summary>
 64public sealed class DurableFlowIdConflictException : InvalidOperationException
 65{
 66    /// <summary>Creates the conflict with an operator-facing message naming the flow id.</summary>
 67    public DurableFlowIdConflictException(string message) : base(message)
 68    {
 69    }
 70}
 71
 72/// <summary>
 73/// Thrown by <c>IDurableFlows.StartAsync</c> when the flow's start job could not be published to the
 74/// worker transport after retries. <b>Nothing was persisted</b>: the publish is the start's commit
 75/// point (the job carries the initial ledger and its execution creates the run), so a failed
 76/// publish leaves no orphaned <c>Running</c> ledger behind — the caller simply retries the start.
 77/// <see cref="FlowId"/> carries the id the start would have used, including a generated one, so a
 78/// retry can reuse it and stay idempotent: an identical start of an id that already exists
 79/// re-enqueues the existing run rather than creating a second one.
 80/// <para>
 81/// Publication is retried before this surfaces, so it means the transport stayed unavailable, not
 82/// that it blinked. The ambiguous case is deliberately included: a publish that may or may not
 83/// have landed also throws here. If it did land, the job creates and runs the flow on its own; a
 84/// retried start with the SAME id then dedupes against that run, while a retry with a fresh
 85/// generated id starts a second, independent run — supply deterministic ids where callers retry.
 86/// </para>
 87/// </summary>
 88public sealed class DurableFlowNotDispatchedException : InvalidOperationException
 89{
 90    /// <summary>Creates the failure for <paramref name="flowId"/>.</summary>
 91    public DurableFlowNotDispatchedException(string flowId, Exception? innerException = null)
 892        : base(
 893            $"Durable flow '{flowId}' could not be started: its worker job was not published, so nothing " +
 894            $"was persisted and nothing is scheduled to execute it. Retry the start with this same flow id — an " +
 895            $"identical start is idempotent, so a job that did land is not duplicated.",
 896            innerException)
 897        => FlowId = flowId;
 98
 99    /// <summary>The id the start would have used, so a caller can retry idempotently with it.</summary>
 20100    public string FlowId { get; }
 101}