106 lines
3.1 KiB
Rust
106 lines
3.1 KiB
Rust
|
use crate::error::RuleError;
|
||
|
use crate::validation::context::ValidatorContext;
|
||
|
use crate::validation::visitor::Visitor;
|
||
|
use graphql_parser::query::{Document, FragmentDefinition, FragmentSpread};
|
||
|
use graphql_parser::Pos;
|
||
|
use std::collections::{HashMap, HashSet};
|
||
|
|
||
|
struct CycleDetector<'a> {
|
||
|
visited: HashSet<&'a str>,
|
||
|
spreads: &'a HashMap<&'a str, Vec<(&'a str, Pos)>>,
|
||
|
path_indices: HashMap<&'a str, usize>,
|
||
|
errors: Vec<RuleError>,
|
||
|
}
|
||
|
|
||
|
impl<'a> CycleDetector<'a> {
|
||
|
fn detect_from(&mut self, from: &'a str, path: &mut Vec<(&'a str, Pos)>) {
|
||
|
self.visited.insert(from);
|
||
|
|
||
|
if !self.spreads.contains_key(from) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
self.path_indices.insert(from, path.len());
|
||
|
|
||
|
for (name, pos) in &self.spreads[from] {
|
||
|
let index = self.path_indices.get(name).cloned();
|
||
|
|
||
|
if let Some(index) = index {
|
||
|
let err_pos = if index < path.len() {
|
||
|
path[index].1
|
||
|
} else {
|
||
|
*pos
|
||
|
};
|
||
|
|
||
|
self.errors.push(RuleError {
|
||
|
locations: vec![err_pos],
|
||
|
message: format!("Cannot spread fragment \"{}\"", name),
|
||
|
});
|
||
|
} else if !self.visited.contains(name) {
|
||
|
path.push((name, *pos));
|
||
|
self.detect_from(name, path);
|
||
|
path.pop();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
self.path_indices.remove(from);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Default)]
|
||
|
pub struct NoFragmentCycles<'a> {
|
||
|
current_fragment: Option<&'a str>,
|
||
|
spreads: HashMap<&'a str, Vec<(&'a str, Pos)>>,
|
||
|
fragment_order: Vec<&'a str>,
|
||
|
}
|
||
|
|
||
|
impl<'a> Visitor<'a> for NoFragmentCycles<'a> {
|
||
|
fn exit_document(&mut self, ctx: &mut ValidatorContext<'a>, _doc: &'a Document) {
|
||
|
let mut detector = CycleDetector {
|
||
|
visited: HashSet::new(),
|
||
|
spreads: &self.spreads,
|
||
|
path_indices: HashMap::new(),
|
||
|
errors: Vec::new(),
|
||
|
};
|
||
|
|
||
|
for frag in &self.fragment_order {
|
||
|
if !detector.visited.contains(frag) {
|
||
|
let mut path = Vec::new();
|
||
|
detector.detect_from(frag, &mut path);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ctx.append_errors(detector.errors);
|
||
|
}
|
||
|
|
||
|
fn enter_fragment_definition(
|
||
|
&mut self,
|
||
|
_ctx: &mut ValidatorContext<'a>,
|
||
|
fragment_definition: &'a FragmentDefinition,
|
||
|
) {
|
||
|
self.current_fragment = Some(&fragment_definition.name);
|
||
|
self.fragment_order.push(&fragment_definition.name);
|
||
|
}
|
||
|
|
||
|
fn exit_fragment_definition(
|
||
|
&mut self,
|
||
|
_ctx: &mut ValidatorContext<'a>,
|
||
|
_fragment_definition: &'a FragmentDefinition,
|
||
|
) {
|
||
|
self.current_fragment = None;
|
||
|
}
|
||
|
|
||
|
fn enter_fragment_spread(
|
||
|
&mut self,
|
||
|
_ctx: &mut ValidatorContext<'a>,
|
||
|
fragment_spread: &'a FragmentSpread,
|
||
|
) {
|
||
|
if let Some(current_fragment) = self.current_fragment {
|
||
|
self.spreads
|
||
|
.entry(current_fragment)
|
||
|
.or_insert_with(Vec::new)
|
||
|
.push((&fragment_spread.fragment_name, fragment_spread.position));
|
||
|
}
|
||
|
}
|
||
|
}
|