use std::path::PathBuf;
use super::node::Span;
use thiserror::Error;
pub type GraphResult<T> = Result<T, GraphBuilderError>;
#[derive(Debug, Error)]
pub enum GraphBuilderError {
#[error("Failed to parse AST node at {span:?}: {reason}")]
ParseError {
span: Span,
reason: String,
},
#[error("Unsupported language construct: {construct} at {span:?}")]
UnsupportedConstruct {
construct: String,
span: Span,
},
#[error("Failed to resolve symbol '{symbol}' in {file}")]
SymbolResolutionError {
symbol: String,
file: PathBuf,
},
#[error("Invalid cross-language edge: {reason}")]
CrossLanguageError {
reason: String,
},
#[error("IO error reading {file}: {source}")]
IoError {
file: PathBuf,
#[source]
source: std::io::Error,
},
#[error("Graph build timed out after {timeout_ms} ms during {phase} in {file}")]
BuildTimedOut {
file: PathBuf,
phase: &'static str,
timeout_ms: u64,
},
#[error("Internal graph builder error: {reason}")]
Internal {
reason: String,
},
#[error("graph build cancelled")]
Cancelled,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::node::Position;
fn make_test_span() -> Span {
Span::new(Position::new(10, 5), Position::new(10, 25))
}
#[test]
fn test_graph_result_ok() {
let result: GraphResult<i32> = Ok(42);
assert!(result.is_ok());
if let Ok(value) = result {
assert_eq!(value, 42);
}
}
#[test]
fn test_graph_result_err() {
let result: GraphResult<i32> = Err(GraphBuilderError::ParseError {
span: make_test_span(),
reason: "test error".to_string(),
});
assert!(result.is_err());
}
#[test]
fn test_parse_error_display() {
let err = GraphBuilderError::ParseError {
span: make_test_span(),
reason: "unexpected token".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("Failed to parse AST node"));
assert!(msg.contains("unexpected token"));
}
#[test]
fn test_parse_error_debug() {
let err = GraphBuilderError::ParseError {
span: make_test_span(),
reason: "test".to_string(),
};
let debug = format!("{err:?}");
assert!(debug.contains("ParseError"));
assert!(debug.contains("span"));
assert!(debug.contains("reason"));
}
#[test]
fn test_unsupported_construct_display() {
let err = GraphBuilderError::UnsupportedConstruct {
construct: "async generator".to_string(),
span: make_test_span(),
};
let msg = format!("{err}");
assert!(msg.contains("Unsupported language construct"));
assert!(msg.contains("async generator"));
}
#[test]
fn test_unsupported_construct_debug() {
let err = GraphBuilderError::UnsupportedConstruct {
construct: "macro".to_string(),
span: make_test_span(),
};
let debug = format!("{err:?}");
assert!(debug.contains("UnsupportedConstruct"));
assert!(debug.contains("macro"));
}
#[test]
fn test_symbol_resolution_error_display() {
let err = GraphBuilderError::SymbolResolutionError {
symbol: "MyClass".to_string(),
file: PathBuf::from("src/main.rs"),
};
let msg = format!("{err}");
assert!(msg.contains("Failed to resolve symbol"));
assert!(msg.contains("MyClass"));
assert!(msg.contains("src/main.rs"));
}
#[test]
fn test_symbol_resolution_error_debug() {
let err = GraphBuilderError::SymbolResolutionError {
symbol: "helper_fn".to_string(),
file: PathBuf::from("lib.rs"),
};
let debug = format!("{err:?}");
assert!(debug.contains("SymbolResolutionError"));
assert!(debug.contains("helper_fn"));
}
#[test]
fn test_cross_language_error_display() {
let err = GraphBuilderError::CrossLanguageError {
reason: "incompatible metadata formats".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("Invalid cross-language edge"));
assert!(msg.contains("incompatible metadata formats"));
}
#[test]
fn test_cross_language_error_debug() {
let err = GraphBuilderError::CrossLanguageError {
reason: "test reason".to_string(),
};
let debug = format!("{err:?}");
assert!(debug.contains("CrossLanguageError"));
assert!(debug.contains("test reason"));
}
#[test]
fn test_io_error_display() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err = GraphBuilderError::IoError {
file: PathBuf::from("/tmp/missing.rs"),
source: io_err,
};
let msg = format!("{err}");
assert!(msg.contains("IO error reading"));
assert!(msg.contains("/tmp/missing.rs"));
}
#[test]
fn test_io_error_source() {
use std::error::Error;
let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
let err = GraphBuilderError::IoError {
file: PathBuf::from("/etc/passwd"),
source: io_err,
};
let source = err.source();
assert!(source.is_some());
}
#[test]
fn test_io_error_debug() {
let io_err = std::io::Error::other("test");
let err = GraphBuilderError::IoError {
file: PathBuf::from("test.rs"),
source: io_err,
};
let debug = format!("{err:?}");
assert!(debug.contains("IoError"));
assert!(debug.contains("test.rs"));
}
#[test]
fn test_build_timed_out_display() {
let err = GraphBuilderError::BuildTimedOut {
file: PathBuf::from("large.cpp"),
phase: "walk_tree_for_graph",
timeout_ms: 10_000,
};
let msg = format!("{err}");
assert!(msg.contains("Graph build timed out"));
assert!(msg.contains("large.cpp"));
assert!(msg.contains("walk_tree_for_graph"));
}
#[test]
fn test_error_pattern_matching() {
let err = GraphBuilderError::ParseError {
span: make_test_span(),
reason: "test".to_string(),
};
match err {
GraphBuilderError::ParseError { span, reason } => {
assert_eq!(span.start.line, 10);
assert_eq!(reason, "test");
}
_ => panic!("Expected ParseError variant"),
}
}
#[test]
fn test_all_variants_are_error() {
use std::error::Error;
let errors: Vec<GraphBuilderError> = vec![
GraphBuilderError::ParseError {
span: make_test_span(),
reason: "test".to_string(),
},
GraphBuilderError::UnsupportedConstruct {
construct: "test".to_string(),
span: make_test_span(),
},
GraphBuilderError::SymbolResolutionError {
symbol: "test".to_string(),
file: PathBuf::from("test.rs"),
},
GraphBuilderError::CrossLanguageError {
reason: "test".to_string(),
},
GraphBuilderError::IoError {
file: PathBuf::from("test.rs"),
source: std::io::Error::other("test"),
},
GraphBuilderError::BuildTimedOut {
file: PathBuf::from("test.rs"),
phase: "test-phase",
timeout_ms: 1_000,
},
GraphBuilderError::Internal {
reason: "test".to_string(),
},
GraphBuilderError::Cancelled,
];
for err in errors {
let _: &dyn Error = &err;
assert!(!format!("{err}").is_empty());
}
}
#[test]
fn test_cancelled_display() {
let err = GraphBuilderError::Cancelled;
let msg = format!("{err}");
assert_eq!(msg, "graph build cancelled");
}
#[test]
fn test_cancelled_debug() {
let err = GraphBuilderError::Cancelled;
let debug = format!("{err:?}");
assert_eq!(debug, "Cancelled");
}
}