1module Validation
2
3type ValidationError =
4 | Required of field: string
5 | TooShort of field: string * minLen: int
6 | TooLong of field: string * maxLen: int
7 | InvalidFormat of field: string * expected: string
8
9type Result<'a> =
10 | Ok of 'a
11 | Error of ValidationError list
12
13let bind f result =
14 match result with
15 | Ok value -> f value
16 | Error errors -> Error errors
17
18let map f result =
19 match result with
20 | Ok value -> Ok (f value)
21 | Error errors -> Error errors
22
23let combine results =
24 results
25 |> List.fold (fun acc r ->
26 match acc, r with
27 | Ok values, Ok v -> Ok (values @ [v])
28 | Error e1, Error e2 -> Error (e1 @ e2)
29 | Error e, _ -> Error e
30 | _, Error e -> Error e
31 ) (Ok [])
32
33type UserInput = {
34 Name: string
35 Email: string
36 Age: int
37}
38
39type ValidUser = {
40 Name: string
41 Email: string
42 Age: int
43}
44
45let validateName input =
46 if System.String.IsNullOrEmpty input.Name then
47 Error [Required "Name"]
48 elif input.Name.Length < 2 then
49 Error [TooShort ("Name", 2)]
50 elif input.Name.Length > 50 then
51 Error [TooLong ("Name", 50)]
52 else Ok input.Name
53
54let validateEmail input =
55 if System.String.IsNullOrEmpty input.Email then
56 Error [Required "Email"]
57 elif not (input.Email.Contains "@") then
58 Error [InvalidFormat ("Email", "must contain @")]
59 else Ok input.Email
60
61let validateAge input =
62 if input.Age < 0 || input.Age > 150 then
63 Error [InvalidFormat ("Age", "must be 0-150")]
64 else Ok input.Age
65
66let validateUser input =
67 match validateName input, validateEmail input, validateAge input with
68 | Ok n, Ok e, Ok a ->
69 Ok { Name = n; Email = e; Age = a }
70 | r1, r2, r3 ->
71 let errors =
72 [r1; r2; r3]
73 |> List.collect (fun r ->
74 match r with
75 | Error es -> es
76 | _ -> [])
77 Error errors