|
| 1 | +# Changeset |
| 2 | + |
| 3 | +`sparkling/changeset` provides Ecto-style data casting and validation. |
| 4 | + |
| 5 | +## Basic usage |
| 6 | + |
| 7 | +```gleam |
| 8 | +import gleam/option.{None, Some} |
| 9 | +import sparkling/changeset |
| 10 | +
|
| 11 | +type UserData { |
| 12 | + UserData(name: String, email: String, age: Int) |
| 13 | +} |
| 14 | +
|
| 15 | +let data = UserData(name: "", email: "", age: 0) |
| 16 | +
|
| 17 | +let cs = |
| 18 | + changeset.new(data) |
| 19 | + |> changeset.put_change("name", "Alice") |
| 20 | + |> changeset.put_change("email", "alice@example.com") |
| 21 | + |> changeset.put_change("age", "28") |
| 22 | + |> changeset.validate_required("name") |
| 23 | + |> changeset.validate_required("email") |
| 24 | + |> changeset.validate_email("email") |
| 25 | + |> changeset.validate_length("name", Some(2), Some(100)) |
| 26 | + |> changeset.validate_number("age", Some(0), Some(120)) |
| 27 | +
|
| 28 | +case changeset.apply(cs) { |
| 29 | + Ok(_) -> io.println("valid!") |
| 30 | + Error(errors) -> io.println(changeset.format_errors(errors)) |
| 31 | +} |
| 32 | +``` |
| 33 | + |
| 34 | +## Validators |
| 35 | + |
| 36 | +```gleam |
| 37 | +// Required — field must be present in changes |
| 38 | +|> changeset.validate_required("name") |
| 39 | +
|
| 40 | +// Length — min/max for string fields |
| 41 | +|> changeset.validate_length("name", Some(2), Some(100)) |
| 42 | +|> changeset.validate_length("bio", None, Some(500)) // max only |
| 43 | +
|
| 44 | +// Number — min/max for int fields (value parsed from change string) |
| 45 | +|> changeset.validate_number("age", Some(0), Some(120)) |
| 46 | +
|
| 47 | +// Email format |
| 48 | +|> changeset.validate_email("email") |
| 49 | +
|
| 50 | +// Not empty string |
| 51 | +|> changeset.validate_not_empty("name") |
| 52 | +
|
| 53 | +// Custom format check |
| 54 | +|> changeset.validate_format("slug", fn(v) { |
| 55 | + string.all(v, fn(c) { c == "-" || c >= "a" && c <= "z" || c >= "0" && c <= "9" }) |
| 56 | +}, "must contain only lowercase letters, digits, and hyphens") |
| 57 | +``` |
| 58 | + |
| 59 | +## Reading changes and errors |
| 60 | + |
| 61 | +```gleam |
| 62 | +changeset.is_valid(cs) // => True / False |
| 63 | +changeset.get_changes(cs) // => Dict(String, String) |
| 64 | +changeset.get_errors(cs) // => List(FieldError) |
| 65 | +
|
| 66 | +case changeset.get_change(cs, "name") { |
| 67 | + Ok(name) -> io.println("name is: " <> name) |
| 68 | + Error(Nil) -> io.println("name not set") |
| 69 | +} |
| 70 | +``` |
| 71 | + |
| 72 | +## Manual errors |
| 73 | + |
| 74 | +```gleam |
| 75 | +let cs = |
| 76 | + changeset.new(data) |
| 77 | + |> changeset.put_change("email", "alice@example.com") |
| 78 | + |> changeset.add_error("email", "already taken") |
| 79 | +
|
| 80 | +changeset.format_errors(changeset.get_errors(cs)) |
| 81 | +// => "email: already taken" |
| 82 | +``` |
0 commit comments