graphql.language.AstSignature Maven / Gradle / Ivy
package graphql.language;
import com.google.common.collect.ImmutableList;
import graphql.PublicApi;
import graphql.collect.ImmutableKit;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;
import java.math.BigDecimal;
import java.math.BigInteger;
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 and privacy safe query documents that can be used for query categorisation and 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.
*
* A signature says two queries with the same signature can be thought of as the same query. For example
* a tracing system will want a canonical signature for queries to group them.
*
* @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(true,
dropUnusedQueryDefinitions(document, operationName)))
);
}
/**
* This can produce a "privacy safe" AST that some what conforms to the algorithm as outlined
* here
* which removes excess operations, removes any field aliases, hides some literal values and sorts the result.
*
* This is not a signature. For example object literal structures are retained like `{ a : "", b : 0}` which means
* you can infer what was asked for but not what the values are. This differs from {@link AstSignature#signatureQuery(Document, String)}
* which collapses literals to create more canonical signatures. A privacy safe Query is useful for logging say to show
* what shapes was asked for without revealing what data was provided
*
* @param document the document to make a privacy safe 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 privacy safe query in document form
*/
public Document privacySafeQuery(Document document, String operationName) {
return sortAST(
removeAliases(
hideLiterals(false,
dropUnusedQueryDefinitions(document, operationName)))
);
}
private Document hideLiterals(boolean signatureMode, 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) {
if (signatureMode) {
return changeNode(context, node.transform(builder -> builder.values(ImmutableKit.emptyList())));
}
return TraversalControl.CONTINUE;
}
@Override
public TraversalControl visitObjectValue(ObjectValue node, TraverserContext context) {
if (signatureMode) {
return changeNode(context, node.transform(builder -> builder.objectFields(ImmutableKit.emptyList())));
}
return TraversalControl.CONTINUE;
}
@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