decodeOp function
Parses untrusted input into an op, or throws with the reason. Schema checks happen later.
Implementation
Op decodeOp(Object? input) {
if (input is! Map) throw AccordException('op must be an object');
final o = input;
final opId = _str(o, 'op_id');
final record = _str(o, 'record');
final field = _str(o, 'field');
final hlc = Hlc.decode(_str(o, 'hlc'));
final device = parseOpId(opId).device;
recordType(record);
if (device != hlc.node) throw AccordException('op $opId carries a clock from "${hlc.node}"');
switch (o['kind']) {
case 'assign':
if (!o.containsKey('value')) throw AccordException('assign needs a value');
return AssignOp(
opId: opId,
record: record,
field: field,
hlc: hlc,
value: o['value'],
deps: _deps(o),
);
case 'inc':
final by = o['by'];
// JSON decoders may hand a whole number over as a double (`3.0`); JavaScript sees one number.
final int? n = by is int
? by
: (by is double && by == by.truncateToDouble() ? by.toInt() : null);
if (n == null || n.abs() > maxSafeInteger) {
throw AccordException('inc needs an integer "by"');
}
return IncOp(opId: opId, record: record, field: field, hlc: hlc, by: n);
case 'add':
return AddOp(
opId: opId,
record: record,
field: field,
hlc: hlc,
element: _element(o),
deps: o.containsKey('deps') ? _deps(o) : const [],
);
case 'remove':
return RemoveOp(
opId: opId,
record: record,
field: field,
hlc: hlc,
element: _element(o),
deps: _deps(o),
);
default:
throw AccordException('unknown op kind ${o['kind']}');
}
}