Skip to content

Commit 1c6f444

Browse files
Add requireSomeWith and requireNoneWith for lazy error evaluation (#352)
* Initial plan * Add requireSomeWith and requireNoneWith helpers to Result, AsyncResult, TaskResult, and JobResult Co-authored-by: TheAngryByrd <1490044+TheAngryByrd@users.noreply.github.com> * Add gitbook documentation for requireSomeWith and requireNoneWith Co-authored-by: TheAngryByrd <1490044+TheAngryByrd@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TheAngryByrd <1490044+TheAngryByrd@users.noreply.github.com>
1 parent e22fba8 commit 1c6f444

12 files changed

Lines changed: 292 additions & 0 deletions

File tree

gitbook/asyncResult/others.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ Converts an async-wrapped Option to a Result, using the given error if None.
2020
```fsharp
2121
'a -> Async<'b option> -> Async<Result<'b, 'a>>`
2222
```
23+
### requireSomeWith
24+
25+
Converts an async-wrapped Option to a Result, using the given error factory if None. The error factory is only called when the value is `None`.
26+
```fsharp
27+
(unit -> 'a) -> Async<'b option> -> Async<Result<'b, 'a>>
28+
```
2329
### requireNone
2430

2531
Converts an async-wrapped Option to a Result, using the given error if Some.
@@ -28,6 +34,14 @@ Converts an async-wrapped Option to a Result, using the given error if Some.
2834
'a -> Async<'b option> -> Async<Result<unit, 'a>>`
2935
```
3036

37+
### requireNoneWith
38+
39+
Converts an async-wrapped Option to a Result, using the given error factory if Some. The error factory is only called when the value is `Some`.
40+
41+
```fsharp
42+
(unit -> 'a) -> Async<'b option> -> Async<Result<unit, 'a>>
43+
```
44+
3145
### requireValueSome
3246

3347
Converts an async-wrapped ValueOption to a Result, using the given error if ValueNone.

gitbook/jobResult/others.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ Converts an job-wrapped Option to a Result, using the given error if None.
2020
```fsharp
2121
'a -> job<'b option> -> job<Result<'b, 'a>>`
2222
```
23+
### requireSomeWith
24+
25+
Converts an job-wrapped Option to a Result, using the given error factory if None. The error factory is only called when the value is `None`.
26+
```fsharp
27+
(unit -> 'a) -> job<'b option> -> job<Result<'b, 'a>>
28+
```
2329
### requireNone
2430

2531
Converts an job-wrapped Option to a Result, using the given error if Some.
@@ -28,6 +34,14 @@ Converts an job-wrapped Option to a Result, using the given error if Some.
2834
'a -> job<'b option> -> job<Result<unit, 'a>>`
2935
```
3036

37+
### requireNoneWith
38+
39+
Converts an job-wrapped Option to a Result, using the given error factory if Some. The error factory is only called when the value is `Some`.
40+
41+
```fsharp
42+
(unit -> 'a) -> job<'b option> -> job<Result<unit, 'a>>
43+
```
44+
3145
### requireValueSome
3246

3347
Converts an job-wrapped ValueOption to a Result, using the given error if ValueNone.

gitbook/result/requireFunctions.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,38 @@ let result : Result<unit, string> =
9696
// Error "Value must be Some"
9797
```
9898

99+
## requireSomeWith
100+
101+
Converts an Option to a Result, using the given error factory if None. The error factory is only called when the value is `None`.
102+
103+
### Function Signature
104+
105+
```fsharp
106+
(unit -> 'a) -> 'b option -> Result<'b, 'a>
107+
```
108+
109+
### Examples
110+
111+
#### Example 1
112+
113+
```fsharp
114+
let result : Result<int, string> =
115+
Some 1
116+
|> Result.requireSomeWith (fun () -> "Value must be Some")
117+
118+
// Ok 1
119+
```
120+
121+
#### Example 2
122+
123+
```fsharp
124+
let result : Result<int, string> =
125+
None
126+
|> Result.requireSomeWith (fun () -> "Value must be Some")
127+
128+
// Error "Value must be Some"
129+
```
130+
99131
## requireNone
100132

101133
Converts an Option to a Result, using the given error if Some.
@@ -128,6 +160,38 @@ let result : Result<unit, string> =
128160
// Error "Value must be None"
129161
```
130162

163+
## requireNoneWith
164+
165+
Converts an Option to a Result, using the given error factory if Some. The error factory is only called when the value is `Some`.
166+
167+
### Function Signature
168+
169+
```fsharp
170+
(unit -> 'a) -> 'b option -> Result<unit, 'a>
171+
```
172+
173+
### Examples
174+
175+
#### Example 1
176+
177+
```fsharp
178+
let result : Result<unit, string> =
179+
None
180+
|> Result.requireNoneWith (fun () -> "Value must be None")
181+
182+
// Ok ()
183+
```
184+
185+
#### Example 2
186+
187+
```fsharp
188+
let result : Result<unit, string> =
189+
Some 1
190+
|> Result.requireNoneWith (fun () -> "Value must be None")
191+
192+
// Error "Value must be None"
193+
```
194+
131195
## requireValueSome
132196

133197
Converts an ValueOption to a Result, using the given error if ValueNone.

gitbook/taskResult/others.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ Converts an task-wrapped Option to a Result, using the given error if None.
2020
```fsharp
2121
'a -> Task<'b option> -> Task<Result<'b, 'a>>`
2222
```
23+
### requireSomeWith
24+
25+
Converts an task-wrapped Option to a Result, using the given error factory if None. The error factory is only called when the value is `None`.
26+
```fsharp
27+
(unit -> 'a) -> Task<'b option> -> Task<Result<'b, 'a>>
28+
```
2329
### requireNone
2430

2531
Converts an task-wrapped Option to a Result, using the given error if Some.
@@ -28,6 +34,14 @@ Converts an task-wrapped Option to a Result, using the given error if Some.
2834
'a -> Task<'b option> -> Task<Result<unit, 'a>>`
2935
```
3036

37+
### requireNoneWith
38+
39+
Converts an task-wrapped Option to a Result, using the given error factory if Some. The error factory is only called when the value is `Some`.
40+
41+
```fsharp
42+
(unit -> 'a) -> Task<'b option> -> Task<Result<unit, 'a>>
43+
```
44+
3145
### requireValueSome
3246

3347
Converts an task-wrapped ValueOption to a Result, using the given error if ValueNone.

src/FsToolkit.ErrorHandling.JobResult/JobResult.fs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,21 @@ module JobResult =
122122
option
123123
|> Job.map (Result.requireSome error)
124124

125+
// Converts an job-wrapped Option to a Result, using the given error factory if None.
126+
let inline requireSomeWith ([<InlineIfLambda>] errorFactory: unit -> 'error) option =
127+
option
128+
|> Job.map (Result.requireSomeWith errorFactory)
129+
125130
// Converts an job-wrapped Option to a Result, using the given error if Some.
126131
let inline requireNone error option =
127132
option
128133
|> Job.map (Result.requireNone error)
129134

135+
// Converts an job-wrapped Option to a Result, using the given error factory if Some.
136+
let inline requireNoneWith ([<InlineIfLambda>] errorFactory: unit -> 'error) option =
137+
option
138+
|> Job.map (Result.requireNoneWith errorFactory)
139+
130140
// Converts an job-wrapped ValueOption to a Result, using the given error if ValueNone.
131141
let inline requireValueSome error voption =
132142
voption

src/FsToolkit.ErrorHandling/AsyncResult.fs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,14 @@ module AsyncResult =
158158
value
159159
|> Async.map (Result.requireSome error)
160160

161+
// Converts an async-wrapped Option to a Result, using the given error factory if None.
162+
let inline requireSomeWith
163+
([<InlineIfLambda>] errorFactory: unit -> 'error)
164+
(value: Async<'ok option>)
165+
: Async<Result<'ok, 'error>> =
166+
value
167+
|> Async.map (Result.requireSomeWith errorFactory)
168+
161169
// Converts an async-wrapped Option to a Result, using the given error if Some.
162170
let inline requireNone
163171
(error: 'error)
@@ -166,6 +174,14 @@ module AsyncResult =
166174
value
167175
|> Async.map (Result.requireNone error)
168176

177+
// Converts an async-wrapped Option to a Result, using the given error factory if Some.
178+
let inline requireNoneWith
179+
([<InlineIfLambda>] errorFactory: unit -> 'error)
180+
(value: Async<'ok option>)
181+
: Async<Result<unit, 'error>> =
182+
value
183+
|> Async.map (Result.requireNoneWith errorFactory)
184+
169185
// Converts an async-wrapped ValueOption to a Result, using the given error if ValueNone.
170186
let inline requireValueSome
171187
(error: 'error)

src/FsToolkit.ErrorHandling/Result.fs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,20 @@ module Result =
307307
| Some x -> Ok x
308308
| None -> Error error
309309

310+
/// <summary>
311+
/// Requires a value to be <c>Some</c>, otherwise returns an error result using the given error factory.
312+
/// </summary>
313+
/// <param name="errorFactory">A function to produce the error value if the value is <c>None</c>.</param>
314+
/// <param name="option">The <c>Option</c> value to check.</param>
315+
/// <returns>An <c>Ok</c> result if the value is <c>Some</c>, otherwise an Error result with the value produced by <paramref name="errorFactory" />.</returns>
316+
let inline requireSomeWith
317+
([<InlineIfLambda>] errorFactory: unit -> 'error)
318+
(option: 'ok option)
319+
: Result<'ok, 'error> =
320+
match option with
321+
| Some x -> Ok x
322+
| None -> Error(errorFactory ())
323+
310324
/// <summary>
311325
/// Requires a value to be <c>None</c>, otherwise returns an error result.
312326
///
@@ -320,6 +334,20 @@ module Result =
320334
| Some _ -> Error error
321335
| None -> Ok()
322336

337+
/// <summary>
338+
/// Requires a value to be <c>None</c>, otherwise returns an error result using the given error factory.
339+
/// </summary>
340+
/// <param name="errorFactory">A function to produce the error value if the value is <c>Some</c>.</param>
341+
/// <param name="option">The <c>Option</c> value to check.</param>
342+
/// <returns>An <c>Ok</c> result if the value is <c>None</c>, otherwise an Error result with the value produced by <paramref name="errorFactory" />.</returns>
343+
let inline requireNoneWith
344+
([<InlineIfLambda>] errorFactory: unit -> 'error)
345+
(option: 'value option)
346+
: Result<unit, 'error> =
347+
match option with
348+
| Some _ -> Error(errorFactory ())
349+
| None -> Ok()
350+
323351
/// <summary>
324352
/// Requires a value to be <c>ValueSome</c>, otherwise returns an error result.
325353
///

src/FsToolkit.ErrorHandling/TaskResult.fs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,21 @@ module TaskResult =
107107
option
108108
|> Task.map (Result.requireSome error)
109109

110+
// Converts an task-wrapped Option to a Result, using the given error factory if None.
111+
let inline requireSomeWith ([<InlineIfLambda>] errorFactory: unit -> 'error) option =
112+
option
113+
|> Task.map (Result.requireSomeWith errorFactory)
114+
110115
// Converts an task-wrapped Option to a Result, using the given error if Some.
111116
let inline requireNone error option =
112117
option
113118
|> Task.map (Result.requireNone error)
114119

120+
// Converts an task-wrapped Option to a Result, using the given error factory if Some.
121+
let inline requireNoneWith ([<InlineIfLambda>] errorFactory: unit -> 'error) option =
122+
option
123+
|> Task.map (Result.requireNoneWith errorFactory)
124+
115125
// Converts an task-wrapped ValueOption to a Result, using the given error if ValueNone.
116126
let inline requireValueSome error voption =
117127
voption

tests/FsToolkit.ErrorHandling.JobResult.Tests/JobResult.fs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,22 @@ let requireSomeTests =
284284
|> Expect.hasJobErrorValueSync err
285285
]
286286

287+
[<Tests>]
288+
let requireSomeWithTests =
289+
testList "JobResult.requireSomeWith Tests" [
290+
testCase "requireSomeWith happy path"
291+
<| fun _ ->
292+
toJob (Some 42)
293+
|> JobResult.requireSomeWith (fun () -> err)
294+
|> Expect.hasJobOkValueSync 42
295+
296+
testCase "requireSomeWith error path"
297+
<| fun _ ->
298+
toJob None
299+
|> JobResult.requireSomeWith (fun () -> err)
300+
|> Expect.hasJobErrorValueSync err
301+
]
302+
287303
[<Tests>]
288304
let requireNoneTests =
289305
testList "JobResult.requireNone Tests" [
@@ -300,6 +316,22 @@ let requireNoneTests =
300316
|> Expect.hasJobErrorValueSync err
301317
]
302318

319+
[<Tests>]
320+
let requireNoneWithTests =
321+
testList "JobResult.requireNoneWith Tests" [
322+
testCase "requireNoneWith happy path"
323+
<| fun _ ->
324+
toJob None
325+
|> JobResult.requireNoneWith (fun () -> err)
326+
|> Expect.hasJobOkValueSync ()
327+
328+
testCase "requireNoneWith error path"
329+
<| fun _ ->
330+
toJob (Some 42)
331+
|> JobResult.requireNoneWith (fun () -> err)
332+
|> Expect.hasJobErrorValueSync err
333+
]
334+
303335
[<Tests>]
304336
let requireValueSomeTests =
305337
testList "JobResult.requireValueSome Tests" [

tests/FsToolkit.ErrorHandling.Tests/AsyncResult.fs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,20 @@ let requireSomeTests =
296296
]
297297

298298

299+
let requireSomeWithTests =
300+
testList "AsyncResult.requireSomeWith Tests" [
301+
testCaseAsync "requireSomeWith happy path"
302+
<| (toAsync (Some 42)
303+
|> AsyncResult.requireSomeWith (fun () -> err)
304+
|> Expect.hasAsyncOkValue 42)
305+
306+
testCaseAsync "requireSomeWith error path"
307+
<| (toAsync None
308+
|> AsyncResult.requireSomeWith (fun () -> err)
309+
|> Expect.hasAsyncErrorValue err)
310+
]
311+
312+
299313
let requireNoneTests =
300314
testList "AsyncResult.requireNone Tests" [
301315
testCaseAsync "requireNone happy path"
@@ -310,6 +324,20 @@ let requireNoneTests =
310324
]
311325

312326

327+
let requireNoneWithTests =
328+
testList "AsyncResult.requireNoneWith Tests" [
329+
testCaseAsync "requireNoneWith happy path"
330+
<| (toAsync None
331+
|> AsyncResult.requireNoneWith (fun () -> err)
332+
|> Expect.hasAsyncOkValue ())
333+
334+
testCaseAsync "requireNoneWith error path"
335+
<| (toAsync (Some 42)
336+
|> AsyncResult.requireNoneWith (fun () -> err)
337+
|> Expect.hasAsyncErrorValue err)
338+
]
339+
340+
313341
let requireValueSomeTests =
314342
testList "AsyncResult.requireValueSome Tests" [
315343
testCaseAsync "requireValueSome happy path"
@@ -1034,7 +1062,9 @@ let allTests =
10341062
requireTrueTests
10351063
requireFalseTests
10361064
requireSomeTests
1065+
requireSomeWithTests
10371066
requireNoneTests
1067+
requireNoneWithTests
10381068
requireValueSomeTests
10391069
requireValueNoneTests
10401070
requireEqualToTests

0 commit comments

Comments
 (0)