use crate::schema::graph::SchemaGraph;
use crate::schema::metadata::TypeId;
use crate::schema::schema_type::SchemaType;
use crate::schema::schema_value::SchemaValue;
use std::collections::HashSet;
pub trait SchemaWalker {
type Output;
type Error;
fn walk(
&mut self,
graph: &SchemaGraph,
ty: &SchemaType,
value: &SchemaValue,
) -> Result<Self::Output, Self::Error>;
}
pub fn walk<W: SchemaWalker>(
walker: &mut W,
graph: &SchemaGraph,
ty: &SchemaType,
value: &SchemaValue,
) -> Result<W::Output, WalkerError<W::Error>> {
let mut visited: HashSet<TypeId> = HashSet::new();
walk_inner(walker, graph, ty, value, &mut visited)
}
fn walk_inner<W: SchemaWalker>(
walker: &mut W,
graph: &SchemaGraph,
ty: &SchemaType,
value: &SchemaValue,
visited: &mut HashSet<TypeId>,
) -> Result<W::Output, WalkerError<W::Error>> {
resolve_ref(graph, ty, visited, |graph, body| {
walker.walk(graph, body, value).map_err(WalkerError::Walker)
})
}
pub fn resolve_ref<F, T, E>(
graph: &SchemaGraph,
ty: &SchemaType,
visited: &mut HashSet<TypeId>,
f: F,
) -> Result<T, WalkerError<E>>
where
F: FnOnce(&SchemaGraph, &SchemaType) -> Result<T, WalkerError<E>>,
{
let mut current = ty;
let mut entered: Vec<TypeId> = Vec::new();
let result = loop {
match current {
SchemaType::Ref { id, .. } => {
if !visited.insert(id.clone()) {
break Err(WalkerError::RefCycle(id.clone()));
}
entered.push(id.clone());
match graph.lookup(id) {
Some(def) => current = &def.body,
None => break Err(WalkerError::DanglingRef(id.clone())),
}
}
other => break f(graph, other),
}
};
for id in entered {
visited.remove(&id);
}
result
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum WalkerError<E> {
RefCycle(TypeId),
DanglingRef(TypeId),
Walker(E),
}
impl<E: std::fmt::Display> std::fmt::Display for WalkerError<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WalkerError::RefCycle(id) => write!(f, "reference cycle through `{id}`"),
WalkerError::DanglingRef(id) => write!(f, "dangling reference `{id}`"),
WalkerError::Walker(inner) => write!(f, "{inner}"),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for WalkerError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
WalkerError::Walker(inner) => Some(inner),
_ => None,
}
}
}