Created
August 8, 2020 14:47
-
-
Save ianrussellsoftwarepark/2d11367c69d5f14231d439580034742d to your computer and use it in GitHub Desktop.
Code for post 12 of Intro to FP in F# series
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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