aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md3
-rw-r--r--src/gleam/dynamic.gleam81
-rw-r--r--test/gleam/dynamic_test.gleam51
3 files changed, 129 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 348df3f..00945c2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,8 @@
- The `int` and `float` modules gain the `negate` function.
- The `int` module gains the `to_float` function.
- The `result` module gains the `all` function.
-- The `dynamic` module gains the `option` function.
+- The `dynamic` module gains the `option`, `result` and `typed_result`
+ functions.
- The `uri` module gains the `percent_encode` and `percent_decode` functions.
- The `os` module gains the `erlang_timestamp` function.
- The `iterator` module gains the `append`, `flatten`, `flat_map`, `step`,
diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam
index e4656c4..e4b67b6 100644
--- a/src/gleam/dynamic.gleam
+++ b/src/gleam/dynamic.gleam
@@ -1,9 +1,10 @@
+import gleam/atom as atom_mod
import gleam/bit_string.{BitString} as bit_string_mod
import gleam/list as list_mod
-import gleam/atom as atom_mod
import gleam/map.{Map}
-import gleam/result
import gleam/option.{None, Option, Some}
+import gleam/result as result_mod
+import gleam/string_builder
/// `Dynamic` data is data that we don"t know the type of yet.
/// We likely get data like this from interop with Erlang, or from
@@ -55,7 +56,7 @@ pub external fn bit_string(from: Dynamic) -> Result(BitString, String) =
///
pub fn string(from: Dynamic) -> Result(String, String) {
bit_string(from)
- |> result.then(fn(raw) {
+ |> result_mod.then(fn(raw) {
case bit_string_mod.to_string(raw) {
Ok(string) -> Ok(string)
Error(Nil) -> Error("Expected a string, got a bit_string")
@@ -153,6 +154,76 @@ pub external fn thunk(from: Dynamic) -> Result(fn() -> Dynamic, String) =
pub external fn list(from: Dynamic) -> Result(List(Dynamic), String) =
"gleam_stdlib" "decode_list"
+/// Check to see whether a Dynamic value is a result, and return the result if
+/// it is
+///
+/// ## Examples
+///
+/// > result(from(Ok(1)))
+/// Ok(Ok(from(1)))
+///
+/// > result(from(Error("boom")))
+/// Ok(Error(from("boom")))
+///
+/// > result(from(123))
+/// Error("Expected a 2 element tuple, got an int")
+///
+pub fn result(from: Dynamic) -> Result(Result(Dynamic, Dynamic), String) {
+ try tuple(key, val) = tuple2(from)
+ try tag = atom(key)
+
+ let ok_atom = atom_mod.create_from_string("ok")
+ let error_atom = atom_mod.create_from_string("error")
+
+ case tag {
+ tag if tag == ok_atom -> Ok(Ok(val))
+ tag if tag == error_atom -> Ok(Error(val))
+ tag ->
+ "Expected a tag of \"ok\" or \"error\", got \""
+ |> string_builder.from_string
+ |> string_builder.append(atom_mod.to_string(tag))
+ |> string_builder.append("\"")
+ |> string_builder.to_string
+ |> Error
+ }
+}
+
+/// Check to see whether a Dynamic value is a result of a particular type, and
+/// return the result if it is
+///
+/// The `ok` and `error` arguments are decoders for decoding the `Ok` and
+/// `Error` values of the result.
+///
+/// ## Examples
+///
+/// > typed_result(of: from(Ok(1)), ok: int, error: string)
+/// Ok(Ok(1))
+///
+/// > typed_result(of: from(Error("boom")), ok: int, error: string)
+/// Ok(Error("boom"))
+///
+/// > typed_result(of: from(123), ok: int, error: string)
+/// Error("Expected a 2 element tuple, got an int")
+///
+pub fn typed_result(
+ of dynamic: Dynamic,
+ ok decode_ok: Decoder(a),
+ error decode_error: Decoder(e),
+) -> Result(Result(a, e), String) {
+ try inner_result = result(dynamic)
+
+ case inner_result {
+ Ok(raw) ->
+ raw
+ |> decode_ok
+ |> result_mod.map(Ok)
+ Error(raw) ->
+ raw
+ |> decode_error
+ |> result_mod.map(Error)
+ }
+}
+
/// Check to see whether a Dynamic value is a list of a particular type, and
/// return the list if it is.
///
@@ -180,7 +251,7 @@ pub fn typed_list(
) -> Result(List(inner), String) {
dynamic
|> list
- |> result.then(list_mod.try_map(_, decoder_type))
+ |> result_mod.then(list_mod.try_map(_, decoder_type))
}
/// Check to see if a Dynamic value is an Option of a particular type, and return
@@ -339,5 +410,5 @@ pub fn any(
) -> Result(t, String) {
decoders
|> list_mod.find_map(fn(decoder) { decoder(data) })
- |> result.map_error(fn(_) { "Unexpected value" })
+ |> result_mod.map_error(fn(_) { "Unexpected value" })
}
diff --git a/test/gleam/dynamic_test.gleam b/test/gleam/dynamic_test.gleam
index f4e78a0..d8b3be4 100644
--- a/test/gleam/dynamic_test.gleam
+++ b/test/gleam/dynamic_test.gleam
@@ -389,3 +389,54 @@ pub fn list_test() {
|> dynamic.list
|> should.equal(Error("Expected a list, got an int"))
}
+
+pub fn result_test() {
+ Ok(1)
+ |> dynamic.from
+ |> dynamic.result
+ |> should.equal(Ok(Ok(dynamic.from(1))))
+
+ Error("error")
+ |> dynamic.from
+ |> dynamic.result
+ |> should.equal(Ok(Error(dynamic.from("error"))))
+
+ 1
+ |> dynamic.from
+ |> dynamic.result
+ |> should.equal(Error("Expected a 2 element tuple, got an int"))
+
+ let tag = atom.create_from_string("bad")
+
+ tuple(tag, "value")
+ |> dynamic.from
+ |> dynamic.result
+ |> should.equal(Error("Expected a tag of \"ok\" or \"error\", got \"bad\""))
+}
+
+pub fn typed_result_test() {
+ Ok(1)
+ |> dynamic.from
+ |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string)
+ |> should.equal(Ok(Ok(1)))
+
+ Error("error")
+ |> dynamic.from
+ |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string)
+ |> should.equal(Ok(Error("error")))
+
+ Ok("1")
+ |> dynamic.from
+ |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string)
+ |> should.equal(Error("Expected an int, got a binary"))
+
+ Error(1)
+ |> dynamic.from
+ |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string)
+ |> should.equal(Error("Expected a bit_string, got an int"))
+
+ 1
+ |> dynamic.from
+ |> dynamic.typed_result(ok: dynamic.int, error: dynamic.string)
+ |> should.equal(Error("Expected a 2 element tuple, got an int"))
+}