Skip to content

Commit

Permalink
Return index of undecodable element in dynamic.list DecodeError path
Browse files Browse the repository at this point in the history
  • Loading branch information
johtso committed Jun 9, 2024
1 parent 3ef0a87 commit 7beeb3a
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 6 deletions.
11 changes: 7 additions & 4 deletions src/gleam/dynamic.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ pub fn result(
///
/// ```gleam
/// from([1, 2, 3]) |> list(of: string)
/// // -> Error([DecodeError(expected: "String", found: "Int", path: ["*"])])
/// // -> Error([DecodeError(expected: "String", found: "Int", path: ["0"])])
/// ```
///
/// ```gleam
Expand All @@ -322,9 +322,12 @@ pub fn list(
) -> Decoder(List(inner)) {
fn(dynamic) {
use list <- result.try(shallow_list(dynamic))
list
|> list.try_map(decoder_type)
|> map_errors(push_path(_, "*"))
let result = list.try_map_with_index(list, decoder_type)
case result {
Ok(values) -> Ok(values)
Error(#(index, errors)) ->
Error(list.map(errors, push_path(_, int.to_string(index))))
}
}
}

Expand Down
30 changes: 30 additions & 0 deletions src/gleam/list.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,36 @@ pub fn try_map(
do_try_map(list, fun, [])
}

fn do_try_map_with_index(
list: List(a),
fun: fn(a) -> Result(b, e),
acc: List(b),
i: Int,
) -> Result(List(b), #(Int, e)) {
case list {
[] -> Ok(reverse(acc))
[x, ..xs] ->
case fun(x) {
Ok(y) -> do_try_map_with_index(xs, fun, [y, ..acc], i + 1)
Error(error) -> Error(#(i, error))
}
}
}

/// This is the same as try_map but rather than just returning an error it
/// returns a tuple of the index of the element that failed and the error.
///
/// ```gleam
/// try_map([[1], [], [2]], first)
/// // -> Error(#(1, Nil))
/// ```
pub fn try_map_with_index(
over list: List(a),
with fun: fn(a) -> Result(b, e),
) -> Result(List(b), #(Int, e)) {
do_try_map_with_index(list, fun, [], 0)
}

/// Returns a list that is the given list with up to the given number of
/// elements removed from the front of the list.
///
Expand Down
4 changes: 2 additions & 2 deletions test/gleam/dynamic_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,14 @@ pub fn list_test() {
|> dynamic.from
|> dynamic.list(dynamic.int)
|> should.equal(
Error([DecodeError(expected: "Int", found: "String", path: ["*"])]),
Error([DecodeError(expected: "Int", found: "String", path: ["0"])]),
)

[dynamic.from(1), dynamic.from("not an int")]
|> dynamic.from
|> dynamic.list(dynamic.int)
|> should.equal(
Error([DecodeError(expected: "Int", found: "String", path: ["*"])]),
Error([DecodeError(expected: "Int", found: "String", path: ["1"])]),
)
}

Expand Down
25 changes: 25 additions & 0 deletions test/gleam/list_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,31 @@ pub fn try_map_test() {
|> list.try_map(fun)
}

pub fn try_map_with_index_test() {
let fun = fn(x) {
case x == 6 || x == 5 || x == 4 {
True -> Ok(x * 2)
False -> Error(x)
}
}

[5, 6, 5, 6]
|> list.try_map_with_index(fun)
|> should.equal(Ok([10, 12, 10, 12]))

[4, 6, 5, 7, 3]
|> list.try_map_with_index(fun)
|> should.equal(Error(#(3, 7)))

[7, 6, 5]
|> list.try_map_with_index(fun)
|> should.equal(Error(#(0, 7)))

// TCO test
list.repeat(6, recursion_test_cycles)
|> list.try_map_with_index(fun)
}

pub fn drop_test() {
[]
|> list.drop(5)
Expand Down

0 comments on commit 7beeb3a

Please sign in to comment.