///|
fn json_field(j : Json, key : String) -> Json raise TdmsError {
match j {
Object(m) =>
match m.get(key) {
Some(v) => v
None => raise Invalid("missing JSON field: " + key)
}
_ => raise Invalid("expected JSON object")
}
}
///|
fn json_optional(j : Json, key : String) -> Json? raise TdmsError {
match j {
Object(m) => m.get(key)
_ => raise Invalid("expected JSON object")
}
}
///|
fn json_text(j : Json) -> String raise TdmsError {
match j {
String(s) => s
_ => raise Invalid("expected JSON string")
}
}
///|
fn json_array(j : Json) -> Array[Json] raise TdmsError {
match j {
Array(a) => a
_ => raise Invalid("expected JSON array")
}
}
///|
fn json_integer(j : Json) -> Int32 raise TdmsError {
match j {
Number(n, ..) => {
demand(
finite(n) && n >= 0.0 && n <= 2147483647.0 && n == n.trunc(),
"expected bounded nonnegative integer",
)
n.to_int()
}
_ => raise Invalid("expected JSON integer")
}
}
///|
fn json_bool(j : Json) -> Bool raise TdmsError {
match j {
True => true
False => false
_ => raise Invalid("expected JSON boolean")
}
}
///|
fn value_to_json(v : Value) -> Json {
match v {
Signed(n) => Json::string(n.to_string())
Unsigned(n) => Json::string(n.to_string())
Textual(s) => Json::string(s)
Logical(b) => Json::boolean(b)
Real(n) =>
if n.is_nan() {
Json::string("NaN")
} else if n.is_inf() {
Json::string(if n > 0.0 { "Infinity" } else { "-Infinity" })
} else {
Json::number(n)
}
Instant(t) =>
Json::object({
"seconds": Json::string(t.seconds.to_string()),
"fractions": Json::string(t.fractions.to_string()),
})
}
}
///|
fn value_from_json(j : Json, kind : DataType) -> Value raise TdmsError {
let value : Value = match kind {
I8 | I16 | I32 | I64 => {
let s = json_text(j)
let n = @string.parse_int64(s) catch {
_ => raise Invalid("invalid signed integer string")
}
Signed(n)
}
U8 | U16 | U32 | U64 => {
let s = json_text(j)
let n = @string.parse_uint64(s) catch {
_ => raise Invalid("invalid unsigned integer string")
}
Unsigned(n)
}
F32 | F64 =>
match j {
Number(n, ..) => Real(n)
String("NaN") => Real(0.0 / 0.0)
String("Infinity") => Real(1.0 / 0.0)
String("-Infinity") => Real(-1.0 / 0.0)
_ => raise Invalid("expected float or named nonfinite value")
}
Text => Textual(json_text(j))
Boolean => Logical(json_bool(j))
Time => {
let seconds_text = json_text(json_field(j, "seconds"))
let fraction_text = json_text(json_field(j, "fractions"))
let seconds = @string.parse_int64(seconds_text) catch {
_ => raise Invalid("invalid timestamp seconds")
}
let fractions = @string.parse_uint64(fraction_text) catch {
_ => raise Invalid("invalid timestamp fractions")
}
Instant({ seconds, fractions, })
}
}
value.validate(kind)
value
}
///|
fn object_to_json(obj : DataObject) -> Json raise TdmsError {
let properties = obj.properties.map(p => {
Json::object({
"name": Json::string(p.name),
"type": Json::number(p.kind.code().to_double()),
"value": value_to_json(p.value),
})
})
Json::object({
"path": Json::string(obj.path.text()),
"type": match obj.kind {
Some(k) => Json::number(k.code().to_double())
None => Json::null()
},
"values": Json::array(obj.values.map(value_to_json)),
"properties": Json::array(properties),
})
}
///|
fn document_to_json(doc : Document) -> Json raise TdmsError {
let objects : Array[Json] = []
for obj in doc.objects {
objects.push(object_to_json(obj))
}
Json::array(objects)
}
///|
fn document_from_json(j : Json, limits : Limits) -> Document raise TdmsError {
let objects = json_array(j)
if objects.length() > limits.max_objects {
raise Budget("JSON objects")
}
let doc = Document::new()
let mut samples = 0
let mut property_count = 0
for o in objects {
let path = parse_path(json_text(json_field(o, "path")))
let kind = match json_optional(o, "type") {
Some(Null) | None => None
Some(v) => Some(data_type(json_integer(v).reinterpret_as_uint()))
}
let values_json = match json_optional(o, "values") {
Some(v) => json_array(v)
None => []
}
if values_json.length() > limits.max_samples - samples {
raise Budget("JSON samples")
}
samples += values_json.length()
let values : Array[Value] = []
for v in values_json {
match kind {
Some(k) => values.push(value_from_json(v, k))
None => raise Invalid("values without channel type")
}
}
let properties_json = match json_optional(o, "properties") {
Some(v) => json_array(v)
None => []
}
if properties_json.length() > limits.max_properties - property_count {
raise Budget("JSON properties")
}
property_count += properties_json.length()
let properties : Array[Property] = []
for p in properties_json {
let name = json_text(json_field(p, "name"))
let k = data_type(
json_integer(json_field(p, "type")).reinterpret_as_uint(),
)
properties.push({
name,
kind: k,
value: value_from_json(json_field(p, "value"), k),
})
}
doc.objects.push({ path, kind, values, properties, })
}
doc.validate(limits)
doc
}
///|
fn optional_bool(request : Json, key : String) -> Bool raise TdmsError {
match json_optional(request, key) {
Some(v) => json_bool(v)
None => false
}
}
///|
pub fn process_json(
text : String,
limits~ : Limits = Limits::default(),
) -> String raise TdmsError {
limits.validate()
if text.length() > limits.max_bytes {
raise Budget("JSON request characters")
}
let request = @json.parse(
text,
max_nesting_depth=32,
reject_duplicate_keys=true,
) catch {
_ => raise Invalid("invalid JSON request")
}
demand(
json_text(json_field(request, "protocol")) == "moontdms/1",
"unsupported protocol",
)
let op = json_text(json_field(request, "op"))
let result = if op == "encode" {
let doc = document_from_json(json_field(request, "objects"), limits)
let big_endian = optional_bool(request, "big_endian")
let bytes = if optional_bool(request, "interleaved") {
write_interleaved(doc, big_endian~, limits~)
} else {
write(doc, big_endian~, limits~)
}
Json::object({ "hex": Json::string(encode_hex(bytes)) })
} else {
let bytes = decode_hex(json_text(json_field(request, "hex")), limits~)
if op == "inspect" {
let segments = inspect(bytes, limits~).map(s => {
Json::object({
"offset": Json::number(s.offset.to_double()),
"raw_offset": Json::number(s.raw_offset.to_double()),
"end_offset": Json::number(s.end_offset.to_double()),
"toc": Json::number(s.toc.to_double()),
"big_endian": Json::boolean(s.big_endian),
})
})
Json::object({ "segments": Json::array(segments) })
} else {
let doc = read(bytes, limits~)
if op == "decode" {
Json::object({ "objects": document_to_json(doc) })
} else if op == "csv" {
Json::object({
"csv": Json::string(
doc.csv(
waveform_time=optional_bool(request, "waveform_time"),
limits~,
),
),
})
} else if op == "select" {
let paths_json = json_array(json_field(request, "paths"))
let paths : Array[ObjectPath] = []
for p in paths_json {
paths.push(parse_path(json_text(p)))
}
let start = match json_optional(request, "start") {
Some(v) => json_integer(v)
None => 0
}
let end = match json_optional(request, "end") {
Some(v) => Some(json_integer(v))
None => None
}
let selected = doc.select(paths, start~, end?, limits~)
Json::object({
"hex": Json::string(encode_hex(write(selected, limits~))),
})
} else {
raise Invalid("unknown operation")
}
}
}
Json::object({ "protocol": Json::string("moontdms/1"), "result": result }).stringify()
}