Skip to content

Commit

Permalink
Refactor AsyncValidationCE module for bugfix (#260)
Browse files Browse the repository at this point in the history
* Refactor AsyncValidationCE module and add new test cases

* Added missing Task bindings. Fixed source overloads. Removed unnecessary Result qualifiers

* Fixed tests per review
  • Loading branch information
1eyewonder committed May 2, 2024
1 parent ac5b82c commit 8f2f654
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 84 deletions.
159 changes: 95 additions & 64 deletions src/FsToolkit.ErrorHandling/AsyncValidationCE.fs
Original file line number Diff line number Diff line change
Expand Up @@ -124,69 +124,100 @@ module AsyncValidationCE =
) : AsyncValidation<'left * 'right, 'error> =
AsyncValidation.zip left right

/// <summary>
/// Method lets us transform data types into our internal representation. This is the identity method to recognize the self type.
///
/// See https://stackoverflow.com/questions/35286541/why-would-you-use-builder-source-in-a-custom-computation-expression-builder
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
member inline _.Source
(result: AsyncValidation<'ok, 'error>)
: AsyncValidation<'ok, 'error> =
result

let asyncValidation = AsyncValidationBuilder()

[<AutoOpen>]
module HighPriority =

// Having members as extensions gives them lower priority in
// overload resolution and allows skipping more type annotations.
type AsyncValidationBuilder with

/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
member inline _.Source(s: Async<Result<'ok, 'error>>) : AsyncValidation<_, 'error> =
s
|> AsyncResult.mapError (fun e -> [ e ])

/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
member inline _.Source(s: Result<'ok, 'error>) : AsyncValidation<'ok, 'error> =
AsyncValidation.ofResult s

/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
/// <returns></returns>
member inline _.Source(a: Async<'ok>) : AsyncValidation<'ok, 'error> =
async {
let! result = a
return! AsyncValidation.ok result
}

/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
/// <returns></returns>
member inline _.Source(choice: Choice<'ok, 'error>) : AsyncValidation<'ok, 'error> =
AsyncValidation.ofChoice choice

/// <summary>
/// Needed to allow `for..in` and `for..do` functionality
/// </summary>
member inline _.Source(s: #seq<_>) : #seq<_> = s

[<AutoOpen>]
module LowPriority =

type AsyncValidationBuilder with

/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
member inline _.Source(s: Validation<'ok, 'error>) : AsyncValidation<'ok, 'error> =
Async.retn s
[<AutoOpen>]
module LowPriority =

type AsyncValidationBuilder with

/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
/// <returns></returns>
member inline _.Source(a: Async<'ok>) : AsyncValidation<'ok, 'error> =
async {
let! result = a
return! AsyncValidation.ok result
}

/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
member inline _.Source(s: Result<'ok, 'error>) : AsyncValidation<'ok, 'error> =
AsyncValidation.ofResult s

/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
/// <returns></returns>
member inline _.Source(choice: Choice<'ok, 'error>) : AsyncValidation<'ok, 'error> =
AsyncValidation.ofChoice choice

/// <summary>
/// Needed to allow `for..in` and `for..do` functionality
/// </summary>
member inline _.Source(s: #seq<_>) : #seq<_> = s

[<AutoOpen>]
module MediumPriority =

open System.Threading.Tasks

type AsyncValidationBuilder with

/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
member inline _.Source(s: Async<Result<'ok, 'error>>) : AsyncValidation<'ok, 'error> =
AsyncResult.mapError List.singleton s

#if !FABLE_COMPILER

/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
member inline _.Source(s: Task<Result<'ok, 'error>>) : AsyncValidation<'ok, 'error> =
Async.AwaitTask s
|> AsyncResult.mapError List.singleton

#endif

[<AutoOpen>]
module HighPriority =

open System.Threading.Tasks

// Having members as extensions gives them lower priority in
// overload resolution and allows skipping more type annotations.
type AsyncValidationBuilder with

/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
member inline _.Source(s: Validation<'ok, 'error>) : AsyncValidation<'ok, 'error> =
Async.retn s

#if !FABLE_COMPILER

/// <summary>
/// Method lets us transform data types into our internal representation.
/// </summary>
member inline _.Source
(result: Task<Validation<'ok, 'error>>)
: AsyncValidation<'ok, 'error> =
Async.AwaitTask result

#endif

/// <summary>
/// Method lets us transform data types into our internal representation. This is the identity method to recognize the self type.
///
/// See https://stackoverflow.com/questions/35286541/why-would-you-use-builder-source-in-a-custom-computation-expression-builder
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
member inline _.Source
(result: AsyncValidation<'ok, 'error>)
: AsyncValidation<'ok, 'error> =
result
83 changes: 63 additions & 20 deletions tests/FsToolkit.ErrorHandling.Tests/AsyncValidationCE.fs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ let ``AsyncValidationCE return Tests`` =
<| async {
let data = "Foo"
let! actual = asyncValidation { return data }
Expect.equal actual (Result.Ok data) "Should be ok"
Expect.equal actual (Ok data) "Should be ok"
}
]

let ``AsyncValidationCE return! Tests`` =
testList "AsyncValidationCE return! Tests" [
testCaseAsync "Return Ok result"
<| async {
let data = Result.Ok "Foo"
let data = Ok "Foo"
let! actual = asyncValidation { return! data }
Expect.equal actual (data) "Should be ok"
}
Expand All @@ -48,7 +48,7 @@ let ``AsyncValidationCE return! Tests`` =
let innerData = "Foo"
let data = Choice1Of2 innerData
let! actual = asyncValidation { return! data }
Expect.equal actual (Result.Ok innerData) "Should be ok"
Expect.equal actual (Ok innerData) "Should be ok"
}
testCaseAsync "Return Error Choice"
<| async {
Expand All @@ -63,7 +63,7 @@ let ``AsyncValidationCE return! Tests`` =
let innerData = "Foo"
let data = Validation.ok innerData
let! actual = asyncValidation { return! data }
Expect.equal actual (Result.Ok innerData) "Should be ok"
Expect.equal actual (Ok innerData) "Should be ok"
}
testCaseAsync "Return Error Validation"
<| async {
Expand Down Expand Up @@ -92,7 +92,7 @@ let ``AsyncValidationCE bind Tests`` =
}
testCaseAsync "let! Ok result"
<| async {
let data = Result.Ok "Foo"
let data = Ok "Foo"

let! actual =
asyncValidation {
Expand Down Expand Up @@ -127,7 +127,7 @@ let ``AsyncValidationCE bind Tests`` =
return f
}

Expect.equal actual (Result.Ok innerData) "Should be ok"
Expect.equal actual (Ok innerData) "Should be ok"
}
testCaseAsync "let! Error Choice"
<| async {
Expand All @@ -153,24 +153,26 @@ let ``AsyncValidationCE bind Tests`` =
return f
}

Expect.equal actual (Result.Ok innerData) "Should be ok"
Expect.equal actual (Ok innerData) "Should be ok"
}
testCaseAsync "let! Error Validation"
<| async {
let innerData = "Foo"
let error = Error innerData
let expected = Error [ innerData ]

let! actual =
asyncValidation {
let! f = validation { return! expected }
let! f = validation { return! error }
and! _ = validation { return! Ok innerData }
return f
}

Expect.equal actual expected "Should be ok"
}
testCaseAsync "do! Ok result"
<| async {
let data = Result.Ok()
let data = Ok()
let! actual = asyncValidation { do! data }
Expect.equal actual (data) "Should be ok"
}
Expand Down Expand Up @@ -232,7 +234,7 @@ let ``AsyncValidationCE combine/zero/delay/run Tests`` =
return result
}

Expect.equal actual (Result.Ok data) "Should be ok"
Expect.equal actual (Ok data) "Should be ok"
}
]

