use std::time::Duration;
use fallow_types::envelope::{ElapsedMs, Meta, SchemaVersion, ToolVersion};
use fallow_types::output::NextStep;
use serde::Serialize;
use fallow_types::workspace::WorkspaceDiagnostic;
use crate::{
GroupByMode, RootEnvelopeMode, apply_root_kind, attach_telemetry_meta, strip_root_prefix,
};
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(title = "fallow health --format json"))]
pub struct HealthOutput<Report, Group> {
pub schema_version: SchemaVersion,
pub version: ToolVersion,
pub elapsed_ms: ElapsedMs,
#[serde(flatten)]
pub report: Report,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub grouped_by: Option<GroupByMode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub groups: Option<Vec<Group>>,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub next_steps: Vec<NextStep>,
}
#[derive(Debug, Clone)]
pub struct HealthOutputInput<Report, Group> {
pub schema_version: u32,
pub version: String,
pub elapsed: Duration,
pub report: Report,
pub grouped_by: Option<GroupByMode>,
pub groups: Option<Vec<Group>>,
pub meta: Option<Meta>,
pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
pub next_steps: Vec<NextStep>,
}
#[derive(Debug, Clone)]
pub struct HealthJsonOutputInput<'a, Report, Group> {
pub output: HealthOutputInput<Report, Group>,
pub root_prefix: Option<&'a str>,
pub envelope_mode: RootEnvelopeMode,
pub analysis_run_id: Option<&'a str>,
}
#[must_use]
pub fn build_health_output<Report, Group>(
input: HealthOutputInput<Report, Group>,
) -> HealthOutput<Report, Group> {
HealthOutput {
schema_version: SchemaVersion(input.schema_version),
version: ToolVersion(input.version),
elapsed_ms: ElapsedMs(input.elapsed.as_millis() as u64),
report: input.report,
grouped_by: input.grouped_by,
groups: input.groups,
meta: input.meta,
workspace_diagnostics: input.workspace_diagnostics,
next_steps: input.next_steps,
}
}
pub fn serialize_health_json_output<Report, Group>(
input: HealthJsonOutputInput<'_, Report, Group>,
) -> Result<serde_json::Value, serde_json::Error>
where
Report: Serialize,
Group: Serialize,
{
let envelope = build_health_output(input.output);
let mut output = serde_json::to_value(envelope)?;
apply_root_kind(&mut output, "health", input.envelope_mode);
if let Some(root_prefix) = input.root_prefix {
strip_root_prefix(&mut output, root_prefix);
}
attach_telemetry_meta(&mut output, input.analysis_run_id);
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize_health_json_output_tags_and_strips_root_paths() {
let output = serialize_health_json_output(HealthJsonOutputInput {
output: HealthOutputInput {
schema_version: 7,
version: "test".to_string(),
elapsed: Duration::ZERO,
report: serde_json::json!({ "findings": [{ "path": "/repo/src/a.ts" }] }),
grouped_by: None,
groups: None::<Vec<serde_json::Value>>,
meta: None,
workspace_diagnostics: Vec::new(),
next_steps: Vec::new(),
},
root_prefix: Some("/repo/"),
envelope_mode: RootEnvelopeMode::Tagged,
analysis_run_id: Some("run-health"),
})
.expect("health output should serialize");
assert_eq!(output["kind"], "health");
assert_eq!(output["findings"][0]["path"], "src/a.ts");
assert_eq!(
output["_meta"]["telemetry"]["analysis_run_id"],
"run-health"
);
}
#[test]
fn serialize_health_json_output_can_keep_legacy_root_shape() {
let output = serialize_health_json_output(HealthJsonOutputInput {
output: HealthOutputInput {
schema_version: 7,
version: "test".to_string(),
elapsed: Duration::ZERO,
report: serde_json::json!({ "summary": {} }),
grouped_by: None,
groups: None::<Vec<serde_json::Value>>,
meta: None,
workspace_diagnostics: Vec::new(),
next_steps: Vec::new(),
},
root_prefix: None,
envelope_mode: RootEnvelopeMode::Legacy,
analysis_run_id: None,
})
.expect("health output should serialize");
assert!(output.get("kind").is_none());
assert_eq!(output["schema_version"], 7);
}
}