///|
/// Ordered key-value pair used by record payloads and event headers.
pub(all) struct Pair {
key : String
value : String
} derive(Eq, @debug.Debug)
///|
/// Construct an ordered string pair.
pub fn pair(key : StringView, value : StringView) -> Pair {
{ key: key.to_owned(), value: value.to_owned() }
}
///|
/// Structured event payload. Records keep declaration order so exported
/// snapshots stay deterministic across targets and test runs.
pub(all) enum EventValue {
VNull
VBool(Bool)
VInt(Int)
VText(String)
VList(Array[EventValue])
VRecord(Array[(String, EventValue)])
} derive(Eq, @debug.Debug)
///|
pub fn null_value() -> EventValue {
VNull
}
///|
pub fn bool_value(value : Bool) -> EventValue {
VBool(value)
}
///|
pub fn int_value(value : Int) -> EventValue {
VInt(value)
}
///|
pub fn text_value(value : StringView) -> EventValue {
VText(value.to_owned())
}
///|
pub fn list_value(values : ArrayView[EventValue]) -> EventValue {
VList(values.to_owned())
}
///|
pub fn record_value(fields : ArrayView[(String, EventValue)]) -> EventValue {
VRecord(fields.to_owned())
}
///|
pub fn field(key : StringView, value : EventValue) -> (String, EventValue) {
(key.to_owned(), value)
}
///|
pub fn EventValue::as_text(self : EventValue) -> String? {
match self {
VText(value) => Some(value)
_ => None
}
}
///|
pub fn EventValue::as_int(self : EventValue) -> Int? {
match self {
VInt(value) => Some(value)
_ => None
}
}
///|
pub fn EventValue::as_bool(self : EventValue) -> Bool? {
match self {
VBool(value) => Some(value)
_ => None
}
}
///|
pub fn EventValue::get(self : EventValue, key : StringView) -> EventValue? {
let wanted = key.to_owned()
match self {
VRecord(fields) => {
for entry in fields {
let (name, value) = entry
if name == wanted {
return Some(value)
}
}
None
}
_ => None
}
}
///|
pub fn EventValue::get_path(
self : EventValue,
path : StringView,
) -> EventValue? {
let parts = path.to_owned().split(".").to_array()
guard parts.length() > 0 else { return None }
for part in parts; current = Some(self) {
match current {
Some(value) => continue value.get(part)
None => break None
}
} nobreak {
current
}
}
///|
pub fn EventValue::require_path(
self : EventValue,
path : StringView,
) -> Result[EventValue, EventRailError] {
match self.get_path(path) {
Some(value) => Ok(value)
None => Err(MissingField(path.to_owned()))
}
}
///|
pub fn EventValue::is_scalar(self : EventValue) -> Bool {
match self {
VNull | VBool(_) | VInt(_) | VText(_) => true
VList(_) | VRecord(_) => false
}
}
///|
pub fn EventValue::field_count(self : EventValue) -> Int {
match self {
VRecord(fields) => fields.length()
_ => 0
}
}
///|
pub fn EventValue::list_count(self : EventValue) -> Int {
match self {
VList(values) => values.length()
_ => 0
}
}
///|
pub fn EventValue::to_wire(self : EventValue) -> String {
match self {
VNull => "null"
VBool(value) => if value { "true" } else { "false" }
VInt(value) => value.to_string()
VText(value) => "\"\{escape_wire_text(value)}\""
VList(values) => {
let buf = StringBuilder()
buf.write_char('[')
for idx, item in values {
if idx > 0 {
buf.write_char(',')
}
buf.write_string(item.to_wire())
}
buf.write_char(']')
buf.to_string()
}
VRecord(fields) => {
let buf = StringBuilder()
buf.write_char('{')
for idx, entry in fields {
let (name, value) = entry
if idx > 0 {
buf.write_char(',')
}
buf.write_string(escape_wire_text(name))
buf.write_char('=')
buf.write_string(value.to_wire())
}
buf.write_char('}')
buf.to_string()
}
}
}
///|
pub fn escape_wire_text(value : String) -> String {
let buf = StringBuilder()
for ch in value {
match ch {
'\\' => buf.write_string("\\\\")
'"' => buf.write_string("\\\"")
'\n' => buf.write_string("\\n")
'\r' => buf.write_string("\\r")
'\t' => buf.write_string("\\t")
',' => buf.write_string("\\,")
'=' => buf.write_string("\\=")
'{' => buf.write_string("\\{")
'}' => buf.write_string("\\}")
'[' => buf.write_string("\\[")
']' => buf.write_string("\\]")
_ => buf.write_char(ch)
}
}
buf.to_string()
}
///|
pub fn pairs_to_wire(pairs : ArrayView[Pair]) -> String {
let buf = StringBuilder()
for idx, p in pairs {
if idx > 0 {
buf.write_char(';')
}
buf.write_string(escape_wire_text(p.key))
buf.write_char('=')
buf.write_string(escape_wire_text(p.value))
}
buf.to_string()
}