///|
/// Ordered dictionaries retain input order. Duplicate keys are invalid.
pub(all) enum Value {
Null
Boolean(Bool)
Integer(Int64)
Unsigned(UInt64)
Real(Double)
Text(String)
Data(Bytes)
Date(Double)
Uid(UInt64)
List(Array[Value])
Dict(Array[(String, Value)])
} derive(Eq)
///|
pub(all) suberror PlistError {
Invalid(String)
Limit(String)
Unsupported(String)
PathError(String)
}
///|
/// Budgets apply before allocation and to expanded trees, not just binary IDs.
pub(all) struct Limits {
max_bytes : Int32
max_objects : Int32
max_depth : Int32
max_items : Int32
}
///|
pub fn Limits::default() -> Limits {
{
max_bytes: 16777216,
max_objects: 100000,
max_depth: 64,
max_items: 100000,
}
}
///|
fn Limits::check(self : Limits) -> Unit raise PlistError {
if self.max_bytes < 8 ||
self.max_objects < 1 ||
self.max_depth < 1 ||
self.max_depth > 256 ||
self.max_items < 1 {
raise Limit("invalid limits; depth must be 1..256")
}
}
///|
pub fn Value::kind(self : Value) -> String {
match self {
Null => "null"
Boolean(_) => "bool"
Integer(_) | Unsigned(_) => "integer"
Real(_) => "real"
Text(_) => "string"
Data(_) => "data"
Date(_) => "date"
Uid(_) => "uid"
List(_) => "array"
Dict(_) => "dict"
}
}
///|
pub fn Value::get(self : Value, key : String) -> Value? {
match self {
Dict(entries) => {
for entry in entries {
if entry.0 == key {
return Some(entry.1)
}
}
None
}
_ => None
}
}
///|
pub fn validate(
value : Value,
limits~ : Limits = Limits::default(),
) -> Unit raise PlistError {
limits.check()
let count = Ref(0)
fn walk(v : Value, depth : Int32) -> Unit raise PlistError {
count.val += 1
if count.val > limits.max_objects || depth > limits.max_depth {
raise Limit("tree budget exceeded")
}
match v {
Text(s) =>
if s.length() > limits.max_bytes {
raise Limit("string budget exceeded")
}
Data(b) =>
if b.length() > limits.max_bytes {
raise Limit("data budget exceeded")
}
Date(d) =>
if d.is_nan() || d.is_inf() {
raise Invalid("date must be finite")
}
Unsigned(n) =>
if n <= 9223372036854775807UL {
raise Invalid("use Integer for signed-range values")
}
List(items) => {
if items.length() > limits.max_items {
raise Limit("array budget exceeded")
}
for item in items {
walk(item, depth + 1)
}
}
Dict(entries) => {
if entries.length() > limits.max_items {
raise Limit("dictionary budget exceeded")
}
let keys : Map[String, Bool] = Map([])
for (key, item) in entries {
if key.length() > limits.max_bytes {
raise Limit("key budget exceeded")
}
if keys.contains(key) {
raise Invalid("duplicate dictionary key")
}
keys[key] = true
count.val += 1
walk(item, depth + 1)
}
}
_ => ()
}
}
walk(value, 0)
}
///|
pub extend Value with Eq::{equal, not_equal}