///|
fn property_width(code : Int32) -> Int32 {
match code {
0 | 1 | 2 => 1
3 | 4 => 2
5 | 6 | 9 => 4
7 | 8 | 10 => 8
_ => 0
}
}
///|
fn read_property(
data : Bytes,
at : Int32,
code : Int32,
limits : Limits,
) -> (Property, Int32) raise FgbError {
let width = property_width(code)
if width > 0 {
require_range(data, at, width)
}
let value : Property = match code {
0 => {
let n = u8(data, at)
Integer((if n >= 128 { n - 256 } else { n }).to_int64())
}
1 => Unsigned(u8(data, at).to_uint64())
2 => {
let n = u8(data, at)
if n > 1 {
raise InvalidData(at, "boolean property is not 0 or 1")
}
Boolean(n == 1)
}
3 => Integer(i16(data, at).to_int64())
4 => Unsigned(u16(data, at).to_uint64())
5 => Integer(i32(data, at).to_int64())
6 => Unsigned(u32(data, at).to_uint64())
7 => Integer(u64(data, at).reinterpret_as_int64())
8 => Unsigned(u64(data, at))
9 => Real(f32(data, at))
10 => Real(f64(data, at))
11 | 12 | 13 | 14 => {
let size = size32(data, at)
enforce(size, limits.max_string_bytes, "property bytes")
let start = checked_add(at, 4)
let end = checked_add(start, size)
require_range(data, start, size)
let property = if code == 14 {
Binary(slice_bytes(data, start, size))
} else {
let text = decode_text(data, start, size, limits)
if code == 12 {
try @json.parse(text) |> ignore catch {
_ => raise InvalidData(start, "invalid JSON property")
}
}
Text(text)
}
return (property, end)
}
_ => raise Unsupported("property type \{code}")
}
(value, at + width)
}
///|
pub fn decode_properties(
data : Bytes,
columns : Array[Column],
limits~ : Limits = Limits::default(),
) -> Array[Property] raise FgbError {
limits.validate()
enforce(columns.length(), limits.max_columns, "column count")
let values = Array::make(columns.length(), Property::Null)
let seen = Array::make(columns.length(), false)
let mut cursor = 0
while cursor < data.length() {
let key = u16(data, cursor)
cursor += 2
if key >= columns.length() {
raise InvalidData(cursor - 2, "property key exceeds schema")
}
if seen[key] {
raise InvalidData(cursor - 2, "duplicate property key")
}
let (value, end) = read_property(
data,
cursor,
columns[key].type_code,
limits,
)
values[key] = value
seen[key] = true
cursor = end
}
for i in 0.. Int64 raise FgbError {
guard value is Integer(n) else {
raise InvalidArgument("expected signed integer property")
}
if n < minimum || n > maximum {
raise InvalidArgument("signed integer out of range")
}
n
}
///|
fn unsigned_value(value : Property, maximum : UInt64) -> UInt64 raise FgbError {
let n = match value {
Unsigned(n) => n
Integer(n) if n >= 0L => n.reinterpret_as_uint64()
_ => raise InvalidArgument("expected unsigned integer property")
}
if n > maximum {
raise InvalidArgument("unsigned integer out of range")
}
n
}
///|
fn real_value(value : Property) -> Double raise FgbError {
let n = match value {
Real(n) => n
Integer(n) => n.to_double()
Unsigned(n) => n.to_double()
_ => raise InvalidArgument("expected numeric property")
}
if !finite(n) {
raise InvalidArgument("numeric property must be finite")
}
n
}
///|
fn write_property(
writer : ByteWriter,
value : Property,
code : Int32,
limits : Limits,
) -> Unit raise FgbError {
match code {
0 => writer.byte(signed_value(value, -128L, 127L).to_int())
1 => writer.byte(unsigned_value(value, 255UL).to_int())
2 => {
guard value is Boolean(flag) else {
raise InvalidArgument("expected Bool property")
}
writer.byte(if flag { 1 } else { 0 })
}
3 => writer.short(signed_value(value, -32768L, 32767L).to_int())
4 => writer.short(unsigned_value(value, 65535UL).to_int())
5 =>
writer.word(
signed_value(value, -2147483648L, 2147483647L)
.to_int()
.reinterpret_as_uint(),
)
6 => writer.word(unsigned_value(value, 4294967295UL).to_uint())
7 => {
guard value is Integer(n) else {
raise InvalidArgument("expected Long property")
}
writer.long(n.reinterpret_as_uint64())
}
8 => writer.long(unsigned_value(value, 18446744073709551615UL))
9 => {
let n = real_value(value)
if n > 3.4028234663852886e38 || n < -3.4028234663852886e38 {
raise InvalidArgument("Float property overflow")
}
writer.float(n)
}
10 => writer.double(real_value(value))
11 | 12 | 13 => {
guard value is Text(text) else {
raise InvalidArgument("expected textual property")
}
if code == 12 {
try @json.parse(text) |> ignore catch {
_ => raise InvalidArgument("invalid JSON property")
}
}
let bytes = @utf8.encode(text)
enforce(bytes.length(), limits.max_string_bytes, "property string")
writer.word(bytes.length().reinterpret_as_uint())
writer.bytes(bytes)
}
14 => {
guard value is Binary(bytes) else {
raise InvalidArgument("expected Binary property")
}
enforce(bytes.length(), limits.max_string_bytes, "binary property")
writer.word(bytes.length().reinterpret_as_uint())
writer.bytes(bytes)
}
_ => raise Unsupported("property type \{code}")
}
}
///|
pub fn encode_properties(
values : Array[Property],
columns : Array[Column],
limits~ : Limits = Limits::default(),
) -> Bytes raise FgbError {
limits.validate()
enforce(columns.length(), limits.max_columns, "column count")
if values.length() != columns.length() {
raise InvalidArgument("property count differs from schema")
}
let writer = ByteWriter::new()
for i in 0..