1use std::env;
8use std::fs;
9use std::path::{Path, PathBuf};
10use std::process;
11
12use serde::{Deserialize, Serialize};
13
14#[derive(Clone, Debug, Serialize, Deserialize)]
16pub struct Node {
17 pub id: String,
19 pub label: String,
21 pub kind: NodeKind,
23 pub module_path: String,
25 pub file: String,
27 pub line: u32,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub source: Option<String>,
32}
33
34#[derive(Clone, Debug, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37pub enum NodeKind {
38 Function,
40 Type,
42 Test,
44}
45
46#[derive(Clone, Debug, Serialize, Deserialize)]
48pub struct Edge {
49 pub from: String,
51 pub to: String,
53 #[serde(default)]
55 pub kind: EdgeKind,
56 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub label: Option<String>,
59}
60
61#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
63#[serde(rename_all = "snake_case")]
64pub enum EdgeKind {
65 #[default]
67 ControlFlow,
68 DataFlow,
70 TestLink,
72}
73
74#[derive(Clone, Debug, Serialize, Deserialize)]
76pub struct ValueSlot {
77 pub name: String,
79 pub preview: String,
81}
82
83#[derive(Clone, Debug, Serialize, Deserialize)]
85pub struct Event {
86 pub seq: u64,
88 pub call_id: Option<u64>,
90 pub parent_call_id: Option<u64>,
92 pub node_id: String,
94 pub kind: EventKind,
96 pub title: String,
98 pub values: Vec<ValueSlot>,
100}
101
102#[derive(Clone, Debug, Serialize, Deserialize)]
104#[serde(rename_all = "snake_case")]
105pub enum EventKind {
106 FunctionEnter,
108 FunctionExit,
110 ValueSnapshot,
112 TestStarted,
114 TestPassed,
116 TestFailed,
118}
119
120#[derive(Clone, Debug, Serialize, Deserialize)]
122pub struct Session {
123 pub title: String,
125 pub nodes: Vec<Node>,
127 pub edges: Vec<Edge>,
129 pub events: Vec<Event>,
131}
132
133impl Session {
134 pub fn new(title: impl Into<String>) -> Self {
136 Self {
137 title: title.into(),
138 nodes: Vec::new(),
139 edges: Vec::new(),
140 events: Vec::new(),
141 }
142 }
143}
144
145#[derive(Clone, Copy, Debug)]
147pub struct FunctionMeta {
148 pub id: &'static str,
150 pub label: &'static str,
152 pub module_path: &'static str,
154 pub file: &'static str,
156 pub line: u32,
158 pub source: &'static str,
160}
161
162#[derive(Clone, Copy, Debug)]
164pub struct TypeMeta {
165 pub id: &'static str,
167 pub label: &'static str,
169 pub module_path: &'static str,
171 pub file: &'static str,
173 pub line: u32,
175 pub source: &'static str,
177}
178
179pub fn write_session_json(session: &Session, path: impl AsRef<Path>) -> std::io::Result<()> {
185 let json = serde_json::to_string_pretty(session).map_err(std::io::Error::other)?;
186
187 if let Some(parent) = path.as_ref().parent() {
188 fs::create_dir_all(parent)?;
189 }
190
191 fs::write(path, json)
192}
193
194pub fn read_session_json(path: impl AsRef<Path>) -> std::io::Result<Session> {
196 let content = fs::read_to_string(path)?;
197 serde_json::from_str(&content).map_err(std::io::Error::other)
198}
199
200fn sanitize_filename(label: &str) -> String {
202 let sanitized: String = label
203 .chars()
204 .map(|ch| match ch {
205 'a'..='z' | 'A'..='Z' | '0'..='9' => ch,
206 _ => '-',
207 })
208 .collect();
209
210 sanitized.trim_matches('-').to_owned()
211}
212
213pub fn write_session_snapshot_in_dir(
215 session: &Session,
216 dir: impl AsRef<Path>,
217 label: impl AsRef<str>,
218) -> std::io::Result<PathBuf> {
219 fs::create_dir_all(&dir)?;
220
221 let file_name = format!(
222 "{}-{}.json",
223 sanitize_filename(label.as_ref()),
224 process::id()
225 );
226 let path = dir.as_ref().join(file_name);
227 write_session_json(session, &path)?;
228 Ok(path)
229}
230
231pub fn write_session_snapshot_from_env(
235 session: &Session,
236 label: impl AsRef<str>,
237) -> std::io::Result<Option<PathBuf>> {
238 match env::var("DBG_SESSION_DIR") {
239 Ok(dir) => write_session_snapshot_in_dir(session, dir, label).map(Some),
240 Err(env::VarError::NotPresent) => Ok(None),
241 Err(error) => Err(std::io::Error::other(error)),
242 }
243}