Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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**
Expand All @@ -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
- Build decoders for custom objects and enums
4 changes: 2 additions & 2 deletions gleam.toml
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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"
6 changes: 3 additions & 3 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
131 changes: 73 additions & 58 deletions src/convert.gleam
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import gleam/dict
import gleam/dynamic
import gleam/dynamic/decode
import gleam/list
import gleam/option
import gleam/result
Expand All @@ -13,15 +14,15 @@ pub type GlitrType {
Bool
Float
Int
Dynamic
BitArray
Null
List(of: GlitrType)
Dict(key: GlitrType, value: GlitrType)
Object(fields: List(#(String, GlitrType)))
Optional(of: GlitrType)
Result(result: GlitrType, error: GlitrType)
Enum(variants: List(#(String, GlitrType)))
Dynamic
BitArray
}

/// This type is used to represent data values.
Expand All @@ -32,23 +33,23 @@ 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))
ObjectValue(value: List(#(String, 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.
/// You can build converters using the provided constructors.
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,
Expand All @@ -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)),
)
}

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -210,22 +210,54 @@ 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,
0,
)
}

/// 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(
fn(_: Nil) { NullValue },
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,
Expand All @@ -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),
Expand All @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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,
Expand All @@ -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) })),
Expand All @@ -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:
Expand All @@ -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) {
Expand All @@ -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"
}
}

Expand All @@ -547,11 +546,27 @@ 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
}

/// Return the GlitrType associated with the converter
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
}
Loading