use ascii; use encoding::json; use fmt; use io; use math; use memio; use strings; use types; export type deser = struct { val: *json::value, link: ((*deser, (str | size)) | void), }; export type error = !str; export fn newdeser(val: *json::value) deser = deser { val = val, link = void }; export fn fail(de: *deser, fmt: str, args: fmt::field...) error = { let path: [](str | size) = []; defer free(path); for (let de_ = de; true) match (de_.link) { case let link: (*deser, (str | size)) => append(path, link.1); de_ = link.0; case void => break; }; let err = memio::dynamic(); if (len(path) == 0 || !(path[len(path) - 1] is str)) { memio::concat(&err, ".")!; }; for (let i = len(path); i > 0) { i -= 1; match (path[i]) { case let s: str => memio::concat(&err, ".")!; printfield(&err, s)!; case let n: size => fmt::fprintf(&err, "[{}]", n)!; }; }; memio::concat(&err, ": ")!; fmt::fprintf(&err, fmt, args...)!; return memio::string(&err)!; }; export fn printfield(out: io::handle, s: str) (void | io::error) = { let valid = true; let first = true; let it = strings::iter(s); for (true) match (strings::next(&it)) { case let ch: rune => if (!ascii::isalpha(ch) && ch != '_' && (first || !ascii::isdigit(ch))) { json::dump(out, s)?; return; }; first = false; case done => break; }; memio::concat(out, s)?; return; }; export fn strfield(s: str) str = { let out = memio::dynamic(); printfield(&out, s)!; return memio::string(&out)!; }; export fn typename(val: *json::value) str = { match (*val) { case f64 => return "number"; case str => return "string"; case bool => return "boolean"; case json::_null => return "null"; case []json::value => return "array"; case json::object => return "object"; }; }; export fn optfield(de: *deser, name: str) (deser | void | error) = { const obj = object(de)?; match (json::get(obj, name)) { case let val: *json::value => return deser { val = val, link = (de, name), }; case void => void; }; }; export fn field(de: *deser, name: str) (deser | error) = { match (optfield(de, name)) { case let de_field: deser => return de_field; case void => let fieldname = strfield(name); defer free(fieldname); return fail(de, "Field {} is required but missing", fieldname); }; }; export fn index(de: *deser, i: size) (deser | error) = { const arr = array(de)?; if (i < len(arr)) { return deser { val = &arr[i], link = (de, i), }; } else { return fail(de, "Array index {} is required but missing", i); }; }; export fn length(de: *deser) (size | error) = len(array(de)?); export fn assert_length(de: *deser, n: size) (void | error) = { if (length(de)? != n) return fail(de, "Array has {} elements, {} expected", length(de)?, n); }; export fn count(de: *deser) (size | error) = json::count(object(de)?); export fn assert_fields(de: *deser, names: str...) (void | error) = { const obj = object(de)?; let err = memio::dynamic(); defer io::close(&err)!; let n = 0z; let it = json::iter(obj); for :fields (true) { match (json::next(&it)) { case let entry: (const str, const *json::value) => for (let i = 0z; i < len(names); i += 1) { if (entry.0 == names[i]) { continue :fields; }; }; if (n != 0) { memio::concat(&err, ", ")!; }; printfield(&err, entry.0)!; n += 1; case void => break; }; }; switch (n) { case 0 => return; case 1 => return fail(de, "Unknown field {}", memio::string(&err)!); case => return fail(de, "Unknown fields {}", memio::string(&err)!); }; }; export type iterator = struct { iter: json::iterator, deser: *deser, }; export fn iter(de: *deser) (iterator | error) = { const obj = object(de)?; return iterator { iter = json::iter(obj), deser = de, }; }; export fn next(it: *iterator) ((str, deser) | void) = { match (json::next(&it.iter)) { case let entry: (const str, const *json::value) => return (entry.0, deser { val = entry.1, link = (it.deser, entry.0), }); case void => void; }; }; export fn object(de: *deser) (*json::object | error) = { if (*de.val is json::object) { return &(de.val: *struct { t: int, val: json::object }).val; } else { return fail(de, "Expected object, found {}", typename(de.val)); }; }; export fn array(de: *deser) ([]json::value | error) = { match (*de.val) { case let val: []json::value => return val; case => return fail(de, "Expected array, found {}", typename(de.val)); }; }; export fn string(de: *deser) (str | error) = { match (*de.val) { case let val: str => return val; case => return fail(de, "Expected string, found {}", typename(de.val)); }; }; export fn boolean(de: *deser) (bool | error) = { match (*de.val) { case let val: bool => return val; case => return fail(de, "Expected boolean, found {}", typename(de.val)); }; }; export fn number(de: *deser) (f64 | error) = { match (*de.val) { case let val: f64 => return val; case => return fail(de, "Expected number, found {}", typename(de.val)); }; }; export fn number_frange(de: *deser, a: f64, b: f64) (f64 | error) = { const val = number(de)?; if (val < a || val > b) return fail(de, "Number {} is outside the allowed range [{},{}]", val, a, b); return val; }; export fn number_irange(de: *deser, a: i64, b: i64) (i64 | error) = { const val = number(de)?; if (math::absf64(val) > 9007199254740991.0) return fail(de, "Number {} is too large to be safely converted from f64 to an integer", val); const (ival, frac) = math::modfracf64(val); if (frac != 0.0) return fail(de, "Number {} is not an integer", val); const ival = ival: i64; if (ival < a || ival > b) return fail(de, "Number {} is outside the allowed range [{},{}]", val, a, b); return ival; }; export fn number_u8(de: *deser) (u8 | error) = number_irange(de, 0, types::U8_MAX: i64)?: u8; export fn number_u16(de: *deser) (u16 | error) = number_irange(de, 0, types::U16_MAX: i64)?: u16; export fn number_u32(de: *deser) (u32 | error) = number_irange(de, 0, types::U32_MAX: i64)?: u32; export fn number_u64(de: *deser) (u64 | error) = number_irange(de, 0, types::I64_MAX)?: u64; export fn number_i8(de: *deser) (i8 | error) = number_irange(de, types::I8_MIN, types::I8_MAX)?: i8; export fn number_i16(de: *deser) (i16 | error) = number_irange(de, types::I16_MIN, types::I16_MAX)?: i16; export fn number_i32(de: *deser) (i32 | error) = number_irange(de, types::I32_MIN, types::I32_MAX)?: i32; export fn number_i64(de: *deser) (i64 | error) = number_irange(de, types::I64_MIN, types::I64_MAX);