This commit is contained in:
parent
a61dc3d878
commit
0a1f06dbe5
4 changed files with 88 additions and 9 deletions
|
@ -9,6 +9,7 @@ gleam = ">= 1.6.2"
|
|||
[dependencies]
|
||||
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
|
||||
ieee_float = ">= 1.0.3 and < 2.0.0"
|
||||
birl = ">= 1.7.1 and < 2.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
gleeunit = ">= 1.0.0 and < 2.0.0"
|
||||
|
|
|
@ -2,13 +2,16 @@
|
|||
# You typically do not need to edit this file
|
||||
|
||||
packages = [
|
||||
{ name = "birl", version = "1.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "5C66647D62BCB11FE327E7A6024907C4A17954EF22865FE0940B54A852446D01" },
|
||||
{ name = "gleam_erlang", version = "0.32.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "B18643083A0117AC5CFD0C1AEEBE5469071895ECFA426DCC26517A07F6AD9948" },
|
||||
{ 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 = "ieee_float", version = "1.0.3", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "ieee_float", source = "hex", outer_checksum = "0E9B6233343BD8EDBF813B50D5480139A3E318D257EA206D5C500984A829C515" },
|
||||
{ name = "ranger", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "B8F3AFF23A3A5B5D9526B8D18E7C43A7DFD3902B151B97EC65397FE29192B695" },
|
||||
]
|
||||
|
||||
[requirements]
|
||||
birl = { version = ">= 1.7.1 and < 2.0.0" }
|
||||
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
|
||||
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
||||
ieee_float = { version = ">= 1.0.3 and < 2.0.0" }
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import birl.{type Time}
|
||||
import gleam/bit_array
|
||||
import gleam/list.{reverse}
|
||||
import gleam/result.{try}
|
||||
import ieee_float.{
|
||||
type IEEEFloat, from_bytes_16_be, from_bytes_32_be, from_bytes_64_be,
|
||||
type IEEEFloat, finite, from_bytes_16_be, from_bytes_32_be, from_bytes_64_be,
|
||||
multiply, power, round,
|
||||
}
|
||||
|
||||
pub type DecodedValue {
|
||||
|
@ -13,6 +15,7 @@ pub type DecodedValue {
|
|||
String(value: String)
|
||||
Array(List(DecodedValue))
|
||||
Map(List(#(DecodedValue, DecodedValue)))
|
||||
DateTime(Time)
|
||||
Null
|
||||
Undefined
|
||||
}
|
||||
|
@ -24,9 +27,6 @@ pub type DecodeResult =
|
|||
DecoderResult(DecodedValue)
|
||||
|
||||
pub opaque type DecodeError {
|
||||
// More data was expected but file/stream ended prematurely
|
||||
UnexpectedEnd
|
||||
|
||||
// Malformed sequence found
|
||||
Malformed
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ pub fn decode(bits: BitArray) -> DecodeResult {
|
|||
<<5:3, rest:bits>> ->
|
||||
decode_map(rest)
|
||||
|> map_decoded(Map)
|
||||
<<6:3, rest:bits>> -> decode_tag(rest)
|
||||
<<7:3, x:5, rest:bits>> if x < 25 -> decode_simple(<<x:5, rest:bits>>)
|
||||
<<7:3, x:5, rest:bits>> if x >= 25 ->
|
||||
decode_float(<<x:5, rest:bits>>)
|
||||
|
@ -68,7 +69,7 @@ fn decode_int(bits: BitArray) -> DecoderResult(Int) {
|
|||
<<27:5, val:int-unsigned-size(64), rest:bits>> -> Ok(#(val, rest))
|
||||
<<x:5, rest:bits>> if x < 24 -> Ok(#(x, rest))
|
||||
<<x:5, _:bits>> if x >= 24 -> Error(Malformed)
|
||||
_ -> Error(UnexpectedEnd)
|
||||
_ -> Error(Malformed)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,6 +107,57 @@ fn decode_map(
|
|||
Ok(#(pairs, remainder))
|
||||
}
|
||||
|
||||
fn decode_tag(bits: BitArray) -> DecodeResult {
|
||||
use #(tag, rest) <- try(decode_int(bits))
|
||||
|
||||
case tag {
|
||||
0 -> decode_datestring(rest) |> map_decoded(DateTime)
|
||||
1 -> decode_unixtime(rest) |> map_decoded(DateTime)
|
||||
// CBOR magic number (no-op for us)
|
||||
55_799 -> decode(rest)
|
||||
_ -> Error(Malformed)
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_datestring(bits: BitArray) -> DecoderResult(Time) {
|
||||
use string_content <- try(case bits {
|
||||
<<3:3, remainder:bits>> -> Ok(remainder)
|
||||
_ -> Error(Malformed)
|
||||
})
|
||||
|
||||
use #(datestring, rest) <- try(decode_string(string_content))
|
||||
|
||||
case birl.parse(datestring) {
|
||||
Ok(datetime) -> Ok(#(datetime, rest))
|
||||
_ -> Error(Malformed)
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_unixtime(bits: BitArray) -> DecoderResult(Time) {
|
||||
case bits {
|
||||
<<0:3, rest:bits>> ->
|
||||
decode_int(rest)
|
||||
|> map_decoded(birl.from_unix)
|
||||
// This is probably undefined behavior, as I doubt birl supports negative unixtimes
|
||||
<<1:3, rest:bits>> ->
|
||||
decode_int(rest)
|
||||
|> map_decoded(birl.from_unix)
|
||||
// Float unixtimes is also very iffy and the spec says we should be able to ignore it
|
||||
// However, lets give it a shot and turn it into a millisecond value
|
||||
<<7:3, rest:bits>> ->
|
||||
decode_float(rest)
|
||||
|> try(fn(x) {
|
||||
let #(time, remainder) = x
|
||||
let milli_time = multiply(time, power(finite(10.0), finite(3.0)))
|
||||
case round(milli_time) {
|
||||
Ok(milli_int) -> Ok(#(birl.from_unix_milli(milli_int), remainder))
|
||||
_ -> Error(Malformed)
|
||||
}
|
||||
})
|
||||
_ -> Error(Malformed)
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_float(bits: BitArray) -> DecoderResult(IEEEFloat) {
|
||||
case bits {
|
||||
<<25:5, float16:bytes-size(2), rest:bits>> ->
|
||||
|
@ -157,8 +209,8 @@ fn decode_next_n(
|
|||
|
||||
fn map_decoded(
|
||||
result: DecoderResult(t),
|
||||
map_fn: fn(t) -> DecodedValue,
|
||||
) -> DecodeResult {
|
||||
map_fn: fn(t) -> out,
|
||||
) -> DecoderResult(out) {
|
||||
use #(value, rest) <- try(result)
|
||||
Ok(#(map_fn(value), rest))
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import birl
|
||||
import gleam/list
|
||||
import gleeunit
|
||||
import gleeunit/should
|
||||
import ieee_float.{finite, nan, positive_infinity}
|
||||
import kmer.{
|
||||
type DecodeResult, type DecodedValue, Array, Boolean, Bytes, Float, Integer,
|
||||
Map, Null, String, Undefined,
|
||||
type DecodeResult, type DecodedValue, Array, Boolean, Bytes, DateTime, Float,
|
||||
Integer, Map, Null, String, Undefined,
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
|
@ -155,6 +156,28 @@ pub fn decode_map_test() {
|
|||
|> table_fn(table_test_decode)
|
||||
}
|
||||
|
||||
pub fn decode_time_test() {
|
||||
let assert Ok(textdate_example) = birl.parse("2024-11-30T21:56:27Z")
|
||||
|
||||
[
|
||||
// ISO8601 date
|
||||
#(
|
||||
<<6:3, 0:5, 3:3, 20:5, "2024-11-30T21:56:27Z">>,
|
||||
DateTime(textdate_example),
|
||||
),
|
||||
// Unixtime
|
||||
#(
|
||||
<<6:3, 1:5, 1:3, 26:5, 1_733_005_876:32>>,
|
||||
DateTime(birl.from_unix(1_733_005_876)),
|
||||
),
|
||||
#(
|
||||
<<6:3, 1:5, 7:3, 27:5, 0x41D9D2E48D200000:64>>,
|
||||
DateTime(birl.from_unix_milli(1_733_005_876_500)),
|
||||
),
|
||||
]
|
||||
|> table_fn(table_test_decode)
|
||||
}
|
||||
|
||||
fn table_fn(tests: List(#(a, b)), test_fn: fn(a, b) -> Nil) {
|
||||
list.map(tests, fn(t) {
|
||||
let #(test_case, expected) = t
|
||||
|
|
Loading…
Reference in a new issue