Skip to main content

vyre_debug/
naga_trace.rs

1use naga::Module;
2use std::fs::File;
3use std::io::{BufRead, BufReader};
4use vyre_emit_naga::BindResultEntry;
5
6/// Error type for [`load_bind_result_log`] failures.
7#[derive(Debug)]
8pub enum BindResultLogError {
9    /// The log file could not be opened (missing, permission denied, etc.).
10    Open(std::io::Error),
11    /// A line could not be read from the file.
12    Read(std::io::Error),
13    /// A line in the log was not valid JSON for [`BindResultEntry`].
14    /// Contains the line number (1-based) and the raw parse error.
15    Parse(usize, serde_json::Error),
16}
17
18pub struct FailureTrace {
19    pub text: String,
20}
21
22pub fn failure_trace(module: &Module, error: &naga::valid::ValidationError) -> FailureTrace {
23    let text = format!(
24        "FAILURE: {:#?}\nentry_points={}\nfunctions={}\nglobals={}",
25        error,
26        module.entry_points.len(),
27        module.functions.len(),
28        module.global_variables.len()
29    );
30    FailureTrace { text }
31}
32
33pub fn failure_trace_wgsl(
34    module: &Module,
35    info: &naga::valid::ModuleInfo,
36    err: &naga::back::wgsl::Error,
37) -> FailureTrace {
38    let text = format!(
39        "FAILURE: {:#?}\nentry_points={}\nfunctions={}\nglobals={}\nmodule_info={:#?}",
40        err,
41        module.entry_points.len(),
42        module.functions.len(),
43        module.global_variables.len(),
44        info
45    );
46    FailureTrace { text }
47}
48
49/// Load a bind-result log file produced by vyre-emit-naga.
50///
51/// Returns `Err` on any I/O or parse failure so the caller can surface the
52/// problem. Never silently returns a partial or empty result, the complete
53/// log is required for accurate trace data.
54pub fn load_bind_result_log(
55    path: &str,
56) -> Result<Vec<BindResultEntry>, BindResultLogError> {
57    let file = File::open(path).map_err(BindResultLogError::Open)?;
58    let reader = BufReader::new(file);
59    let mut entries = Vec::new();
60    for (line_no, raw) in reader.lines().enumerate() {
61        let line = raw.map_err(BindResultLogError::Read)?;
62        let entry: BindResultEntry = serde_json::from_str(&line)
63            .map_err(|e| BindResultLogError::Parse(line_no + 1, e))?;
64        entries.push(entry);
65    }
66    Ok(entries)
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn load_bind_result_log_missing_file_returns_open_error() {
75        let r = load_bind_result_log("/nonexistent/path/bind_log.jsonl");
76        assert!(
77            matches!(r, Err(BindResultLogError::Open(_))),
78            "expected Err(Open(_)) for missing file, got {:?}",
79            r.err().map(|e| format!("{e:?}"))
80        );
81    }
82
83    #[test]
84    fn load_bind_result_log_valid_entries_parses_all() {
85        // Build a minimal valid BindResultEntry JSON line.
86        let entry = BindResultEntry {
87            vyre_op_id: 7,
88            op_kind: "Literal".to_string(),
89            init_handle: 42,
90            init_scalar_kind: Some("Uint".to_string()),
91            child_body_depth: 0,
92            value_types_at_call: None,
93            publish_path: "root/op7".to_string(),
94            local_allocated_ty: None,
95        };
96        let line = serde_json::to_string(&entry).unwrap();
97        let dir = tempfile::tempdir().unwrap();
98        let path = dir.path().join("bind.jsonl");
99        std::fs::write(&path, format!("{line}\n")).unwrap();
100
101        let result = load_bind_result_log(path.to_str().unwrap()).unwrap();
102        assert_eq!(result.len(), 1, "expected exactly 1 entry");
103        assert_eq!(result[0].vyre_op_id, 7);
104        assert_eq!(result[0].op_kind, "Literal");
105        assert_eq!(result[0].init_handle, 42);
106        assert_eq!(result[0].publish_path, "root/op7");
107    }
108
109    #[test]
110    fn load_bind_result_log_malformed_json_line_returns_parse_error() {
111        let dir = tempfile::tempdir().unwrap();
112        let path = dir.path().join("bad.jsonl");
113        // First line is valid, second is malformed.
114        let entry = BindResultEntry {
115            vyre_op_id: 1,
116            op_kind: "Load".to_string(),
117            init_handle: 0,
118            init_scalar_kind: None,
119            child_body_depth: 0,
120            value_types_at_call: None,
121            publish_path: "p".to_string(),
122            local_allocated_ty: None,
123        };
124        let valid_line = serde_json::to_string(&entry).unwrap();
125        std::fs::write(&path, format!("{valid_line}\nnot valid json\n")).unwrap();
126
127        let r = load_bind_result_log(path.to_str().unwrap());
128        assert!(
129            matches!(r, Err(BindResultLogError::Parse(2, _))),
130            "expected Err(Parse(2, _)) for malformed line 2, got {:?}",
131            r.err().map(|e| format!("{e:?}"))
132        );
133    }
134}