Skip to main content

spec_driven_docs/
output.rs

1//! The output boundary: every byte the binary prints goes through here.
2//!
3//! stdout carries the result — findings, summaries, rendered documents —
4//! as plain text, which is what pre-commit shows a consumer. stderr carries
5//! exactly one JSON error envelope for operational failures. No other
6//! module (main.rs aside) prints.
7
8use std::fmt::Display;
9
10use crate::domain::finding::Finding;
11use crate::error::AppError;
12
13/// Print one result line to stdout.
14pub fn line(text: impl Display) {
15    println!("{text}");
16}
17
18/// Write pre-rendered bytes — completions, man pages — to stdout.
19pub fn raw(bytes: &[u8]) {
20    use std::io::Write;
21    let _ = std::io::stdout().write_all(bytes);
22}
23
24/// Print every finding, one line each, to stdout.
25pub fn findings(findings: &[Finding]) {
26    for finding in findings {
27        println!("{finding}");
28    }
29}
30
31/// Emit the one structured error envelope to stderr.
32///
33/// Violations carry no envelope: their findings are already on stdout and
34/// the exit code is the report.
35pub fn error_envelope(error: &AppError) {
36    if matches!(error, AppError::Violations { .. }) {
37        return;
38    }
39    let envelope = serde_json::json!({
40        "ok": false,
41        "error": {
42            "kind": error.kind(),
43            "message": error.to_string(),
44            "exit_code": error.exit_code(),
45        }
46    });
47    eprintln!("{envelope}");
48}