Logo Orsak

Errors

An Effect<'r, 'a, 'e> either succeeds with an 'a or fails with an 'e. The error is part of the type, so the ways an effect can fail are visible in its signature, and the compiler checks that they are handled. Exceptions are left for what is truly unexpected: a bug, or a failure nothing sensible can be done about.

This page shows how to model errors, how to combine effects that fail in different ways, how to handle errors, how to collect several errors at once, and where exceptions fit in. It is a script: it runs with dotnet fsi docs/errors.fsx once Orsak is built, and the output shown is from such a run.

Errors as a union

The examples place orders in a shop, which needs a stock service and a payment service. Each is described by an interface in the usual way, and each fails in its own way, described by a union:

open System
open System.Threading.Tasks
open Orsak

type StockError =
    | OutOfStock of sku: string
    | UnknownProduct of sku: string

type IStock =
    abstract member Reserve: sku: string * quantity: int -> Task<Result<unit, StockError>>

type IStockProvider =
    abstract member Stock: IStock

type PaymentError =
    | Declined of reason: string
    | ProviderUnavailable

type IPayments =
    abstract member Charge: customer: string * amount: decimal -> Task<Result<string, PaymentError>>

type IPaymentsProvider =
    abstract member Payments: IPayments

When the function given to Effect.Create returns a Result, its error becomes the effect's error:

module Stock =
    let reserve sku quantity =
        Effect.Create(fun (p: #IStockProvider) -> p.Stock.Reserve(sku, quantity))

module Payments =
    let charge customer amount =
        Effect.Create(fun (p: #IPaymentsProvider) -> p.Payments.Charge(customer, amount))

Stock.reserve is an Effect<#IStockProvider, unit, StockError>, and Payments.charge an Effect<#IPaymentsProvider, string, PaymentError>.

Inside eff, binding an effect that fails ends the whole effect with that error, so the code only describes the successful path. return! Error ... fails on purpose, and a plain Result can be bound like an effect:

let catalog = set [ "mug"; "plate" ]

let checkProduct sku =
    if catalog.Contains sku then Ok sku else Error(UnknownProduct sku)

let reserveAll (lines: (string * int) list) = eff {
    for sku, quantity in lines do
        let! sku = checkProduct sku
        do! Stock.reserve sku quantity
}

The first line that can't be reserved ends reserveAll, and the lines after it are never tried.

To run the examples, fake services are enough. The stock has two mugs and no plates, and the payment provider declines amounts over 100:

type FakeStock() =
    let mutable mugs = 2

    interface IStock with
        member _.Reserve(sku, quantity) =
            Task.FromResult(
                match sku with
                | "mug" when quantity <= mugs ->
                    mugs <- mugs - quantity
                    Ok()
                | "mug"
                | "plate" -> Error(OutOfStock sku)
                | _ -> Error(UnknownProduct sku)
            )

type FakePayments() =
    interface IPayments with
        member _.Charge(customer, amount) =
            Task.FromResult(if amount > 100m then Error(Declined "limit") else Ok $"receipt for {customer}")

type Env() =
    let stock = FakeStock()
    let payments = FakePayments()

    interface IStockProvider with
        member _.Stock = stock

    interface IPaymentsProvider with
        member _.Payments = payments

/// Runs an effect with a new environment, and prints its result.
let show (effect: Effect<Env, 'a, 'e>) =
    let result = effect |> Effect.run (Env())
    printfn $"%A{result.AsTask().Result}"

reserveAll [ "mug", 1; "plate", 1; "mug", 1 ] |> show
reserveAll [ "mug", 1; "bowl", 1 ] |> show
Error (OutOfStock "plate")
Error (UnknownProduct "bowl")

Different errors

An effect has a single error type, so Stock.reserve and Payments.charge can't be bound in the same eff as they are. The usual approach is a union for the workflow, with a case for each way it can fail, and Effect.changeError to wrap the errors of the effects it uses:

type OrderError =
    | Stock of StockError
    | Payment of PaymentError

let placeOrder customer sku quantity price = eff {
    do! Stock.reserve sku quantity |> Effect.changeError Stock
    let! receipt = Payments.charge customer (decimal quantity * price) |> Effect.changeError Payment
    return receipt
}

placeOrder "ada" "mug" 1 12m |> show
placeOrder "ada" "mug" 1 120m |> show
placeOrder "ada" "mug" 3 12m |> show
Ok "receipt for ada"
Error (Payment (Declined "limit"))
Error (Stock (OutOfStock "mug"))

placeOrder is an Effect<#IStockProvider & #IPaymentsProvider, string, OrderError>: its callers see every way it can fail, and nothing else. For a plain Result, Result.mapError does the same as Effect.changeError.

Handling errors

Matching on the result after running the effect handles errors at the composition root. Often an error is better handled where it happens, and the Effect module has functions for that, each running only when the effect fails:

Here, a declined payment becomes a message for the customer, while other errors stay errors:

let placeOrderOrExplain customer sku quantity price =
    placeOrder customer sku quantity price
    |> Effect.tryRecover (function
        | Payment(Declined reason) -> Ok $"payment declined ({reason}), the order was not placed"
        | other -> Error other)

placeOrderOrExplain "ada" "mug" 1 120m |> show
Ok "payment declined (limit), the order was not placed"

Sometimes the decision depends on more than the error. Effect.asResult makes the outcome a value, to be matched on inside eff, while the surrounding effect carries on:

let reserveOrBackorder sku quantity = eff {
    match! Stock.reserve sku quantity |> Effect.asResult with
    | Ok() -> return "reserved"
    | Error(OutOfStock _) -> return "backordered"
    | Error other -> return! Error other
}

reserveOrBackorder "mug" 5 |> show
Ok "backordered"

Collecting errors

Binding one effect after another stops at the first error, which is usually what is wanted: there is no point charging for an order that couldn't be reserved. When checking input, though, it is friendlier to report everything that is wrong at once. and! binds effects together, running them in parallel where possible, and when more than one of them fails, their errors are combined with the error type's + operator:

type Problems =
    | Problems of string list

    static member (+)(Problems a, Problems b) = Problems(a @ b)

let problem message = Error(Problems [ message ])

let checkCustomer (customer: string) = eff {
    if String.IsNullOrWhiteSpace customer then
        return! problem "the customer is missing"
    else
        return customer
}

let checkLine (sku: string) quantity = eff {
    if quantity <= 0 then
        return! problem $"the quantity of {sku} must be positive"
    else
        return sku, quantity
}

let checkOrder customer sku quantity = eff {
    let! customer = checkCustomer customer
    and! sku, quantity = checkLine sku quantity
    return customer, sku, quantity
}

checkOrder "ada" "mug" 1 |> show
checkOrder "" "mug" 0 |> show
Ok ("ada", "mug", 1)
Error (Problems ["the customer is missing"; "the quantity of mug must be positive"])

An error type without + can't be used with and!, which the compiler reports. Effect.zip combines two effects the same way as and!. Effect.whenAll and Effect.par, which run any number of effects in parallel, don't combine errors: they fail with the error of the first failing effect, in the order the effects were given.

Checking the input and placing the order fit together with one more case in OrderError:

type OrderError =
    | Invalid of string list
    | Stock of StockError
    | Payment of PaymentError

let checkAndPlaceOrder customer sku quantity price = eff {
    let! customer, sku, quantity =
        checkOrder customer sku quantity |> Effect.changeError (fun (Problems problems) -> Invalid problems)
    return! placeOrder customer sku quantity price
}

Exceptions

An exception thrown by an effect isn't turned into an error: it propagates, and the ValueTask returned by Effect.run faults with it, as a Task would. try ... with works inside eff, for the exceptions that should become errors, such as those of a client library that reports failures by throwing:

type IShipping =
    abstract member Book: sku: string -> Task<string>

type IShippingProvider =
    abstract member Shipping: IShipping

type ShippingError = ShippingUnavailable of message: string

let bookShipping sku = eff {
    try
        return! Effect.Create(fun (p: #IShippingProvider) -> p.Shipping.Book sku)
    with :? System.Net.Http.HttpRequestException as e ->
        return! Error(ShippingUnavailable e.Message)
}

type ShippingEnv() =
    interface IShippingProvider with
        member _.Shipping =
            { new IShipping with
                member _.Book _ =
                    Task.FromException<string>(System.Net.Http.HttpRequestException "connection refused")
            }

let booking = bookShipping "mug" |> Effect.run (ShippingEnv())
printfn $"%A{booking.AsTask().Result}"
Error (ShippingUnavailable "connection refused")

Going the other way, Effect.runOrFail runs an effect and throws when it fails, with the error's string representation as the message. It suits the edges of a program where a failure should end things, such as a script or a test, while Effect.run, returning the error as a value, suits everywhere else.

namespace System
namespace System.Threading
namespace System.Threading.Tasks
namespace Orsak
type StockError = | OutOfStock of sku: string | UnknownProduct of sku: string
Multiple items
val string: value: 'T -> string

--------------------
type string = String
type IStock = abstract Reserve: sku: string * quantity: int -> Task<Result<unit,StockError>>
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
Multiple items
type Task = interface IAsyncResult interface IDisposable new: action: Action -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable + 1 overload member ContinueWith: continuationAction: Action<Task,obj> * state: obj -> Task + 19 overloads member Dispose: unit -> unit member GetAwaiter: unit -> TaskAwaiter member RunSynchronously: unit -> unit + 1 overload member Start: unit -> unit + 1 overload member Wait: unit -> unit + 5 overloads ...
<summary>Represents an asynchronous operation.</summary>

--------------------
type Task<'TResult> = inherit Task new: ``function`` : Func<obj,'TResult> * state: obj -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable<'TResult> + 1 overload member ContinueWith: continuationAction: Action<Task<'TResult>,obj> * state: obj -> Task + 19 overloads member GetAwaiter: unit -> TaskAwaiter<'TResult> member WaitAsync: cancellationToken: CancellationToken -> Task<'TResult> + 4 overloads member Result: 'TResult static member Factory: TaskFactory<'TResult>
<summary>Represents an asynchronous operation that can return a value.</summary>
<typeparam name="TResult">The type of the result produced by this <see cref="T:System.Threading.Tasks.Task`1" />.</typeparam>


--------------------
Task(action: Action) : Task
Task(action: Action, cancellationToken: Threading.CancellationToken) : Task
Task(action: Action, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj) : Task
Task(action: Action, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: Threading.CancellationToken) : Task
Task(action: Action<obj>, state: obj, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task

--------------------
Task(``function`` : Func<'TResult>) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj) : Task<'TResult>
Task(``function`` : Func<'TResult>, cancellationToken: Threading.CancellationToken) : Task<'TResult>
Task(``function`` : Func<'TResult>, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, cancellationToken: Threading.CancellationToken) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<'TResult>, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, cancellationToken: Threading.CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
Multiple items
module Result from Microsoft.FSharp.Core

--------------------
type Result<'T,'TError> = | Ok of ResultValue: 'T | Error of ErrorValue: 'TError
type unit = Unit
type IStockProvider = abstract Stock: IStock
type PaymentError = | Declined of reason: string | ProviderUnavailable
type IPayments = abstract Charge: customer: string * amount: decimal -> Task<Result<string,PaymentError>>
Multiple items
val decimal: value: 'T -> decimal (requires member op_Explicit)

--------------------
type decimal = Decimal

--------------------
type decimal<'Measure> = decimal
type IPaymentsProvider = abstract Payments: IPayments
val reserve: sku: string -> quantity: int -> Effect<#IStockProvider,unit,StockError>
val sku: string
val quantity: int
Multiple items
union case Effect.Effect: EffectDelegate<'r,'a,'e> -> Effect<'r,'a,'e>

--------------------
module Effect from Orsak
<summary> Functions for running, combining, recovering and repeating effects. </summary>

--------------------
type Effect = static member Create: f: ('a -> Task<Result<'b,'e>>) -> Effect<'a,'b,'e> + 2 overloads static member Error: error: 'e -> Effect<'a,'b,'e>
<summary> Creates effects. <c>Effect.Create</c> turns a function of an environment into an effect, and is how effects are made from the interfaces that describe side effects. The function may return a plain value, a <c>Result</c>, a <c>Task</c>, a <c>ValueTask</c> or an <c>Async</c>, of a value or of a <c>Result</c>; returning a <c>Result</c> lets the effect fail. </summary>
<example> The usual pattern: an interface describing the side effect, a provider interface exposing it, and a function creating the effect, which requires any environment that implements the provider (<c>#IConsoleProvider</c>). <code lang="fsharp"> type IConsole = abstract ReadLine: unit -&gt; string abstract WriteLine: string -&gt; unit type IConsoleProvider = abstract Console: IConsole module Console = let readLine () = Effect.Create(fun (p: #IConsoleProvider) -&gt; p.Console.ReadLine()) let writeLine line = Effect.Create(fun (p: #IConsoleProvider) -&gt; p.Console.WriteLine line) </code> The Orsak.Myriad generators can write the functions of such a module from the interface. </example>


--------------------
type Effect<'r,'a,'e> = | Effect of EffectDelegate<'r,'a,'e> member Run: r: 'r -> AsyncResult<'a,'e> member RunOrFail: r: 'r -> Task<'a> static member (<*>)<'b,'a (requires member (+))> : f: Effect<'r,('b -> 'a),'a0> * e: Effect<'r,'b,'a0> -> Effect<'r,'a,'a0> (requires member (+)) static member (>>=) : h: Effect<'r,'a,'e> * f: ('a -> Effect<'r,'b,'e>) -> Effect<'r,'b,'e> static member Map: h: Effect<'r,'a,'e> * f: ('a -> 'b) -> Effect<'r,'b,'e> static member Return<'a,'b,'c> : a: 'a0 -> Effect<'b,'a0,'c> static member ap<'r,'a,'b,'e> : applicative: Effect<'r,('b -> 'a),'e> -> e: Effect<'r,'b,'e> -> Effect<'r,'a,'e>
<summary> Describes an effect that can either succeed with <typeparamref name="'a" />, or fail with <typeparamref name="'e" />. The effect is 'cold', and only starts when run with an <typeparamref name="'r" />. </summary>
<remarks> Effects are usually written with the <c>eff</c> computation expression, and created from interfaces that describe side effects with <c>Effect.Create</c>. The environment <typeparamref name="'r" /> is inferred from what the effect uses, and supplied when the effect is run. </remarks>
<example><code lang="fsharp"> let greet () = eff { let! name = Console.readLine () do! Console.writeLine $"Hello {name}" } // at the composition root, with an environment providing the console effects: let! result = greet () |&gt; Effect.run env </code></example>
<typeparam name="'r"> The environment required to run the effect </typeparam>
<typeparam name="'a"> The resulting type when the effect runs successfully </typeparam>
<typeparam name="'e"> The resulting type when the effect fails</typeparam>
static member Effect.Create: f: ('a -> 'b) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> Async<'b>) -> Effect<'a,'b,'a0>
static member Effect.Create: f: ('a -> Result<'b,'e>) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> Task<'b>) -> Effect<'a,'b,'a0>
static member Effect.Create: f: ('a -> ValueTask<'b>) -> Effect<'a,'b,'a0>
static member Effect.Create: f: ('a -> Async<Result<'b,'e>>) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> ValueTask<Result<'b,'e>>) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> Task<Result<'b,'e>>) -> Effect<'a,'b,'e>
val p: #IStockProvider
property IStockProvider.Stock: IStock with get
abstract IStock.Reserve: sku: string * quantity: int -> Task<Result<unit,StockError>>
val charge: customer: string -> amount: decimal -> Effect<#IPaymentsProvider,string,PaymentError>
val customer: string
val amount: decimal
val p: #IPaymentsProvider
property IPaymentsProvider.Payments: IPayments with get
abstract IPayments.Charge: customer: string * amount: decimal -> Task<Result<string,PaymentError>>
val catalog: Set<string>
val set: elements: 'T seq -> Set<'T> (requires comparison)
val checkProduct: sku: string -> Result<string,StockError>
member Set.Contains: value: 'T -> bool
union case Result.Ok: ResultValue: 'T -> Result<'T,'TError>
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
union case StockError.UnknownProduct: sku: string -> StockError
val reserveAll: lines: (string * int) list -> Effect<#IStockProvider,unit,StockError>
val lines: (string * int) list
type 'T list = List<'T>
val eff: EffBuilder
<summary> The computation expression for writing effects. Binding (<c>let!</c>, <c>do!</c>, <c>return!</c>) works with other effects, and with <c>Task</c>, <c>ValueTask</c>, <c>Async</c> and <c>Result</c>, so .NET APIs can be used directly. It supports <c>for</c>, <c>while</c>, <c>use</c>, <c>try/with</c> and <c>try/finally</c>, and <c>and!</c> runs effects concurrently. A failed effect stops the rest of the expression, with its error. </summary>
<example><code lang="fsharp"> let placeOrder (order: Order) = eff { let! id = GuidGenerator.genGuid () let! now = Time.utcNow () do! Orders.save { order with Id = id; Placed = now } let! customer = Customers.load order.CustomerId and! stock = Stock.reserve order.Lines return id } </code></example>
module Stock from Errors
Multiple items
type FakeStock = interface IStock new: unit -> FakeStock

--------------------
new: unit -> FakeStock
val mutable mugs: int
Task.FromResult<'TResult>(result: 'TResult) : Task<'TResult>
union case StockError.OutOfStock: sku: string -> StockError
Multiple items
type FakePayments = interface IPayments new: unit -> FakePayments

--------------------
new: unit -> FakePayments
union case PaymentError.Declined: reason: string -> PaymentError
Multiple items
type Env = interface IPaymentsProvider interface IStockProvider new: unit -> Env

--------------------
new: unit -> Env
val stock: FakeStock
val payments: FakePayments
module Payments from Errors
val show: effect: Effect<Env,'a,'e> -> unit
 Runs an effect with a new environment, and prints its result.
val effect: Effect<Env,'a,'e>
'a
'e
val result: AsyncResult<'a,'e>
val run: env: 'r -> e: Effect<'r,'a,'e> -> AsyncResult<'a,'e>
<summary> Starts the effect. </summary>
<typeparam name="'r"> The environment required to run the effect </typeparam>
<typeparam name="'a"> The resulting type when the effect runs successfully </typeparam>
<typeparam name="'e"> The resulting type when the effect fails</typeparam>
<param name="env">The environment needed to start the effect</param>
<param name="e">The effect to run</param>
<example><code lang="fsharp"> task { match! placeOrder order |&gt; Effect.run env with | Ok id -&gt; printfn $"Placed {id}" | Error err -&gt; printfn $"Failed: {err}" } </code></example>
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
ValueTask.AsTask() : Task<Result<'a,'e>>
type OrderError = | Stock of StockError | Payment of PaymentError
val placeOrder: customer: string -> sku: string -> quantity: int -> price: decimal -> Effect<'a,string,OrderError> (requires 'a :> IPaymentsProvider and 'a :> IStockProvider)
val price: decimal
Multiple items
union case OrderError.Stock: StockError -> OrderError

--------------------
module Stock from Errors
val changeError: f: ('e1 -> 'e2) -> e: Effect<'r,'a,'e1> -> Effect<'r,'a,'e2>
<summary> Transforms an effect which fails with an error of <typeparamref name="'e1" /> to an effect which fails with an error of type <typeparamref name="'e2" />. </summary>
<param name="f">Function which maps the error, in case the effect had failed</param>
<param name="e">The effect</param>
val receipt: string
union case OrderError.Payment: PaymentError -> OrderError
val placeOrderOrExplain: customer: string -> sku: string -> quantity: int -> price: decimal -> Effect<'a,string,OrderError> (requires 'a :> IPaymentsProvider and 'a :> IStockProvider)
val tryRecover: f: ('e -> Result<'a,'e>) -> e: Effect<'r,'a,'e> -> Effect<'r,'a,'e>
<summary> Potentially recovers a failed effect by producing an <see cref="Result{'a, 'e}" /> in case of failure. If the effect was already successful, <paramref name="f" /> is never run. </summary>
<param name="f">The function to run in case of failure</param>
<param name="e">The effect</param>
val reason: string
val other: OrderError
val reserveOrBackorder: sku: string -> quantity: int -> Effect<#IStockProvider,string,StockError>
val asResult: e: Effect<'r,'a,'e> -> Effect<'r,Result<'a,'e>,'e>
<summary> Allows the effect to be bound as a result, rather than failing the surrounding effect when bound. This allows for taking recovery action inside the effect, based on the outcome of a sub-effect. </summary>
<param name="e">The effect</param>
val other: StockError
type Problems = | Problems of string list static member (+) : Problems * Problems -> Problems
Multiple items
union case Problems.Problems: string list -> Problems

--------------------
type Problems = | Problems of string list static member (+) : Problems * Problems -> Problems
val a: string list
val b: string list
val problem: message: string -> Result<'a,Problems>
val message: string
val checkCustomer: customer: string -> Effect<'a,string,Problems>
Multiple items
type String = interface seq<char> interface IEnumerable interface ICloneable interface IComparable interface IComparable<string> interface IConvertible interface IEquatable<string> interface IParsable<string> interface ISpanParsable<string> new: value: nativeptr<char> -> unit + 8 overloads ...
<summary>Represents text as a sequence of UTF-16 code units.</summary>

--------------------
String(value: nativeptr<char>) : String
String(value: char array) : String
String(value: ReadOnlySpan<char>) : String
String(value: nativeptr<sbyte>) : String
String(c: char, count: int) : String
String(value: nativeptr<char>, startIndex: int, length: int) : String
String(value: char array, startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : String
String.IsNullOrWhiteSpace( value: string) : bool
val checkLine: sku: string -> quantity: int -> Effect<'a,(string * int),Problems>
val checkOrder: customer: string -> sku: string -> quantity: int -> Effect<'a,(string * string * int),Problems>
type OrderError = | Invalid of string list | Stock of obj | Payment of obj
Multiple items
val string: value: 'T -> string

--------------------
type string = System.String
val checkAndPlaceOrder: customer: 'a -> sku: 'b -> quantity: 'c -> price: 'd -> 'e
val customer: 'a
val sku: 'b
val quantity: 'c
val price: 'd
union case OrderError.Invalid: string list -> OrderError
type IShipping = abstract Book: sku: string -> Task<string>
type IShippingProvider = abstract Shipping: IShipping
type ShippingError = | ShippingUnavailable of message: string
val bookShipping: sku: string -> Effect<#IShippingProvider,string,ShippingError>
val p: #IShippingProvider
property IShippingProvider.Shipping: IShipping with get
abstract IShipping.Book: sku: string -> Task<string>
namespace System.Net
namespace System.Net.Http
Multiple items
type HttpRequestException = inherit exn new: unit -> unit + 4 overloads member HttpRequestError: HttpRequestError member StatusCode: Nullable<HttpStatusCode>
<summary>A base class for exceptions thrown by the <see cref="T:System.Net.Http.HttpClient" /> and <see cref="T:System.Net.Http.HttpMessageHandler" /> classes.</summary>

--------------------
Net.Http.HttpRequestException() : Net.Http.HttpRequestException
Net.Http.HttpRequestException(message: string) : Net.Http.HttpRequestException
Net.Http.HttpRequestException(message: string, inner: exn) : Net.Http.HttpRequestException
Net.Http.HttpRequestException(message: string, inner: exn, statusCode: Nullable<Net.HttpStatusCode>) : Net.Http.HttpRequestException
Net.Http.HttpRequestException(httpRequestError: Net.Http.HttpRequestError, ?message: string, ?inner: exn, ?statusCode: Nullable<Net.HttpStatusCode>) : Net.Http.HttpRequestException
val e: Net.Http.HttpRequestException
union case ShippingError.ShippingUnavailable: message: string -> ShippingError
property Exception.Message: string with get
Multiple items
type ShippingEnv = interface IShippingProvider new: unit -> ShippingEnv

--------------------
new: unit -> ShippingEnv
Task.FromException<'TResult>(``exception`` : exn) : Task<'TResult>
Task.FromException(``exception`` : exn) : Task
val booking: AsyncResult<string,ShippingError>
ValueTask.AsTask() : Task<Result<string,ShippingError>>

Type something to start searching.