Skip to content

Commit aabde8d

Browse files
PI-GorboSamLaptop
andauthored
feat: Add Require and Check helper methods (#295)
* wip changes * Added check for async result * Update check.md * Update check.md * fix: Removed extra test --------- Co-authored-by: SamLaptop <[email protected]>
1 parent c48b7a1 commit aabde8d

File tree

12 files changed

+555
-0
lines changed

12 files changed

+555
-0
lines changed

gitbook/asyncResult/check.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Result.check
2+
3+
Namespace: `FsToolkit.ErrorHandling`
4+
5+
The intent of check is to allow an Ok result value to be validated.
6+
7+
`check` takes a validation function of the form `'ok -> Async<Result<unit, 'error>>` and a result of the form `Async<Result<'ok, 'error>>`.
8+
9+
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.
10+
11+
## Function Signature
12+
```fsharp
13+
('ok -> Async<Result<unit,'error>>) -> Async<Result<'ok,'error>> -> Async<Result<'ok,'error>>
14+
```
15+
16+
## Examples
17+
18+
Given the following function that returns true for the id `123`
19+
```fsharp
20+
checkEnabled : int -> Async<bool>
21+
```
22+
23+
### Example 1
24+
25+
```fsharp
26+
AsyncResult.ok (
27+
{|
28+
PolicyId = 123
29+
AccessPolicyName = "UserCanAccessResource"
30+
|}
31+
32+
)
33+
|> AsyncResult.check (fun policy ->
34+
asyncResult {
35+
let! isEnabled = checkEnabled policy.PolicyId
36+
37+
return
38+
if not isEnabled then
39+
Error(
40+
$"The policy {policy.AccessPolicyName} cannot be used because its disabled."
41+
)
42+
else
43+
Ok()
44+
45+
}
46+
)
47+
// AsyncResult.Ok {| AccessPolicyName = "UserCanAccessResource"; IsEnabled = true; |}
48+
```
49+
50+
### Example 2
51+
52+
```fsharp
53+
AsyncResult.ok (
54+
{|
55+
PolicyId = 456
56+
AccessPolicyName = "UserCanAccessResource"
57+
|}
58+
59+
)
60+
|> AsyncResult.check (fun policy ->
61+
asyncResult {
62+
let! isEnabled = checkEnabled policy.PolicyId
63+
64+
return
65+
if not isEnabled then
66+
Error(
67+
$"The policy {policy.AccessPolicyName} cannot be used because its disabled."
68+
)
69+
else
70+
Ok()
71+
72+
}
73+
)
74+
75+
// AsyncResult.Error "The policy UserCanAccessResource cannot be used because its disabled."
76+
```

gitbook/asyncResult/others.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ Returns the first item of the sequence if it exists, or the specified error if t
8484
'a -> Async<'b> -> Async<Result<'c, 'a>>
8585
```
8686

87+
### require
88+
89+
Returns the provided async-wrapped result if it is Ok and the predicate is true, or if the async-wrapped result is Error.
90+
If the predicate is false, returns a new async-wrapped Error result with the error value.
91+
92+
```fsharp
93+
('a -> bool) -> 'b -> Async<Result<'a,'b>> -> Async<Result<'a,'b>>
94+
```
8795

8896
### setError
8997

gitbook/result/check.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Result.check
2+
3+
Namespace: `FsToolkit.ErrorHandling`
4+
5+
The intent of check is to allow an Ok result value to be validated.
6+
7+
`check` takes a validation function of the form `'ok -> Result<unit, 'error>` and a result of the form `Result<'ok, 'error>`.
8+
9+
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.
10+
11+
## Function Signature
12+
```fsharp
13+
('ok -> Result<unit,'error>) -> Result<'ok,'error> -> Result<'ok,'error>
14+
```
15+
16+
## Examples
17+
18+
### Example 1
19+
20+
```fsharp
21+
Ok (
22+
{|
23+
AccessPolicyName = "UserCanAccessResource"
24+
IsEnabled = true
25+
|}
26+
27+
)
28+
|> Result.check (fun policy ->
29+
if not policy.IsEnabled then
30+
Error (
31+
$"The policy {policy.AccessPolicyName} cannot be used because its disabled."
32+
)
33+
else
34+
Ok ()
35+
)
36+
37+
// Ok {| AccessPolicyName = "UserCanAccessResource"; IsEnabled = true; |}
38+
```
39+
40+
### Example 2
41+
42+
```fsharp
43+
Ok (
44+
{|
45+
AccessPolicyName = "UserCanAccessResource"
46+
IsEnabled = false
47+
|}
48+
49+
)
50+
|> Result.check (fun policy ->
51+
if not policy.IsEnabled then
52+
Error (
53+
$"The policy {policy.AccessPolicyName} cannot be used because its disabled."
54+
)
55+
else
56+
Ok ()
57+
)
58+
59+
// Error "The policy UserCanAccessResource cannot be used because its disabled."
60+
```

gitbook/result/requireFunctions.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,43 @@ let result : Result<int, string> =
381381
382382
// Error "Seq must have head"
383383
```
384+
385+
## require
386+
387+
### Function Signature
388+
389+
If the input result is `Ok`, applies a predicate to the `Ok` value.
390+
If the predicate returns true, then returns the original `Ok` Result.
391+
Otherwise, returns a new `Error` result with the provided error.
392+
393+
```fsharp
394+
('ok -> bool) -> 'error -> Result<'ok,'error> -> Result<'ok,'error>
395+
```
396+
Note:
397+
If you find that you need the Ok value to produce an appropriate error, use the `check` method instead.
398+
399+
#### Example 1
400+
401+
```fsharp
402+
let result: Result<string, string> =
403+
Result.Ok "F#"
404+
|> Result.require
405+
(_.Contains("#"))
406+
"Provided input does not contain #"
407+
408+
// Ok "F#"
409+
```
410+
411+
#### Example 2
412+
413+
```fsharp
414+
let result: Result<string, string> =
415+
Result.Ok "Hello World!"
416+
|> Result.require
417+
(_.Contains("#"))
418+
"Provided input does not contain #"
419+
420+
// Error "Provided input does not contain #"
421+
```
422+
423+

gitbook/taskResult/check.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Result.check
2+
3+
Namespace: `FsToolkit.ErrorHandling`
4+
5+
The intent of check is to allow an Ok result value to be validated.
6+
7+
`check` takes a validation function of the form `'ok -> Task<Result<unit, 'error>>` and a result of the form `Task<Result<'ok, 'error>>`.
8+
9+
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.
10+
11+
## Function Signature
12+
```fsharp
13+
('ok -> Task<Result<unit,'error>>) -> Task<Result<'ok,'error>> -> Task<Result<'ok,'error>>
14+
```
15+
16+
## Examples
17+
18+
Given the following function that returns true for the id `123`
19+
```fsharp
20+
checkEnabled : int -> Task<bool>
21+
```
22+
23+
### Example 1
24+
25+
```fsharp
26+
TaskResult.ok (
27+
{|
28+
PolicyId = 123
29+
AccessPolicyName = "UserCanAccessResource"
30+
|}
31+
32+
)
33+
|> TaskResult.check (fun policy ->
34+
taskResult {
35+
let! isEnabled = checkEnabled policy.PolicyId
36+
37+
return
38+
if not isEnabled then
39+
Error(
40+
$"The policy {policy.AccessPolicyName} cannot be used because its disabled."
41+
)
42+
else
43+
Ok()
44+
45+
}
46+
)
47+
// TaskResult.Ok {| AccessPolicyName = "UserCanAccessResource"; IsEnabled = true; |}
48+
```
49+
50+
### Example 2
51+
52+
```fsharp
53+
TaskResult.ok (
54+
{|
55+
PolicyId = 456
56+
AccessPolicyName = "UserCanAccessResource"
57+
|}
58+
59+
)
60+
|> TaskResult.check (fun policy ->
61+
taskResult {
62+
let! isEnabled = checkEnabled policy.PolicyId
63+
64+
return
65+
if not isEnabled then
66+
Error(
67+
$"The policy {policy.AccessPolicyName} cannot be used because its disabled."
68+
)
69+
else
70+
Ok()
71+
72+
}
73+
)
74+
75+
// TaskResult.Error "The policy UserCanAccessResource cannot be used because its disabled."
76+
```

gitbook/taskResult/others.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ Returns the first item of the sequence if it exists, or the specified error if t
8383
'a -> Task<'b> -> Task<Result<'c, 'a>>
8484
```
8585

86+
### require
87+
88+
Returns the provided task-wrapped result if it is Ok and the predicate is true, or if the task-wrapped result is Error.
89+
If the predicate is false, returns a new task-wrapped Error result with the error value.
90+
91+
```fsharp
92+
('a -> bool) -> 'b -> Task<Result<'a,'b>> -> Task<Result<'a,'b>>
93+
```
8694

8795
### setError
8896

src/FsToolkit.ErrorHandling/AsyncResult.fs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,13 @@ module AsyncResult =
221221
values
222222
|> Async.map (Result.requireHead error)
223223

224+
225+
/// Returns the async-wrapped result if it is Ok and the predicate is true, or if the async wrapped result is Error.
226+
/// If the predicate is false, returns a new async-wrapped Error result with the error value.
227+
let inline require predicate error result =
228+
result
229+
|> Async.map (Result.require predicate error)
230+
224231
/// Replaces an error value of an async-wrapped result with a custom error
225232
/// value.
226233
let inline setError
@@ -434,3 +441,12 @@ module AsyncResult =
434441
Result.requireHead error
435442
>> Async.singleton
436443
)
444+
445+
/// 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.
446+
/// If the checkFunc returns an async-wrapped Error result, returns the async-wrapped Error result.
447+
let inline check ([<InlineIfLambda>] checkFunc) (result) =
448+
result
449+
|> bind (fun o ->
450+
checkFunc o
451+
|> map (fun _ -> o)
452+
)

src/FsToolkit.ErrorHandling/Result.fs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,28 @@ module Result =
424424
| Some x -> Ok x
425425
| None -> Error error
426426

427+
/// <summary>
428+
/// If the input result is <c>Ok</c>, applies a predicate to the <c>Ok</c> value.
429+
/// If the predicate returns true, then returns the original <c>Ok</c> Result.
430+
/// Otherwise, returns a new <c>Error</c> result with the provided error.
431+
///
432+
/// If the input result is <c>Error</c>, just returns the input result.
433+
///
434+
/// Documentation is found here: <href>https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/result/requirefunctions#require</href>
435+
/// </summary>
436+
/// <param name="predicate">Predicate applied to the <c>Ok</c> value of the result. </param>
437+
/// <param name="error">The error to return if the predicate returns false.</param>
438+
/// <param name="result">The input result.</param>
439+
/// <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>
440+
let inline require
441+
([<InlineIfLambda>] predicate: 'ok -> bool)
442+
(error: 'error)
443+
(result: Result<'ok, 'error>)
444+
: Result<'ok, 'error> =
445+
match result with
446+
| Error err -> Error err
447+
| Ok ok -> if predicate ok then Ok ok else Error(error)
448+
427449
/// <summary>
428450
/// Replaces an error value with a custom error value.
429451
///
@@ -656,3 +678,26 @@ module Result =
656678
| Error x1res, Error x2res -> Error(x1res, x2res)
657679
| Ok e, _ -> Ok e
658680
| _, Ok e -> Ok e
681+
682+
683+
/// <summary>
684+
/// When <paramref name="result"/> is <c>Ok</c>, applies <paramref name="checkFunc"/> to the <c>Ok</c> value.
685+
/// If <paramref name="checkFunc"/> returns an <c>Ok</c> unit value, returns <paramref name="result"/>.
686+
/// Otherwise, returns the <c>Error</c> value from the <paramref name="checkFunc"/> as a new result.
687+
///
688+
/// When <paramref name="result"/> is <c>Error</c>, returns <paramref name="result"/>.
689+
///
690+
/// Documentation is found here: <href>https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/result/check</href>
691+
/// </summary>
692+
/// <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>
693+
/// <param name="result">The input result.</param>
694+
/// <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>
695+
let inline check
696+
([<InlineIfLambda>] checkFunc: 'ok -> Result<unit, 'error>)
697+
(result: Result<'ok, 'error>)
698+
: Result<'ok, 'error> =
699+
match result with
700+
| Error err -> Error err
701+
| Ok ok ->
702+
checkFunc ok
703+
|> Result.map (fun _ -> ok)

src/FsToolkit.ErrorHandling/TaskResult.fs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@ module TaskResult =
150150
xs
151151
|> Task.map (Result.requireHead error)
152152

153+
/// Returns the task-wrapped result if it is Ok and the predicate is true, or if the task-wrapped result is Error.
154+
/// If the predicate is false, returns a new task-wrapped Error result with the error value.
155+
let inline require predicate error result =
156+
result
157+
|> Task.map (Result.require predicate error)
158+
153159
/// Replaces an error value of an task-wrapped result with a custom error
154160
/// value.
155161
let inline setError error taskResult =
@@ -334,3 +340,12 @@ module TaskResult =
334340
(input: Task<Result<'input, 'inputError>>)
335341
: Task<'output> =
336342
Task.map (Result.either onSuccess onError) input
343+
344+
/// 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.
345+
/// If the checkFunc returns an task-wrapped Error result, returns the task-wrapped Error result.
346+
let inline check ([<InlineIfLambda>] checkFunc) (result) =
347+
result
348+
|> bind (fun o ->
349+
checkFunc o
350+
|> map (fun _ -> o)
351+
)

0 commit comments

Comments
 (0)