use crate::graph::Graph;
use crate::utils::error::Result;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PassType {
Normalization,
Extraction,
Emission,
Canonicalization,
Receipt,
}
impl PassType {
pub fn order_index(&self) -> u32 {
match self {
PassType::Normalization => 1,
PassType::Extraction => 2,
PassType::Emission => 3,
PassType::Canonicalization => 4,
PassType::Receipt => 5,
}
}
pub fn name(&self) -> &'static str {
match self {
PassType::Normalization => "μ₁:normalization",
PassType::Extraction => "μ₂:extraction",
PassType::Emission => "μ₃:emission",
PassType::Canonicalization => "μ₄:canonicalization",
PassType::Receipt => "μ₅:receipt",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PassResult {
pub success: bool,
pub error: Option<String>,
pub triples_added: usize,
pub files_generated: Vec<PathBuf>,
pub duration: Duration,
}
impl PassResult {
pub fn success() -> Self {
Self {
success: true,
error: None,
triples_added: 0,
files_generated: Vec::new(),
duration: Duration::ZERO,
}
}
pub fn failure(error: impl Into<String>) -> Self {
Self {
success: false,
error: Some(error.into()),
triples_added: 0,
files_generated: Vec::new(),
duration: Duration::ZERO,
}
}
pub fn with_triples(mut self, count: usize) -> Self {
self.triples_added = count;
self
}
pub fn with_files(mut self, files: Vec<PathBuf>) -> Self {
self.files_generated = files;
self
}
pub fn with_duration(mut self, duration: Duration) -> Self {
self.duration = duration;
self
}
}
pub struct PassContext<'a> {
pub graph: &'a Graph,
pub base_path: PathBuf,
pub output_dir: PathBuf,
pub bindings: BTreeMap<String, serde_json::Value>,
pub generated_files: Vec<PathBuf>,
pub project_name: String,
pub project_version: String,
}
impl<'a> PassContext<'a> {
pub fn new(graph: &'a Graph, base_path: PathBuf, output_dir: PathBuf) -> Self {
Self {
graph,
base_path,
output_dir,
bindings: BTreeMap::new(),
generated_files: Vec::new(),
project_name: String::new(),
project_version: String::new(),
}
}
pub fn with_project(mut self, name: String, version: String) -> Self {
self.project_name = name;
self.project_version = version;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PassExecution {
pub name: String,
pub pass_type: PassType,
pub order_index: u32,
pub duration_ms: u64,
pub query_hash: Option<String>,
pub triples_produced: usize,
pub files_generated: Vec<PathBuf>,
pub success: bool,
pub error: Option<String>,
}
pub trait Pass: Send + Sync {
fn pass_type(&self) -> PassType;
fn name(&self) -> &str;
fn order_index(&self) -> u32 {
self.pass_type().order_index()
}
fn is_idempotent(&self) -> bool {
true }
fn is_deterministic(&self) -> bool {
true }
fn execute(&self, ctx: &mut PassContext<'_>) -> Result<PassResult>;
fn create_execution_record(&self, result: &PassResult) -> PassExecution {
PassExecution {
name: self.name().to_string(),
pass_type: self.pass_type(),
order_index: self.order_index(),
duration_ms: result.duration.as_millis() as u64,
query_hash: None,
triples_produced: result.triples_added,
files_generated: result.files_generated.clone(),
success: result.success,
error: result.error.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pass_type_ordering() {
assert!(PassType::Normalization.order_index() < PassType::Extraction.order_index());
assert!(PassType::Extraction.order_index() < PassType::Emission.order_index());
assert!(PassType::Emission.order_index() < PassType::Canonicalization.order_index());
assert!(PassType::Canonicalization.order_index() < PassType::Receipt.order_index());
}
#[test]
fn test_pass_type_names() {
assert_eq!(PassType::Normalization.name(), "μ₁:normalization");
assert_eq!(PassType::Extraction.name(), "μ₂:extraction");
assert_eq!(PassType::Emission.name(), "μ₃:emission");
assert_eq!(PassType::Canonicalization.name(), "μ₄:canonicalization");
assert_eq!(PassType::Receipt.name(), "μ₅:receipt");
}
#[test]
fn test_pass_result_success() {
let result = PassResult::success()
.with_triples(10)
.with_duration(Duration::from_millis(100));
assert!(result.success);
assert!(result.error.is_none());
assert_eq!(result.triples_added, 10);
}
#[test]
fn test_pass_result_failure() {
let result = PassResult::failure("Query failed");
assert!(!result.success);
assert_eq!(result.error, Some("Query failed".to_string()));
}
}