///|
/// Compiler-level constants retain values outside their wire integer width.
/// The wire conversion validates widths separately when values are serialized.
fn Schema::const_json(
self : Schema,
owner : String,
t : IdlType,
value : IdlConst,
trail : Array[String],
depth : Int,
budget? : Ref[Int] = Ref(0),
) -> Json raise SchemaError {
schema_tick(budget, depth)
if value is NameConstant(name) {
if name == "true" || name == "false" {
return self.const_json(
owner,
t,
IntegerConstant(if name == "true" { 1L } else { 0L }),
trail,
depth + 1,
budget~,
)
}
if t is Named(target) {
match self.definitions.get(target) {
Some(Record(_, _, _, _)) =>
raise InvalidSchema("struct default requires a map literal")
Some(Enumeration(_, _, _)) =>
if !name.contains(".") {
raise InvalidSchema("enum constant identifier must be qualified")
}
_ => ()
}
}
if self.enum_number(owner, t, name) is Some(n) {
return self.const_json(
owner,
t,
IntegerConstant(n.to_int64()),
trail,
depth + 1,
budget~,
)
}
let (name, definition) = self.find(owner, name)
if trail.contains(name) {
raise InvalidSchema("circular constant " + name)
}
if definition is Constant(_, declared, constant) {
let context = definition_owner(name)
let declared = self.resolve(context, declared, 0)
// Apache 0.24 resolves an integer-initialized double through its double slot.
let constant = if declared == Base("double") &&
constant is IntegerConstant(_) {
FloatConstant(0.0)
} else {
constant
}
trail.push(name)
defer ignore(trail.pop())
return self.const_json(context, t, constant, trail, depth + 1, budget~)
}
raise InvalidSchema("not a constant " + name)
}
match (t, value) {
(Base("bool"), IntegerConstant(n)) => (n != 0L).to_json()
(Base("byte" | "i16" | "i32" | "double"), IntegerConstant(n)) =>
Json::number(n.to_double(), repr=n.to_string())
(Base("i64"), IntegerConstant(n)) => n.to_string().to_json()
(Named(name), IntegerConstant(n)) if self.definitions.get(name)
is Some(Enumeration(_, _, _)) =>
Json::number(n.to_double(), repr=n.to_string())
(Base("double"), FloatConstant(n)) =>
if n.is_nan() {
"NaN".to_json()
} else if n.is_inf() {
(if n < 0.0 { "-Infinity" } else { "Infinity" }).to_json()
} else {
n.to_json()
}
(Base("string"), StringConstant(s)) => s.to_json()
(Base("binary"), StringConstant(s)) =>
Json::object({ "$binary": schema_hex(@utf8.encode(s)).to_json() })
(Base("uuid"), StringConstant(s)) => {
ignore(schema_uuid(s))
s.to_lower().to_json()
}
(ListOf(elem) | SetOf(elem), ListConstant(values)) =>
Json::array(
values.map(v => {
self.const_json(owner, elem, v, trail, depth + 1, budget~)
}),
)
(MapOf(key, tvalue), MapConstant(values)) =>
Json::array(
values.map(pair => {
Json::array([
self.const_json(owner, key, pair.0, trail, depth + 1, budget~),
self.const_json(owner, tvalue, pair.1, trail, depth + 1, budget~),
])
}),
)
(Named(name), MapConstant(values)) => {
let fields = match self.definitions.get(name) {
Some(Record(_, _, fields, _)) => fields
_ => raise InvalidSchema("map constant requires struct type")
}
let object = Map([])
for pair in values {
let label = match pair.0 {
StringConstant(s) | NameConstant(s) => s
_ => raise InvalidSchema("struct constant key must name a field")
}
let field = match fields.iter().find_first(f => f.name == label) {
Some(field) => field
None => raise InvalidSchema("unknown struct constant field " + label)
}
object[label] = self.const_json(
owner,
self.resolve(definition_owner(name), field.field_type, 0),
pair.1,
trail,
depth + 1,
budget~,
)
}
Json::object(object)
}
_ => raise InvalidSchema("constant type mismatch")
}
}