Expand All @@ -255,7 +257,7 @@ let ``AsyncValidationCE try Tests`` =
return data
}

Expect.equal actual (Result.Ok data) "Should be ok"
Expect.equal actual (Ok data) "Should be ok"
}
testCaseAsync "Try Finally"
<| async {
Expand All @@ -273,7 +275,7 @@ let ``AsyncValidationCE try Tests`` =
return data
}

Expect.equal actual (Result.Ok data) "Should be ok"
Expect.equal actual (Ok data) "Should be ok"
}
]

Expand All @@ -294,7 +296,7 @@ let ``AsyncValidationCE using Tests`` =
return data
}

Expect.equal actual (Result.Ok data) "Should be ok"
Expect.equal actual (Ok data) "Should be ok"
}
testCaseAsync "use! normal wrapped disposable"
<| async {
Expand All @@ -304,12 +306,12 @@ let ``AsyncValidationCE using Tests`` =
asyncValidation {
use! d =
makeDisposable ()
|> Result.Ok
|> Ok

return data
}

Expect.equal actual (Result.Ok data) "Should be ok"
Expect.equal actual (Ok data) "Should be ok"
}
testCaseAsync "use null disposable"
<| async {
Expand All @@ -321,7 +323,7 @@ let ``AsyncValidationCE using Tests`` =
return data
}

Expect.equal actual (Result.Ok data) "Should be ok"
Expect.equal actual (Ok data) "Should be ok"
}
]

Expand Down Expand Up @@ -403,7 +405,7 @@ let ``AsyncValidationCE loop Tests`` =
return data
}

Expect.equal actual (Result.Ok data) "Should be ok"
Expect.equal actual (Ok data) "Should be ok"
}
testCaseAsync "for to"
<| async {
Expand All @@ -417,7 +419,7 @@ let ``AsyncValidationCE loop Tests`` =
return data
}

Expect.equal actual (Result.Ok data) "Should be ok"
Expect.equal actual (Ok data) "Should be ok"
}
]

Expand Down Expand Up @@ -448,7 +450,7 @@ let ``AsyncValidationCE applicative tests`` =
Expect.equal actual (Ok 4) "Should be ok"
}

testCaseAsync "Happy Path Result/Valiation"
testCaseAsync "Happy Path Result/Validation"
<| async {
let! actual =
asyncValidation {
Expand All @@ -474,19 +476,60 @@ let ``AsyncValidationCE applicative tests`` =
Expect.equal actual (Ok 4) "Should be ok"
}

testCaseAsync "Happy Path Result/Choice/Validation"
testCaseAsync "Happy Path Result/Choice/Task/Validation"
<| async {
let! actual =
asyncValidation {
let! a = Ok 3
and! b = Choice1Of2 2
and! c = AsyncValidation.ok 1

return a + b - c
}

Expect.equal actual (Ok 4) "Should be ok"
}

testCaseAsync "Sad Path Async Result/Async Result"
<| async {
let expected =
Error [
"Hello"
"World"
]

let! actual =
asyncValidation {
let! _ = async { return Error "Hello" }
and! _ = async { return Error "World" }
return ()
}

Expect.equal actual expected "Should be error"
}

#if !FABLE_COMPILER

testCaseAsync "Sad Path Task Result/Task Result"
<| async {
let expected =
Error [
"Hello"
"World"
]

let! actual =
asyncValidation {
let! _ = task { return Error "Hello" }
and! _ = task { return Error "World" }
return ()
}

Expect.equal actual expected "Should be error"
}

#endif

testCaseAsync "Fail Path Result"
<| async {
let expected =
Expand Down

0 comments on commit 8f2f654

Please sign in to comment.