Skip to content

Instantly share code, notes, and snippets.

@ianrussellsoftwarepark
Created August 8, 2020 14:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ianrussellsoftwarepark/2d11367c69d5f14231d439580034742d to your computer and use it in GitHub Desktop.
Save ianrussellsoftwarepark/2d11367c69d5f14231d439580034742d to your computer and use it in GitHub Desktop.
Code for post 12 of Intro to FP in F# series
namespace ComputationExpression
module AsyncResultDemo =
open System
open FsToolkit.ErrorHandling
type AuthError =
| UserBannedOrSuspended
type TokenError =
| BadThingHappened of string
type LoginError =
| InvalidUser
| InvalidPwd
| Unauthorized of AuthError
| TokenErr of TokenError
type AuthToken = AuthToken of Guid
type UserStatus =
| Active
| Suspended
| Banned
type User = {
Name : string
Password : string
Status : UserStatus
}
[<Literal>]
let ValidPassword = "password"
[<Literal>]
let ValidUser = "isvalid"
[<Literal>]
let SuspendedUser = "issuspended"
[<Literal>]
let BannedUser = "isbanned"
[<Literal>]
let BadLuckUser = "hasbadluck"
[<Literal>]
let AuthErrorMessage = "Earth's core stopped spinning"
let tryGetUser (username:string) : Async<User option> =
async {
let user = { Name = username; Password = ValidPassword; Status = Active }
return
match username with
| ValidUser -> Some user
| SuspendedUser -> Some { user with Status = Suspended }
| BannedUser -> Some { user with Status = Banned }
| BadLuckUser -> Some user
| _ -> None
}
let isPwdValid (password:string) (user:User) : bool =
password = user.Password
let authorize (user:User) : Async<Result<unit, AuthError>> =
async {
return
match user.Status with
| Active -> Ok ()
| _ -> UserBannedOrSuspended |> Error
}
let createAuthToken (user:User) : Result<AuthToken, TokenError> =
try
if user.Name = BadLuckUser then failwith AuthErrorMessage
else Guid.NewGuid() |> AuthToken |> Ok
with
| ex -> ex.Message |> BadThingHappened |> Error
let login (username: string) (password: string) : Async<Result<AuthToken, LoginError>> =
asyncResult {
let! user = username |> tryGetUser |> AsyncResult.requireSome InvalidUser
do! user |> isPwdValid password |> Result.requireTrue InvalidPwd
do! user |> authorize |> AsyncResult.mapError Unauthorized
return! user |> createAuthToken |> Result.mapError TokenErr
}
module AsyncResultDemoTests =
open AsyncResultDemo
[<Literal>]
let BadPassword = "notpassword"
[<Literal>]
let NotValidUser = "notvalid"
let isOk (input:Result<_,_>) : bool =
match input with
| Ok _ -> true
| _ -> false
let matchError (error:LoginError) (input:Result<_,LoginError>) =
match input with
| Error ex -> ex = error
| _ -> false
let runWithValidPassword (username:string) =
login username ValidPassword |> Async.RunSynchronously
let success =
let result = runWithValidPassword ValidUser
result |> isOk
let badPassword =
let result = login ValidUser BadPassword |> Async.RunSynchronously
result |> matchError InvalidPwd
let invalidUser =
let result = runWithValidPassword NotValidUser
result |> matchError InvalidUser
let isSuspended =
let result = runWithValidPassword SuspendedUser
result |> matchError (UserBannedOrSuspended |> Unauthorized)
let isBanned =
let result = runWithValidPassword BannedUser
result |> matchError (UserBannedOrSuspended |> Unauthorized)
let hasBadLuck =
let result = runWithValidPassword BadLuckUser
result |> matchError (AuthErrorMessage |> BadThingHappened |> TokenErr)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment