use crate::RuntimeCoverageReport;
use crate::root_envelopes::{RootEnvelopeMode, attach_telemetry_meta, serialize_named_json_output};
use fallow_types::envelope::{ElapsedMs, Meta, ToolVersion};
use serde::Serialize;
use std::time::Duration;
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(title = "fallow coverage setup --json"))]
pub struct CoverageSetupOutput {
pub schema_version: CoverageSetupSchemaVersion,
pub framework_detected: CoverageSetupFramework,
pub package_manager: Option<CoverageSetupPackageManager>,
pub runtime_targets: Vec<CoverageSetupRuntimeTarget>,
pub members: Vec<CoverageSetupMember>,
pub config_written: Option<serde_json::Value>,
pub commands: Vec<String>,
pub files_to_edit: Vec<CoverageSetupFileToEdit>,
pub snippets: Vec<CoverageSetupSnippet>,
pub dockerfile_snippet: Option<String>,
pub next_steps: Vec<String>,
pub warnings: Vec<String>,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum CoverageSetupSchemaVersion {
#[serde(rename = "1")]
V1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum CoverageSetupFramework {
#[serde(rename = "nextjs")]
NextJs,
#[serde(rename = "nestjs")]
NestJs,
Nuxt,
#[serde(rename = "sveltekit")]
SvelteKit,
Astro,
Remix,
Vite,
PlainNode,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "lowercase")]
pub enum CoverageSetupPackageManager {
Npm,
Pnpm,
Yarn,
Bun,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "lowercase")]
pub enum CoverageSetupRuntimeTarget {
Node,
Browser,
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CoverageSetupMember {
pub name: String,
pub path: String,
pub framework_detected: CoverageSetupFramework,
pub package_manager: Option<CoverageSetupPackageManager>,
pub runtime_targets: Vec<CoverageSetupRuntimeTarget>,
pub files_to_edit: Vec<CoverageSetupFileToEdit>,
pub snippets: Vec<CoverageSetupSnippet>,
pub dockerfile_snippet: Option<String>,
pub warnings: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CoverageSetupFileToEdit {
pub path: String,
pub reason: String,
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CoverageSetupSnippet {
pub label: String,
pub path: String,
pub content: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum CoverageAnalyzeSchemaVersion {
#[serde(rename = "1")]
V1,
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(
feature = "schema",
schemars(title = "fallow coverage analyze --format json")
)]
pub struct CoverageAnalyzeOutput {
pub schema_version: CoverageAnalyzeSchemaVersion,
pub version: ToolVersion,
pub elapsed_ms: ElapsedMs,
pub runtime_coverage: RuntimeCoverageReport,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
}
pub fn serialize_coverage_setup_json_output(
output: CoverageSetupOutput,
mode: RootEnvelopeMode,
analysis_run_id: Option<&str>,
) -> Result<serde_json::Value, serde_json::Error> {
let mut value = serialize_named_json_output(output, "coverage-setup", mode)?;
attach_telemetry_meta(&mut value, analysis_run_id);
Ok(value)
}
#[must_use]
pub fn build_coverage_analyze_output(
report: &RuntimeCoverageReport,
elapsed: Duration,
version: impl Into<String>,
) -> CoverageAnalyzeOutput {
CoverageAnalyzeOutput {
schema_version: CoverageAnalyzeSchemaVersion::V1,
version: ToolVersion(version.into()),
elapsed_ms: ElapsedMs(u64::try_from(elapsed.as_millis()).unwrap_or(u64::MAX)),
runtime_coverage: report.clone(),
meta: None,
}
}
pub fn serialize_coverage_analyze_json_output(
output: CoverageAnalyzeOutput,
mode: RootEnvelopeMode,
explain_meta: Option<serde_json::Value>,
analysis_run_id: Option<&str>,
) -> Result<serde_json::Value, serde_json::Error> {
let mut value = serialize_named_json_output(output, "coverage-analyze", mode)?;
if let Some(meta) = explain_meta
&& let Some(map) = value.as_object_mut()
{
map.insert("_meta".to_owned(), meta);
}
attach_telemetry_meta(&mut value, analysis_run_id);
Ok(value)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn coverage_setup_json_output_uses_named_root_contract() {
let output = CoverageSetupOutput {
schema_version: CoverageSetupSchemaVersion::V1,
framework_detected: CoverageSetupFramework::Unknown,
package_manager: None,
runtime_targets: Vec::new(),
members: Vec::new(),
config_written: None,
commands: Vec::new(),
files_to_edit: Vec::new(),
snippets: Vec::new(),
dockerfile_snippet: None,
next_steps: Vec::new(),
warnings: Vec::new(),
meta: None,
};
let value =
serialize_coverage_setup_json_output(output, RootEnvelopeMode::Tagged, Some("run-1"))
.expect("coverage setup should serialize");
assert_eq!(value["kind"], "coverage-setup");
assert_eq!(value["schema_version"], "1");
assert_eq!(value["_meta"]["telemetry"]["analysis_run_id"], "run-1");
}
#[test]
fn coverage_analyze_json_output_inserts_explain_meta_and_telemetry() {
let report = RuntimeCoverageReport::default();
let output = build_coverage_analyze_output(&report, Duration::from_millis(7), "test");
let value = serialize_coverage_analyze_json_output(
output,
RootEnvelopeMode::Tagged,
Some(json!({"docs": "coverage"})),
Some("run-2"),
)
.expect("coverage analyze should serialize");
assert_eq!(value["kind"], "coverage-analyze");
assert_eq!(value["schema_version"], "1");
assert_eq!(value["elapsed_ms"], 7);
assert_eq!(value["_meta"]["docs"], "coverage");
assert_eq!(value["_meta"]["telemetry"]["analysis_run_id"], "run-2");
}
}