diff options
author | Louis Pilfold <louis@lpil.uk> | 2024-12-22 14:04:59 +0000 |
---|---|---|
committer | Louis Pilfold <louis@lpil.uk> | 2024-12-22 14:04:59 +0000 |
commit | 928f8fb7f6d8c46ebb42c0f71b654afb19a1607e (patch) | |
tree | 2ad5659e9d30081ac6b2e80fba5df6e0aa7511bf | |
parent | cbec8f5109415882a47e191af2a5e1a7d2b412d7 (diff) | |
download | gleam_stdlib-928f8fb7f6d8c46ebb42c0f71b654afb19a1607e.tar.gz gleam_stdlib-928f8fb7f6d8c46ebb42c0f71b654afb19a1607e.zip |
v0.51.0v0.51.0
-rw-r--r-- | CHANGELOG.md | 5 | ||||
-rw-r--r-- | gleam.toml | 2 | ||||
-rw-r--r-- | src/gleam/dynamic/decode.gleam | 69 | ||||
-rw-r--r-- | src/gleam_stdlib_decode_ffi.mjs | 3 | ||||
-rw-r--r-- | test/gleam/dynamic/decode_test.gleam | 29 | ||||
-rw-r--r-- | test/gleam/string_test.gleam | 10 |
6 files changed, 79 insertions, 39 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 44fa101..dfc290d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v0.51.0 - 2024-12-22 + +- `dynamic/decode` now has its own error type. +- The `new_primitive_decoder` function in the `dynamic/decode` has a new API. + ## v0.50.0 - 2024-12-22 - The `dynamic/decode` module has been added. This module will replace the @@ -1,5 +1,5 @@ name = "gleam_stdlib" -version = "0.50.0" +version = "0.51.0" gleam = ">= 0.32.0" licences = ["Apache-2.0"] description = "A standard library for the Gleam programming language" diff --git a/src/gleam/dynamic/decode.gleam b/src/gleam/dynamic/decode.gleam index bd68c50..02f8470 100644 --- a/src/gleam/dynamic/decode.gleam +++ b/src/gleam/dynamic/decode.gleam @@ -257,7 +257,7 @@ //// ``` import gleam/dict.{type Dict} -import gleam/dynamic.{type DecodeError, DecodeError} +import gleam/dynamic import gleam/int import gleam/list import gleam/option.{type Option, None, Some} @@ -271,6 +271,12 @@ import gleam/result pub type Dynamic = dynamic.Dynamic +/// Error returned when unexpected data is encountered +/// +pub type DecodeError { + DecodeError(expected: String, found: String, path: List(String)) +} + /// A decoder is a value that can be used to turn dynamically typed `Dynamic` /// data into typed data using the `run` function. /// @@ -278,7 +284,7 @@ pub type Dynamic = /// functions such as `list` and `field`. /// pub opaque type Decoder(t) { - Decoder(function: fn(Dynamic) -> #(t, List(dynamic.DecodeError))) + Decoder(function: fn(Dynamic) -> #(t, List(DecodeError))) } /// The same as [`field`](#field), except taking a path to the value rather @@ -339,10 +345,7 @@ pub fn subfield( /// decode.run(data, decoder) /// ``` /// -pub fn run( - data: Dynamic, - decoder: Decoder(t), -) -> Result(t, List(dynamic.DecodeError)) { +pub fn run(data: Dynamic, decoder: Decoder(t)) -> Result(t, List(DecodeError)) { let #(maybe_invalid_data, errors) = decoder.function(data) case errors { [] -> Ok(maybe_invalid_data) @@ -392,10 +395,10 @@ pub fn at(path: List(segment), inner: Decoder(a)) -> Decoder(a) { fn index( path: List(a), position: List(a), - inner: fn(Dynamic) -> #(b, List(dynamic.DecodeError)), + inner: fn(Dynamic) -> #(b, List(DecodeError)), data: Dynamic, - handle_miss: fn(Dynamic, List(a)) -> #(b, List(dynamic.DecodeError)), -) -> #(b, List(dynamic.DecodeError)) { + handle_miss: fn(Dynamic, List(a)) -> #(b, List(DecodeError)), +) -> #(b, List(DecodeError)) { case path { [] -> { inner(data) @@ -477,7 +480,7 @@ pub fn success(data: t) -> Decoder(t) { pub fn decode_error( expected expected: String, found found: Dynamic, -) -> List(dynamic.DecodeError) { +) -> List(DecodeError) { [DecodeError(expected: expected, found: dynamic.classify(found), path: [])] } @@ -601,10 +604,14 @@ fn run_dynamic_function( data: Dynamic, zero: t, f: dynamic.Decoder(t), -) -> #(t, List(dynamic.DecodeError)) { +) -> #(t, List(DecodeError)) { case f(data) { Ok(data) -> #(data, []) - Error(errors) -> #(zero, errors) + Error(errors) -> { + let errors = + list.map(errors, fn(e) { DecodeError(e.expected, e.found, e.path) }) + #(zero, errors) + } } } @@ -619,7 +626,7 @@ fn run_dynamic_function( /// pub const string: Decoder(String) = Decoder(decode_string) -fn decode_string(data: Dynamic) -> #(String, List(dynamic.DecodeError)) { +fn decode_string(data: Dynamic) -> #(String, List(DecodeError)) { run_dynamic_function(data, "", dynamic.string) } @@ -634,7 +641,7 @@ fn decode_string(data: Dynamic) -> #(String, List(dynamic.DecodeError)) { /// pub const bool: Decoder(Bool) = Decoder(decode_bool) -fn decode_bool(data: Dynamic) -> #(Bool, List(dynamic.DecodeError)) { +fn decode_bool(data: Dynamic) -> #(Bool, List(DecodeError)) { run_dynamic_function(data, False, dynamic.bool) } @@ -649,7 +656,7 @@ fn decode_bool(data: Dynamic) -> #(Bool, List(dynamic.DecodeError)) { /// pub const int: Decoder(Int) = Decoder(decode_int) -fn decode_int(data: Dynamic) -> #(Int, List(dynamic.DecodeError)) { +fn decode_int(data: Dynamic) -> #(Int, List(DecodeError)) { run_dynamic_function(data, 0, dynamic.int) } @@ -664,7 +671,7 @@ fn decode_int(data: Dynamic) -> #(Int, List(dynamic.DecodeError)) { /// pub const float: Decoder(Float) = Decoder(decode_float) -fn decode_float(data: Dynamic) -> #(Float, List(dynamic.DecodeError)) { +fn decode_float(data: Dynamic) -> #(Float, List(DecodeError)) { run_dynamic_function(data, 0.0, dynamic.float) } @@ -679,7 +686,7 @@ fn decode_float(data: Dynamic) -> #(Float, List(dynamic.DecodeError)) { /// pub const dynamic: Decoder(Dynamic) = Decoder(decode_dynamic) -fn decode_dynamic(data: Dynamic) -> #(Dynamic, List(dynamic.DecodeError)) { +fn decode_dynamic(data: Dynamic) -> #(Dynamic, List(DecodeError)) { #(data, []) } @@ -694,7 +701,7 @@ fn decode_dynamic(data: Dynamic) -> #(Dynamic, List(dynamic.DecodeError)) { /// pub const bit_array: Decoder(BitArray) = Decoder(decode_bit_array) -fn decode_bit_array(data: Dynamic) -> #(BitArray, List(dynamic.DecodeError)) { +fn decode_bit_array(data: Dynamic) -> #(BitArray, List(DecodeError)) { run_dynamic_function(data, <<>>, dynamic.bit_array) } @@ -719,11 +726,11 @@ pub fn list(of inner: Decoder(a)) -> Decoder(List(a)) { @external(javascript, "../../gleam_stdlib_decode_ffi.mjs", "list") fn decode_list( data: Dynamic, - item: fn(Dynamic) -> #(t, List(dynamic.DecodeError)), + item: fn(Dynamic) -> #(t, List(DecodeError)), push_path: fn(#(t, List(DecodeError)), key) -> #(t, List(DecodeError)), index: Int, acc: List(t), -) -> #(List(t), List(dynamic.DecodeError)) +) -> #(List(t), List(DecodeError)) /// A decoder that decodes dicts where all keys and vales are decoded with /// given decoders. @@ -762,12 +769,12 @@ pub fn dict( } fn fold_dict( - acc: #(Dict(k, v), List(dynamic.DecodeError)), + acc: #(Dict(k, v), List(DecodeError)), key: Dynamic, value: Dynamic, - key_decoder: fn(Dynamic) -> #(k, List(dynamic.DecodeError)), - value_decoder: fn(Dynamic) -> #(v, List(dynamic.DecodeError)), -) -> #(Dict(k, v), List(dynamic.DecodeError)) { + key_decoder: fn(Dynamic) -> #(k, List(DecodeError)), + value_decoder: fn(Dynamic) -> #(v, List(DecodeError)), +) -> #(Dict(k, v), List(DecodeError)) { // First we decode the key. case key_decoder(key) { #(key, []) -> @@ -966,18 +973,24 @@ pub fn failure(zero: a, expected: String) -> Decoder(a) { /// import decode/decode /// /// pub fn string_decoder() -> decode.Decoder(String) { -/// decode.new_primitive_decoder(dynamic.string, "") +/// let default = "" +/// decode.new_primitive_decoder("String", fn(data) { +/// case dynamic.string { +/// Ok(x) -> Ok(x) +/// Error(x) -> Error(default) +/// } +/// }) /// } /// ``` /// pub fn new_primitive_decoder( - decoding_function: fn(Dynamic) -> Result(t, List(DecodeError)), - zero: t, + name: String, + decoding_function: fn(Dynamic) -> Result(t, t), ) -> Decoder(t) { Decoder(function: fn(d) { case decoding_function(d) { Ok(t) -> #(t, []) - Error(errors) -> #(zero, errors) + Error(zero) -> #(zero, [DecodeError(name, dynamic.classify(d), [])]) } }) } diff --git a/src/gleam_stdlib_decode_ffi.mjs b/src/gleam_stdlib_decode_ffi.mjs index 58291b5..f31c464 100644 --- a/src/gleam_stdlib_decode_ffi.mjs +++ b/src/gleam_stdlib_decode_ffi.mjs @@ -1,7 +1,8 @@ import { Ok, Error, List, NonEmpty } from "./gleam.mjs"; import { default as Dict } from "./dict.mjs"; import { Some, None } from "./gleam/option.mjs"; -import { DecodeError, classify } from "./gleam/dynamic.mjs"; +import { classify } from "./gleam/dynamic.mjs"; +import { DecodeError } from "./gleam/dynamic/decode.mjs"; export function strict_index(data, key) { const int = Number.isInteger(key); diff --git a/test/gleam/dynamic/decode_test.gleam b/test/gleam/dynamic/decode_test.gleam index b176c70..a97f597 100644 --- a/test/gleam/dynamic/decode_test.gleam +++ b/test/gleam/dynamic/decode_test.gleam @@ -1,9 +1,10 @@ import gleam/dict -import gleam/dynamic.{type Dynamic, DecodeError} -import gleam/dynamic/decode +import gleam/dynamic.{type Dynamic} +import gleam/dynamic/decode.{DecodeError} import gleam/float import gleam/int import gleam/option +import gleam/result import gleam/should pub type User { @@ -768,29 +769,45 @@ pub fn documentation_variants_example_test() { } pub fn new_primitive_decoder_string_ok_test() { + let decoder = + decode.new_primitive_decoder("String", fn(x) { + dynamic.string(x) |> result.replace_error("") + }) dynamic.from("Hello!") - |> decode.run(decode.new_primitive_decoder(dynamic.string, "")) + |> decode.run(decoder) |> should.be_ok |> should.equal("Hello!") } pub fn new_primitive_decoder_string_error_test() { + let decoder = + decode.new_primitive_decoder("String", fn(x) { + dynamic.string(x) |> result.replace_error("") + }) dynamic.from(123) - |> decode.run(decode.new_primitive_decoder(dynamic.string, "")) + |> decode.run(decoder) |> should.be_error |> should.equal([DecodeError("String", "Int", [])]) } pub fn new_primitive_decoder_float_ok_test() { + let decoder = + decode.new_primitive_decoder("Float", fn(x) { + dynamic.float(x) |> result.replace_error(0.0) + }) dynamic.from(12.4) - |> decode.run(decode.new_primitive_decoder(dynamic.float, 0.0)) + |> decode.run(decoder) |> should.be_ok |> should.equal(12.4) } pub fn new_primitive_decoder_float_error_test() { + let decoder = + decode.new_primitive_decoder("Float", fn(x) { + dynamic.float(x) |> result.replace_error(0.0) + }) dynamic.from("blah") - |> decode.run(decode.new_primitive_decoder(dynamic.float, 0.0)) + |> decode.run(decoder) |> should.be_error |> should.equal([DecodeError("Float", "String", [])]) } diff --git a/test/gleam/string_test.gleam b/test/gleam/string_test.gleam index ffd2f9b..4eddb9c 100644 --- a/test/gleam/string_test.gleam +++ b/test/gleam/string_test.gleam @@ -1,12 +1,16 @@ import gleam/dict -import gleam/int -import gleam/list import gleam/option.{None, Some} import gleam/order -import gleam/result import gleam/should import gleam/string +@target(erlang) +import gleam/int +@target(erlang) +import gleam/list +@target(erlang) +import gleam/result + pub fn length_test() { string.length("ß↑e̊") |> should.equal(3) |