diff options
author | Louis Pilfold <louis@lpil.uk> | 2021-09-09 19:44:35 +0100 |
---|---|---|
committer | Louis Pilfold <louis@lpil.uk> | 2021-09-09 19:44:35 +0100 |
commit | c45c1038f8b12acf7d2010eba070ad3dc54765a2 (patch) | |
tree | 39ceed83733a3c54ba4048122a5b4d0b8ce823ed | |
parent | c5d415fdc206ae3403b2992c9a3e395188b45280 (diff) | |
download | gleam_stdlib-c45c1038f8b12acf7d2010eba070ad3dc54765a2.tar.gz gleam_stdlib-c45c1038f8b12acf7d2010eba070ad3dc54765a2.zip |
JS dynamic string
-rw-r--r-- | src/gleam/dynamic.gleam | 114 | ||||
-rw-r--r-- | src/gleam_stdlib.js | 23 | ||||
-rw-r--r-- | test/gleam/dynamic_test.gleam | 51 |
3 files changed, 129 insertions, 59 deletions
diff --git a/src/gleam/dynamic.gleam b/src/gleam/dynamic.gleam index 98cbef0..ccc621e 100644 --- a/src/gleam/dynamic.gleam +++ b/src/gleam/dynamic.gleam @@ -1,39 +1,66 @@ +import gleam/bit_string +import gleam/list +import gleam/map +import gleam/option +import gleam/result +import gleam/string_builder + +if erlang { + import gleam/map.{Map} + import gleam/option.{Option} +} + /// `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 /// IO with the outside world. pub external type Dynamic -if erlang { - import gleam/bit_string - import gleam/list - import gleam/map.{Map} - import gleam/option.{Option} - import gleam/result - import gleam/string_builder +/// Error returned when unexpected data is encountered +pub type DecodeError { + DecodeError(expected: String, found: String) +} - /// Error returned when unexpected data is encountered - pub type DecodeError { - DecodeError(expected: String, found: String) - } +pub type Decoder(t) = + fn(Dynamic) -> Result(t, DecodeError) - pub type Decoder(t) = - fn(Dynamic) -> Result(t, DecodeError) +/// Converts any Gleam data into `Dynamic` data. +/// +pub fn from(a) -> Dynamic { + do_from(a) +} - /// Converts any Gleam data into `Dynamic` data. - /// - pub external fn from(a) -> Dynamic = +if erlang { + external fn do_from(anything) -> Dynamic = "gleam_stdlib" "identity" +} - /// Unsafely casts a Dynamic value into any other type. - /// - /// This is an escape hatch for the type system that may be useful when wrapping - /// native Erlang APIs. It is to be used as a last measure only! - /// - /// If you can avoid using this function, do! - /// - pub external fn unsafe_coerce(Dynamic) -> a = +if javascript { + external fn do_from(anything) -> Dynamic = + "../gleam_stdlib.js" "identity" +} + +/// Unsafely casts a Dynamic value into any other type. +/// +/// This is an escape hatch for the type system that may be useful when wrapping +/// native Erlang APIs. It is to be used as a last measure only! +/// +/// If you can avoid using this function, do! +/// +pub fn unsafe_coerce(a: Dynamic) -> anything { + do_unsafe_coerce(a) +} + +if erlang { + external fn do_unsafe_coerce(Dynamic) -> a = "gleam_stdlib" "identity" +} +if javascript { + external fn do_unsafe_coerce(Dynamic) -> a = + "../gleam_stdlib.js" "identity" +} + +if erlang { /// Checks to see whether a Dynamic value is a bit_string, and return the bit_string if /// it is. /// @@ -47,20 +74,26 @@ if erlang { /// pub external fn bit_string(from: Dynamic) -> Result(BitString, DecodeError) = "gleam_stdlib" "decode_bit_string" +} - /// Checks to see whether a Dynamic value is a string, and return the string if - /// it is. - /// - /// ## Examples - /// - /// > string(from("Hello")) - /// Ok("Hello") - /// - /// > string(from(123)) - /// Error(DecodeError(expected: "String", found: "Int")) - /// - pub fn string(from: Dynamic) -> Result(String, DecodeError) { - bit_string(from) +/// Checks to see whether a Dynamic value is a string, and return the string if +/// it is. +/// +/// ## Examples +/// +/// > string(from("Hello")) +/// Ok("Hello") +/// +/// > string(from(123)) +/// Error(DecodeError(expected: "String", found: "Int")) +/// +pub fn string(from data: Dynamic) -> Result(String, DecodeError) { + decode_string(data) +} + +if erlang { + fn decode_string(data: Dynamic) -> Result(String, DecodeError) { + bit_string(data) |> result.map_error(fn(error) { DecodeError(..error, expected: "String") }) |> result.then(fn(raw) { case bit_string.to_string(raw) { @@ -69,7 +102,14 @@ if erlang { } }) } +} +if javascript { + external fn decode_string(Dynamic) -> Result(String, DecodeError) = + "../gleam_stdlib.js" "decode_string" +} + +if erlang { /// Checks to see whether a Dynamic value is an int, and return the int if it /// is. /// diff --git a/src/gleam_stdlib.js b/src/gleam_stdlib.js index bb63cbb..75b0882 100644 --- a/src/gleam_stdlib.js +++ b/src/gleam_stdlib.js @@ -12,6 +12,7 @@ import { CompileError as RegexCompileError, Match as RegexMatch, } from "./gleam/regex.js"; +import { DecodeError } from "./gleam/dynamic.js"; import { Some, None } from "./gleam/option.js"; const HASHCODE_CACHE = new WeakMap(); @@ -467,3 +468,25 @@ export function decode64(sBase64) { return new Ok(new BitString(taBytes)); } + +function classify_dynamic(data) { + if (typeof data === "string") { + return "String"; + } else if (List.isList(data)) { + return "List"; + } else if (Number.isInteger(data)) { + return "Int"; + } else { + return typeof data; + } +} + +function decoder_error(expected, got) { + return new Error(new DecodeError(expected, classify_dynamic(got))); +} + +export function decode_string(data) { + return typeof data === "string" + ? new Ok(data) + : decoder_error("String", data); +} diff --git a/test/gleam/dynamic_test.gleam b/test/gleam/dynamic_test.gleam index 5f6ae21..df92ffb 100644 --- a/test/gleam/dynamic_test.gleam +++ b/test/gleam/dynamic_test.gleam @@ -1,8 +1,9 @@ +import gleam/should +import gleam/dynamic.{DecodeError} + if erlang { import gleam/bit_string - import gleam/dynamic.{DecodeError} import gleam/list - import gleam/should import gleam/result import gleam/map import gleam/option.{None, Some} @@ -33,34 +34,40 @@ if erlang { |> dynamic.bit_string |> should.equal(Error(DecodeError(expected: "BitString", found: "List"))) } +} - pub fn string_test() { - "" - |> dynamic.from - |> dynamic.string - |> should.equal(Ok("")) - - "Hello" - |> dynamic.from - |> dynamic.string - |> should.equal(Ok("Hello")) +pub fn string_test() { + "" + |> dynamic.from + |> dynamic.string + |> should.equal(Ok("")) + + "Hello" + |> dynamic.from + |> dynamic.string + |> should.equal(Ok("Hello")) + + 1 + |> dynamic.from + |> dynamic.string + |> should.equal(Error(DecodeError(expected: "String", found: "Int"))) + + [] + |> dynamic.from + |> dynamic.string + |> should.equal(Error(DecodeError(expected: "String", found: "List"))) +} +if erlang { + pub fn string_non_utf8_test() { <<65535:16>> |> dynamic.from |> dynamic.string |> should.equal(Error(DecodeError(expected: "String", found: "BitString"))) - - 1 - |> dynamic.from - |> dynamic.string - |> should.equal(Error(DecodeError(expected: "String", found: "Int"))) - - [] - |> dynamic.from - |> dynamic.string - |> should.equal(Error(DecodeError(expected: "String", found: "List"))) } +} +if erlang { pub fn int_test() { 1 |> dynamic.from |