Skip to content

Commit ba53f4b

Browse files
authored
Updated asyncValidation CE (#271)
* Updated asyncValidation CE * Updated unit tests
1 parent 6f50643 commit ba53f4b

File tree

5 files changed

+154
-67
lines changed

5 files changed

+154
-67
lines changed

src/FsToolkit.ErrorHandling/AsyncValidationCE.fs

Lines changed: 25 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -23,82 +23,53 @@ module AsyncValidationCE =
2323
member inline this.Zero() : AsyncValidation<unit, 'error> = this.Return()
2424

2525
member inline _.Delay
26-
([<InlineIfLambda>] generator: unit -> AsyncValidation<'ok, 'error>)
27-
: unit -> AsyncValidation<'ok, 'error> =
28-
generator
29-
30-
member inline _.Run
3126
([<InlineIfLambda>] generator: unit -> AsyncValidation<'ok, 'error>)
3227
: AsyncValidation<'ok, 'error> =
33-
generator ()
28+
async.Delay generator
3429

3530
member inline this.Combine
3631
(
37-
result: AsyncValidation<unit, 'error>,
38-
[<InlineIfLambda>] binder: unit -> AsyncValidation<'ok, 'error>
32+
validation1: AsyncValidation<unit, 'error>,
33+
validation2: AsyncValidation<'ok, 'error>
3934
) : AsyncValidation<'ok, 'error> =
40-
this.Bind(result, binder)
35+
this.Bind(validation1, (fun () -> validation2))
4136

42-
member inline this.TryWith
37+
member inline _.TryWith
4338
(
44-
[<InlineIfLambda>] generator: unit -> AsyncValidation<'ok, 'error>,
39+
computation: AsyncValidation<'ok, 'error>,
4540
[<InlineIfLambda>] handler: exn -> AsyncValidation<'ok, 'error>
4641
) : AsyncValidation<'ok, 'error> =
47-
async {
48-
return!
49-
try
50-
this.Run generator
51-
with e ->
52-
handler e
53-
}
54-
55-
member inline this.TryFinally
42+
async.TryWith(computation, handler)
43+
44+
member inline _.TryFinally
5645
(
57-
[<InlineIfLambda>] generator: unit -> AsyncValidation<'ok, 'error>,
46+
computation: AsyncValidation<'ok, 'error>,
5847
[<InlineIfLambda>] compensation: unit -> unit
5948
) : AsyncValidation<'ok, 'error> =
60-
async {
61-
return!
62-
try
63-
this.Run generator
64-
finally
65-
compensation ()
66-
}
67-
68-
member inline this.Using
49+
async.TryFinally(computation, compensation)
50+
51+
member inline _.Using
6952
(
7053
resource: 'disposable :> IDisposable,
7154
[<InlineIfLambda>] binder: 'disposable -> AsyncValidation<'okOutput, 'error>
7255
) : AsyncValidation<'okOutput, 'error> =
73-
this.TryFinally(
74-
(fun () -> binder resource),
75-
(fun () ->
76-
if not (obj.ReferenceEquals(resource, null)) then
77-
resource.Dispose()
78-
)
79-
)
56+
async.Using(resource, binder)
8057

8158
member inline this.While
8259
(
8360
[<InlineIfLambda>] guard: unit -> bool,
84-
[<InlineIfLambda>] generator: unit -> AsyncValidation<unit, 'error>
61+
computation: AsyncValidation<unit, 'error>
8562
) : AsyncValidation<unit, 'error> =
86-
let mutable doContinue = true
87-
let mutable result = Ok()
88-
89-
async {
90-
while doContinue
91-
&& guard () do
92-
let! x = generator ()
93-
94-
match x with
95-
| Ok() -> ()
96-
| Error e ->
97-
doContinue <- false
98-
result <- Error e
99-
100-
return result
101-
}
63+
if guard () then
64+
let mutable whileAsync = Unchecked.defaultof<_>
65+
66+
whileAsync <-
67+
this.Bind(computation, (fun () -> if guard () then whileAsync else this.Zero()))
68+
69+
whileAsync
70+
else
71+
this.Zero()
72+
10273

10374
member inline this.For
10475
(

tests/FsToolkit.ErrorHandling.Tests/AsyncOptionCE.fs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,32 @@ let ``AsyncOptionCE using Tests`` =
261261
Expect.equal actual (Some data) "Should be ok"
262262
Expect.isTrue isFinished ""
263263
}
264+
265+
testCaseAsync "disposable not disposed too early"
266+
<| async {
267+
let mutable disposed = false
268+
let mutable finished = false
269+
let f1 _ = Async.retn (Some 42)
270+
271+
let! actual =
272+
asyncOption {
273+
use d =
274+
makeDisposable (fun () ->
275+
disposed <- true
276+
277+
if not finished then
278+
failwith "Should not be disposed too early"
279+
)
280+
281+
let! data = f1 d
282+
finished <- true
283+
return data
284+
}
285+
286+
Expect.equal actual (Some 42) "Should be some"
287+
Expect.isTrue disposed "Should be disposed"
288+
}
289+
264290
#if NET7_0
265291
testCaseAsync "use sync asyncdisposable"
266292
<| async {

tests/FsToolkit.ErrorHandling.Tests/AsyncResultCE.fs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ let ``AsyncResultCE using Tests`` =
279279
}
280280

281281
Expect.equal actual (Result.Ok data) "Should be ok"
282-
Expect.isTrue isFinished ""
282+
Expect.isTrue isFinished "Expected disposable to be disposed"
283283
}
284284
#if NET7_0
285285
testCaseAsync "use sync asyncdisposable"
@@ -301,7 +301,7 @@ let ``AsyncResultCE using Tests`` =
301301
}
302302

