All Downloads are FREE. Search and download functionalities are using the official Maven repository.

graphql.validation.rules.NoFragmentCycles Maven / Gradle / Ivy

There is a newer version: 230521-nf-execution
Show newest version
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