///|
/// Internal failure used by the stable Engine JSON boundary.
pub(all) suberror JsonBridgeError {
JsonBridgeFailure(String)
} derive(Debug)
///|
fn json_bridge_number_is_finite(value : Double) -> Bool {
!value.is_nan() && value != 1.0 / 0.0 && value != -1.0 / 0.0
}
///|
/// Copy MoonBit JSON into a realm without consulting the realm's global JSON
/// object or invoking JavaScript code.
pub fn json_to_realm_value(
realm_state : RealmState,
json : Json,
) -> Value raise JsonBridgeError {
match json {
Json::Null => Null
Json::True => Bool(true)
Json::False => Bool(false)
Json::String(value) => String_(value)
Json::Number(value, ..) => {
guard json_bridge_number_is_finite(value) else {
raise JsonBridgeFailure("JSON numbers must be finite")
}
Number(value)
}
Json::Array(items) => {
let values : Array[Value] = []
for item in items {
values.push(json_to_realm_value(realm_state, item))
}
make_array(values)
}
Json::Object(entries) => {
let properties : Map[String, Value] = Map([])
for entry in entries.iter() {
let (key, value) = entry
properties[key] = json_to_realm_value(realm_state, value)
}
make_object(
properties,
realm_state.get_obj_proto(),
None,
"Object",
Map([]),
true,
)
}
}
}
///|
fn json_bridge_same_container(left : Value, right : Value) -> Bool {
match (left, right) {
(Object(a), Object(b)) => physical_equal(a, b)
(Array(a), Array(b)) => physical_equal(a, b)
_ => false
}
}
///|
fn json_bridge_push_unique(
stack : Array[Value],
value : Value,
) -> Unit raise JsonBridgeError {
for active in stack {
if json_bridge_same_container(active, value) {
raise JsonBridgeFailure("cyclic JavaScript values are not JSON data")
}
}
stack.push(value)
}
///|
fn json_bridge_value_to_json(
value : Value,
object_prototype : Value,
stack : Array[Value],
) -> Json raise JsonBridgeError {
match value {
Null => Json::null()
Bool(value) => Json::boolean(value)
String_(value) => Json::string(value)
Number(value) => {
guard json_bridge_number_is_finite(value) else {
raise JsonBridgeFailure("JavaScript numbers must be finite")
}
Json::number(value)
}
Undefined => raise JsonBridgeFailure("undefined is not JSON data")
Symbol(_) => raise JsonBridgeFailure("Symbol values are not JSON data")
Promise(_) =>
raise JsonBridgeFailure(
"Promise results are asynchronous; call_json only accepts synchronous JSON results",
)
Proxy(_) =>
raise JsonBridgeFailure(
"Proxy values are not accepted because JSON conversion must not execute traps",
)
Map(_) => raise JsonBridgeFailure("Map values are not JSON data")
Set(_) => raise JsonBridgeFailure("Set values are not JSON data")
Array(data) => {
guard data.holes.length() == 0 else {
raise JsonBridgeFailure("sparse arrays are not accepted as JSON data")
}
guard data.bag.properties.length() == 0 &&
data.bag.descriptors.length() == 0 &&
data.bag.symbol_properties.length() == 0 &&
data.bag.symbol_descriptors.length() == 0 else {
raise JsonBridgeFailure(
"arrays with custom properties or descriptors are not accepted as JSON data",
)
}
json_bridge_push_unique(stack, value)
let items : Array[Json] = []
for item in data.elements {
items.push(json_bridge_value_to_json(item, object_prototype, stack))
}
ignore(stack.pop())
Json::array(items)
}
Object(data) => {
let has_plain_prototype = data.prototype is Null ||
strict_equal(data.prototype, object_prototype)
guard data.class_name == "Object" &&
data.callable is None &&
has_plain_prototype else {
raise JsonBridgeFailure(
"only plain non-callable objects are accepted as JSON data",
)
}
guard data.bag.symbol_properties.length() == 0 &&
data.bag.symbol_descriptors.length() == 0 else {
raise JsonBridgeFailure(
"objects with symbol properties are not accepted as JSON data",
)
}
guard data.bag.internal_slots.length() == 0 &&
data.bag.host_slots.length() == 0 else {
raise JsonBridgeFailure(
"objects with internal or host state are not accepted as JSON data",
)
}
json_bridge_push_unique(stack, value)
let entries : Map[String, Json] = Map([])
for entry in data.bag.properties.iter() {
let (key, property) = entry
match data.bag.descriptors.get(key) {
Some(descriptor) if descriptor.is_accessor =>
raise JsonBridgeFailure(
"accessor properties are not accepted as JSON data: " + key,
)
Some(descriptor) if !descriptor.enumerable =>
raise JsonBridgeFailure(
"non-enumerable properties are not accepted as JSON data: " + key,
)
_ =>
entries[key] = json_bridge_value_to_json(
property, object_prototype, stack,
)
}
}
ignore(stack.pop())
Json::object(entries)
}
}
}
///|
/// Copy a runtime value into strict JSON data without performing property
/// lookup, calling getters or `toJSON`, or consulting a mutable global.
pub fn realm_value_to_json(
realm_state : RealmState,
value : Value,
) -> Json raise JsonBridgeError {
json_bridge_value_to_json(value, realm_state.get_obj_proto(), [])
}