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]
|
[dependencies]
|
||||||
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
|
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
|
||||||
ieee_float = ">= 1.0.3 and < 2.0.0"
|
ieee_float = ">= 1.0.3 and < 2.0.0"
|
||||||
|
birl = ">= 1.7.1 and < 2.0.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gleeunit = ">= 1.0.0 and < 2.0.0"
|
gleeunit = ">= 1.0.0 and < 2.0.0"
|
||||||
|
|
|
@ -2,13 +2,16 @@
|
||||||
# You typically do not need to edit this file
|
# You typically do not need to edit this file
|
||||||
|
|
||||||
packages = [
|
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_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 = "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 = "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 = "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]
|
[requirements]
|
||||||
|
birl = { version = ">= 1.7.1 and < 2.0.0" }
|
||||||
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
|
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
|
||||||
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
||||||
ieee_float = { version = ">= 1.0.3 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/bit_array
|
||||||
import gleam/list.{reverse}
|
import gleam/list.{reverse}
|
||||||
import gleam/result.{try}
|
import gleam/result.{try}
|
||||||
import ieee_float.{
|
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 {
|
pub type DecodedValue {
|
||||||
|
@ -13,6 +15,7 @@ pub type DecodedValue {
|
||||||
String(value: String)
|
String(value: String)
|
||||||
Array(List(DecodedValue))
|
Array(List(DecodedValue))
|
||||||
Map(List(#(DecodedValue, DecodedValue)))
|
Map(List(#(DecodedValue, DecodedValue)))
|
||||||
|
DateTime(Time)
|
||||||
Null
|
Null
|
||||||
Undefined
|
Undefined
|
||||||
}
|
}
|
||||||
|
@ -24,9 +27,6 @@ pub type DecodeResult =
|
||||||
DecoderResult(DecodedValue)
|
DecoderResult(DecodedValue)
|
||||||
|
|
||||||
pub opaque type DecodeError {
|
pub opaque type DecodeError {
|
||||||
// More data was expected but file/stream ended prematurely
|
|
||||||
UnexpectedEnd
|
|
||||||
|
|
||||||
// Malformed sequence found
|
// Malformed sequence found
|
||||||
Malformed
|
Malformed
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ pub fn decode(bits: BitArray) -> DecodeResult {
|
||||||
<<5:3, rest:bits>> ->
|
<<5:3, rest:bits>> ->
|
||||||
decode_map(rest)
|
decode_map(rest)
|
||||||
|> map_decoded(Map)
|
|> 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_simple(<<x:5, rest:bits>>)
|
||||||
<<7:3, x:5, rest:bits>> if x >= 25 ->
|
<<7:3, x:5, rest:bits>> if x >= 25 ->
|
||||||
decode_float(<<x:5, rest:bits>>)
|
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))
|
<<27:5, val:int-unsigned-size(64), rest:bits>> -> Ok(#(val, rest))
|
||||||
<<x:5, rest:bits>> if x < 24 -> Ok(#(x, rest))
|
<<x:5, rest:bits>> if x < 24 -> Ok(#(x, rest))
|
||||||
<<x:5, _:bits>> if x >= 24 -> Error(Malformed)
|
<<x:5, _:bits>> if x >= 24 -> Error(Malformed)
|
||||||
_ -> Error(UnexpectedEnd)
|
_ -> Error(Malformed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +107,57 @@ fn decode_map(
|
||||||
Ok(#(pairs, remainder))
|
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) {
|
fn decode_float(bits: BitArray) -> DecoderResult(IEEEFloat) {
|
||||||
case bits {
|
case bits {
|
||||||
<<25:5, float16:bytes-size(2), rest:bits>> ->
|
<<25:5, float16:bytes-size(2), rest:bits>> ->
|
||||||
|
@ -157,8 +209,8 @@ fn decode_next_n(
|
||||||
|
|
||||||
fn map_decoded(
|
fn map_decoded(
|
||||||
result: DecoderResult(t),
|
result: DecoderResult(t),
|
||||||
map_fn: fn(t) -> DecodedValue,
|
map_fn: fn(t) -> out,
|
||||||
) -> DecodeResult {
|
) -> DecoderResult(out) {
|
||||||
use #(value, rest) <- try(result)
|
use #(value, rest) <- try(result)
|
||||||
Ok(#(map_fn(value), rest))
|
Ok(#(map_fn(value), rest))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
import birl
|
||||||
import gleam/list
|
import gleam/list
|
||||||
import gleeunit
|
import gleeunit
|
||||||
import gleeunit/should
|
import gleeunit/should
|
||||||
import ieee_float.{finite, nan, positive_infinity}
|
import ieee_float.{finite, nan, positive_infinity}
|
||||||
import kmer.{
|
import kmer.{
|
||||||
type DecodeResult, type DecodedValue, Array, Boolean, Bytes, Float, Integer,
|
type DecodeResult, type DecodedValue, Array, Boolean, Bytes, DateTime, Float,
|
||||||
Map, Null, String, Undefined,
|
Integer, Map, Null, String, Undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
|
@ -155,6 +156,28 @@ pub fn decode_map_test() {
|
||||||
|> table_fn(table_test_decode)
|
|> 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) {
|
fn table_fn(tests: List(#(a, b)), test_fn: fn(a, b) -> Nil) {
|
||||||
list.map(tests, fn(t) {
|
list.map(tests, fn(t) {
|
||||||
let #(test_case, expected) = t
|
let #(test_case, expected) = t
|
||||||
|
|
Loading…
Reference in a new issue