diff --git a/gleam.toml b/gleam.toml index 8043c35..0146f97 100644 --- a/gleam.toml +++ b/gleam.toml @@ -3,7 +3,7 @@ version = "1.0.0" description = "A Gleam implementation of the CBOR standard" licences = ["Apache-2.0"] -repository = { type = "github", user = "Beaudidly", repo = "https://github.com/Beaudidly/gbor" } +repository = { type = "github", user = "Beaudidly", repo = "gbor" } [dependencies] gleam_stdlib = ">= 0.44.0 and < 2.0.0" @@ -12,5 +12,3 @@ gleam_erlang = ">= 1.2.0 and < 2.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" -simplifile = ">= 2.3.0 and < 3.0.0" -gleam_json = ">= 3.0.1 and < 4.0.0" diff --git a/src/gbor.gleam b/src/gbor.gleam index a048387..c6fb4e1 100644 --- a/src/gbor.gleam +++ b/src/gbor.gleam @@ -1,3 +1,12 @@ +//// Module where we can find the base types used for the CBOR values + +/// The base types used for representing CBOR values with Gleam types, used +/// as a primary output from the decoding process, as well as input for the +/// encoding process. +/// +/// > **Note**: This type may become opaque in future major types as we evaluate +/// > the API needs for this package. +/// pub type CBOR { CBInt(Int) CBString(String) diff --git a/src/gbor/decode.gleam b/src/gbor/decode.gleam index 619432a..5e87d4e 100644 --- a/src/gbor/decode.gleam +++ b/src/gbor/decode.gleam @@ -1,3 +1,5 @@ +//// Tools for decoding binary CBOR data into Gleam types + import gleam/bit_array import gleam/bool import gleam/dynamic @@ -12,6 +14,7 @@ import ieee_float as i import gbor as g +/// The error type for decoding CBOR data into Gleam types pub type CborDecodeError { DynamicDecodeError(List(gdd.DecodeError)) MajorTypeError(Int) @@ -20,6 +23,7 @@ pub type CborDecodeError { UnassignedError } +/// Convert a CBOR Gleam value to a dynamic value for use with `gleam/dynamic/decode` pub fn cbor_to_dynamic(cbor: g.CBOR) -> dynamic.Dynamic { case cbor { g.CBInt(v) -> dynamic.int(v) @@ -38,6 +42,21 @@ pub fn cbor_to_dynamic(cbor: g.CBOR) -> dynamic.Dynamic { } } +/// Decode a tagged CBOR value. +/// +/// Provided tag is the expected tag number for the value, and the decoder is run +/// on the value corresponding to the tag. +/// +/// For example, for a CBOR value with a tag number of `0`, the expected data item +/// is a text string representing a standard time string, so one would call: +/// +/// ```gleam +/// import gleam/dynamic/decode as gdd +/// tagged_decoder(0, gdd.string, "") +/// ``` +/// +/// Reference: [RFC 8949 : 3.4 Tagging of Items](https://www.rfc-editor.org/rfc/rfc8949.html#name-tagging-of-items) +/// pub fn tagged_decoder( expected_tag: Int, decoder: gdd.Decoder(a), @@ -58,7 +77,27 @@ pub fn tagged_decoder( gdd.at([2], decoder) } -pub fn decode(data: BitArray) -> Result(#(g.CBOR, BitArray), CborDecodeError) { +/// Decode a CBOR value from a bit array +/// +/// This function is the main entry point for decoding CBOR data into Gleam types. +/// +/// It takes a bit array and returns a result containing the decoded CBOR value +/// +pub fn decode(data: BitArray) -> Result(g.CBOR, CborDecodeError) { + case decode_helper(data) { + Ok(#(v, <<>>)) -> Ok(v) + Ok(#(_, rest)) -> + Error( + DynamicDecodeError(gdd.decode_error( + expected: "Expected data, but got more", + found: dynamic.bit_array(rest), + )), + ) + Error(e) -> Error(e) + } +} + +fn decode_helper(data: BitArray) -> Result(#(g.CBOR, BitArray), CborDecodeError) { case data { <<0:3, min:5, rest:bits>> -> decode_uint(min, rest) <<1:3, min:5, rest:bits>> -> decode_int(min, rest) @@ -105,7 +144,7 @@ fn decode_uint( } } -pub fn decode_int( +fn decode_int( min: Int, data: BitArray, ) -> Result(#(g.CBOR, BitArray), CborDecodeError) { @@ -127,7 +166,7 @@ pub fn decode_int( Ok(#(g.CBInt(-1 - v), rest)) } -pub fn decode_float_or_simple_value( +fn decode_float_or_simple_value( min: Int, data: BitArray, ) -> Result(#(g.CBOR, BitArray), CborDecodeError) { @@ -155,7 +194,7 @@ pub fn decode_float_or_simple_value( } } -pub fn decode_float( +fn decode_float( data: BitArray, rest: BitArray, ) -> Result(#(g.CBOR, BitArray), CborDecodeError) { @@ -186,7 +225,7 @@ pub fn decode_float( } } -pub fn decode_bytes( +fn decode_bytes( min: Int, data: BitArray, ) -> Result(#(g.CBOR, BitArray), CborDecodeError) { @@ -194,7 +233,7 @@ pub fn decode_bytes( Ok(#(g.CBBinary(v), rest)) } -pub fn decode_string( +fn decode_string( min: Int, data: BitArray, ) -> Result(#(g.CBOR, BitArray), CborDecodeError) { @@ -214,7 +253,7 @@ pub fn decode_string( Ok(#(v, rest)) } -pub fn decode_bytes_helper( +fn decode_bytes_helper( min: Int, data: BitArray, ) -> Result(#(BitArray, BitArray), CborDecodeError) { @@ -238,7 +277,7 @@ pub fn decode_bytes_helper( } } -pub fn decode_array( +fn decode_array( min: Int, data: BitArray, ) -> Result(#(g.CBOR, BitArray), CborDecodeError) { @@ -268,7 +307,7 @@ pub fn decode_array( Ok(#(g.CBArray(list.reverse(values)), rest)) } -pub fn decode_array_helper( +fn decode_array_helper( data: BitArray, n: Int, acc: List(g.CBOR), @@ -276,13 +315,13 @@ pub fn decode_array_helper( case n { 0 -> Ok(#(acc, data)) _ -> { - use #(v, rest_data) <- result.try(decode(data)) + use #(v, rest_data) <- result.try(decode_helper(data)) decode_array_helper(rest_data, n - 1, [v, ..acc]) } } } -pub fn decode_map( +fn decode_map( min: Int, data: BitArray, ) -> Result(#(g.CBOR, BitArray), CborDecodeError) { @@ -333,7 +372,7 @@ pub fn decode_map( Ok(#(map, rest)) } -pub fn decode_tagged( +fn decode_tagged( min: Int, data: BitArray, ) -> Result(#(g.CBOR, BitArray), CborDecodeError) { @@ -352,15 +391,10 @@ pub fn decode_tagged( } }) - use #(tag_value, rest) <- result.try(decode(rest)) + use #(tag_value, rest) <- result.try(decode_helper(rest)) 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) +fn ffi_to_tagged(tag: Int, value: dynamic.Dynamic) -> dynamic.Dynamic diff --git a/src/gbor/encode.gleam b/src/gbor/encode.gleam index 036c757..a9acb3c 100644 --- a/src/gbor/encode.gleam +++ b/src/gbor/encode.gleam @@ -1,3 +1,5 @@ +//// Module where we can find the functions used for getting the CBOR binary representation of a value + import gleam/bit_array import gleam/list import gleam/result @@ -11,46 +13,12 @@ pub type EncodeError { EncodeError(String) } -pub fn int(value: Int) -> g.CBOR { - g.CBInt(value) -} - -pub fn map(value: List(#(g.CBOR, g.CBOR))) -> g.CBOR { - g.CBMap(value) -} - -pub fn string(value: String) -> g.CBOR { - g.CBString(value) -} - -pub fn array(value: List(g.CBOR)) -> g.CBOR { - g.CBArray(value) -} - -pub fn bool(value: Bool) -> g.CBOR { - g.CBBool(value) -} - -pub fn null() -> g.CBOR { - g.CBNull -} - -pub fn undefined() -> g.CBOR { - g.CBUndefined -} - -pub fn float(value: Float) -> g.CBOR { - g.CBFloat(value) -} - -pub fn binary(value: BitArray) -> g.CBOR { - g.CBBinary(value) -} - +/// Encode a CBOR value to a bit array pub fn to_bit_array(value: g.CBOR) -> Result(BitArray, EncodeError) { case value { g.CBInt(v) if v >= 0 -> uint_encode(v) g.CBInt(v) if v < 0 -> int_encode(v) + g.CBInt(v) -> uint_encode(v) g.CBFloat(v) -> Ok(float_encode(v)) g.CBBinary(v) -> binary_encode(BinaryEncoding(v)) g.CBString(v) -> binary_encode(StringEncoding(v)) @@ -60,13 +28,10 @@ pub fn to_bit_array(value: g.CBOR) -> Result(BitArray, EncodeError) { g.CBTagged(t, v) -> tagged_encode(t, v) g.CBNull -> Ok(null_encode()) g.CBUndefined -> Ok(undefined_encode()) - v -> { - Error(EncodeError("Unknown CBOR value: " <> string.inspect(v))) - } } } -pub fn uint_encode(value: Int) -> Result(BitArray, EncodeError) { +fn uint_encode(value: Int) -> Result(BitArray, EncodeError) { case value { v if v < 24 -> Ok(<<0:3, v:5>>) // TODO verify limits @@ -78,7 +43,7 @@ pub fn uint_encode(value: Int) -> Result(BitArray, EncodeError) { } } -pub fn int_encode(value: Int) -> Result(BitArray, EncodeError) { +fn int_encode(value: Int) -> Result(BitArray, EncodeError) { let value = { value * -1 } - 1 case value { v if v < 24 -> Ok(<<1:3, v:5>>) @@ -167,16 +132,6 @@ fn map_encode(value: List(#(g.CBOR, g.CBOR))) -> Result(BitArray, EncodeError) { }), ) - //let data = list.try - - //let data = - // list.fold_right(value, <<>>, fn(acc, a) { - // let k_data = to_bit_array(a.0) - // let v_data = to_bit_array(a.1) - - // bit_array.concat([k_data, v_data, acc]) - // }) - case n_pairs { v if v < 24 -> Ok(<<5:3, v:5, data:bits>>) v if v < 0x100 -> Ok(<<5:3, 25:5, v:8, data:bits>>) diff --git a/src/gbor/erl_gbor.erl b/src/gbor/erl_gbor.erl index 0ff3db3..f602aed 100644 --- a/src/gbor/erl_gbor.erl +++ b/src/gbor/erl_gbor.erl @@ -1,15 +1,6 @@ -module(erl_gbor). --export([to_tagged/2, check_tagged/1]). +-export([to_tagged/2]). 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 3b5ffa8..92e3d53 100644 --- a/test/round_trip_test.gleam +++ b/test/round_trip_test.gleam @@ -254,7 +254,7 @@ pub fn decode_dynamic_test() { "A2646E616D6574323031332D30332D32315432303A30343A30305A63646F62C074323031332D30332D32315432303A30343A30305A", ) - let assert Ok(#(cbor_val, <<>>)) = d.decode(bin) + let assert Ok(cbor_val) = d.decode(bin) let dyn_val = d.cbor_to_dynamic(cbor_val) let decoder = { @@ -272,7 +272,7 @@ pub fn decode_dynamic_test() { fn round_trip(expected: g.CBOR, hex: String) -> Result(Nil, String) { let assert Ok(binary) = bit_array.base16_decode(hex) - let assert Ok(#(v, <<>>)) = d.decode(binary) + let assert Ok(v) = d.decode(binary) use <- bool.guard( v != expected, Error(