#![allow(clippy::result_large_err)]
use std::path::PathBuf;
use crate::error::SaraError;
use crate::graph::KnowledgeGraph;
use crate::model::{Item, ItemId, RelationshipType};
#[derive(Debug, Default)]
pub struct GraphBuilder {
items: Vec<Item>,
strict_mode: bool,
repositories: Vec<PathBuf>,
}
impl GraphBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_strict_mode(mut self, strict: bool) -> Self {
self.strict_mode = strict;
self
}
pub fn add_repository(mut self, path: impl Into<PathBuf>) -> Self {
self.repositories.push(path.into());
self
}
pub fn add_item(mut self, item: Item) -> Self {
self.items.push(item);
self
}
pub fn add_items(mut self, items: impl IntoIterator<Item = Item>) -> Self {
self.items.extend(items);
self
}
pub fn build(self) -> Result<KnowledgeGraph, SaraError> {
let mut graph = KnowledgeGraph::new(self.strict_mode);
for item in &self.items {
graph.add_item(item.clone());
}
for item in &self.items {
self.add_relationships_for_item(&mut graph, item);
}
Ok(graph)
}
fn add_relationships_for_item(&self, graph: &mut KnowledgeGraph, item: &Item) {
for target_id in &item.upstream.refines {
graph.add_relationship(&item.id, target_id, RelationshipType::Refines);
}
for target_id in &item.upstream.derives_from {
graph.add_relationship(&item.id, target_id, RelationshipType::DerivesFrom);
}
for target_id in &item.upstream.satisfies {
graph.add_relationship(&item.id, target_id, RelationshipType::Satisfies);
}
for target_id in &item.downstream.is_refined_by {
graph.add_relationship(&item.id, target_id, RelationshipType::IsRefinedBy);
graph.add_relationship(target_id, &item.id, RelationshipType::Refines);
}
for target_id in &item.downstream.derives {
graph.add_relationship(&item.id, target_id, RelationshipType::Derives);
graph.add_relationship(target_id, &item.id, RelationshipType::DerivesFrom);
}
for target_id in &item.downstream.is_satisfied_by {
graph.add_relationship(&item.id, target_id, RelationshipType::IsSatisfiedBy);
graph.add_relationship(target_id, &item.id, RelationshipType::Satisfies);
}
for target_id in &item.attributes.depends_on {
graph.add_relationship(&item.id, target_id, RelationshipType::DependsOn);
}
}
}
pub fn resolve_cross_repository_refs(graph: &mut KnowledgeGraph) -> Vec<(ItemId, ItemId)> {
let mut unresolved = Vec::new();
let referenced_ids: Vec<ItemId> = graph
.items()
.flat_map(|item| item.all_references())
.cloned()
.collect();
for ref_id in referenced_ids {
if !graph.contains(&ref_id) {
for item in graph.items() {
if item.all_references().iter().any(|id| **id == ref_id) {
unresolved.push((item.id.clone(), ref_id.clone()));
break;
}
}
}
}
unresolved
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{ItemBuilder, ItemType, SourceLocation, UpstreamRefs};
use std::path::PathBuf;
fn create_test_item(id: &str, item_type: ItemType) -> Item {
let source = SourceLocation::new(PathBuf::from("/repo"), format!("{}.md", id), 1);
let mut builder = ItemBuilder::new()
.id(ItemId::new_unchecked(id))
.item_type(item_type)
.name(format!("Test {}", id))
.source(source);
if item_type.requires_specification() {
builder = builder.specification("Test specification");
}
builder.build().unwrap()
}
fn create_test_item_with_upstream(
id: &str,
item_type: ItemType,
upstream: UpstreamRefs,
) -> Item {
let source = SourceLocation::new(PathBuf::from("/repo"), format!("{}.md", id), 1);
let mut builder = ItemBuilder::new()
.id(ItemId::new_unchecked(id))
.item_type(item_type)
.name(format!("Test {}", id))
.source(source)
.upstream(upstream);
if item_type.requires_specification() {
builder = builder.specification("Test specification");
}
builder.build().unwrap()
}
#[test]
fn test_build_simple_graph() {
let graph = GraphBuilder::new()
.add_item(create_test_item("SOL-001", ItemType::Solution))
.build()
.unwrap();
assert_eq!(graph.item_count(), 1);
}
#[test]
fn test_build_graph_with_relationships() {
let sol = create_test_item("SOL-001", ItemType::Solution);
let uc = create_test_item_with_upstream(
"UC-001",
ItemType::UseCase,
UpstreamRefs {
refines: vec![ItemId::new_unchecked("SOL-001")],
..Default::default()
},
);
let graph = GraphBuilder::new()
.add_item(sol)
.add_item(uc)
.build()
.unwrap();
assert_eq!(graph.item_count(), 2);
assert_eq!(graph.relationship_count(), 1);
}
#[test]
fn test_strict_mode() {
let graph = GraphBuilder::new()
.with_strict_mode(true)
.add_item(create_test_item("SOL-001", ItemType::Solution))
.build()
.unwrap();
assert!(graph.is_strict_mode());
}
}