FsToolkit.ErrorHandling is a utility library to work with the Result
type in F#, and allows you to do clear, simple and powerful error handling.
The library provides utility functions like map
, bind
, apply
, traverse
, sequence
as well as computation expressions and infix operators to work with Result<'a, 'b>
, Result<'a option, 'b>
, Async<Result<'a, 'b>>
, Async<Result<'a option, 'b>>
, and Result<'a, 'b list>
.
It was inspired by Chessie and Cvdm.ErrorHandling (the latter has now been merged into FsToolkit.ErrorHandling).
FsToolkit.ErrorHandling targets .NET Standard 2.0, .NET Standard2.1 and supports Fable.
The documentation is available here.
- The main resource as to learning this style of programming Railway Oriented Programming
- However Result isn't a panacea, see what pitfalls and where you shouldn't use
Result
. In defense of Exceptions: Throw (away) your Result
GitHub Actions |
---|
Package name | Release | Prelease |
---|---|---|
FsToolkit.ErrorHandling | ||
FsToolkit.ErrorHandling.TaskResult | ||
FsToolkit.ErrorHandling.JobResult | ||
FsToolkit.ErrorHandling.AsyncSeq | ||
FsToolkit.ErrorHandling.IcedTasks |
This repository has a devcontainer setup for VSCode. For more infomation see:
To test fable builds locally you will need:
- Node
- v18.0.0 or Higher
- Not required but recommend that you use NVM to easily manage multiple versions of Node
- Python
- v3.10.0 or higher
- Required for Fable-Python
> build.cmd <optional buildtarget> // on windows
$ ./build.sh <optional buildtarget>// on unix
Without specifying a build target, the default target is DotnetPack
, which will run tests for all projects on dotnet and then pack the projects into nuget packages. For additional notable targets see below.
Clean
- Will clean all projectsbin
andobj
foldersDotnetTest
- Will run tests fordotnet
projectsNpmTest
- Will run tests forfable-javascript
projectsPythonTest
- Will run tests forfable-python
projectsRunTests
- Will run tests fordotnet
,fable-javascript
andfable-python
projectsFormatCode
- Will runfantomas
to format the codebase
This is not an exhausting list. Additional targets can be found in the ./build/build.fs
file.
This example of composing a login flow shows one example of how this library can aid in clear, simple, and powerful error handling, using just a computation expression and a few helper functions. (The library has many more helper functions and computation expressions as well as infix operators; see the documentation for details.)
// Given the following functions:
// tryGetUser: string -> Async<User option>
// isPwdValid: string -> User -> bool
// authorize: User -> Async<Result<unit, AuthError>>
// createAuthToken: User -> Result<AuthToken, TokenError>
type LoginError = InvalidUser | InvalidPwd | Unauthorized of AuthError | TokenErr of TokenError
let login (username: string) (password: string) : Async<Result<AuthToken, LoginError>> =
asyncResult {
// requireSome unwraps a Some value or gives the specified error if None
let! user = username |> tryGetUser |> AsyncResult.requireSome InvalidUser
// requireTrue gives the specified error if false
do! user |> isPwdValid password |> Result.requireTrue InvalidPwd
// Error value is wrapped/transformed (Unauthorized has signature AuthError -> LoginError)
do! user |> authorize |> AsyncResult.mapError Unauthorized
// Same as above, but synchronous, so we use the built-in mapError
return! user |> createAuthToken |> Result.mapError TokenErr
}