Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Require and Check helper methods #295

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions gitbook/asyncResult/check.md
Original file line number Diff line number Diff line change
@@ -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<Result<unit, 'error>>` and a result of the form `Async<Result<'ok, 'error>>`.

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<Result<unit,'error>>) -> Async<Result<'ok,'error>> -> Async<Result<'ok,'error>>
```

## Examples

Given the following function that returns true for the id `123`
```fsharp
checkEnabled : int -> Async<bool>
```

### 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."
```
8 changes: 8 additions & 0 deletions gitbook/asyncResult/others.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ Returns the first item of the sequence if it exists, or the specified error if t
'a -> Async<'b> -> Async<Result<'c, 'a>>
```

### 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<Result<'a,'b>> -> Async<Result<'a,'b>>
```

### setError

Expand Down
60 changes: 60 additions & 0 deletions gitbook/result/check.md
Original file line number Diff line number Diff line change
@@ -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<unit, 'error>` 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<unit,'error>) -> 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."
```
40 changes: 40 additions & 0 deletions gitbook/result/requireFunctions.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,43 @@ let result : Result<int, string> =

// 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<string, string> =
Result.Ok "F#"
|> Result.require
(_.Contains("#"))
"Provided input does not contain #"

// Ok "F#"
```

#### Example 2

```fsharp
let result: Result<string, string> =
Result.Ok "Hello World!"
|> Result.require
(_.Contains("#"))
"Provided input does not contain #"

// Error "Provided input does not contain #"
```


76 changes: 76 additions & 0 deletions gitbook/taskResult/check.md
Original file line number Diff line number Diff line change
@@ -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<Result<unit, 'error>>` and a result of the form `Task<Result<'ok, 'error>>`.

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<Result<unit,'error>>) -> Task<Result<'ok,'error>> -> Task<Result<'ok,'error>>
```

## Examples

Given the following function that returns true for the id `123`
```fsharp
checkEnabled : int -> Task<bool>
```

### 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."
```
8 changes: 8 additions & 0 deletions gitbook/taskResult/others.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ Returns the first item of the sequence if it exists, or the specified error if t
'a -> Task<'b> -> Task<Result<'c, 'a>>
```

### 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<Result<'a,'b>> -> Task<Result<'a,'b>>
```

### setError

Expand Down
16 changes: 16 additions & 0 deletions src/FsToolkit.ErrorHandling/AsyncResult.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ([<InlineIfLambda>] checkFunc) (result) =
result
|> bind (fun o ->
checkFunc o
|> map (fun _ -> o)
)
45 changes: 45 additions & 0 deletions src/FsToolkit.ErrorHandling/Result.fs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,28 @@ module Result =
| Some x -> Ok x
| None -> Error error

/// <summary>
/// If the input result is <c>Ok</c>, applies a predicate to the <c>Ok</c> value.
/// If the predicate returns true, then returns the original <c>Ok</c> Result.
/// Otherwise, returns a new <c>Error</c> result with the provided error.
///
/// If the input result is <c>Error</c>, just returns the input result.
///
/// Documentation is found here: <href>https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/result/requirefunctions#require</href>
/// </summary>
/// <param name="predicate">Predicate applied to the <c>Ok</c> value of the result. </param>
/// <param name="error">The error to return if the predicate returns false.</param>
/// <param name="result">The input result.</param>
/// <returns> The input <paramref name="result"/> if it is <c>Error</c> or if the provided <paramref name="predicate"/> returns true. Otherwise returns a new <c>Error</c> result with the value <paramref name="error"/>.</returns>
let inline require
([<InlineIfLambda>] 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)

/// <summary>
/// Replaces an error value with a custom error value.
///
Expand Down Expand Up @@ -656,3 +678,26 @@ module Result =
| Error x1res, Error x2res -> Error(x1res, x2res)
| Ok e, _ -> Ok e
| _, Ok e -> Ok e


/// <summary>
/// When <paramref name="result"/> is <c>Ok</c>, applies <paramref name="checkFunc"/> to the <c>Ok</c> value.
/// If <paramref name="checkFunc"/> returns an <c>Ok</c> unit value, returns <paramref name="result"/>.
/// Otherwise, returns the <c>Error</c> value from the <paramref name="checkFunc"/> as a new result.
///
/// When <paramref name="result"/> is <c>Error</c>, returns <paramref name="result"/>.
///
/// Documentation is found here: <href>https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/result/check</href>
/// </summary>
/// <param name="checkFunc">The function that performs a check against the <c>Ok</c> value of <paramref name="result"/>. Returns <c>Error</c> value from the function if error.</param>
/// <param name="result">The input result.</param>
/// <returns>The input <paramref name="result"/> if <c>Error</c> or <paramref name="checkFunc"/> returns a <c>Ok</c> result. Returns the <c>Error</c> value from <paramref name="checkFunc"/> if it returns an <c>Error</c> result.</returns>
let inline check
([<InlineIfLambda>] checkFunc: 'ok -> Result<unit, 'error>)
(result: Result<'ok, 'error>)
: Result<'ok, 'error> =
match result with
| Error err -> Error err
| Ok ok ->
checkFunc ok
|> Result.map (fun _ -> ok)
15 changes: 15 additions & 0 deletions src/FsToolkit.ErrorHandling/TaskResult.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -334,3 +340,12 @@ module TaskResult =
(input: Task<Result<'input, 'inputError>>)
: 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 ([<InlineIfLambda>] checkFunc) (result) =
result
|> bind (fun o ->
checkFunc o
|> map (fun _ -> o)
)
Loading
Loading