///|
/// A JSON-shaped semantic value. Requests and reports travel as these; object
/// key order is never significant, and numbers are finite doubles.
pub(all) enum Value {
Null
Bool(Bool)
Number(Double)
String(String)
Array(Array[Value])
Object(Map[String, Value])
} derive(Eq, @debug.Debug)
///|
pub extend Value with Eq::{equal, not_equal}
///|
pub extend Value with @debug.Debug::{to_repr}
///|
pub fn from_json(value : Json) -> Value {
match value {
Json::Null => Null
Json::True => Bool(true)
Json::False => Bool(false)
Json::Number(n, ..) => Number(n)
Json::String(s) => String(s)
Json::Array(items) => Array(items.map(from_json))
Json::Object(fields) => Object(fields.map(fn(_, v) { from_json(v) }))
}
}
///|
pub fn Value::to_json(self : Value) -> Json {
match self {
Null => Json::null()
Bool(b) => Json::boolean(b)
Number(n) => Json::number(n)
String(s) => Json::string(s)
Array(items) => Json::array(items.map(fn(v) { v.to_json() }))
Object(fields) => Json::object(fields.map(fn(_, v) { v.to_json() }))
}
}
///|
pub(all) suberror InputError {
InputError(String)
} derive(Eq, @debug.Debug)
///|
pub extend InputError with Eq::{equal, not_equal}
///|
pub extend InputError with @debug.Debug::{to_repr}
///|
pub impl Show for InputError with fn output(self, logger) {
let InputError(message) = self
logger.write_string(message)
}
///|
pub extend InputError with Show::{to_string, output}
///|
pub fn get(value : Value, key : String) -> Value {
match value {
Object(fields) => fields.get(key).unwrap_or(Null)
_ => Null
}
}
///|
pub fn has(value : Value, key : String) -> Bool {
match value {
Object(fields) => fields.contains(key)
_ => false
}
}
///|
pub fn required(value : Value, key : String) -> Value raise InputError {
if !has(value, key) {
raise InputError("missing field: " + key)
}
get(value, key)
}
///|
pub fn str(value : Value) -> String raise InputError {
match value {
String(s) => s
_ => raise InputError("expected string")
}
}
///|
pub fn arr(value : Value) -> Array[Value] raise InputError {
match value {
Array(items) => items
_ => raise InputError("expected array")
}
}
///|
pub fn num(value : Value) -> Double raise InputError {
match value {
Number(n) if !n.is_nan() && !n.is_inf() => n
_ => raise InputError("expected finite number")
}
}
///|
pub fn record(fields : Array[(String, Value)]) -> Value {
let result : Map[String, Value] = Map([])
for (key, value) in fields {
result[key] = value
}
Object(result)
}
///|
pub fn fields_only(
value : Value,
allowed : Array[String],
) -> Unit raise InputError {
let fields = match value {
Object(fields) => fields
_ => raise InputError("expected object")
}
for key, _ in fields {
if !allowed.contains(key) {
raise InputError("unknown field: " + key)
}
}
}
///|
/// The value of one hex digit code point, or None when it is not one.
/// '0'-'9' are 0..9; 'a'-'f' and 'A'-'F' fold onto 10..15.
fn hex_digit_value(digit : Char) -> Int32? {
let code = digit.to_int()
if code >= 48 && code <= 57 {
Some(code - 48)
} else if code >= 97 && code <= 102 {
Some(code - 87)
} else if code >= 65 && code <= 70 {
Some(code - 55)
} else {
None
}
}
///|
/// Hex decode with an exact, named refusal: even length, digits only.
fn parse_hex(text : String) -> Array[Int32] raise InputError {
if text.length() % 2 != 0 {
raise InputError("hex string must have an even number of digits")
}
let out : Array[Int32] = []
let chars = text.to_array()
let half = chars.length() / 2
for i in 0.. out.push(h * 16 + l)
_ =>
raise InputError(
"hex string may contain digits and a-f only, found an invalid digit",
)
}
}
out
}
///|
fn to_hex(bytes : Array[Int32]) -> String {
let digits = [
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f",
]
let mut out = ""
for byte in bytes {
out = out + digits[(byte >> 4) & 0xF]
out = out + digits[byte & 0xF]
}
out
}
///|
test "hex round trips and refuses junk" {
assert_eq(to_hex([0, 15, 16, 255]), "000f10ff")
assert_eq(parse_hex("000f10ff"), [0, 15, 16, 255])
assert_eq(parse_hex("000F10FF"), [0, 15, 16, 255])
let odd = try {
let _ = parse_hex("abc")
"no error"
} catch {
InputError(message) => message
}
assert_eq(odd, "hex string must have an even number of digits")
let junk = try {
let _ = parse_hex("zz")
"no error"
} catch {
InputError(message) => message
}
assert_eq(
junk, "hex string may contain digits and a-f only, found an invalid digit",
)
}