use serde::Serialize;
use crate::{Envelope, EnvelopeCache, FaceError, FlatCluster, Page, ResultBlock, to_flat};
pub(super) fn render_nested(envelope: &Envelope) -> Result<String, FaceError> {
serde_json::to_string_pretty(envelope).map_err(map_serde)
}
pub(super) fn render_flat(envelope: &Envelope) -> Result<String, FaceError> {
let flat = FlatEnvelope {
result: &envelope.result,
meta: &envelope.meta,
clusters: to_flat(&envelope.clusters),
page: &envelope.page,
cache: &envelope.cache,
};
serde_json::to_string_pretty(&flat).map_err(map_serde)
}
pub(super) fn render_jsonl_items(envelope: &Envelope) -> String {
let mut out = String::new();
for item in &envelope.page.items {
out.push_str(&serde_json::to_string(item).unwrap_or_default());
out.push('\n');
}
out
}
#[derive(Serialize)]
struct FlatEnvelope<'a> {
result: &'a ResultBlock,
meta: &'a serde_json::Map<String, serde_json::Value>,
clusters: Vec<FlatCluster>,
page: &'a Page,
#[serde(skip_serializing_if = "cache_ref_is_empty")]
cache: &'a EnvelopeCache,
}
fn cache_ref_is_empty(cache: &&EnvelopeCache) -> bool {
cache.is_empty()
}
fn map_serde(err: serde_json::Error) -> FaceError {
FaceError::InputParse {
format: crate::InputFormat::Json,
message: err.to_string(),
}
}
#[cfg(test)]
mod tests {
use crate::format::{OutputFormat, RenderOptions, render};
use crate::{Cluster, ClusterId, ClusterIdSegment, Envelope};
use serde_json::json;
fn child(
parent_axis: &str,
parent_value: &str,
axis: &str,
value: &str,
total: u64,
) -> Cluster {
Cluster {
id: ClusterId::new(vec![
ClusterIdSegment {
axis: parent_axis.into(),
value: parent_value.into(),
},
ClusterIdSegment {
axis: axis.into(),
value: value.into(),
},
]),
label: value.to_string(),
axis: axis.to_string(),
value: json!(value),
total,
score_min: None,
score_max: None,
clusters: vec![],
}
}
fn parent(axis: &str, value: &str, total: u64, children: Vec<Cluster>) -> Cluster {
Cluster {
id: ClusterId::new(vec![ClusterIdSegment {
axis: axis.into(),
value: value.into(),
}]),
label: value.to_string(),
axis: axis.to_string(),
value: json!(value),
total,
score_min: None,
score_max: None,
clusters: children,
}
}
#[test]
fn nested_round_trips_with_serde() {
let env = Envelope {
result: crate::ResultBlock {
input_total: 38,
..crate::ResultBlock::default()
},
clusters: vec![parent(
"file",
"src/cli.rs",
38,
vec![child("file", "src/cli.rs", "score", "excellent", 12)],
)],
..Envelope::default()
};
let out = render(&env, OutputFormat::Json, &RenderOptions::default()).unwrap();
let back: Envelope = serde_json::from_str(&out).unwrap();
assert_eq!(env, back);
}
#[test]
fn flat_uses_parent_id() {
let env = Envelope {
clusters: vec![parent(
"file",
"src/cli.rs",
38,
vec![child("file", "src/cli.rs", "score", "excellent", 12)],
)],
..Envelope::default()
};
let out = render(&env, OutputFormat::JsonFlat, &RenderOptions::default()).unwrap();
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
let arr = v["clusters"].as_array().unwrap();
assert_eq!(arr.len(), 2);
assert!(arr[0]["parent_id"].is_null());
assert_eq!(arr[1]["parent_id"], json!("file:src/cli.rs"));
assert!(arr[0].get("clusters").is_none());
}
#[test]
fn flat_keeps_top_level_envelope_keys() {
let env = Envelope::default();
let out = render(&env, OutputFormat::JsonFlat, &RenderOptions::default()).unwrap();
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
let obj = v.as_object().unwrap();
assert!(obj.contains_key("result"));
assert!(obj.contains_key("meta"));
assert!(obj.contains_key("clusters"));
assert!(obj.contains_key("page"));
}
#[test]
fn jsonl_items_one_per_line() {
let env = Envelope {
page: crate::Page {
items: vec![json!({"a": 1}), json!({"a": 2}), json!({"a": 3})],
..crate::Page::default()
},
..Envelope::default()
};
let out = render(&env, OutputFormat::JsonlItems, &RenderOptions::default()).unwrap();
let lines: Vec<&str> = out.lines().collect();
assert_eq!(lines.len(), 3);
assert_eq!(lines[0], "{\"a\":1}");
assert_eq!(lines[1], "{\"a\":2}");
assert_eq!(lines[2], "{\"a\":3}");
assert!(out.ends_with('\n'));
}
#[test]
fn jsonl_items_empty_when_page_empty() {
let env = Envelope::default();
let out = render(&env, OutputFormat::JsonlItems, &RenderOptions::default()).unwrap();
assert_eq!(out, "");
}
}