diff options
author | rubytree <rt@rubytree.me> | 2023-03-30 17:41:10 +0200 |
---|---|---|
committer | Louis Pilfold <louis@lpil.uk> | 2023-05-13 16:32:38 +0100 |
commit | ae0021a2025c9adc6e336cacab750dd2271365e2 (patch) | |
tree | a3657fc66a6cee8de87d44c3a19c99e149ea03d7 /src | |
parent | 8e92e3e87602f6cbb38291dee18d9068148c9da6 (diff) | |
download | gleam_stdlib-ae0021a2025c9adc6e336cacab750dd2271365e2.tar.gz gleam_stdlib-ae0021a2025c9adc6e336cacab750dd2271365e2.zip |
Fixes #420
Diffstat (limited to 'src')
-rw-r--r-- | src/gleam/dynamic.gleam | 160 | ||||
-rw-r--r-- | src/gleam_stdlib.erl | 5 | ||||
-rw-r--r-- | src/gleam_stdlib.mjs | 4 |
3 files changed, 150 insertions, 19 deletions
diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam index b500ad6..14ceb2f 100644 --- a/src/gleam/dynamic.gleam +++ b/src/gleam/dynamic.gleam @@ -610,7 +610,7 @@ fn at_least_decode_tuple_error( data: Dynamic, ) -> Result(a, DecodeErrors) { let s = case size { - 0 -> "" + s if s <= 1 -> "" _ -> "s" } let error = @@ -621,6 +621,22 @@ fn at_least_decode_tuple_error( Error([error]) } +fn exact_decode_list_error(size: Int, data: Dynamic) -> Result(a, DecodeErrors) { + let expected = list_size_error_msg(size) + let error = DecodeError(expected: expected, found: classify(data), path: []) + Error([error]) +} + +fn list_size_error_msg(size: Int) -> String { + let s = case size { + s if s <= 1 -> "" + _ -> "s" + } + ["List of ", int.to_string(size), " element", s] + |> string_builder.from_strings + |> string_builder.to_string +} + // A tuple of unknown size external type UnknownTuple @@ -633,6 +649,9 @@ if erlang { external fn tuple_size(UnknownTuple) -> Int = "gleam_stdlib" "size_of_tuple" + + external fn list_to_tuple(Dynamic) -> Result(Dynamic, DecodeErrors) = + "gleam_stdlib" "list_to_tuple" } if javascript { @@ -644,6 +663,9 @@ if javascript { external fn tuple_size(UnknownTuple) -> Int = "../gleam_stdlib.mjs" "length" + + external fn list_to_tuple(Dynamic) -> Result(Dynamic, DecodeErrors) = + "../gleam_stdlib.mjs" "list_to_tuple" } fn tuple_errors( @@ -656,10 +678,32 @@ fn tuple_errors( } } +fn ensure_tuple( + value: Dynamic, + desired_size: Int, +) -> Result(Dynamic, DecodeErrors) { + do_ensure_tuple(classify(value), value, desired_size) +} + +fn do_ensure_tuple( + value_type: String, + value: Dynamic, + desired_size: Int, +) -> Result(Dynamic, DecodeErrors) { + case value_type { + "Tuple" <> _ -> assert_is_tuple(value, desired_size) + "List" -> { + use _ <- result.then(assert_is_list(value, desired_size)) + list_to_tuple(value) + } + _ -> exact_decode_tuple_error(desired_size, value) + } +} + fn assert_is_tuple( value: Dynamic, desired_size: Int, -) -> Result(Nil, DecodeErrors) { +) -> Result(Dynamic, DecodeErrors) { let expected = string_builder.to_string(string_builder.from_strings([ "Tuple of ", @@ -671,11 +715,31 @@ fn assert_is_tuple( put_expected(_, expected), )) case tuple_size(tuple) { - size if size == desired_size -> Ok(Nil) + size if size == desired_size -> Ok(value) _ -> exact_decode_tuple_error(desired_size, value) } } +fn assert_is_list( + value: Dynamic, + desired_size: Int, +) -> Result(List(Dynamic), DecodeErrors) { + let expected = list_size_error_msg(desired_size) + use list <- result.then(map_errors( + decode_list(value), + put_expected(_, expected), + )) + case list.length(list) { + size if size == desired_size -> Ok(list) + size if size != desired_size -> { + let found = list_size_error_msg(size) + let error = DecodeError(expected: expected, found: found, path: []) + Error([error]) + } + _ -> exact_decode_list_error(desired_size, value) + } +} + fn put_expected(error: DecodeError, expected: String) -> DecodeError { DecodeError(..error, expected: expected) } @@ -693,7 +757,7 @@ fn push_path(error: DecodeError, name: t) -> DecodeError { DecodeError(..error, path: [name, ..error.path]) } -/// Checks to see if a `Dynamic` value is a 2 element tuple containing +/// Checks to see if a `Dynamic` value is a 2-element tuple, list or array containing /// specifically typed elements. /// /// ## Examples @@ -711,6 +775,18 @@ fn push_path(error: DecodeError, name: t) -> DecodeError { /// ``` /// /// ```gleam +/// > from([1, 2]) +/// > |> tuple2(int, int) +/// Ok(#(1, 2)) +/// ``` +/// +/// ```gleam +/// > from([from(1), from(2.0)]) +/// > |> tuple2(int, float) +/// Ok(#(1, 2.0)) +/// ``` +/// +/// ```gleam /// > from(#(1, 2, 3)) /// > |> tuple2(int, float) /// Error([ @@ -729,8 +805,8 @@ pub fn tuple2( second decode2: Decoder(b), ) -> Decoder(#(a, b)) { fn(value) { - use _ <- result.try(assert_is_tuple(value, 2)) - let #(a, b) = unsafe_coerce(value) + use tuple <- result.try(ensure_tuple(value, 2)) + let #(a, b) = unsafe_coerce(tuple) case decode1(a), decode2(b) { Ok(a), Ok(b) -> Ok(#(a, b)) a, b -> @@ -741,7 +817,7 @@ pub fn tuple2( } } -/// Checks to see if a `Dynamic` value is a 3-element tuple containing +/// Checks to see if a `Dynamic` value is a 3-element tuple, list or array containing /// specifically typed elements. /// /// ## Examples @@ -759,6 +835,18 @@ pub fn tuple2( /// ``` /// /// ```gleam +/// > from([1, 2, 3]) +/// > |> tuple3(int, int, int) +/// Ok(#(1, 2, 3)) +/// ``` +/// +/// ```gleam +/// > from([from(1), from(2.0), from("3")]) +/// > |> tuple2(int, float, string) +/// Ok(#(1, 2.0, "3")) +/// ``` +/// +/// ```gleam /// > from(#(1, 2)) /// > |> tuple3(int, float, string) /// Error([ @@ -780,8 +868,8 @@ pub fn tuple3( third decode3: Decoder(c), ) -> Decoder(#(a, b, c)) { fn(value) { - use _ <- result.try(assert_is_tuple(value, 3)) - let #(a, b, c) = unsafe_coerce(value) + use tuple <- result.try(ensure_tuple(value, 3)) + let #(a, b, c) = unsafe_coerce(tuple) case decode1(a), decode2(b), decode3(c) { Ok(a), Ok(b), Ok(c) -> Ok(#(a, b, c)) a, b, c -> @@ -793,7 +881,7 @@ pub fn tuple3( } } -/// Checks to see if a `Dynamic` value is a 4 element tuple containing +/// Checks to see if a `Dynamic` value is a 4-element tuple, list or array containing /// specifically typed elements. /// /// ## Examples @@ -809,6 +897,18 @@ pub fn tuple3( /// > |> tuple4(int, float, string, int) /// Ok(#(1, 2.0, "3", 4)) /// +/// ```gleam +/// > from([1, 2, 3, 4]) +/// > |> tuple3(int, int, int, int) +/// Ok(#(1, 2, 3, 4)) +/// ``` +/// +/// ```gleam +/// > from([from(1), from(2.0), from("3"), from(4)]) +/// > |> tuple2(int, float, string, int) +/// Ok(#(1, 2.0, "3", 4)) +/// ``` +/// /// > from(#(1, 2)) /// > |> tuple4(int, float, string, int) /// Error([ @@ -831,8 +931,8 @@ pub fn tuple4( fourth decode4: Decoder(d), ) -> Decoder(#(a, b, c, d)) { fn(value) { - use _ <- result.try(assert_is_tuple(value, 4)) - let #(a, b, c, d) = unsafe_coerce(value) + use tuple <- result.try(ensure_tuple(value, 4)) + let #(a, b, c, d) = unsafe_coerce(tuple) case decode1(a), decode2(b), decode3(c), decode4(d) { Ok(a), Ok(b), Ok(c), Ok(d) -> Ok(#(a, b, c, d)) a, b, c, d -> @@ -845,7 +945,7 @@ pub fn tuple4( } } -/// Checks to see if a `Dynamic` value is a 5-element tuple containing +/// Checks to see if a `Dynamic` value is a 5-element tuple, list or array containing /// specifically typed elements. /// /// ## Examples @@ -863,6 +963,18 @@ pub fn tuple4( /// ``` /// /// ```gleam +/// > from([1, 2, 3, 4, 5]) +/// > |> tuple3(int, int, int, int, int) +/// Ok(#(1, 2, 3, 4, 5)) +/// ``` +/// +/// ```gleam +/// > from([from(1), from(2.0), from("3"), from(4), from(True)]) +/// > |> tuple2(int, float, string, int, bool) +/// Ok(#(1, 2.0, "3", 4, True)) +/// ``` +/// +/// ```gleam /// > from(#(1, 2)) /// > |> tuple5(int, float, string, int, int) /// Error([ @@ -882,8 +994,8 @@ pub fn tuple5( fifth decode5: Decoder(e), ) -> Decoder(#(a, b, c, d, e)) { fn(value) { - use _ <- result.try(assert_is_tuple(value, 5)) - let #(a, b, c, d, e) = unsafe_coerce(value) + use tuple <- result.try(ensure_tuple(value, 5)) + let #(a, b, c, d, e) = unsafe_coerce(tuple) case decode1(a), decode2(b), decode3(c), decode4(d), decode5(e) { Ok(a), Ok(b), Ok(c), Ok(d), Ok(e) -> Ok(#(a, b, c, d, e)) a, b, c, d, e -> @@ -897,7 +1009,7 @@ pub fn tuple5( } } -/// Checks to see if a `Dynamic` value is a 6-element tuple containing +/// Checks to see if a `Dynamic` value is a 6-element tuple, list or array containing /// specifically typed elements. /// /// ## Examples @@ -915,6 +1027,18 @@ pub fn tuple5( /// ``` /// /// ```gleam +/// > from([1, 2, 3, 4, 5, 6]) +/// > |> tuple3(int, int, int, int, int, int) +/// Ok(#(1, 2, 3, 4, 5, 6)) +/// ``` +/// +/// ```gleam +/// > from([from(1), from(2.0), from("3"), from(4), from(True), from(False)]) +/// > |> tuple2(int, float, string, int, bool, bool) +/// Ok(#(1, 2.0, "3", 4, True, False)) +/// ``` +/// +/// ```gleam /// > from(#(1, 2)) /// > |> tuple6(int, float, string, int, int, int) /// Error([ @@ -937,8 +1061,8 @@ pub fn tuple6( sixth decode6: Decoder(f), ) -> Decoder(#(a, b, c, d, e, f)) { fn(value) { - use _ <- result.try(assert_is_tuple(value, 6)) - let #(a, b, c, d, e, f) = unsafe_coerce(value) + use tuple <- result.try(ensure_tuple(value, 6)) + let #(a, b, c, d, e, f) = unsafe_coerce(tuple) case decode1(a), decode2(b), diff --git a/src/gleam_stdlib.erl b/src/gleam_stdlib.erl index e8746a2..2ede96a 100644 --- a/src/gleam_stdlib.erl +++ b/src/gleam_stdlib.erl @@ -11,7 +11,7 @@ base_decode64/1, parse_query/1, bit_string_concat/1, size_of_tuple/1, decode_tuple/1, tuple_get/2, classify_dynamic/1, print/1, println/1, print_error/1, println_error/1, inspect/1, float_to_string/1, - int_from_base_string/2]). + int_from_base_string/2, list_to_tuple/1]). %% Taken from OTP's uri_string module -define(DEC2HEX(X), @@ -418,3 +418,6 @@ inspect_maybe_utf8_string(Binary, Acc) -> float_to_string(Float) when is_float(Float) -> erlang:iolist_to_binary(io_lib_format:fwrite_g(Float)). + +list_to_tuple(Data) when is_list(Data) -> {ok, erlang:list_to_tuple(Data)}; +list_to_tuple(Data) -> decode_error_msg(<<"List">>, Data).
\ No newline at end of file diff --git a/src/gleam_stdlib.mjs b/src/gleam_stdlib.mjs index bbbd06a..102158c 100644 --- a/src/gleam_stdlib.mjs +++ b/src/gleam_stdlib.mjs @@ -616,6 +616,10 @@ export function decode_tuple(data) { return Array.isArray(data) ? new Ok(data) : decoder_error("Tuple", data); } +export function list_to_tuple(data) { + return List.isList(data) ? new Ok([...data]) : decoder_error("List", data); +} + export function tuple_get(data, index) { return index >= 0 && data.length > index ? new Ok(data[index]) |