use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Node {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub function_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub call_id: Option<u64>,
pub label: String,
pub kind: NodeKind,
pub module_path: String,
pub file: String,
pub line: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NodeKind {
Function,
Type,
Test,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Edge {
pub from: String,
pub to: String,
#[serde(default)]
pub kind: EdgeKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum EdgeKind {
#[default]
ControlFlow,
DataFlow,
TestLink,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ValueSlot {
pub name: String,
pub preview: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Event {
pub seq: u64,
pub call_id: Option<u64>,
pub parent_call_id: Option<u64>,
pub node_id: String,
pub kind: EventKind,
pub title: String,
pub values: Vec<ValueSlot>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventKind {
FunctionEnter,
FunctionExit,
ValueSnapshot,
TestStarted,
TestPassed,
TestFailed,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Session {
pub title: String,
pub nodes: Vec<Node>,
pub edges: Vec<Edge>,
pub events: Vec<Event>,
}
impl Session {
pub fn new(title: impl Into<String>) -> Self {
Self {
title: title.into(),
nodes: Vec::new(),
edges: Vec::new(),
events: Vec::new(),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct FunctionMeta {
pub id: &'static str,
pub label: &'static str,
pub module_path: &'static str,
pub file: &'static str,
pub line: u32,
pub source: &'static str,
}
#[derive(Clone, Copy, Debug)]
pub struct TypeMeta {
pub id: &'static str,
pub label: &'static str,
pub module_path: &'static str,
pub file: &'static str,
pub line: u32,
pub source: &'static str,
}
pub fn write_session_json(session: &Session, path: impl AsRef<Path>) -> std::io::Result<()> {
let json = serde_json::to_string_pretty(session).map_err(std::io::Error::other)?;
if let Some(parent) = path.as_ref().parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, json)
}
pub fn read_session_json(path: impl AsRef<Path>) -> std::io::Result<Session> {
let content = fs::read_to_string(path)?;
serde_json::from_str(&content).map_err(std::io::Error::other)
}
fn sanitize_filename(label: &str) -> String {
let sanitized: String = label
.chars()
.map(|ch| match ch {
'a'..='z' | 'A'..='Z' | '0'..='9' => ch,
_ => '-',
})
.collect();
sanitized.trim_matches('-').to_owned()
}
pub fn write_session_snapshot_in_dir(
session: &Session,
dir: impl AsRef<Path>,
label: impl AsRef<str>,
) -> std::io::Result<PathBuf> {
fs::create_dir_all(&dir)?;
let file_name = format!(
"{}-{}.json",
sanitize_filename(label.as_ref()),
process::id()
);
let path = dir.as_ref().join(file_name);
write_session_json(session, &path)?;
Ok(path)
}
pub fn write_session_snapshot_from_env(
session: &Session,
label: impl AsRef<str>,
) -> std::io::Result<Option<PathBuf>> {
match env::var("DBG_SESSION_DIR") {
Ok(dir) => write_session_snapshot_in_dir(session, dir, label).map(Some),
Err(env::VarError::NotPresent) => Ok(None),
Err(error) => Err(std::io::Error::other(error)),
}
}