|
| 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