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

graphql.language.AstSignature Maven / Gradle / Ivy

package graphql.language;

import com.google.common.collect.ImmutableList;
import graphql.PublicApi;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import static graphql.util.TreeTransformerUtil.changeNode;

/**
 * This will produce signature query documents that can be used say for logging.
 */
@PublicApi
public class AstSignature {

    /**
     * This can produce a "signature" canonical AST that conforms to the algorithm as outlined
     *  here
     * which removes excess operations, removes any field aliases, hides literal values and sorts the result into a canonical
     * query
     *
     * @param document      the document to make a signature query from
     * @param operationName the name of the operation to do it for (since only one query can be run at a time)
     *
     * @return the signature query in document form
     */
    public Document signatureQuery(Document document, String operationName) {
        return sortAST(
                removeAliases(
                        hideLiterals(
                                dropUnusedQueryDefinitions(document, operationName)))
        );
    }

    private Document hideLiterals(Document document) {
        final Map variableRemapping = new HashMap<>();
        final AtomicInteger variableCount = new AtomicInteger();

        NodeVisitorStub visitor = new NodeVisitorStub() {
            @Override
            public TraversalControl visitIntValue(IntValue node, TraverserContext context) {
                return changeNode(context, node.transform(builder -> builder.value(BigInteger.ZERO)));
            }

            @Override
            public TraversalControl visitFloatValue(FloatValue node, TraverserContext context) {
                return changeNode(context, node.transform(builder -> builder.value(BigDecimal.ZERO)));
            }

            @Override
            public TraversalControl visitStringValue(StringValue node, TraverserContext context) {
                return changeNode(context, node.transform(builder -> builder.value("")));
            }

            @Override
            public TraversalControl visitBooleanValue(BooleanValue node, TraverserContext context) {
                return changeNode(context, node.transform(builder -> builder.value(false)));
            }

            @Override
            public TraversalControl visitArrayValue(ArrayValue node, TraverserContext context) {
                return changeNode(context, node.transform(builder -> builder.values(Collections.emptyList())));
            }

            @Override
            public TraversalControl visitObjectValue(ObjectValue node, TraverserContext context) {
                return changeNode(context, node.transform(builder -> builder.objectFields(Collections.emptyList())));
            }

            @Override
            public TraversalControl visitVariableReference(VariableReference node, TraverserContext context) {
                String varName = remapVariable(node.getName(), variableRemapping, variableCount);
                return changeNode(context, node.transform(builder -> builder.name(varName)));
            }

            @Override
            public TraversalControl visitVariableDefinition(VariableDefinition node, TraverserContext context) {
                String varName = remapVariable(node.getName(), variableRemapping, variableCount);
                return changeNode(context, node.transform(builder -> builder.name(varName)));
            }
        };
        return transformDoc(document, visitor);
    }

    private String remapVariable(String varName, Map variableRemapping, AtomicInteger variableCount) {
        String mappedName = variableRemapping.get(varName);
        if (mappedName == null) {
            mappedName = "var" + variableCount.incrementAndGet();
            variableRemapping.put(varName, mappedName);
        }
        return mappedName;
    }

    private Document removeAliases(Document document) {
        NodeVisitorStub visitor = new NodeVisitorStub() {
            @Override
            public TraversalControl visitField(Field node, TraverserContext context) {
                return changeNode(context, node.transform(builder -> builder.alias(null)));
            }
        };
        return transformDoc(document, visitor);
    }

    private Document sortAST(Document document) {
        return new AstSorter().sort(document);
    }

    private Document dropUnusedQueryDefinitions(Document document, final String operationName) {
        NodeVisitorStub visitor = new NodeVisitorStub() {
            @Override
            public TraversalControl visitDocument(Document node, TraverserContext context) {
                List wantedDefinitions = node.getDefinitions().stream()
                        .filter(d -> {
                            if (d instanceof OperationDefinition) {
                                OperationDefinition operationDefinition = (OperationDefinition) d;
                                return isThisOperation(operationDefinition, operationName);
                            }
                            return d instanceof FragmentDefinition;
                            // SDL in a query makes no sense - its gone should it be present
                        })
                        .collect(ImmutableList.toImmutableList());

                Document changedNode = node.transform(builder -> {
                    builder.definitions(wantedDefinitions);
                });
                return changeNode(context, changedNode);
            }
        };
        return transformDoc(document, visitor);
    }

    private boolean isThisOperation(OperationDefinition operationDefinition, String operationName) {
        String name = operationDefinition.getName();
        if (operationName == null) {
            return name == null;
        }
        return operationName.equals(name);
    }

    private Document transformDoc(Document document, NodeVisitorStub visitor) {
        AstTransformer astTransformer = new AstTransformer();
        Node newDoc = astTransformer.transform(document, visitor);
        return (Document) newDoc;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy