use std::collections::HashSet;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum OutputFormat {
#[default]
Json,
Auto,
Table,
}
const CELL_TRUNCATE: usize = 120;
pub fn render_format(value: Value, format: OutputFormat, presentation: PresentationMode) -> String {
match format {
OutputFormat::Json => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
OutputFormat::Auto | OutputFormat::Table => {
let reduced = if presentation == PresentationMode::Verbose {
value
} else {
apply_redundancy_drop(value)
};
match format {
OutputFormat::Auto => render_auto(reduced),
OutputFormat::Table => render_table_forced(reduced),
OutputFormat::Json => unreachable!(),
}
}
}
}
pub fn apply_redundancy_drop(value: Value) -> Value {
match value {
Value::Object(_) => drop_record(value),
Value::Array(arr) => Value::Array(
arr.into_iter()
.map(|v| if v.is_object() { drop_record(v) } else { v })
.collect(),
),
other => other,
}
}
fn drop_record(value: Value) -> Value {
let Value::Object(mut map) = value else {
return value;
};
map.remove("full_id");
if map.get("namespace").and_then(Value::as_str) == Some("local") {
map.remove("namespace");
}
let props_val = map.remove("properties");
if let Some(Value::Object(props)) = props_val {
let mut new_props = Map::new();
for (k, v) in props {
if map.get(&k) != Some(&v) {
new_props.insert(k, v);
}
}
if !new_props.is_empty() {
map.insert("properties".to_string(), Value::Object(new_props));
}
} else if let Some(other) = props_val {
map.insert("properties".to_string(), other);
}
let out: Map<String, Value> = map
.into_iter()
.map(|(k, v)| {
let v = match v {
Value::Array(arr) => Value::Array(
arr.into_iter()
.map(|item| {
if item.is_object() {
drop_record(item)
} else {
item
}
})
.collect(),
),
other => other,
};
(k, v)
})
.collect();
Value::Object(out)
}
fn render_auto(value: Value) -> String {
if let Some((records, keys)) = find_record_array(&value) {
return render_table(&records, &keys);
}
if value.is_object() {
return render_kv_block(&value, 0);
}
serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string())
}
fn render_table_forced(value: Value) -> String {
if let Some((records, keys)) = find_record_array(&value) {
return render_table(&records, &keys);
}
serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string())
}
fn find_record_array(value: &Value) -> Option<(Vec<Value>, Vec<String>)> {
match value {
Value::Array(arr) if arr.len() >= 2 && arr.iter().all(Value::is_object) => {
let keys = collect_keys(arr);
Some((arr.clone(), keys))
}
Value::Object(map) => {
for v in map.values() {
if let Value::Array(arr) = v {
if arr.len() >= 2 && arr.iter().all(Value::is_object) {
let keys = collect_keys(arr);
return Some((arr.clone(), keys));
}
}
}
None
}
_ => None,
}
}
fn collect_keys(records: &[Value]) -> Vec<String> {
let mut seen = HashSet::new();
let mut keys = Vec::new();
for record in records {
if let Value::Object(map) = record {
for k in map.keys() {
if seen.insert(k.clone()) {
keys.push(k.clone());
}
}
}
}
keys
}
fn render_table(records: &[Value], keys: &[String]) -> String {
let mut out = String::new();
out.push('|');
for k in keys {
out.push(' ');
out.push_str(k);
out.push_str(" |");
}
out.push('\n');
out.push('|');
for _ in keys {
out.push_str("---|");
}
out.push('\n');
for record in records {
out.push('|');
for k in keys {
let cell = record.get(k).unwrap_or(&Value::Null);
let text = cell_text(cell);
out.push(' ');
out.push_str(&text);
out.push_str(" |");
}
out.push('\n');
}
out
}
fn cell_text(value: &Value) -> String {
let raw = match value {
Value::Null => String::new(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => s.clone(),
other => serde_json::to_string(other).unwrap_or_default(),
};
let escaped = raw.replace('|', "\\|").replace(['\n', '\r'], " ");
let char_count = escaped.chars().count();
if char_count > CELL_TRUNCATE {
let truncated: String = escaped.chars().take(CELL_TRUNCATE).collect();
format!("{truncated}...")
} else {
escaped
}
}
fn render_kv_block(value: &Value, depth: usize) -> String {
let indent = " ".repeat(depth);
match value {
Value::Object(map) => {
let mut out = String::new();
for (k, v) in map {
match v {
Value::Object(_) => {
out.push_str(&format!("{}{}:\n", indent, k));
out.push_str(&render_kv_block(v, depth + 1));
}
Value::Array(arr) if arr.iter().any(Value::is_object) => {
out.push_str(&format!("{}{}:\n", indent, k));
for item in arr {
if item.is_object() {
out.push_str(&render_kv_block(item, depth + 1));
} else {
out.push_str(&format!("{} - {}\n", indent, cell_text(item)));
}
}
}
_ => {
out.push_str(&format!("{}{}: {}\n", indent, k, cell_text(v)));
}
}
}
out
}
other => format!("{}{}\n", indent, cell_text(other)),
}
}
pub fn micros_to_iso(micros: i64) -> String {
chrono::DateTime::<chrono::Utc>::from_timestamp_micros(micros)
.unwrap_or_else(chrono::Utc::now)
.to_rfc3339_opts(chrono::SecondsFormat::Micros, true)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum PresentationMode {
#[default]
Agent,
Verbose,
Human,
}
const LIFECYCLE_NULL_PRESERVE: &[&str] = &[
"completed_at",
"deleted_at",
"due_at",
"read_at",
"started_at",
"superseded_at",
"applied_at",
"withdrawn_at",
"reviewed_at",
"parent_id",
"superseded_by",
"replaced_by",
];
const PAYLOAD_TIMESTAMP_FIELDS: &[&str] = &["trigger_at", "due"];
const SCORE_FIELDS: &[&str] = &[
"score",
"salience",
"decay_factor",
"rrf_score",
"similarity",
"cross_encoder_score",
"graph_proximity_score",
];
const UUID_CANONICAL_LEN: usize = 36;
fn should_shorten_uuid_field(key: &str) -> bool {
if key == "full_id" {
return false;
}
key == "id" || key.ends_with("_id") || matches!(key, "superseded_by" | "replaced_by")
}
pub fn present(value: Value, mode: PresentationMode, now_unix_seconds: i64) -> Value {
match mode {
PresentationMode::Verbose | PresentationMode::Human => value,
PresentationMode::Agent => {
let lifecycle_preserve: HashSet<&str> =
LIFECYCLE_NULL_PRESERVE.iter().copied().collect();
let score_fields: HashSet<&str> = SCORE_FIELDS.iter().copied().collect();
let payload_timestamps: HashSet<&str> =
PAYLOAD_TIMESTAMP_FIELDS.iter().copied().collect();
transform_agent(
value,
&lifecycle_preserve,
&score_fields,
&payload_timestamps,
now_unix_seconds,
false,
)
}
}
}
fn transform_agent(
value: Value,
lifecycle: &HashSet<&str>,
scores: &HashSet<&str>,
payload_timestamps: &HashSet<&str>,
now: i64,
inside_properties: bool,
) -> Value {
match value {
Value::Object(map) => {
let mut out = Map::new();
for (k, v) in map {
let child_inside_properties = inside_properties || k == "properties";
let transformed = transform_field_agent(
&k,
v,
lifecycle,
scores,
payload_timestamps,
now,
child_inside_properties,
);
match transformed {
None => {} Some(tv) => {
out.insert(k, tv);
}
}
}
Value::Object(out)
}
Value::Array(arr) => {
let items: Vec<Value> = arr
.into_iter()
.map(|v| {
transform_agent(
v,
lifecycle,
scores,
payload_timestamps,
now,
inside_properties,
)
})
.collect();
Value::Array(items)
}
other => other,
}
}
fn transform_field_agent(
key: &str,
value: Value,
lifecycle: &HashSet<&str>,
scores: &HashSet<&str>,
payload_timestamps: &HashSet<&str>,
now: i64,
inside_properties: bool,
) -> Option<Value> {
match &value {
Value::Null => {
if lifecycle.contains(key) {
Some(value)
} else {
None
}
}
Value::String(s) if s.is_empty() => None,
Value::Array(a) if a.is_empty() => None,
Value::Object(o) if o.is_empty() => None,
Value::Number(_) if scores.contains(key) => {
if let Some(f) = value.as_f64() {
Some(truncate_to_3_sig_figs(f))
} else {
Some(value)
}
}
Value::String(s) if is_canonical_uuid(s) && should_shorten_uuid_field(key) => {
Some(Value::String(s[..8].to_string()))
}
Value::String(s)
if !inside_properties && !payload_timestamps.contains(key) && looks_like_iso8601(s) =>
{
Some(Value::String(compact_timestamp(s, now)))
}
Value::Object(_) | Value::Array(_) => Some(transform_agent(
value,
lifecycle,
scores,
payload_timestamps,
now,
inside_properties,
)),
_ => Some(value),
}
}
fn is_canonical_uuid(s: &str) -> bool {
if s.len() != UUID_CANONICAL_LEN {
return false;
}
let b = s.as_bytes();
b[8] == b'-'
&& b[13] == b'-'
&& b[18] == b'-'
&& b[23] == b'-'
&& b[..8].iter().all(|c| c.is_ascii_hexdigit())
&& b[9..13].iter().all(|c| c.is_ascii_hexdigit())
&& b[14..18].iter().all(|c| c.is_ascii_hexdigit())
&& b[19..23].iter().all(|c| c.is_ascii_hexdigit())
&& b[24..].iter().all(|c| c.is_ascii_hexdigit())
}
fn looks_like_iso8601(s: &str) -> bool {
if s.len() < 16 {
return false;
}
let b = s.as_bytes();
b[4] == b'-'
&& b[7] == b'-'
&& b[10] == b'T'
&& b[13] == b':'
&& b[..4].iter().all(|c| c.is_ascii_digit())
&& b[5..7].iter().all(|c| c.is_ascii_digit())
&& b[8..10].iter().all(|c| c.is_ascii_digit())
&& b[11..13].iter().all(|c| c.is_ascii_digit())
}
fn compact_timestamp(s: &str, now: i64) -> String {
if let Some(unix) = parse_iso8601_unix(s) {
let diff = now - unix;
if (0..86400).contains(&diff) {
return relative_time(diff);
}
}
s.chars().take(16).collect()
}
fn parse_iso8601_unix(s: &str) -> Option<i64> {
if s.len() < 19 {
return None;
}
let b = s.as_bytes();
let year: i64 = parse_digits(&b[0..4])?;
let month: i64 = parse_digits(&b[5..7])?;
let day: i64 = parse_digits(&b[8..10])?;
let hour: i64 = parse_digits(&b[11..13])?;
let minute: i64 = parse_digits(&b[14..16])?;
let second: i64 = parse_digits(&b[17..19])?;
let days_since_epoch = days_from_civil(year, month, day);
let local = days_since_epoch * 86400 + hour * 3600 + minute * 60 + second;
let offset_secs = parse_tz_offset_secs(&s[19..])?;
Some(local - offset_secs)
}
fn parse_tz_offset_secs(tail: &str) -> Option<i64> {
let mut rest = tail;
if let Some(after_dot) = rest.strip_prefix('.') {
let frac_len = after_dot.bytes().take_while(u8::is_ascii_digit).count();
if frac_len == 0 {
return None;
}
rest = &after_dot[frac_len..];
}
if rest.is_empty() || rest == "Z" {
return Some(0);
}
let sign: i64 = match rest.as_bytes().first()? {
b'+' => 1,
b'-' => -1,
_ => return None,
};
let digits = &rest[1..];
let (hh, mm) = match digits.len() {
5 if digits.as_bytes()[2] == b':' => (
parse_digits(&digits.as_bytes()[0..2])?,
parse_digits(&digits.as_bytes()[3..5])?,
),
4 => (
parse_digits(&digits.as_bytes()[0..2])?,
parse_digits(&digits.as_bytes()[2..4])?,
),
_ => return None,
};
if hh > 23 || mm > 59 {
return None;
}
Some(sign * (hh * 3600 + mm * 60))
}
fn parse_digits(b: &[u8]) -> Option<i64> {
let s = std::str::from_utf8(b).ok()?;
s.parse().ok()
}
fn days_from_civil(y: i64, m: i64, d: i64) -> i64 {
let y = if m <= 2 { y - 1 } else { y };
let era = y.div_euclid(400);
let yoe = y - era * 400;
let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
era * 146097 + doe - 719468
}
fn relative_time(diff_secs: i64) -> String {
if diff_secs < 60 {
format!("{diff_secs}s ago")
} else if diff_secs < 3600 {
format!("{}m ago", diff_secs / 60)
} else {
format!("{}h ago", diff_secs / 3600)
}
}
fn truncate_to_3_sig_figs(f: f64) -> Value {
if f == 0.0 || !f.is_finite() {
return Value::from(f);
}
let magnitude = f.abs().log10().floor() as i32;
let factor = 10f64.powi(2 - magnitude);
let rounded = (f * factor).round() / factor;
serde_json::Number::from_f64(rounded)
.map(Value::Number)
.unwrap_or(Value::from(rounded))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
const NOW: i64 = 1_748_016_480;
fn agent(v: Value) -> Value {
present(v, PresentationMode::Agent, NOW)
}
#[test]
fn verbose_passthrough() {
let v = json!({"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "title": "X"});
let out = present(v.clone(), PresentationMode::Verbose, NOW);
assert_eq!(out, v);
}
#[test]
fn agent_shortens_uuid() {
let v = json!({"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"});
let out = agent(v);
assert_eq!(out["id"], json!("a1b2c3d4"));
}
#[test]
fn agent_drops_empty_string() {
let v = json!({"title": "ok", "description": ""});
let out = agent(v);
assert!(out.get("description").is_none());
assert_eq!(out["title"], json!("ok"));
}
#[test]
fn agent_drops_empty_array() {
let v = json!({"tags": [], "title": "ok"});
let out = agent(v);
assert!(out.get("tags").is_none());
}
#[test]
fn agent_drops_empty_object() {
let v = json!({"properties": {}, "title": "ok"});
let out = agent(v);
assert!(out.get("properties").is_none());
}
#[test]
fn agent_drops_non_lifecycle_null() {
let v = json!({"result": null, "title": "ok"});
let out = agent(v);
assert!(out.get("result").is_none());
}
#[test]
fn agent_preserves_lifecycle_null() {
let v = json!({"completed_at": null, "due_at": null, "title": "ok"});
let out = agent(v);
assert_eq!(out["completed_at"], json!(null));
assert_eq!(out["due_at"], json!(null));
}
#[test]
fn agent_preserves_relationship_null() {
let v = json!({"parent_id": null, "superseded_by": null});
let out = agent(v);
assert_eq!(out["parent_id"], json!(null));
assert_eq!(out["superseded_by"], json!(null));
}
#[test]
fn agent_truncates_score_field() {
let v = json!({"score": 0.12345678});
let out = agent(v);
let s = out["score"].as_f64().unwrap();
assert!((s - 0.123).abs() < 1e-9, "expected ~0.123, got {s}");
}
#[test]
fn agent_compacts_old_timestamp_to_minutes() {
let v = json!({"created_at": "2020-01-01T10:30:45.123456Z"});
let out = agent(v);
assert_eq!(out["created_at"], json!("2020-01-01T10:30"));
}
#[test]
fn agent_compacts_recent_timestamp_to_relative() {
let ts_unix = NOW - 180;
let ts = unix_to_iso8601(ts_unix);
let v = json!({"updated_at": ts});
let out = agent(v);
assert_eq!(out["updated_at"], json!("3m ago"));
}
#[test]
fn agent_does_not_compact_top_level_trigger_at_field() {
let at = "2026-07-11T19:00:00-04:00";
let v = json!({
"id": "a1b2c3d4",
"full_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"event_type": "remind",
"trigger_at": at,
"repeat": null,
"status": "pending",
});
let out = agent(v);
assert_eq!(out["trigger_at"], json!(at));
}
#[test]
fn agent_does_not_compact_top_level_trigger_at_utc() {
let at = "2026-07-11T23:00:00Z";
let v = json!({"trigger_at": at});
let out = agent(v);
assert_eq!(out["trigger_at"], json!(at));
}
#[test]
fn agent_does_not_compact_top_level_trigger_at_offset_less() {
let at = "2026-07-11T23:00:00";
let v = json!({"trigger_at": at});
let out = agent(v);
assert_eq!(out["trigger_at"], json!(at));
}
#[test]
fn agent_still_compacts_other_top_level_timestamps_alongside_trigger_at() {
let v = json!({
"trigger_at": "2026-07-11T19:00:00-04:00",
"created_at": "2020-01-01T10:30:45.123456Z",
});
let out = agent(v);
assert_eq!(out["trigger_at"], json!("2026-07-11T19:00:00-04:00"));
assert_eq!(out["created_at"], json!("2020-01-01T10:30"));
}
#[test]
fn agent_does_not_compact_top_level_due() {
let due = "2026-08-01T09:30:15-04:00";
let v = json!({"due": due});
let out = agent(v);
assert_eq!(out["due"], json!(due));
}
#[test]
fn agent_still_protects_nested_trigger_at_under_properties() {
let at = "2026-07-11T19:00:00-04:00";
let v = json!({
"id": "a1b2c3d4",
"properties": {"trigger_at": at, "status": "pending"},
});
let out = agent(v);
assert_eq!(out["properties"]["trigger_at"], json!(at));
}
#[test]
fn agent_recurses_into_nested_objects() {
let v = json!({
"items": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"tags": [],
"score": 0.9999
}
]
});
let out = agent(v);
let item = &out["items"][0];
assert_eq!(item["id"], json!("a1b2c3d4"));
assert!(item.get("tags").is_none());
let s = item["score"].as_f64().unwrap();
assert!((s - 1.0).abs() < 1e-9);
}
#[test]
fn agent_preserves_full_id_as_36_chars() {
let uuid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
let v = json!({"id": uuid, "full_id": uuid, "title": "X"});
let out = agent(v);
assert_eq!(
out["id"],
json!("a1b2c3d4"),
"id should be 8-char short form"
);
assert_eq!(
out["full_id"].as_str().unwrap().len(),
36,
"full_id must be 36 chars in agent mode"
);
assert_eq!(
out["full_id"],
json!(uuid),
"full_id must equal the original UUID"
);
assert!(
out["full_id"]
.as_str()
.unwrap()
.starts_with(out["id"].as_str().unwrap()),
"full_id must start with the short id prefix"
);
}
#[test]
fn is_canonical_uuid_recognizes_valid() {
assert!(is_canonical_uuid("a1b2c3d4-e5f6-7890-abcd-ef1234567890"));
assert!(!is_canonical_uuid("a1b2c3d4"));
assert!(!is_canonical_uuid("not-a-uuid-at-all-here---------"));
}
#[test]
fn looks_like_iso8601_recognizes_valid() {
assert!(looks_like_iso8601("2026-05-23T16:18:15.234567Z"));
assert!(!looks_like_iso8601("not a timestamp"));
assert!(!looks_like_iso8601("2026-05-23"));
}
fn unix_to_iso8601(unix: i64) -> String {
let (y, mo, d, h, mi, s) = unix_to_civil(unix);
format!("{y:04}-{mo:02}-{d:02}T{h:02}:{mi:02}:{s:02}Z")
}
fn unix_to_civil(unix: i64) -> (i64, i64, i64, i64, i64, i64) {
let s = unix % 86400;
let days = unix / 86400;
let h = s / 3600;
let m = (s % 3600) / 60;
let sec = s % 60;
let z = days + 719468;
let era = z.div_euclid(146097);
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let mo = if mp < 10 { mp + 3 } else { mp - 9 };
let y = if mo <= 2 { y + 1 } else { y };
(y, mo, d, h, m, sec)
}
#[test]
fn agent_does_not_shorten_uuid_shaped_content_fields() {
let uuid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
let out = agent(json!({
"id": uuid,
"full_id": uuid,
"content": uuid,
"description": uuid,
"title": uuid,
"query": uuid,
}));
assert_eq!(out["id"], json!("a1b2c3d4"));
assert_eq!(out["full_id"], json!(uuid));
assert_eq!(out["content"], json!(uuid));
assert_eq!(out["description"], json!(uuid));
assert_eq!(out["title"], json!(uuid));
assert_eq!(out["query"], json!(uuid));
}
#[test]
fn agent_shortens_suffix_id_fields() {
let uuid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
let out = agent(json!({
"note_id": uuid,
"source_id": uuid,
"target_id": uuid,
}));
assert_eq!(out["note_id"], json!("a1b2c3d4"));
assert_eq!(out["source_id"], json!("a1b2c3d4"));
assert_eq!(out["target_id"], json!("a1b2c3d4"));
}
#[test]
fn format_json_preserves_full_shape() {
let v = json!({
"full_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"namespace": "local",
"properties": {"k": "v"},
"title": "test"
});
let rendered = render_format(v.clone(), OutputFormat::Json, PresentationMode::Agent);
let parsed: Value = serde_json::from_str(&rendered).unwrap();
assert!(
parsed.get("full_id").is_some(),
"json mode must keep full_id"
);
assert_eq!(
parsed.get("namespace").and_then(Value::as_str),
Some("local")
);
assert!(parsed.get("properties").is_some());
}
#[test]
fn format_auto_drops_versus_json_keeps() {
let v = json!({
"full_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"namespace": "local",
"title": "test"
});
let json_rendered = render_format(v.clone(), OutputFormat::Json, PresentationMode::Agent);
let auto_rendered = render_format(v.clone(), OutputFormat::Auto, PresentationMode::Agent);
let json_parsed: Value = serde_json::from_str(&json_rendered).unwrap();
assert!(
json_parsed.get("full_id").is_some(),
"json must keep full_id"
);
assert_eq!(
json_parsed.get("namespace").and_then(Value::as_str),
Some("local")
);
assert!(
!auto_rendered.contains("full_id"),
"auto kv block must drop full_id"
);
assert!(
!auto_rendered.contains("namespace"),
"auto kv block must elide namespace=local"
);
}
#[test]
fn format_auto_homogeneous_array_renders_markdown_table() {
let v = json!([
{"id": "abc", "title": "First"},
{"id": "def", "title": "Second"}
]);
let rendered = render_format(v, OutputFormat::Auto, PresentationMode::Agent);
assert!(rendered.starts_with('|'), "must start with |");
assert!(
rendered.contains("| id |") || rendered.contains("| id"),
"must have id column"
);
assert!(rendered.contains("title"), "must have title column");
assert!(rendered.contains("|---|"), "must have separator row");
assert!(rendered.contains("abc"), "must have first row data");
assert!(rendered.contains("Second"), "must have second row data");
}
#[test]
fn format_auto_single_record_renders_kv_block() {
let v = json!({"id": "abc", "title": "Hello World"});
let rendered = render_format(v, OutputFormat::Auto, PresentationMode::Agent);
assert!(rendered.contains("id: abc"), "must have id: abc");
assert!(
rendered.contains("title: Hello World"),
"must have title line"
);
assert!(
!rendered.starts_with('|'),
"single record must not be a markdown table"
);
}
#[test]
fn format_auto_scalar_fallback_compact_json() {
let v = json!(42);
let rendered = render_format(v, OutputFormat::Auto, PresentationMode::Agent);
assert_eq!(rendered, "42");
}
#[test]
fn format_table_forces_markdown_when_array() {
let v = json!({
"items": [
{"name": "A", "score": 1},
{"name": "B", "score": 2}
]
});
let rendered = render_format(v, OutputFormat::Table, PresentationMode::Agent);
assert!(
rendered.contains("|"),
"table format must produce markdown table"
);
assert!(rendered.contains("name"), "must have name column");
assert!(rendered.contains("score"), "must have score column");
}
#[test]
fn format_table_falls_back_to_json_when_no_array() {
let v = json!({"single": "value"});
let rendered = render_format(v, OutputFormat::Table, PresentationMode::Agent);
let parsed: Value = serde_json::from_str(&rendered).unwrap();
assert_eq!(parsed["single"], json!("value"));
}
#[test]
fn format_auto_verbose_skips_redundancy_drop() {
let v = json!({
"full_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"namespace": "local",
"title": "test"
});
let rendered = render_format(v, OutputFormat::Auto, PresentationMode::Verbose);
assert!(
rendered.contains("full_id"),
"verbose must preserve full_id"
);
assert!(
rendered.contains("namespace"),
"verbose must preserve namespace"
);
}
#[test]
fn redundancy_drop_does_not_corrupt_error_shape() {
let v = json!({"ok": false, "error": "something failed", "namespace": "local"});
let reduced = apply_redundancy_drop(v.clone());
assert!(
reduced.get("error").is_some(),
"redundancy drop must preserve error field"
);
assert_eq!(
reduced.get("ok").and_then(Value::as_bool),
Some(false),
"redundancy drop must preserve ok=false"
);
}
#[test]
fn redundancy_drop_properties_dedup() {
let v = json!({
"id": "abc",
"title": "Same",
"properties": {
"title": "Same", "extra": "unique" }
});
let reduced = apply_redundancy_drop(v);
let props = reduced.get("properties").expect("properties must remain");
assert!(props.get("extra").is_some(), "unique property must be kept");
assert!(
props.get("title").is_none(),
"duplicate top-level property must be removed"
);
}
#[test]
fn cell_text_truncates_long_values() {
let long = "X".repeat(200);
let v = json!([
{"col": long.clone()},
{"col": "short"}
]);
let rendered = render_format(v, OutputFormat::Auto, PresentationMode::Agent);
assert!(
rendered.contains("..."),
"long cell must be truncated with ..."
);
assert!(
!rendered.contains(&long),
"full long string must not appear in table"
);
}
#[test]
fn cell_text_truncation_is_utf8_safe() {
let prefix = "a".repeat(119);
let suffix = "中".repeat(10); let long_multibyte = format!("{prefix}{suffix}trailing");
let v = json!([
{"col": long_multibyte.clone()},
{"col": "ok"}
]);
let rendered = render_format(v, OutputFormat::Auto, PresentationMode::Agent);
assert!(
rendered.contains("..."),
"multibyte cell must be truncated with ..."
);
assert!(
std::str::from_utf8(rendered.as_bytes()).is_ok(),
"rendered output must be valid UTF-8"
);
}
#[test]
fn parse_iso8601_unix_negative_offset_matches_equivalent_utc() {
assert_eq!(
parse_iso8601_unix("2026-07-09T11:55:00-04:00"),
parse_iso8601_unix("2026-07-09T15:55:00Z")
);
}
#[test]
fn parse_iso8601_unix_positive_offset_matches_equivalent_utc() {
assert_eq!(
parse_iso8601_unix("2026-05-23T20:15:00+04:00"),
parse_iso8601_unix("2026-05-23T16:15:00Z")
);
}
#[test]
fn parse_iso8601_unix_zero_offset_matches_z() {
assert_eq!(
parse_iso8601_unix("2026-07-09T15:55:00+00:00"),
parse_iso8601_unix("2026-07-09T15:55:00Z")
);
}
#[test]
fn parse_iso8601_unix_compact_offset_form_matches_colon_form() {
assert_eq!(
parse_iso8601_unix("2026-07-09T11:55:00-0400"),
parse_iso8601_unix("2026-07-09T11:55:00-04:00")
);
}
#[test]
fn parse_iso8601_unix_fractional_seconds_with_offset() {
assert_eq!(
parse_iso8601_unix("2026-07-09T11:55:00.123-04:00"),
parse_iso8601_unix("2026-07-09T15:55:00Z")
);
}
#[test]
fn parse_iso8601_unix_fractional_seconds_with_z() {
assert_eq!(
parse_iso8601_unix("2026-07-09T15:55:00.999Z"),
parse_iso8601_unix("2026-07-09T15:55:00Z")
);
}
#[test]
fn parse_iso8601_unix_bare_form_unchanged() {
assert_eq!(
parse_iso8601_unix("2026-07-09T15:55:00"),
parse_iso8601_unix("2026-07-09T15:55:00Z")
);
}
#[test]
fn parse_iso8601_unix_malformed_tail_returns_none() {
assert_eq!(parse_iso8601_unix("2026-07-09T15:55:00X"), None);
assert_eq!(parse_iso8601_unix("2026-07-09T15:55:00+04"), None);
assert_eq!(parse_iso8601_unix("2026-07-09T15:55:00."), None);
}
#[test]
fn parse_iso8601_unix_out_of_range_offset_returns_none() {
assert_eq!(parse_iso8601_unix("2026-07-09T15:55:00+24:00"), None);
assert_eq!(parse_iso8601_unix("2026-07-09T15:55:00+2400"), None);
assert_eq!(parse_iso8601_unix("2026-07-09T15:55:00+01:60"), None);
assert_eq!(parse_iso8601_unix("2026-07-09T15:55:00+0160"), None);
}
#[test]
fn parse_iso8601_unix_max_valid_offset_boundary_is_accepted() {
assert!(parse_iso8601_unix("2026-07-09T15:55:00+23:59").is_some());
assert!(parse_iso8601_unix("2026-07-09T15:55:00-23:59").is_some());
assert!(parse_iso8601_unix("2026-07-09T15:55:00+2359").is_some());
}
#[test]
fn compact_timestamp_offset_bearing_future_time_not_shown_as_ago() {
let out = compact_timestamp("2025-05-23T16:08:00-02:00", NOW);
assert_ne!(out, "0s ago");
assert_eq!(out, "2025-05-23T16:08");
}
#[test]
fn compact_timestamp_offset_bearing_past_time_renders_relative() {
let out = compact_timestamp("2025-05-23T20:05:00+04:00", NOW);
assert_eq!(out, "3m ago");
}
}