303303
Expect.equal actual (Result.Ok data) "Should be ok"
304-
Expect.isTrue isFinished ""
304+
Expect.isTrue isFinished "Expected disposable to be disposed"
305305
}
306306
testCaseAsync "use async asyncdisposable"
307307
<| async {
@@ -326,7 +326,7 @@ let ``AsyncResultCE using Tests`` =
326326
}
327327

328328
Expect.equal actual (Result.Ok data) "Should be ok"
329-
Expect.isTrue isFinished ""
329+
Expect.isTrue isFinished "Expected disposable to be disposed"
330330
}
331331
#endif
332332
testCaseAsync "use! normal wrapped disposable"
@@ -344,6 +344,32 @@ let ``AsyncResultCE using Tests`` =
344344

345345
Expect.equal actual (Result.Ok data) "Should be ok"
346346
}
347+
348+
testCaseAsync "disposable not disposed too early"
349+
<| async {
350+
let mutable disposed = false
351+
let mutable finished = false
352+
let f1 _ = AsyncResult.ok 42
353+
354+
let! actual =
355+
asyncResult {
356+
use d =
357+
makeDisposable (fun () ->
358+
disposed <- true
359+
360+
if not finished then
361+
failwith "Should not be disposed too early"
362+
)
363+
364+
let! data = f1 d
365+
finished <- true
366+
return data
367+
}
368+
369+
Expect.equal actual (Ok 42) "Should be ok"
370+
Expect.isTrue disposed "Should be disposed"
371+
}
372+
347373
#if !FABLE_COMPILER && NETSTANDARD2_1
348374
// Fable can't handle null disposables you get
349375
// TypeError: Cannot read property 'Dispose' of null

tests/FsToolkit.ErrorHandling.Tests/AsyncResultOptionCE.fs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -438,9 +438,9 @@ let ``AsyncResultOptionCE try Tests`` =
438438
}
439439
]
440440

441-
let makeDisposable () =
441+
let makeDisposable callback =
442442
{ new System.IDisposable with
443-
member this.Dispose() = ()
443+
member this.Dispose() = callback ()
444444
}
445445

446446
let makeAsyncDisposable (callback) =
@@ -454,14 +454,41 @@ let ``AsyncResultOptionCE using Tests`` =
454454
testCaseAsync "use normal disposable"
455455
<| async {
456456
let data = 42
457+
let mutable isFinished = false
457458

458459
let! actual =
459460
asyncResultOption {
460-
use d = makeDisposable ()
461+
use d = makeDisposable (fun () -> isFinished <- true)
461462
return data
462463
}
463464

464465
Expect.equal actual (OkSome data) "Should be ok"
466+
Expect.isTrue isFinished "Expected disposable to be disposed"
467+
}
468+
469+
testCaseAsync "disposable not disposed too early"
470+
<| async {
471+
let mutable disposed = false
472+
let mutable finished = false
473+
let f1 _ = AsyncResult.ok 42
474+
475+
let! actual =
476+
asyncResultOption {
477+
use d =
478+
makeDisposable (fun () ->
479+
disposed <- true
480+
481+
if not finished then
482+
failwith "Should not be disposed too early"
483+
)
484+
485+
let! data = f1 d
486+
finished <- true
487+
return data
488+
}
489+
490+
Expect.equal actual (Ok(Some 42)) "Should be ok"
491+
Expect.isTrue disposed "Should be disposed"
465492
}
466493

