diff options
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | src/gleam/dynamic.gleam | 63 | ||||
-rw-r--r-- | src/gleam/uri.gleam | 13 | ||||
-rw-r--r-- | src/gleam_stdlib.erl | 20 | ||||
-rw-r--r-- | test/gleam/dynamic_test.gleam | 49 |
5 files changed, 134 insertions, 12 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index d7c2d28..7926881 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ `ends_with`, `slice`, `pad_left` and `pad_right` functions. - `uri` module created with `parse`, `parse_query`, `path_segments`, `query_to_string` and `to_string`. +- The `dynamic` module gains the `tuple2`, and `tuple2_of` functions. ## v0.8.0 - 2020-04-28 diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam index bab30d3..e7e665d 100644 --- a/src/gleam/dynamic.gleam +++ b/src/gleam/dynamic.gleam @@ -7,6 +7,9 @@ import gleam/result /// IO with the outside world. pub external type Dynamic +pub type Decoder(t) = + fn(Dynamic) -> Result(t, String) + /// Convert any Gleam data into `Dynamic` data. /// pub external fn from(a) -> Dynamic = @@ -158,7 +161,7 @@ pub external fn field(from: Dynamic, named: a) -> Result(Dynamic, String) = /// ## Examples /// /// > element(from(tuple(1, 2)), 0) -/// Ok(Dynamic) +/// Ok(from(1)) /// /// > element(from(tuple(1, 2)), 2) /// Error("Expected a tuple of at least 3 size, got a tuple of 2 size") @@ -171,3 +174,61 @@ pub external fn element( position: Int, ) -> Result(Dynamic, String) = "gleam_stdlib" "decode_element" + +/// Check to see if the Dynamic value is a 2 element tuple. +/// +/// ## Examples +/// +/// > tuple2(from(tuple(1, 2))) +/// Ok(tuple(from(1), from(2))) +/// +/// > tuple2(from(tuple(1, 2))) +/// Error("Expected a 2 element tuple") +/// +/// > tuple2(from("")) +/// Error("Expected a Tuple, got a binary") +/// +pub external fn tuple2( + from: Dynamic, +) -> Result(tuple(Dynamic, Dynamic), String) = + "gleam_stdlib" "decode_tuple2" + +/// Check to see if the Dynamic value is a 2 element tuple containing two +/// specifically typed elements. +/// +/// ## Examples +/// +/// > tuple2_of(from(tuple(1, 2)), int, int) +/// Ok(tuple(1, 2)) +/// +/// > tuple2_of(from(tuple(1, 2.0)), int, float) +/// Ok(tuple(1, 2.0)) +/// +/// > tuple2_of(from(tuple(1, 2)), int, float) +/// Error("Expected a 2 element tuple") +/// +/// > tuple2_of(from(""), int, float) +/// Error("Expected a Tuple, got a binary") +/// +pub fn tuple2_of( + from tup: Dynamic, + first decode_first: Decoder(a), + second decode_second: Decoder(b), +) -> Result(tuple(a, b), String) { + tup + |> tuple2 + |> result.then( + fn(tup) { + let tuple(first, second) = tup + decode_first(first) + |> result.map(fn(first) { tuple(first, second) }) + }, + ) + |> result.then( + fn(tup) { + let tuple(first, second) = tup + decode_second(second) + |> result.map(fn(second) { tuple(first, second) }) + }, + ) +} diff --git a/src/gleam/uri.gleam b/src/gleam/uri.gleam index 21145df..8536d98 100644 --- a/src/gleam/uri.gleam +++ b/src/gleam/uri.gleam @@ -35,15 +35,20 @@ pub type Uri { pub external fn parse(String) -> Result(Uri, Nil) = "gleam_uri_native" "parse" +external fn erl_parse_query(String) -> Dynamic = + "uri_string" "dissect_query" + /// Parses an urlencoded query string into a list of key value pairs. /// Returns an error for invalid encoding. /// /// The opposite operation is `uri.query_to_string`. /// -pub external fn parse_query( - String, -) -> Result(List(tuple(String, String)), Nil) = - "gleam_uri_native" "parse_query" +pub fn parse_query(query: String) -> Option(List(tuple(String, String))) { + query + |> erl_parse_query + |> dynamic.list(dynamic.tuple2_of(_, dynamic.string, dynamic.string)) + |> result.map_error(fn(_) { Nil }) +} type Encoding { Utf8 diff --git a/src/gleam_stdlib.erl b/src/gleam_stdlib.erl index bbf7630..e12ee87 100644 --- a/src/gleam_stdlib.erl +++ b/src/gleam_stdlib.erl @@ -1,13 +1,15 @@ -module(gleam_stdlib). -include_lib("eunit/include/eunit.hrl"). --export([should_equal/2, should_not_equal/2, should_be_true/1, should_be_false/1, - should_be_ok/1, should_be_error/1, atom_from_string/1, - atom_create_from_string/1, atom_to_string/1, map_get/2, - iodata_append/2, iodata_prepend/2, identity/1, decode_int/1, - decode_string/1, decode_bool/1, decode_float/1, decode_thunk/1, decode_atom/1, - decode_list/1, decode_field/2, decode_element/2, parse_int/1, parse_float/1, compare_strings/2, - string_contains/2, string_starts_with/2, string_ends_with/2, string_pad/4]). +-export([should_equal/2, should_not_equal/2, should_be_true/1, + should_be_false/1, should_be_ok/1, should_be_error/1, + atom_from_string/1, atom_create_from_string/1, atom_to_string/1, + map_get/2, iodata_append/2, iodata_prepend/2, identity/1, + decode_int/1, decode_string/1, decode_bool/1, decode_float/1, + decode_thunk/1, decode_atom/1, decode_list/1, decode_field/2, + decode_element/2, parse_int/1, parse_float/1, compare_strings/2, + string_contains/2, string_starts_with/2, string_ends_with/2, + string_pad/4, decode_tuple2/1]). should_equal(Actual, Expected) -> ?assertEqual(Expected, Actual). should_not_equal(Actual, Expected) -> ?assertNotEqual(Expected, Actual). @@ -48,8 +50,12 @@ classify(X) when is_float(X) -> "a float"; classify(X) when is_list(X) -> "a list"; classify(X) when is_boolean(X) -> "a bool"; classify(X) when is_function(X, 0) -> "a zero arity function"; +classify(X) when is_tuple(X) -> ["a ", integer_to_list(tuple_size(X)), " element tuple"]; classify(_) -> "some other type". +decode_tuple2({_, _} = T) -> {ok, T}; +decode_tuple2(Data) -> decode_error_msg("a 2 element tuple", Data). + decode_atom(Data) when is_atom(Data) -> {ok, Data}; decode_atom(Data) -> decode_error_msg("an atom", Data). diff --git a/test/gleam/dynamic_test.gleam b/test/gleam/dynamic_test.gleam index 4f74556..4f314e9 100644 --- a/test/gleam/dynamic_test.gleam +++ b/test/gleam/dynamic_test.gleam @@ -255,3 +255,52 @@ pub fn element_test() { |> dynamic.element(0) |> should.be_error } + +pub fn tuple2_test() { + tuple(1, 2) + |> dynamic.from + |> dynamic.tuple2 + |> should.equal(Ok(tuple(dynamic.from(1), dynamic.from(2)))) + + tuple(1, "") + |> dynamic.from + |> dynamic.tuple2 + |> should.equal(Ok(tuple(dynamic.from(1), dynamic.from("")))) + + tuple(1, 2, 3) + |> dynamic.from + |> dynamic.tuple2 + |> should.equal(Error("Expected a 2 element tuple, got a 3 element tuple")) + + 1 + |> dynamic.from + |> dynamic.tuple2 + |> should.equal(Error("Expected a 2 element tuple, got an int")) +} + +pub fn tuple2_of_test() { + tuple(1, 2) + |> dynamic.from + |> dynamic.tuple2_of(dynamic.int, dynamic.int) + |> should.equal(Ok(tuple(1, 2))) + + tuple(1, "") + |> dynamic.from + |> dynamic.tuple2_of(dynamic.int, dynamic.string) + |> should.equal(Ok(tuple(1, ""))) + + tuple(1, "") + |> dynamic.from + |> dynamic.tuple2_of(dynamic.int, dynamic.int) + |> should.equal(Error("Expected an int, got a binary")) + + tuple(1, 2, 3) + |> dynamic.from + |> dynamic.tuple2 + |> should.equal(Error("Expected a 2 element tuple, got a 3 element tuple")) + + 1 + |> dynamic.from + |> dynamic.tuple2 + |> should.equal(Error("Expected a 2 element tuple, got an int")) +} |