use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
use crate::hud::BuildError;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "event", rename_all = "snake_case")]
pub enum CompilerEvent {
CompilationStarted {
timestamp_ms: u64,
files: Vec<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
trigger: Option<String>,
},
CompilationSuccess {
timestamp_ms: u64,
duration_ms: u64,
output: PathBuf,
},
CompilationError {
timestamp_ms: u64,
duration_ms: u64,
errors: Vec<CompilerDiagnostic>,
},
FileChanged { timestamp_ms: u64, path: PathBuf },
DevServerStarted {
timestamp_ms: u64,
project: String,
watch_paths: Vec<PathBuf>,
},
ProcessLaunched {
timestamp_ms: u64,
pid: u32,
binary: PathBuf,
},
ProcessExited {
timestamp_ms: u64,
pid: u32,
code: Option<i32>,
},
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CompilerDiagnostic {
pub level: String,
pub message: String,
pub file: String,
pub line: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub column: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rendered: Option<String>,
}
impl From<BuildError> for CompilerDiagnostic {
fn from(err: BuildError) -> Self {
Self {
level: err.level,
message: err.message,
file: err.file,
line: err.line,
column: None,
rendered: err.rendered,
}
}
}
impl CompilerEvent {
pub fn now_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
pub fn emit(&self) {
if let Ok(json) = serde_json::to_string(self) {
println!("{json}");
}
}
pub fn compilation_started(files: Vec<PathBuf>, trigger: Option<String>) -> Self {
Self::CompilationStarted {
timestamp_ms: Self::now_ms(),
files,
trigger,
}
}
pub fn compilation_success(duration_ms: u64, output: PathBuf) -> Self {
Self::CompilationSuccess {
timestamp_ms: Self::now_ms(),
duration_ms,
output,
}
}
pub fn compilation_error(duration_ms: u64, errors: Vec<BuildError>) -> Self {
Self::CompilationError {
timestamp_ms: Self::now_ms(),
duration_ms,
errors: errors.into_iter().map(Into::into).collect(),
}
}
pub fn file_changed(path: PathBuf) -> Self {
Self::FileChanged {
timestamp_ms: Self::now_ms(),
path,
}
}
pub fn dev_server_started(project: String, watch_paths: Vec<PathBuf>) -> Self {
Self::DevServerStarted {
timestamp_ms: Self::now_ms(),
project,
watch_paths,
}
}
pub fn process_launched(pid: u32, binary: PathBuf) -> Self {
Self::ProcessLaunched {
timestamp_ms: Self::now_ms(),
pid,
binary,
}
}
pub fn process_exited(pid: u32, code: Option<i32>) -> Self {
Self::ProcessExited {
timestamp_ms: Self::now_ms(),
pid,
code,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_serialization() {
let event = CompilerEvent::compilation_started(
vec![PathBuf::from("src/main.rs")],
Some("file_change".to_string()),
);
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("\"event\":\"compilation_started\""));
assert!(json.contains("src/main.rs"));
}
#[test]
fn test_error_event() {
let errors = vec![BuildError {
level: "error".to_string(),
message: "cannot find value `x`".to_string(),
file: "src/lib.rs".to_string(),
line: 42,
rendered: None,
}];
let event = CompilerEvent::compilation_error(150, errors);
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("\"event\":\"compilation_error\""));
assert!(json.contains("cannot find value"));
}
}