diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b9ffb2f..6a53a62 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,8 +12,8 @@ jobs: - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1 with: - otp-version: "26.0.2" - gleam-version: "1.6.1" + otp-version: "27.0.0" + gleam-version: "1.11.1" rebar3-version: "3" # elixir-version: "1.15.4" - run: gleam deps download diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f64dc9d..389b5aa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,8 +14,8 @@ jobs: - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1 with: - otp-version: "26.0.2" - gleam-version: "1.6.1" + otp-version: "27.0.0" + gleam-version: "1.11.1" rebar3-version: "3" # elixir-version: "1.15.4" - run: gleam deps download diff --git a/CHANGELOG.md b/CHANGELOG.md index 25f2fad..6dab209 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # CHANGELOG +## 1.0.2 - 16/06/2025 + +**Features** + +- Added the default_value function to get a converter's default value + +**Changes** + +- Update to stdlib 0.60.0 + ## 1.0.1 - 28/12/2024 **Features** @@ -15,4 +25,4 @@ Initial release of convert - Javascript and Erlang targets - Converters for all basic types (except BitArray and tuple) - Define converters for List, Dict, Result & Option -- Build decoders for custom objects and enums \ No newline at end of file +- Build decoders for custom objects and enums diff --git a/gleam.toml b/gleam.toml index 089a700..b03444a 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,5 +1,5 @@ name = "convert" -version = "1.0.1" +version = "1.0.2" # Fill out these fields if you intend to generate HTML documentation or publish # your project to the Hex package manager. @@ -13,7 +13,7 @@ links = [{ title = "Website", href = "https://hexdocs.pm/convert" }] # https://gleam.run/writing-gleam/gleam-toml/. [dependencies] -gleam_stdlib = ">= 0.34.0 and < 2.0.0" +gleam_stdlib = ">= 0.60.0 and < 2.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml index 55e0da1..98e5621 100644 --- a/manifest.toml +++ b/manifest.toml @@ -2,10 +2,10 @@ # You typically do not need to edit this file packages = [ - { name = "gleam_stdlib", version = "0.45.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "206FCE1A76974AECFC55AEBCD0217D59EDE4E408C016E2CFCCC8FF51278F186E" }, - { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, + { name = "gleam_stdlib", version = "0.60.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "621D600BB134BC239CB2537630899817B1A42E60A1D46C5E9F3FAE39F88C800B" }, + { name = "gleeunit", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D33B7736CF0766ED3065F64A1EBB351E72B2E8DE39BAFC8ADA0E35E92A6A934F" }, ] [requirements] -gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } +gleam_stdlib = { version = ">= 0.60.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/src/convert.gleam b/src/convert.gleam index 1c17596..b97c261 100644 --- a/src/convert.gleam +++ b/src/convert.gleam @@ -1,5 +1,6 @@ import gleam/dict import gleam/dynamic +import gleam/dynamic/decode import gleam/list import gleam/option import gleam/result @@ -13,6 +14,8 @@ pub type GlitrType { Bool Float Int + Dynamic + BitArray Null List(of: GlitrType) Dict(key: GlitrType, value: GlitrType) @@ -20,8 +23,6 @@ pub type GlitrType { Optional(of: GlitrType) Result(result: GlitrType, error: GlitrType) Enum(variants: List(#(String, GlitrType))) - Dynamic - BitArray } /// This type is used to represent data values. @@ -32,6 +33,8 @@ pub type GlitrValue { BoolValue(value: Bool) FloatValue(value: Float) IntValue(value: Int) + DynamicValue(value: dynamic.Dynamic) + BitArrayValue(value: BitArray) NullValue ListValue(value: List(GlitrValue)) DictValue(value: dict.Dict(GlitrValue, GlitrValue)) @@ -39,8 +42,6 @@ pub type GlitrValue { OptionalValue(value: option.Option(GlitrValue)) ResultValue(value: Result(GlitrValue, GlitrValue)) EnumValue(variant: String, value: GlitrValue) - DynamicValue(value: dynamic.Dynamic) - BitArrayValue(value: BitArray) } /// A converter is an object with the data necessary to encode and decode a specific Gleam type. @@ -48,7 +49,7 @@ pub type GlitrValue { pub opaque type Converter(a) { Converter( encoder: fn(a) -> GlitrValue, - decoder: fn(GlitrValue) -> Result(a, List(dynamic.DecodeError)), + decoder: fn(GlitrValue) -> Result(a, List(decode.DecodeError)), type_def: GlitrType, // Temporary ugly stuff, while searching for a better solution default_value: a, @@ -59,10 +60,10 @@ pub opaque type Converter(a) { pub opaque type PartialConverter(base) { PartialConverter( encoder: fn(base) -> GlitrValue, - decoder: fn(GlitrValue) -> Result(base, List(dynamic.DecodeError)), + decoder: fn(GlitrValue) -> Result(base, List(decode.DecodeError)), fields_def: List(#(String, GlitrType)), // Temporary ugly stuff, while searching for a better solution - default_value: Result(base, List(dynamic.DecodeError)), + default_value: Result(base, List(decode.DecodeError)), ) } @@ -130,7 +131,7 @@ pub fn field( values |> list.key_find(field_name) |> result.replace_error([ - dynamic.DecodeError("Value", "None", [field_name]), + decode.DecodeError("Value", "None", [field_name]), ]) |> result.then(field_type.decoder) @@ -164,8 +165,7 @@ pub fn string() -> Converter(String) { fn(v: GlitrValue) { case v { StringValue(val) -> Ok(val) - other -> - Error([dynamic.DecodeError("StringValue", get_type(other), [])]) + other -> Error([decode.DecodeError("StringValue", get_type(other), [])]) } }, String, @@ -180,7 +180,7 @@ pub fn bool() -> Converter(Bool) { fn(v: GlitrValue) { case v { BoolValue(val) -> Ok(val) - other -> Error([dynamic.DecodeError("BoolValue", get_type(other), [])]) + other -> Error([decode.DecodeError("BoolValue", get_type(other), [])]) } }, Bool, @@ -195,7 +195,7 @@ pub fn float() -> Converter(Float) { fn(v: GlitrValue) { case v { FloatValue(val) -> Ok(val) - other -> Error([dynamic.DecodeError("FloatValue", get_type(other), [])]) + other -> Error([decode.DecodeError("FloatValue", get_type(other), [])]) } }, Float, @@ -210,7 +210,7 @@ pub fn int() -> Converter(Int) { fn(v: GlitrValue) { case v { IntValue(val) -> Ok(val) - other -> Error([dynamic.DecodeError("IntValue", get_type(other), [])]) + other -> Error([decode.DecodeError("IntValue", get_type(other), [])]) } }, Int, @@ -218,6 +218,38 @@ pub fn int() -> Converter(Int) { ) } +/// Basic converter for Dynamic values +pub fn dynamic() -> Converter(dynamic.Dynamic) { + Converter( + fn(v: dynamic.Dynamic) { DynamicValue(v) }, + fn(v: GlitrValue) { + case v { + DynamicValue(val) -> Ok(val) + other -> + Error([decode.DecodeError("DynamicValue", get_type(other), [])]) + } + }, + Dynamic, + dynamic.nil(), + ) +} + +/// Basic converter for BitArray values +pub fn bit_array() -> Converter(BitArray) { + Converter( + fn(v: BitArray) { BitArrayValue(v) }, + fn(v: GlitrValue) { + case v { + BitArrayValue(val) -> Ok(val) + other -> + Error([decode.DecodeError("BitArrayValue", get_type(other), [])]) + } + }, + BitArray, + <<>>, + ) +} + /// Basic converter for a Nil value pub fn null() -> Converter(Nil) { Converter( @@ -225,7 +257,7 @@ pub fn null() -> Converter(Nil) { fn(v: GlitrValue) { case v { NullValue -> Ok(Nil) - other -> Error([dynamic.DecodeError("NullValue", get_type(other), [])]) + other -> Error([decode.DecodeError("NullValue", get_type(other), [])]) } }, Null, @@ -250,7 +282,7 @@ pub fn list(of: Converter(a)) -> Converter(List(a)) { _, Error(errs) | Error(errs), _ -> Error(errs) } }) - other -> Error([dynamic.DecodeError("ListValue", get_type(other), [])]) + other -> Error([decode.DecodeError("ListValue", get_type(other), [])]) } }, List(of.type_def), @@ -270,7 +302,7 @@ pub fn optional(of: Converter(a)) -> Converter(option.Option(a)) { OptionalValue(option.Some(val)) -> val |> of.decoder |> result.map(option.Some) other -> - Error([dynamic.DecodeError("OptionalValue", get_type(other), [])]) + Error([decode.DecodeError("OptionalValue", get_type(other), [])]) } }, Optional(of.type_def), @@ -296,8 +328,7 @@ pub fn result( case v { ResultValue(Ok(val)) -> val |> res.decoder |> result.map(Ok) ResultValue(Error(val)) -> val |> error.decoder |> result.map(Error) - other -> - Error([dynamic.DecodeError("ResultValue", get_type(other), [])]) + other -> Error([decode.DecodeError("ResultValue", get_type(other), [])]) } }, Result(res.type_def, error.type_def), @@ -349,7 +380,7 @@ pub fn dict( } }) |> result.map(dict.from_list) - other -> Error([dynamic.DecodeError("DictValue", get_type(other), [])]) + other -> Error([decode.DecodeError("DictValue", get_type(other), [])]) } }, Dict(key.type_def, value.type_def), @@ -422,7 +453,7 @@ pub fn enum( converters |> list.key_find(variant_name) |> result.replace_error([ - dynamic.DecodeError( + decode.DecodeError( "One of: " <> converters |> list.map(fn(v) { v.0 }) |> string.join("/"), variant_name, @@ -432,7 +463,7 @@ pub fn enum( ) variant.decoder(value) } - other -> Error([dynamic.DecodeError("EnumValue", get_type(other), [])]) + other -> Error([decode.DecodeError("EnumValue", get_type(other), [])]) } }, Enum(converters |> list.map(fn(var) { #(var.0, { var.1 }.type_def) })), @@ -443,38 +474,6 @@ pub fn enum( ) } -/// Basic converter for Dynamic values -pub fn dynamic() -> Converter(dynamic.Dynamic) { - Converter( - fn(v: dynamic.Dynamic) { DynamicValue(v) }, - fn(v: GlitrValue) { - case v { - DynamicValue(val) -> Ok(val) - other -> - Error([dynamic.DecodeError("DynamicValue", get_type(other), [])]) - } - }, - Dynamic, - dynamic.from(Nil), - ) -} - -/// Basic converter for BitArray values -pub fn bit_array() -> Converter(BitArray) { - Converter( - fn(v: BitArray) { BitArrayValue(v) }, - fn(v: GlitrValue) { - case v { - BitArrayValue(val) -> Ok(val) - other -> - Error([dynamic.DecodeError("BitArrayValue", get_type(other), [])]) - } - }, - BitArray, - <<>>, - ) -} - /// Create a converter by mapping the encode and decode functions from an existing one /// /// Example: @@ -494,15 +493,15 @@ pub fn bit_array() -> Converter(BitArray) { /// [y, m, d, ..] -> Ok(Date(y, m, d)) /// _ -> Error([]) /// }, +/// Date(0, 0, 0) // This is required for now... /// } -/// Date(0, 0, 0) // This is required for now... /// ) /// } /// ``` pub fn map( converter: Converter(a), encode_map: fn(b) -> a, - decode_map: fn(a) -> Result(b, List(dynamic.DecodeError)), + decode_map: fn(a) -> Result(b, List(decode.DecodeError)), default_value: b, // Kinda required until I find a more elegant way around this ) -> Converter(b) { @@ -527,14 +526,14 @@ fn get_type(val: GlitrValue) -> String { EnumValue(_, _) -> "EnumValue" FloatValue(_) -> "FloatValue" IntValue(_) -> "IntValue" + DynamicValue(_) -> "DynamicValue" + BitArrayValue(_) -> "BitArrayValue" ListValue(_) -> "ListValue" NullValue -> "NullValue" ObjectValue(_) -> "ObjectValue" OptionalValue(_) -> "OptionalValue" ResultValue(_) -> "ResultValue" StringValue(_) -> "StringValue" - DynamicValue(_) -> "DynamicValue" - BitArrayValue(_) -> "BitArrayValue" } } @@ -547,7 +546,7 @@ pub fn encode(converter: Converter(a)) -> fn(a) -> GlitrValue { /// Decode a GlitrValue using the provided converter. pub fn decode( converter: Converter(a), -) -> fn(GlitrValue) -> Result(a, List(dynamic.DecodeError)) { +) -> fn(GlitrValue) -> Result(a, List(decode.DecodeError)) { converter.decoder } @@ -555,3 +554,19 @@ pub fn decode( pub fn type_def(converter: Converter(a)) -> GlitrType { converter.type_def } + +/// Retrieve the default value associated with a converter. +/// +/// This value is used as a fallback when decoding fails, +/// especially when building decoders that must return a value +/// even in the presence of errors. +/// +/// ## Example +/// ``` +/// let string_conv = string() +/// let default = default_value(string_conv) +/// // default == "" +/// ``` +pub fn default_value(conv: Converter(a)) -> a { + conv.default_value +} diff --git a/test/convert_test.gleam b/test/convert_test.gleam index f166db2..9a4fb49 100644 --- a/test/convert_test.gleam +++ b/test/convert_test.gleam @@ -1,5 +1,5 @@ import convert -import gleam/dynamic +import gleam/dynamic/decode import gleam/int import gleam/list import gleam/string @@ -127,22 +127,22 @@ pub type Date { Date(year: Int, month: Int, day: Int) } -fn date_parse(v: String) -> Result(Date, List(dynamic.DecodeError)) { +fn date_parse(v: String) -> Result(Date, List(decode.DecodeError)) { case string.split(v, "/") { [y, m, d, ..] -> { use year <- result_guard(int.parse(y), [ - dynamic.DecodeError("An integer", y, ["year"]), + decode.DecodeError("An integer", y, ["year"]), ]) use month <- result_guard(int.parse(m), [ - dynamic.DecodeError("An integer", m, ["month"]), + decode.DecodeError("An integer", m, ["month"]), ]) use day <- result_guard(int.parse(d), [ - dynamic.DecodeError("An integer", d, ["day"]), + decode.DecodeError("An integer", d, ["day"]), ]) Ok(Date(year, month, day)) } - _ -> Error([dynamic.DecodeError("A string of format 'Y/M/D'", v, [])]) + _ -> Error([decode.DecodeError("A string of format 'Y/M/D'", v, [])]) } }