diff --git a/gitbook/asyncResult/check.md b/gitbook/asyncResult/check.md new file mode 100644 index 00000000..a4193ae5 --- /dev/null +++ b/gitbook/asyncResult/check.md @@ -0,0 +1,76 @@ +# Result.check + +Namespace: `FsToolkit.ErrorHandling` + +The intent of check is to allow an Ok result value to be validated. + +`check` takes a validation function of the form `'ok -> Async>` and a result of the form `Async>`. + +If the async-wrapped result is `Ok x` then the validation function is applied, and if the validation function returns an error, this new async-wrapped error is returned. Otherwise, the original async-wrapped `Ok x` result is returned. If the original async-wrapped result is an Error, the original async-wrapped result is returned. + +## Function Signature +```fsharp +('ok -> Async>) -> Async> -> Async> +``` + +## Examples + +Given the following function that returns true for the id `123` +```fsharp +checkEnabled : int -> Async +``` + +### Example 1 + +```fsharp +AsyncResult.ok ( + {| + PolicyId = 123 + AccessPolicyName = "UserCanAccessResource" + |} + +) +|> AsyncResult.check (fun policy -> + asyncResult { + let! isEnabled = checkEnabled policy.PolicyId + + return + if not isEnabled then + Error( + $"The policy {policy.AccessPolicyName} cannot be used because its disabled." + ) + else + Ok() + + } +) +// AsyncResult.Ok {| AccessPolicyName = "UserCanAccessResource"; IsEnabled = true; |} +``` + +### Example 2 + +```fsharp +AsyncResult.ok ( + {| + PolicyId = 456 + AccessPolicyName = "UserCanAccessResource" + |} + +) +|> AsyncResult.check (fun policy -> + asyncResult { + let! isEnabled = checkEnabled policy.PolicyId + + return + if not isEnabled then + Error( + $"The policy {policy.AccessPolicyName} cannot be used because its disabled." + ) + else + Ok() + + } +) + +// AsyncResult.Error "The policy UserCanAccessResource cannot be used because its disabled." +``` diff --git a/gitbook/asyncResult/others.md b/gitbook/asyncResult/others.md index daaec08a..9e61c9bb 100644 --- a/gitbook/asyncResult/others.md +++ b/gitbook/asyncResult/others.md @@ -84,6 +84,14 @@ Returns the first item of the sequence if it exists, or the specified error if t 'a -> Async<'b> -> Async> ``` +### require + +Returns the provided async-wrapped result if it is Ok and the predicate is true, or if the async-wrapped result is Error. +If the predicate is false, returns a new async-wrapped Error result with the error value. + +```fsharp +('a -> bool) -> 'b -> Async> -> Async> +``` ### setError diff --git a/gitbook/result/check.md b/gitbook/result/check.md new file mode 100644 index 00000000..c88d95df --- /dev/null +++ b/gitbook/result/check.md @@ -0,0 +1,60 @@ +# Result.check + +Namespace: `FsToolkit.ErrorHandling` + +The intent of check is to allow an Ok result value to be validated. + +`check` takes a validation function of the form `'ok -> Result` and a result of the form `Result<'ok, 'error>`. + +If the result is `Ok x` then the validation function is applied, and if the validation function returns an error, this new error is returned. Otherwise, the original `Ok x` result is returned. If the original result is an Error, the original result is returned. + +## Function Signature +```fsharp +('ok -> Result) -> Result<'ok,'error> -> Result<'ok,'error> +``` + +## Examples + +### Example 1 + +```fsharp +Ok ( + {| + AccessPolicyName = "UserCanAccessResource" + IsEnabled = true + |} + +) +|> Result.check (fun policy -> + if not policy.IsEnabled then + Error ( + $"The policy {policy.AccessPolicyName} cannot be used because its disabled." + ) + else + Ok () +) + +// Ok {| AccessPolicyName = "UserCanAccessResource"; IsEnabled = true; |} +``` + +### Example 2 + +```fsharp +Ok ( + {| + AccessPolicyName = "UserCanAccessResource" + IsEnabled = false + |} + +) +|> Result.check (fun policy -> + if not policy.IsEnabled then + Error ( + $"The policy {policy.AccessPolicyName} cannot be used because its disabled." + ) + else + Ok () +) + +// Error "The policy UserCanAccessResource cannot be used because its disabled." +``` diff --git a/gitbook/result/requireFunctions.md b/gitbook/result/requireFunctions.md index 7fd02eaa..31b152dd 100644 --- a/gitbook/result/requireFunctions.md +++ b/gitbook/result/requireFunctions.md @@ -381,3 +381,43 @@ let result : Result = // Error "Seq must have head" ``` + +## require + +### Function Signature + +If the input result is `Ok`, applies a predicate to the `Ok` value. +If the predicate returns true, then returns the original `Ok` Result. +Otherwise, returns a new `Error` result with the provided error. + +```fsharp +('ok -> bool) -> 'error -> Result<'ok,'error> -> Result<'ok,'error> +``` +Note: +If you find that you need the Ok value to produce an appropriate error, use the `check` method instead. + +#### Example 1 + +```fsharp +let result: Result = + Result.Ok "F#" + |> Result.require + (_.Contains("#")) + "Provided input does not contain #" + +// Ok "F#" +``` + +#### Example 2 + +```fsharp +let result: Result = + Result.Ok "Hello World!" + |> Result.require + (_.Contains("#")) + "Provided input does not contain #" + +// Error "Provided input does not contain #" +``` + + diff --git a/gitbook/taskResult/check.md b/gitbook/taskResult/check.md new file mode 100644 index 00000000..222fa2ac --- /dev/null +++ b/gitbook/taskResult/check.md @@ -0,0 +1,76 @@ +# Result.check + +Namespace: `FsToolkit.ErrorHandling` + +The intent of check is to allow an Ok result value to be validated. + +`check` takes a validation function of the form `'ok -> Task>` and a result of the form `Task>`. + +If the task-wrapped result is `Ok x` then the validation function is applied, and if the validation function returns an error, this new task-wrapped error is returned. Otherwise, the original task-wrapped `Ok x` result is returned. If the original task-wrapped result is an Error, the original task-wrapped result is returned. + +## Function Signature +```fsharp +('ok -> Task>) -> Task> -> Task> +``` + +## Examples + +Given the following function that returns true for the id `123` +```fsharp +checkEnabled : int -> Task +``` + +### Example 1 + +```fsharp +TaskResult.ok ( + {| + PolicyId = 123 + AccessPolicyName = "UserCanAccessResource" + |} + +) +|> TaskResult.check (fun policy -> + taskResult { + let! isEnabled = checkEnabled policy.PolicyId + + return + if not isEnabled then + Error( + $"The policy {policy.AccessPolicyName} cannot be used because its disabled." + ) + else + Ok() + + } +) +// TaskResult.Ok {| AccessPolicyName = "UserCanAccessResource"; IsEnabled = true; |} +``` + +### Example 2 + +```fsharp +TaskResult.ok ( + {| + PolicyId = 456 + AccessPolicyName = "UserCanAccessResource" + |} + +) +|> TaskResult.check (fun policy -> + taskResult { + let! isEnabled = checkEnabled policy.PolicyId + + return + if not isEnabled then + Error( + $"The policy {policy.AccessPolicyName} cannot be used because its disabled." + ) + else + Ok() + + } +) + +// TaskResult.Error "The policy UserCanAccessResource cannot be used because its disabled." +``` diff --git a/gitbook/taskResult/others.md b/gitbook/taskResult/others.md index 160ee601..24d07811 100644 --- a/gitbook/taskResult/others.md +++ b/gitbook/taskResult/others.md @@ -83,6 +83,14 @@ Returns the first item of the sequence if it exists, or the specified error if t 'a -> Task<'b> -> Task> ``` +### require + +Returns the provided task-wrapped result if it is Ok and the predicate is true, or if the task-wrapped result is Error. +If the predicate is false, returns a new task-wrapped Error result with the error value. + +```fsharp +('a -> bool) -> 'b -> Task> -> Task> +``` ### setError diff --git a/src/FsToolkit.ErrorHandling/AsyncResult.fs b/src/FsToolkit.ErrorHandling/AsyncResult.fs index 6fb07844..0335121e 100644 --- a/src/FsToolkit.ErrorHandling/AsyncResult.fs +++ b/src/FsToolkit.ErrorHandling/AsyncResult.fs @@ -221,6 +221,13 @@ module AsyncResult = values |> Async.map (Result.requireHead error) + + /// Returns the async-wrapped result if it is Ok and the predicate is true, or if the async wrapped result is Error. + /// If the predicate is false, returns a new async-wrapped Error result with the error value. + let inline require predicate error result = + result + |> Async.map (Result.require predicate error) + /// Replaces an error value of an async-wrapped result with a custom error /// value. let inline setError @@ -434,3 +441,12 @@ module AsyncResult = Result.requireHead error >> Async.singleton ) + + /// Returns the async-wrapped result if it is Ok and the checkFunc returns an async-wrapped Ok result or if the async-wrapped result is Error. + /// If the checkFunc returns an async-wrapped Error result, returns the async-wrapped Error result. + let inline check ([] checkFunc) (result) = + result + |> bind (fun o -> + checkFunc o + |> map (fun _ -> o) + ) diff --git a/src/FsToolkit.ErrorHandling/Result.fs b/src/FsToolkit.ErrorHandling/Result.fs index 8c00d2d5..fc2f6261 100644 --- a/src/FsToolkit.ErrorHandling/Result.fs +++ b/src/FsToolkit.ErrorHandling/Result.fs @@ -424,6 +424,28 @@ module Result = | Some x -> Ok x | None -> Error error + /// + /// If the input result is Ok, applies a predicate to the Ok value. + /// If the predicate returns true, then returns the original Ok Result. + /// Otherwise, returns a new Error result with the provided error. + /// + /// If the input result is Error, just returns the input result. + /// + /// Documentation is found here: https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/result/requirefunctions#require + /// + /// Predicate applied to the Ok value of the result. + /// The error to return if the predicate returns false. + /// The input result. + /// The input if it is Error or if the provided returns true. Otherwise returns a new Error result with the value . + let inline require + ([] predicate: 'ok -> bool) + (error: 'error) + (result: Result<'ok, 'error>) + : Result<'ok, 'error> = + match result with + | Error err -> Error err + | Ok ok -> if predicate ok then Ok ok else Error(error) + /// /// Replaces an error value with a custom error value. /// @@ -656,3 +678,26 @@ module Result = | Error x1res, Error x2res -> Error(x1res, x2res) | Ok e, _ -> Ok e | _, Ok e -> Ok e + + + /// + /// When is Ok, applies to the Ok value. + /// If returns an Ok unit value, returns . + /// Otherwise, returns the Error value from the as a new result. + /// + /// When is Error, returns . + /// + /// Documentation is found here: https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/result/check + /// + /// The function that performs a check against the Ok value of . Returns Error value from the function if error. + /// The input result. + /// The input if Error or returns a Ok result. Returns the Error value from if it returns an Error result. + let inline check + ([] checkFunc: 'ok -> Result) + (result: Result<'ok, 'error>) + : Result<'ok, 'error> = + match result with + | Error err -> Error err + | Ok ok -> + checkFunc ok + |> Result.map (fun _ -> ok) diff --git a/src/FsToolkit.ErrorHandling/TaskResult.fs b/src/FsToolkit.ErrorHandling/TaskResult.fs index 5e52ace3..8abfa50c 100644 --- a/src/FsToolkit.ErrorHandling/TaskResult.fs +++ b/src/FsToolkit.ErrorHandling/TaskResult.fs @@ -150,6 +150,12 @@ module TaskResult = xs |> Task.map (Result.requireHead error) + /// Returns the task-wrapped result if it is Ok and the predicate is true, or if the task-wrapped result is Error. + /// If the predicate is false, returns a new task-wrapped Error result with the error value. + let inline require predicate error result = + result + |> Task.map (Result.require predicate error) + /// Replaces an error value of an task-wrapped result with a custom error /// value. let inline setError error taskResult = @@ -334,3 +340,12 @@ module TaskResult = (input: Task>) : Task<'output> = Task.map (Result.either onSuccess onError) input + + /// Returns the task-wrapped result if it is Ok and the checkFunc returns an task-wrapped Ok result or if the task-wrapped result is Error. + /// If the checkFunc returns an task-wrapped Error result, returns the task-wrapped Error result. + let inline check ([] checkFunc) (result) = + result + |> bind (fun o -> + checkFunc o + |> map (fun _ -> o) + ) diff --git a/tests/FsToolkit.ErrorHandling.TaskResult.Tests/TaskResult.fs b/tests/FsToolkit.ErrorHandling.TaskResult.Tests/TaskResult.fs index a6f0c6ae..ae36e625 100644 --- a/tests/FsToolkit.ErrorHandling.TaskResult.Tests/TaskResult.fs +++ b/tests/FsToolkit.ErrorHandling.TaskResult.Tests/TaskResult.fs @@ -415,6 +415,42 @@ let requireHeadTests = |> Expect.hasTaskErrorValueSync err ] +[] +let taskResultRequireTests = + testList "TaskResult.require Tests" [ + testCaseTask "True, Ok" + <| fun _ -> + task { + do! + TaskResult.require (fun _ -> true) ("Error!") (TaskResult.ok (1)) + |> Expect.hasTaskOkValue (1) + } + + testCaseTask "True, Error" + <| fun _ -> + task { + do! + TaskResult.require (fun _ -> true) ("Error!") (TaskResult.error ("AHH")) + |> Expect.hasTaskErrorValue ("AHH") + } + + testCaseTask "False, Ok" + <| fun _ -> + task { + do! + TaskResult.require (fun _ -> false) ("Error!") (TaskResult.ok (1)) + |> Expect.hasTaskErrorValue ("Error!") + } + + testCaseTask "False, Error" + <| fun _ -> + task { + do! + TaskResult.require (fun _ -> false) ("Error!") (TaskResult.error ("Ahh")) + |> Expect.hasTaskErrorValue ("Ahh") + } + ] + [] let setErrorTests = testList "TaskResult.setError Tests" [ @@ -986,3 +1022,40 @@ let taskResultBindRequireHeadTests = |> Expect.hasTaskOkValue 1 } ] + + +[] +let taskResultCheckTests = + testList "TaskResult.check Tests" [ + testCaseTask "Ok, Ok" + <| fun _ -> + task { + do! + TaskResult.check (fun number -> TaskResult.ok ()) (TaskResult.ok (1)) + |> Expect.hasTaskOkValue (1) + } + + testCaseTask "Ok, Error" + <| fun _ -> + task { + do! + TaskResult.check (fun number -> TaskResult.ok ()) (TaskResult.error (2)) + |> Expect.hasTaskErrorValue (2) + } + + testCaseTask "Error, OK" + <| fun _ -> + task { + do! + TaskResult.check (fun number -> TaskResult.error ()) (TaskResult.ok (2)) + |> Expect.hasTaskErrorValue (()) + } + + testCaseTask "Error, Error" + <| fun _ -> + task { + do! + TaskResult.check (fun number -> TaskResult.error (1)) (TaskResult.error (2)) + |> Expect.hasTaskErrorValue (2) + } + ] diff --git a/tests/FsToolkit.ErrorHandling.Tests/AsyncResult.fs b/tests/FsToolkit.ErrorHandling.Tests/AsyncResult.fs index 2ccfe609..be85a5e2 100644 --- a/tests/FsToolkit.ErrorHandling.Tests/AsyncResult.fs +++ b/tests/FsToolkit.ErrorHandling.Tests/AsyncResult.fs @@ -408,6 +408,36 @@ let requireHeadTests = |> Expect.hasAsyncErrorValue err) ] +let requireTests = + testList "AsyncResult.require Tests" [ + testCaseAsync "True, Ok" + <| async { + do! + AsyncResult.require (fun _ -> true) ("Error!") (AsyncResult.ok (1)) + |> Expect.hasAsyncOkValue (1) + } + + testCaseAsync "True, Error" + <| async { + do! + AsyncResult.require (fun _ -> true) ("Error!") (AsyncResult.error ("AHH")) + |> Expect.hasAsyncErrorValue ("AHH") + } + + testCaseAsync "False, Ok" + <| async { + do! + AsyncResult.require (fun _ -> false) ("Error!") (AsyncResult.ok (1)) + |> Expect.hasAsyncErrorValue ("Error!") + } + + testCaseAsync "False, Error" + <| async { + do! + AsyncResult.require (fun _ -> false) ("Error!") (AsyncResult.error ("Ahh")) + |> Expect.hasAsyncErrorValue ("Ahh") + } + ] let setErrorTests = testList "AsyncResult.setError Tests" [ @@ -932,6 +962,37 @@ let asyncResultBindRequireHeadTests = } ] +let checkTests = + testList "AsyncResult.check Tests" [ + testCaseAsync "Ok, Ok" + <| async { + do! + AsyncResult.check (fun number -> AsyncResult.ok ()) (AsyncResult.ok (1)) + |> Expect.hasAsyncOkValue (1) + } + + testCaseAsync "Ok, Error" + <| async { + do! + AsyncResult.check (fun number -> AsyncResult.ok ()) (AsyncResult.error (2)) + |> Expect.hasAsyncErrorValue (2) + } + + testCaseAsync "Error, OK" + <| async { + do! + AsyncResult.check (fun number -> AsyncResult.error ()) (AsyncResult.ok (2)) + |> Expect.hasAsyncErrorValue (()) + } + + testCaseAsync "Error, Error" + <| async { + do! + AsyncResult.check (fun number -> AsyncResult.error (1)) (AsyncResult.error (2)) + |> Expect.hasAsyncErrorValue (2) + } + ] + let allTests = testList "Async Result tests" [ mapTests @@ -953,6 +1014,7 @@ let allTests = requireEmptyTests requireNotEmptyTests requireHeadTests + requireTests setErrorTests withErrorTests defaultValueTests @@ -976,4 +1038,5 @@ let allTests = asyncResultBindRequireEmptyTests asyncResultBindRequireNotEmptyTests asyncResultBindRequireHeadTests + checkTests ] diff --git a/tests/FsToolkit.ErrorHandling.Tests/Result.fs b/tests/FsToolkit.ErrorHandling.Tests/Result.fs index b8836a0b..acec025c 100644 --- a/tests/FsToolkit.ErrorHandling.Tests/Result.fs +++ b/tests/FsToolkit.ErrorHandling.Tests/Result.fs @@ -453,6 +453,44 @@ let requireHeadTests = |> Expect.hasErrorValue err ] +let requireTests = + testList "require tests" [ + testCase "False, Error" + <| fun () -> + let output = + Result.require (fun _ -> false) ("Error") (Error("Something went wrong")) + + Expect.equal output (Error("Something went wrong")) "Should be Error" + + testCase "True, Ok" + <| fun () -> + let output = Result.require (fun _ -> true) ("Error") (Ok 1) + Expect.equal output (Ok(1)) "Should be Ok" + + testCase "False, Ok" + <| fun () -> + let output = Result.require (fun _ -> false) ("Error") (Ok 1) + Expect.equal output (Error("Error")) "Should be Error" + + testCase "True, Ok using Ok value in predicate" + <| fun () -> + let output = Result.require (fun number -> number = 1) ("Error") (Ok 1) + Expect.equal output (Ok(1)) "Should be Ok" + + testCase "False, Ok using Ok value in predicate" + <| fun () -> + let output = + Result.require + (fun number -> + number + <> 1 + ) + ("Error") + (Ok 1) + + Expect.equal output (Error("Error")) "Should be Error" + ] + let setErrorTests = testList "setError Tests" [ @@ -822,6 +860,41 @@ let zipErrorTests = Expect.equal actual (Error("Bad1", "Bad2")) "Should be Error" ] +let checkTests = + testList "check tests" [ + testCase "Ok, Error" + <| (fun () -> + let output = Result.check (fun _ -> Ok()) (Error(1)) + Expect.equal output (Error(1)) "Should be error" + ) + + testCase "OK, Ok" + <| (fun () -> + let output = Result.check (fun _ -> Ok()) (Ok(1)) + Expect.equal output (Ok(1)) "Should be Ok" + ) + + testCase "Error, Error" + <| (fun () -> + let output = Result.check (fun _ -> Error(2)) (Error(1)) + Expect.equal output (Error(1)) "Should be Error" + ) + + testCase "Error, Ok" + <| (fun () -> + let output = Result.check (fun _ -> Error(2)) (Ok(1)) + Expect.equal output (Error(2)) "Should be Error" + ) + + testCase "Using the result value in the predicate" + <| (fun () -> + let output = + Result.check (fun number -> if number = 1 then Error(2) else Ok()) (Ok(1)) + + Expect.equal output (Error(2)) "Should be Error" + ) + ] + let allTests = testList "Result Tests" [ resultIsOk @@ -850,6 +923,7 @@ let allTests = requireEmptyTests requireNotEmptyTests requireHeadTests + requireTests setErrorTests withErrorTests defaultValueTests @@ -865,4 +939,5 @@ let allTests = valueOrTests zipTests zipErrorTests + checkTests ]