Skip to content

Commit 56daa09

Browse files
committed
Implement TaskValidation module
1 parent def0948 commit 56daa09

File tree

2 files changed

+293
-0
lines changed

2 files changed

+293
-0
lines changed

src/FsToolkit.ErrorHandling/FsToolkit.ErrorHandling.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<Compile Include="ValidationOp.fs" />
3232
<Compile Include="ValidationCE.fs" />
3333
<Compile Include="AsyncValidation.fs" />
34+
<Compile Include="TaskValidation.fs" />
3435
<Compile Include="AsyncValidationOp.fs" />
3536
<Compile Include="AsyncValidationCE.fs" />
3637
<Compile Include="AsyncOption.fs" />
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
namespace FsToolkit.ErrorHandling
2+
3+
open System.Threading.Tasks
4+
5+
/// TaskValidation<'a, 'err> is defined as Task<Result<'a, 'err list>> meaning you can use many of the functions found in the Result and Task module.
6+
type TaskValidation<'ok, 'error> = Task<Result<'ok, 'error list>>
7+
8+
[<RequireQualifiedAccess>]
9+
module TaskValidation =
10+
11+
let inline ok (value: 'ok) : TaskValidation<'ok, 'error> =
12+
Ok value
13+
|> Task.singleton
14+
15+
let inline error (error: 'error) : TaskValidation<'ok, 'error> =
16+
Error [ error ]
17+
|> Task.singleton
18+
19+
let inline ofResult (result: Result<'ok, 'error>) : TaskValidation<'ok, 'error> =
20+
Result.mapError List.singleton result
21+
|> Task.singleton
22+
23+
let inline ofChoice (choice: Choice<'ok, 'error>) : TaskValidation<'ok, 'error> =
24+
match choice with
25+
| Choice1Of2 x -> ok x
26+
| Choice2Of2 e -> error e
27+
28+
let inline apply
29+
(applier: TaskValidation<'okInput -> 'okOutput, 'error>)
30+
(input: TaskValidation<'okInput, 'error>)
31+
: TaskValidation<'okOutput, 'error> =
32+
task {
33+
let! applier = applier
34+
let! input = input
35+
36+
return
37+
match applier, input with
38+
| Ok f, Ok x -> Ok(f x)
39+
| Error errs, Ok _
40+
| Ok _, Error errs -> Error errs
41+
| Error errs1, Error errs2 ->
42+
Error(
43+
errs1
44+
@ errs2
45+
)
46+
}
47+
48+
/// <summary>
49+
/// Returns <c>validation</c> if it is <c>Ok</c>, otherwise returns <c>ifError</c>
50+
/// </summary>
51+
/// <param name="ifError">The value to use if <c>validation</c> is <c>Error</c></param>
52+
/// <param name="validation">The input validation.</param>
53+
/// <remarks>
54+
/// </remarks>
55+
/// <example>
56+
/// <code>
57+
/// TaskValidation.error "First" |> TaskValidation.orElse (TaskValidation.error "Second") // evaluates to Error [ "Second" ]
58+
/// TaskValidation.error "First" |> TaskValidation.orElse (TaskValidation.ok "Second") // evaluates to Ok ("Second")
59+
/// TaskValidation.ok "First" |> TaskValidation.orElse (TaskValidation.error "Second") // evaluates to Ok ("First")
60+
/// TaskValidation.ok "First" |> TaskValidation.orElse (TaskValidation.ok "Second") // evaluates to Ok ("First")
61+
/// </code>
62+
/// </example>
63+
/// <returns>
64+
/// The result if the validation is <c>Ok</c>, else returns <c>ifError</c>.
65+
/// </returns>
66+
let inline orElse
67+
(ifError: TaskValidation<'ok, 'errorOutput>)
68+
(validation: TaskValidation<'ok, 'errorInput>)
69+
: TaskValidation<'ok, 'errorOutput> =
70+
task {
71+
let! validation = validation
72+
73+
return!
74+
validation
75+
|> Result.either ok (fun _ -> ifError)
76+
}
77+
78+
/// <summary>
79+
/// Returns <c>validation</c> if it is <c>Ok</c>, otherwise executes <c>ifErrorFunc</c> and returns the result.
80+
/// </summary>
81+
/// <param name="ifErrorFunc">A function that provides an alternate validation when evaluated.</param>
82+
/// <param name="validation">The input validation.</param>
83+
/// <remarks>
84+
/// <paramref name="ifErrorFunc"/> is not executed unless <c>validation</c> is an <c>Error</c>.
85+
/// </remarks>
86+
/// <example>
87+
/// <code>
88+
/// TaskValidation.error "First" |> TaskValidation.orElseWith (fun _ -> TaskValidation.error "Second") // evaluates to Error [ "Second" ]
89+
/// TaskValidation.error "First" |> TaskValidation.orElseWith (fun _ -> TaskValidation.ok "Second") // evaluates to Ok ("Second")
90+
/// TaskValidation.ok "First" |> TaskValidation.orElseWith (fun _ -> TaskValidation.error "Second") // evaluates to Ok ("First")
91+
/// TaskValidation.ok "First" |> TaskValidation.orElseWith (fun _ -> TaskValidation.ok "Second") // evaluates to Ok ("First")
92+
/// </code>
93+
/// </example>
94+
/// <returns>
95+
/// The result if the result is <c>Ok</c>, else the result of executing <paramref name="ifErrorFunc"/>.
96+
/// </returns>
97+
let inline orElseWith
98+
([<InlineIfLambda>] ifErrorFunc: 'errorInput list -> TaskValidation<'ok, 'errorOutput>)
99+
(validation : TaskValidation<'ok, 'errorInput>)
100+
: TaskValidation<'ok, 'errorOutput> =
101+
task {
102+
let! validation = validation
103+
104+
return!
105+
match validation with
106+
| Ok x -> ok x
107+
| Error err -> ifErrorFunc err
108+
}
109+
110+
/// <summary>
111+
/// Applies a transformation to the value of a <c>TaskValidation</c> to a new value using the specified mapper function.
112+
///
113+
/// Documentation is found here: <href>https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/taskvalidation/map</href>
114+
/// </summary>
115+
/// <param name="mapper">The function to apply to the value of the <c>TaskValidation</c> if it is <c>Ok</c>.</param>
116+
/// <param name="input">The <c>TaskValidation</c> to map.</param>
117+
/// <returns>A new <c>TaskValidation</c>with the mapped value if the input <c>TaskValidation</c> is <c>Ok</c>, otherwise the original <c>Error</c>.</returns>
118+
let inline map
119+
([<InlineIfLambda>] mapper: 'okInput -> 'okOutput)
120+
(input: TaskValidation<'okInput, 'error>)
121+
: TaskValidation<'okOutput, 'error> =
122+
task {
123+
let! input = input
124+
return Result.map mapper input
125+
}
126+
127+
/// <summary>
128+
/// Applies a mapper function to two input <c>TaskValidation</c>s, producing a new <c>TaskValidation</c>.
129+
///
130+
/// Documentation is found here: <href>https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/taskvalidation/map2</href>
131+
/// </summary>
132+
/// <param name="mapper">The function to apply to the inputs.</param>
133+
/// <param name="input1">The first input <c>TaskValidation</c>.</param>
134+
/// <param name="input2">The second input <c>TaskValidation</c>.</param>
135+
/// <returns>A new <c>TaskValidation</c>containing the output of the mapper function if both input <c>TaskValidation</c>s are <c>Ok</c>, otherwise an <c>Error TaskValidation</c>.</returns>
136+
let inline map2
137+
([<InlineIfLambda>] mapper: 'okInput1 -> 'okInput2 -> 'okOutput)
138+
(input1: TaskValidation<'okInput1, 'error>)
139+
(input2: TaskValidation<'okInput2, 'error>)
140+
: TaskValidation<'okOutput, 'error> =
141+
task {
142+
let! input1 = input1
143+
let! input2 = input2
144+
145+
return
146+
match input1, input2 with
147+
| Ok x, Ok y -> Ok(mapper x y)
148+
| Ok _, Error errs -> Error errs
149+
| Error errs, Ok _ -> Error errs
150+
| Error errs1, Error errs2 ->
151+
Error(
152+
errs1
153+
@ errs2
154+
)
155+
}
156+
157+
/// <summary>
158+
/// Applies a mapper function to three input <c>TaskValidation</c>s, producing a new <c>TaskValidation</c>.
159+
///
160+
/// Documentation is found here: <href>https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/taskvalidation/map3</href>
161+
/// </summary>
162+
/// <param name="mapper">The function to apply to the input <c>TaskValidation</c>s.</param>
163+
/// <param name="input1">The first input <c>TaskValidation</c>.</param>
164+
/// <param name="input2">The second input <c>TaskValidation</c>.</param>
165+
/// <param name="input3">The third input <c>TaskValidation</c>.</param>
166+
/// <returns>A new <c>TaskValidation</c> with the output of the mapper function applied to the input validations, if all <c>TaskValidation</c>s are <c>Ok</c>, otherwise returns the original <c>Error</c></returns>
167+
let inline map3
168+
([<InlineIfLambda>] mapper: 'okInput1 -> 'okInput2 -> 'okInput3 -> 'okOutput)
169+
(input1: TaskValidation<'okInput1, 'error>)
170+
(input2: TaskValidation<'okInput2, 'error>)
171+
(input3: TaskValidation<'okInput3, 'error>)
172+
: TaskValidation<'okOutput, 'error> =
173+
task {
174+
let! input1 = input1
175+
let! input2 = input2
176+
let! input3 = input3
177+
178+
return
179+
match input1, input2, input3 with
180+
| Ok x, Ok y, Ok z -> Ok(mapper x y z)
181+
| Error errs, Ok _, Ok _ -> Error errs
182+
| Ok _, Error errs, Ok _ -> Error errs
183+
| Ok _, Ok _, Error errs -> Error errs
184+
| Error errs1, Error errs2, Ok _ ->
185+
Error(
186+
errs1
187+
@ errs2
188+
)
189+
| Ok _, Error errs1, Error errs2 ->
190+
Error(
191+
errs1
192+
@ errs2
193+
)
194+
| Error errs1, Ok _, Error errs2 ->
195+
Error(
196+
errs1
197+
@ errs2
198+
)
199+
| Error errs1, Error errs2, Error errs3 ->
200+
Error(
201+
errs1
202+
@ errs2
203+
@ errs3
204+
)
205+
}
206+
207+
/// <summary>
208+
/// Maps the error value of a <c>TaskValidation</c>to a new error value using the specified error mapper function.
209+
///
210+
/// Documentation is found here: <href>https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/taskvalidation/maperror</href>
211+
/// </summary>
212+
/// <param name="errorMapper">The function that maps the input error value to the output error value.</param>
213+
/// <param name="input">The <c>TaskValidation</c>value to map the error value of.</param>
214+
/// <returns>A new <c>TaskValidation</c>with the same Ok value and the mapped error value.</returns>
215+
let inline mapError
216+
([<InlineIfLambda>] errorMapper: 'errorInput -> 'errorOutput)
217+
(input: TaskValidation<'ok, 'errorInput>)
218+
: TaskValidation<'ok, 'errorOutput> =
219+
task {
220+
let! input = input
221+
return Result.mapError (List.map errorMapper) input
222+
}
223+
224+
/// <summary>
225+
/// Maps the error values of a <c>TaskValidation</c>to a new error value using the specified error mapper function.
226+
///
227+
/// Documentation is found here: <href>https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/taskvalidation/maperror</href>
228+
/// </summary>
229+
/// <remarks>
230+
/// Similar to <c>TaskValidation.mapError</c>, except that the mapping function is passed the full list of errors, rather than each one individually.
231+
/// </remarks>
232+
/// <param name="errorMapper">The function that maps the input errors to the output errors.</param>
233+
/// <param name="input">The <c>TaskValidation</c>value to map the errors of.</param>
234+
/// <returns>A new <c>TaskValidation</c>with the same Ok value and the mapped errors.</returns>
235+
let inline mapErrors
236+
([<InlineIfLambda>] errorMapper: 'errorInput list -> 'errorOutput list)
237+
(input: TaskValidation<'ok, 'errorInput>)
238+
: TaskValidation<'ok, 'errorOutput> =
239+
task {
240+
let! input = input
241+
return Result.mapError errorMapper input
242+
}
243+
244+
/// <summary>
245+
/// Takes a transformation function and applies it to the <c>TaskValidation</c> if it is <c>Ok</c>.
246+
///
247+
/// Documentation is found here: <href>https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/taskvalidation/bind</href>
248+
/// </summary>
249+
/// <param name="binder">The transformation function</param>
250+
/// <param name="input">The input validation</param>
251+
/// <typeparam name="'okInput">The type of the successful validation.</typeparam>
252+
/// <typeparam name="'okOutput">The type of the validation after binding.</typeparam>
253+
/// <typeparam name="'error">The type of the error.</typeparam>
254+
/// <returns>Returns a new <c>TaskValidation</c> if the input is <c>Ok</c>, otherwise returns the original <c>TaskValidation</c></returns>
255+
let inline bind
256+
([<InlineIfLambda>] binder: 'okInput -> TaskValidation<'okOutput, 'error>)
257+
(input: TaskValidation<'okInput, 'error>)
258+
: TaskValidation<'okOutput, 'error> =
259+
task {
260+
let! input = input
261+
262+
match input with
263+
| Ok x -> return! binder x
264+
| Error e -> return Error e
265+
}
266+
267+
/// <summary>
268+
/// Takes two <c>TaskValidation</c>s and returns a tuple of the pair or <c>Error</c> if either of them are <c>Error</c>
269+
///
270+
/// Documentation is found here: <href>https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoolkit.errorhandling/taskvalidation/zip</href>
271+
/// </summary>
272+
/// <remarks>
273+
/// If both validations are <c>Error</c>, the returned <c>Error</c> contains the concatenated lists of errors
274+
/// </remarks>
275+
/// <param name="left">The first input validation.</param>
276+
/// <param name="right">The second input validation.</param>
277+
/// <returns>A tuple of the pair of the input validation.</returns>
278+
let inline zip
279+
(left: TaskValidation<'left, 'error>)
280+
(right: TaskValidation<'right, 'error>)
281+
: TaskValidation<'left * 'right, 'error> =
282+
task {
283+
let! left = left
284+
let! right = right
285+
286+
return
287+
match left, right with
288+
| Ok x1res, Ok x2res -> Ok(x1res, x2res)
289+
| Error e, Ok _ -> Error e
290+
| Ok _, Error e -> Error e
291+
| Error e1, Error e2 -> Error(e1 @ e2)
292+
}

0 commit comments

Comments
 (0)