graphql.validation.rules.NoFragmentCycles Maven / Gradle / Ivy
package graphql.validation.rules;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import graphql.Internal;
import graphql.language.Definition;
import graphql.language.FragmentDefinition;
import graphql.language.FragmentSpread;
import graphql.language.Node;
import graphql.validation.AbstractRule;
import graphql.validation.DocumentVisitor;
import graphql.validation.LanguageTraversal;
import graphql.validation.ValidationContext;
import graphql.validation.ValidationErrorCollector;
import graphql.validation.ValidationErrorType;
import static graphql.validation.ValidationErrorType.FragmentCycle;
@Internal
public class NoFragmentCycles extends AbstractRule {
private final Map> fragmentSpreads = new HashMap<>();
public NoFragmentCycles(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) {
super(validationContext, validationErrorCollector);
prepareFragmentMap();
}
private void prepareFragmentMap() {
List definitions = getValidationContext().getDocument().getDefinitions();
for (Definition definition : definitions) {
if (definition instanceof FragmentDefinition) {
FragmentDefinition fragmentDefinition = (FragmentDefinition) definition;
fragmentSpreads.put(fragmentDefinition.getName(), gatherSpreads(fragmentDefinition));
}
}
}
private Set gatherSpreads(FragmentDefinition fragmentDefinition) {
final Set fragmentSpreads = new HashSet<>();
DocumentVisitor visitor = new DocumentVisitor() {
@Override
public void enter(Node node, List path) {
if (node instanceof FragmentSpread) {
fragmentSpreads.add(((FragmentSpread) node).getName());
}
}
@Override
public void leave(Node node, List path) {
}
};
new LanguageTraversal().traverse(fragmentDefinition, visitor);
return fragmentSpreads;
}
@Override
public void checkFragmentDefinition(FragmentDefinition fragmentDefinition) {
LinkedList path = new LinkedList<>();
path.add(0, fragmentDefinition.getName());
Map> transitiveSpreads = buildTransitiveSpreads(path, new HashMap<>());
for (Map.Entry> entry : transitiveSpreads.entrySet()) {
if (entry.getValue().contains(entry.getKey())) {
String message = i18n(FragmentCycle, "NoFragmentCycles.cyclesNotAllowed");
addError(ValidationErrorType.FragmentCycle, Collections.singletonList(fragmentDefinition), message);
}
}
}
private Map> buildTransitiveSpreads(LinkedList path, Map> transitiveSpreads) {
String name = path.peekFirst();
if (transitiveSpreads.containsKey(name)) {
return transitiveSpreads;
}
Set spreads = fragmentSpreads.get(name);
// spreads may be null when there is no corresponding FragmentDefinition for this spread.
// This will be handled by KnownFragmentNames
if (spreads == null || spreads.isEmpty()) {
return transitiveSpreads;
}
// Add the current spreads to the transitive spreads of each ancestor in the traversal path
for (String ancestor : path) {
Set ancestorSpreads = transitiveSpreads.get(ancestor);
if (ancestorSpreads == null) {
ancestorSpreads = new HashSet<>();
}
ancestorSpreads.addAll(spreads);
transitiveSpreads.put(ancestor, ancestorSpreads);
}
for (String child : spreads) {
// don't recurse infinitely, expect the recursion check to happen in checkFragmentDefinition
if (path.contains(child) || transitiveSpreads.containsKey(child)) {
continue;
}
// descend into each spread in the current fragment
LinkedList childPath = new LinkedList<>(path);
childPath.add(0, child);
buildTransitiveSpreads(childPath, transitiveSpreads);
}
return transitiveSpreads;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy