///|
priv struct Budget {
mut left : Int
limits : Limits
}
///|
fn Budget::visit(
self : Budget,
depth : Int,
offset : Int,
) -> Unit raise XdrError {
if depth > self.limits.max_depth {
raise Invalid(offset, "value depth limit")
}
if self.left <= 0 {
raise Invalid(offset, "aggregate value item limit")
}
self.left -= 1
}
///|
fn encode_at(
schema : Schema,
ty : Type,
value : Value,
w : Writer,
budget : Budget,
depth : Int,
) -> Unit raise XdrError {
budget.visit(depth, w.length())
match (ty, value) {
(TRef(name), v) =>
encode_at(schema, schema.lookup(name), v, w, budget, depth + 1)
(TVoid, VVoid) => ()
(TInt, VInt(n)) => w.write_int(n)
(TUInt, VUInt(n)) => w.write_uint(n)
(THyper, VHyper(n)) => w.write_hyper(n)
(TUHyper, VUHyper(n)) => w.write_uhyper(n)
(TBool, VBool(n)) => w.write_bool(n)
(TFloat, VFloat(n)) => w.write_float(n)
(TDouble, VDouble(n)) => w.write_double(n)
(TString(bound), VString(s)) => w.write_string(s, bound~)
(TOpaque(bound, variable), VOpaque(b)) =>
if variable {
w.write_opaque(b, bound~)
} else {
if b.length() != bound {
raise Invalid(w.length(), "fixed opaque length mismatch")
}
w.write_fixed(b)
}
(TArray(inner, bound, variable), VArray(values)) => {
if (variable && values.length() > bound) ||
(!variable && values.length() != bound) {
raise Invalid(w.length(), "array length mismatch")
}
if values.length() > budget.left {
raise Invalid(w.length(), "aggregate value item limit")
}
if variable {
w.write_uint(values.length().reinterpret_as_uint())
}
for item in values {
encode_at(schema, inner, item, w, budget, depth + 1)
}
}
(TOptional(inner), VOptional(opt)) => {
w.write_bool(opt is Some(_))
if opt is Some(v) {
encode_at(schema, inner, v, w, budget, depth + 1)
}
}
(TEnum(labels), VEnum(n)) => {
validate_tag(TEnum(labels), n)
w.write_int(n)
}
(TStruct(fields), VStruct(values)) => {
if values.length() != fields.length() {
raise Invalid(w.length(), "record field count mismatch")
}
for i, field in fields {
if values[i].0 != field.name {
raise Invalid(
w.length(),
"record field name or order mismatch: " + field.name,
)
}
encode_at(schema, field.ty, values[i].1, w, budget, depth + 1)
}
}
(TUnion(tag, arms, fallback), VUnion(n, v)) => {
validate_tag(schema.resolve(tag), n)
w.write_int(n)
encode_at(schema, union_arm(arms, fallback, n), v, w, budget, depth + 1)
}
_ => raise Invalid(w.length(), "value does not match schema type")
}
}
///|
pub fn encode(
schema : Schema,
type_name : String,
value : Value,
) -> Array[Byte] raise XdrError {
let w = Writer::new(limits=schema.limits)
let budget : Budget = {
left: schema.limits.max_items,
limits: schema.limits,
}
encode_at(schema, schema.lookup(type_name), value, w, budget, 0)
w.bytes()
}