467494
#if NET7_0
@@ -509,24 +536,26 @@ let ``AsyncResultOptionCE using Tests`` =
509536
}
510537

511538
Expect.equal actual (OkSome data) "Should be ok"
512-
Expect.isTrue isFinished ""
539+
Expect.isTrue isFinished "Expected disposable to be disposed"
513540
}
514541
#endif
515542

516543
testCaseAsync "use! normal wrapped disposable"
517544
<| async {
518545
let data = 42
546+
let mutable isFinished = false
519547

520548
let! actual =
521549
asyncResultOption {
522550
use! d =
523-
makeDisposable ()
551+
makeDisposable (fun () -> isFinished <- true)
524552
|> Result.Ok
525553

526554
return data
527555
}
528556

529557
Expect.equal actual (OkSome data) "Should be ok"
558+
Expect.isTrue isFinished "Expected disposable to be disposed"
530559
}
531560
#if !FABLE_COMPILER
532561
// Fable can't handle null disposables you get

tests/FsToolkit.ErrorHandling.Tests/AsyncValidationCE.fs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -279,40 +279,74 @@ let ``AsyncValidationCE try Tests`` =
279279
}
280280
]
281281

282-
let makeDisposable () =
282+
let makeDisposable callback =
283283
{ new System.IDisposable with
284-
member this.Dispose() = ()
284+
member _.Dispose() = callback ()
285285
}
286286

287287
let ``AsyncValidationCE using Tests`` =
288288
testList "AsyncValidationCE using Tests" [
289289
testCaseAsync "use normal disposable"
290290
<| async {
291291
let data = 42
292+
let mutable isFinished = false
292293

293294
let! actual =
294295
asyncValidation {
295-
use d = makeDisposable ()
296+
use d = makeDisposable (fun () -> isFinished <- true)
296297
return data
297298
}
298299

299-
Expect.equal actual (Ok data) "Should be ok"
300+
Expect.equal actual (Result.Ok data) "Should be ok"
301+
Expect.isTrue isFinished "Expected disposable to be disposed"
300302
}
303+
301304
testCaseAsync "use! normal wrapped disposable"
302305
<| async {
303306
let data = 42
307+
let mutable isFinished = false
304308

305309
let! actual =
306310
asyncValidation {
307311
use! d =
308-
makeDisposable ()
312+
makeDisposable (fun () -> isFinished <- true)
309313
|> Ok
310314

311315
return data
312316
}
313317

314318
Expect.equal actual (Ok data) "Should be ok"
319+
Expect.isTrue isFinished "Expected disposable to be disposed"
315320
}
321+
322+
testCaseAsync "disposable not disposed too early"
323+
<| async {
324+
let mutable disposed = false
325+
let mutable finished = false
326+
let f1 _ = AsyncResult.ok 42
327+
328+
let! actual =
329+
asyncValidation {
330+
use d =
331+
makeDisposable (fun () ->
332+
disposed <- true
333+
334+
if not finished then
335+
failwith "Should not be disposed too early"
336+
)
337+
338+
let! data = f1 d
339+
finished <- true
340+
return data
341+
}
342+
343+
Expect.equal actual (Ok 42) "Should be ok"
344+
Expect.isTrue disposed "Should be disposed"
345+
}
346+
347+
#if !FABLE_COMPILER && NETSTANDARD2_1
348+
// Fable can't handle null disposables you get
349+
// TypeError: Cannot read property 'Dispose' of null
316350
testCaseAsync "use null disposable"
317351
<| async {
318352
let data = 42
@@ -325,6 +359,7 @@ let ``AsyncValidationCE using Tests`` =
325359

326360
Expect.equal actual (Ok data) "Should be ok"
327361
}
362+
#endif
328363
]
329364

330365
let ``AsyncValidationCE loop Tests`` =

0 commit comments

Comments
 (0)