From 7beeb3afd82e8c72f0e6debefe0fe6970db9e0c7 Mon Sep 17 00:00:00 2001 From: Johannes Date: Sun, 9 Jun 2024 22:46:51 +0100 Subject: [PATCH 1/5] Return index of undecodable element in dynamic.list DecodeError path Closes #627 --- src/gleam/dynamic.gleam | 11 +++++++---- src/gleam/list.gleam | 30 ++++++++++++++++++++++++++++++ test/gleam/dynamic_test.gleam | 4 ++-- test/gleam/list_test.gleam | 25 +++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam index fda173f7..e21f38fd 100644 --- a/src/gleam/dynamic.gleam +++ b/src/gleam/dynamic.gleam @@ -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 @@ -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)))) + } } } diff --git a/src/gleam/list.gleam b/src/gleam/list.gleam index 83870b4c..4ccae5a6 100644 --- a/src/gleam/list.gleam +++ b/src/gleam/list.gleam @@ -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. /// 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"])]), ) } diff --git a/test/gleam/list_test.gleam b/test/gleam/list_test.gleam index 79795e0e..53530246 100644 --- a/test/gleam/list_test.gleam +++ b/test/gleam/list_test.gleam @@ -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) From 65c71d3deadf6d18296ac9d7444cdd5aaff9b31b Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 11 Jun 2024 13:21:06 +0100 Subject: [PATCH 2/5] No separate accumulator for the index --- src/gleam/list.gleam | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/gleam/list.gleam b/src/gleam/list.gleam index 4ccae5a6..ff54a25e 100644 --- a/src/gleam/list.gleam +++ b/src/gleam/list.gleam @@ -538,14 +538,13 @@ 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)) + Ok(y) -> do_try_map_with_index(xs, fun, [y, ..acc]) + Error(error) -> Error(#(length(acc), error)) } } } @@ -561,7 +560,7 @@ 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) + do_try_map_with_index(list, fun, []) } /// Returns a list that is the given list with up to the given number of From 4ac2843f1a9d226c57665ed5455b24bb534b8be8 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 12 Jun 2024 14:05:19 +0100 Subject: [PATCH 3/5] No new functions in list --- src/gleam/dynamic.gleam | 31 ++++++++++++++++++++++++++++++- src/gleam/list.gleam | 29 ----------------------------- test/gleam/list_test.gleam | 25 ------------------------- 3 files changed, 30 insertions(+), 55 deletions(-) diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam index e21f38fd..eb4f7932 100644 --- a/src/gleam/dynamic.gleam +++ b/src/gleam/dynamic.gleam @@ -290,6 +290,35 @@ pub fn result( } } +fn do_try_map_with_index( + list: List(a), + fun: fn(a) -> Result(b, e), + acc: List(b), +) -> 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]) + Error(error) -> Error(#(list.length(acc), 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, []) +} + /// Checks to see whether a `Dynamic` value is a list of a particular type, and /// returns that list if it is. /// @@ -322,7 +351,7 @@ pub fn list( ) -> Decoder(List(inner)) { fn(dynamic) { use list <- result.try(shallow_list(dynamic)) - let result = list.try_map_with_index(list, decoder_type) + let result = try_map_with_index(list, decoder_type) case result { Ok(values) -> Ok(values) Error(#(index, errors)) -> diff --git a/src/gleam/list.gleam b/src/gleam/list.gleam index ff54a25e..83870b4c 100644 --- a/src/gleam/list.gleam +++ b/src/gleam/list.gleam @@ -534,35 +534,6 @@ 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), -) -> 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]) - Error(error) -> Error(#(length(acc), 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, []) -} - /// Returns a list that is the given list with up to the given number of /// elements removed from the front of the list. /// diff --git a/test/gleam/list_test.gleam b/test/gleam/list_test.gleam index 53530246..79795e0e 100644 --- a/test/gleam/list_test.gleam +++ b/test/gleam/list_test.gleam @@ -239,31 +239,6 @@ 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) From 65297e03a56e204f14effc3e8f839c7919e614ca Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 17 Jun 2024 14:15:27 +0100 Subject: [PATCH 4/5] Keep track of index instead of using length --- src/gleam/dynamic.gleam | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam index eb4f7932..a731ede3 100644 --- a/src/gleam/dynamic.gleam +++ b/src/gleam/dynamic.gleam @@ -294,13 +294,14 @@ 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]) - Error(error) -> Error(#(list.length(acc), error)) + Ok(y) -> do_try_map_with_index(xs, fun, [y, ..acc], index + 1) + Error(error) -> Error(#(index, error)) } } } @@ -316,7 +317,7 @@ 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, []) + do_try_map_with_index(list, fun, [], 0) } /// Checks to see whether a `Dynamic` value is a list of a particular type, and From 28a002e69df690c440228cb59196485509951da2 Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 17 Jun 2024 14:28:47 +0100 Subject: [PATCH 5/5] Don't convert index to string --- src/gleam/dynamic.gleam | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam index a731ede3..0f56c8b0 100644 --- a/src/gleam/dynamic.gleam +++ b/src/gleam/dynamic.gleam @@ -355,8 +355,7 @@ pub fn list( let result = 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)))) + Error(#(index, errors)) -> Error(list.map(errors, push_path(_, index))) } } }