diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam index fda173f7..0f56c8b0 100644 --- a/src/gleam/dynamic.gleam +++ b/src/gleam/dynamic.gleam @@ -290,6 +290,36 @@ pub fn result( } } +fn do_try_map_with_index( + list: List(a), + fun: fn(a) -> Result(b, e), + acc: List(b), + index: Int, +) -> Result(List(b), #(Int, e)) { + case list { + [] -> Ok(list.reverse(acc)) + [x, ..xs] -> + case fun(x) { + Ok(y) -> do_try_map_with_index(xs, fun, [y, ..acc], index + 1) + Error(error) -> Error(#(index, error)) + } + } +} + +/// This is the same as list.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_with_index([[1], [], [2]], first) +/// // -> Error(#(1, Nil)) +/// ``` +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) +} + /// Checks to see whether a `Dynamic` value is a list of a particular type, and /// returns that list if it is. /// @@ -309,7 +339,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 @@ -322,9 +352,11 @@ 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 = try_map_with_index(list, decoder_type) + case result { + Ok(values) -> Ok(values) + Error(#(index, errors)) -> Error(list.map(errors, push_path(_, index))) + } } } diff --git a/test/gleam/dynamic_test.gleam b/test/gleam/dynamic_test.gleam index 4b6c7975..715ae958 100644 --- a/test/gleam/dynamic_test.gleam +++ b/test/gleam/dynamic_test.gleam @@ -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"])]), ) }