diff --git a/gleam.toml b/gleam.toml index 82cb9c6..8043c35 100644 --- a/gleam.toml +++ b/gleam.toml @@ -8,6 +8,7 @@ repository = { type = "github", user = "Beaudidly", repo = "https://github.com/B [dependencies] gleam_stdlib = ">= 0.44.0 and < 2.0.0" ieee_float = ">= 1.5.0 and < 2.0.0" +gleam_erlang = ">= 1.2.0 and < 2.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/src/gbor/decode.gleam b/src/gbor/decode.gleam index 72b6765..08db749 100644 --- a/src/gbor/decode.gleam +++ b/src/gbor/decode.gleam @@ -1,6 +1,9 @@ import gleam/bit_array +import gleam/bool import gleam/dynamic import gleam/dynamic/decode as gdd +import gleam/erlang/atom +import gleam/int import gleam/list import gleam/result import gleam/string @@ -17,6 +20,46 @@ pub type CborDecodeError { UnassignedError } +pub fn cbor_to_dynamic(cbor: g.CBOR) -> dynamic.Dynamic { + case cbor { + g.CBInt(v) -> dynamic.int(v) + g.CBString(v) -> dynamic.string(v) + g.CBFloat(v) -> dynamic.float(v) + g.CBMap(v) -> + dynamic.properties( + list.map(v, fn(v) { #(cbor_to_dynamic(v.0), cbor_to_dynamic(v.1)) }), + ) + g.CBArray(v) -> dynamic.array(list.map(v, cbor_to_dynamic)) + g.CBBool(v) -> dynamic.bool(v) + g.CBNull -> dynamic.nil() + g.CBUndefined -> dynamic.nil() + g.CBBinary(v) -> dynamic.bit_array(v) + g.CBTagged(tag, value) -> ffi_to_tagged(tag, cbor_to_dynamic(value)) + } +} + +pub fn tagged_decoder( + expected_tag: Int, + decoder: gdd.Decoder(a), + zero: a, +) -> gdd.Decoder(a) { + let error = gdd.failure(zero, "CBOR tagged value") + + use cbor_tag <- gdd.field(0, atom.decoder()) + use <- bool.guard( + cbor_tag != atom.create("cbor_tagged__"), + gdd.failure(zero, "CBOR tagged value"), + ) + + use tag <- gdd.field(1, gdd.int) + use <- bool.guard( + tag != expected_tag, + gdd.failure(zero, int.to_string(expected_tag)), + ) + + gdd.at([2], decoder) +} + pub fn decode(data: BitArray) -> Result(#(g.CBOR, BitArray), CborDecodeError) { case data { <<0:3, min:5, rest:bits>> -> decode_uint(min, rest) @@ -315,3 +358,11 @@ pub fn decode_tagged( Ok(#(g.CBTagged(tag_num, tag_value), rest)) } + +@external(erlang, "erl_gbor", "to_tagged") +pub fn ffi_to_tagged(tag: Int, value: dynamic.Dynamic) -> dynamic.Dynamic + +@external(erlang, "erl_gbor", "check_tagged") +pub fn ffi_check_tagged( + tagged: dynamic.Dynamic, +) -> Result(#(Int, dynamic.Dynamic), String) diff --git a/src/gbor/erl_gbor.erl b/src/gbor/erl_gbor.erl new file mode 100644 index 0000000..0ff3db3 --- /dev/null +++ b/src/gbor/erl_gbor.erl @@ -0,0 +1,15 @@ +-module(erl_gbor). + +-export([to_tagged/2, check_tagged/1]). + +to_tagged(Tag, Value) -> + {cbor_tagged__, Tag, Value}. + + +check_tagged(Tagged) -> + case Tagged of + {cbor_tagged__, Tag, Value} -> + {ok, {Tag, Value}}; + _ -> + {error, invalid_tagged_value} + end. \ No newline at end of file diff --git a/test/round_trip_test.gleam b/test/round_trip_test.gleam index 22bb269..9e8b7d0 100644 --- a/test/round_trip_test.gleam +++ b/test/round_trip_test.gleam @@ -1,4 +1,8 @@ import gleam/bit_array +import gleam/bool +import gleam/dynamic/decode as gdd +import gleam/list +import gleam/result import gleeunit import gbor as g @@ -99,3 +103,29 @@ pub fn decode_taggded_test() { round_trip(g.CBTagged(1, g.CBInt(1_363_896_240)), "c11a514b67b0") } + +type Cat { + Cat(name: String, dob: String) +} + +pub fn decode_dynamic_test() { + let assert Ok(bin) = + bit_array.base16_decode( + "A2646E616D6574323031332D30332D32315432303A30343A30305A63646F62C074323031332D30332D32315432303A30343A30305A", + ) + + let assert Ok(#(cbor_val, <<>>)) = d.decode(bin) + let dyn_val = d.cbor_to_dynamic(cbor_val) + + let decoder = { + use name <- gdd.field("name", gdd.string) + use dob <- gdd.field("dob", d.tagged_decoder(0, gdd.string, "INVALID")) + gdd.success(Cat(name, dob)) + } + + let assert Ok(v) = gdd.run(dyn_val, decoder) + assert v == Cat("2013-03-21T20:04:00Z", "2013-03-21T20:04:00Z") + + let assert Ok(encoded) = e.to_bit_array(cbor_val) + assert encoded == bin +}