eval_magic/pipeline/io.rs
1//! Shared JSON read/write helpers for the pipeline stages.
2//!
3//! Every stage serializes artifacts the same way: pretty-printed, two-space
4//! indent, one trailing newline. `serde_json`'s `preserve_order` feature keeps
5//! object key order stable so artifacts diff cleanly across runs.
6
7use std::fs;
8use std::path::Path;
9use std::time::{SystemTime, UNIX_EPOCH};
10
11use chrono::{DateTime, SecondsFormat};
12use serde::Serialize;
13
14use crate::pipeline::error::PipelineError;
15
16/// Write `value` to `path` as pretty JSON with a trailing newline.
17pub fn write_json<T: Serialize>(path: &Path, value: &T) -> Result<(), PipelineError> {
18 let mut text = serde_json::to_string_pretty(value)?;
19 text.push('\n');
20 fs::write(path, text)?;
21 Ok(())
22}
23
24/// The current wall clock as `2026-06-08T12:00:00.000Z`, matching JS
25/// `new Date().toISOString()` — the `generated` stamp every report carries.
26/// chrono ships without its `clock` feature, so the instant comes from
27/// `std::time` and is formatted via chrono.
28pub fn now_iso8601() -> String {
29 let ms = SystemTime::now()
30 .duration_since(UNIX_EPOCH)
31 .unwrap_or_default()
32 .as_millis() as i64;
33 DateTime::from_timestamp_millis(ms)
34 .unwrap_or_default()
35 .to_rfc3339_opts(SecondsFormat::Millis, true)
36}