checkPortable function
Reject a JSON value that jsonDecode/jsonEncode accept but serde_json
on the Rust side does not. Divergences (all three verified against
serde_json 1.0.151):
- Unpaired surrogate. A Dart
Stringis UTF-16 and may hold one;jsonDecodeaccepts it both raw and as a\uD800escape, andjsonEncodewrites it back out as a\uD800escape.serde_jsonrejects that escape (LoneLeadingSurrogateInHexEscape/UnexpectedEndOfHexEscape). - Non-finite number. Dart parses
1e999toInfinity;serde_jsonrejects it (NumberOutOfRange). - Nesting past
_maxJsonDepth.
All three classify as Category::Syntax, which aimux-ffi reports as
FfiError::InvalidWireJson (code 202), so it reaches Dart through
_ffiError as an invariant StateError — but none of it is
an invariant: text.substring(0, n) cutting an emoji in half is enough to
produce the first. Catch them here, where the parameter still has a name
and the failure is a catchable FormatException.
Implementation
void checkPortable(Object? value, String param, [int depth = 0]) {
if (depth >= _maxJsonDepth) {
throw FormatException(
'$param: JSON nested deeper than $_maxJsonDepth levels', param);
}
if (value is String) {
_checkSurrogates(value, param);
} else if (value is double && !value.isFinite) {
throw FormatException('$param: number out of range: $value', param);
} else if (value is List) {
for (final element in value) {
checkPortable(element, param, depth + 1);
}
} else if (value is Map) {
value.forEach((key, element) {
checkPortable(key, param, depth + 1);
checkPortable(element, param, depth + 1);
});
}
}