use super::graph::{TypeEdge, TypeGraph, TypeRef};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
pub struct ResolvedPathSchema {
pub source: TypeRef,
pub steps: Vec<ResolvedStep>,
pub target: TypeRef,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
pub struct ResolvedStep {
pub edge: TypeEdge,
pub reverse: bool,
}
impl ResolvedPathSchema {
pub fn len(&self) -> usize {
self.steps.len()
}
pub fn is_empty(&self) -> bool {
self.steps.is_empty()
}
pub fn display(&self) -> String {
let mut parts = vec![self.source.name().to_string()];
for step in &self.steps {
let dir = if step.reverse { "<" } else { "" };
parts.push(format!("-[{}{}]->", dir, step.edge.qualifier));
let next = if step.reverse {
step.edge.source.name()
} else {
step.edge.target.name()
};
parts.push(next.to_string());
}
parts.join(" ")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SchemaStep {
pub edge_idx: usize,
pub reverse: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PathSchema {
pub source: TypeRef,
pub steps: Vec<SchemaStep>,
pub target: TypeRef,
}
impl PathSchema {
pub fn len(&self) -> usize {
self.steps.len()
}
pub fn is_empty(&self) -> bool {
self.steps.is_empty()
}
pub fn resolve(&self, type_graph: &TypeGraph) -> ResolvedPathSchema {
ResolvedPathSchema {
source: self.source.clone(),
steps: self
.steps
.iter()
.map(|s| ResolvedStep {
edge: type_graph.edges[s.edge_idx].clone(),
reverse: s.reverse,
})
.collect(),
target: self.target.clone(),
}
}
pub fn display(&self, type_graph: &TypeGraph) -> String {
self.resolve(type_graph).display()
}
}
pub fn enumerate_schemas(
type_graph: &TypeGraph,
source: &TypeRef,
target: Option<&TypeRef>,
max_length: usize,
allow_cycles: bool,
allowed_types: Option<&HashSet<TypeRef>>,
) -> Vec<PathSchema> {
let mut results = Vec::new();
let mut queue: Vec<(TypeRef, Vec<SchemaStep>, Vec<TypeRef>)> =
vec![(source.clone(), Vec::new(), vec![source.clone()])];
while let Some((current, steps, visited)) = queue.pop() {
let matches_target = match target {
Some(tt) => ¤t == tt,
None => true,
};
if !steps.is_empty() && matches_target {
results.push(PathSchema {
source: source.clone(),
steps: steps.clone(),
target: current.clone(),
});
}
if steps.len() >= max_length {
continue;
}
for (edge_idx, neighbor, reverse) in type_graph.neighbors_undirected(¤t) {
if !allow_cycles && visited.contains(neighbor) {
continue;
}
if allowed_types
.is_some_and(|allowed| !allowed.contains(neighbor) && target != Some(neighbor))
{
continue;
}
let mut new_steps = steps.clone();
new_steps.push(SchemaStep { edge_idx, reverse });
let mut new_visited = visited.clone();
new_visited.push(neighbor.clone());
queue.push((neighbor.clone(), new_steps, new_visited));
}
}
results
}