diff options
-rw-r--r-- | src/gleam/dynamic.gleam | 76 | ||||
-rw-r--r-- | test/gleam/dynamic_test.gleam | 26 |
2 files changed, 73 insertions, 29 deletions
diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam index 30a297c..e75f1ad 100644 --- a/src/gleam/dynamic.gleam +++ b/src/gleam/dynamic.gleam @@ -833,34 +833,52 @@ if javascript { "../gleam_stdlib.mjs" "decode_map" } -if erlang { - /// Joins multiple decoders into one. When run they will each be tried in turn - /// until one succeeds, or they all fail. - /// - /// ## Examples - /// - /// > import gleam/result - /// > let bool_or_string = any(_, of: [ - /// > string, - /// > fn(x) { result.map(bool(x), fn(_) { "a bool" }) } - /// > ]) - /// > bool_or_string(from("ok")) - /// Ok("ok") - /// - /// > bool_or_string(from(True)) - /// Ok("a bool") - /// - /// > bool_or_string(from(1)) - /// Error(DecodeError(expected: "unknown", found: "unknown")) - /// - pub fn any( - from data: Dynamic, - of decoders: List(Decoder(t)), - ) -> Result(t, DecodeError) { - decoders - |> list.find_map(fn(decoder) { decoder(data) }) - |> result.map_error(fn(_) { - DecodeError(expected: "unknown", found: "unknown") - }) +/// Joins multiple decoders into one. When run they will each be tried in turn +/// until one succeeds, or they all fail. +/// +/// ## Examples +/// +/// > import gleam/result +/// > let bool_or_string = any(_, of: [ +/// > string, +/// > fn(x) { result.map(bool(x), fn(_) { "a bool" }) } +/// > ]) +/// > bool_or_string(from("ok")) +/// Ok("ok") +/// +/// > bool_or_string(from(True)) +/// Ok("a bool") +/// +/// > bool_or_string(from(1)) +/// Error(DecodeError(expected: "unknown", found: "unknown")) +/// +pub fn any( + from data: Dynamic, + of decoders: List(Decoder(t)), +) -> Result(t, DecodeError) { + do_any(data, decoders, []) +} + +fn do_any( + data: Dynamic, + decoders: List(Decoder(a)), + failed: List(String), +) -> Result(a, DecodeError) { + case decoders { + [] -> { + let expected = + failed + |> list.intersperse(" or ") + |> string_builder.from_strings + |> string_builder.to_string + Error(DecodeError(found: classify(data), expected: expected)) + } + + [decoder, ..decoders] -> + case decoder(data) { + Ok(decoded) -> Ok(decoded) + Error(DecodeError(expected: expected, ..)) -> + do_any(data, decoders, [expected, ..failed]) + } } } diff --git a/test/gleam/dynamic_test.gleam b/test/gleam/dynamic_test.gleam index 57dfdae..8529b9a 100644 --- a/test/gleam/dynamic_test.gleam +++ b/test/gleam/dynamic_test.gleam @@ -1,6 +1,7 @@ import gleam/should import gleam/dynamic.{DecodeError} import gleam/bit_string +import gleam/result import gleam/map import gleam/option.{None, Some} @@ -793,3 +794,28 @@ pub fn result_test() { |> dynamic.result(ok: dynamic.int, error: dynamic.string) |> should.equal(Error(DecodeError(expected: "Result", found: "Int"))) } + +pub fn any_test() { + let decoder = dynamic.any( + _, + [ + fn(x) { result.map(dynamic.int(x), fn(_) { "int" }) }, + fn(x) { result.map(dynamic.float(x), fn(_) { "float" }) }, + ], + ) + + 1 + |> dynamic.from + |> decoder + |> should.equal(Ok("int")) + + 1.1 + |> dynamic.from + |> decoder + |> should.equal(Ok("float")) + + "" + |> dynamic.from + |> decoder + |> should.equal(Error(DecodeError("Float or Int", "String"))) +} |