FFFF This introduces a Traversal Options to allow skipping the coercing of field arguments by bbakerman · Pull Request #3651 · graphql-java/graphql-java · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions src/main/java/graphql/analysis/NodeVisitorWithTypeTracking.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;

import java.util.Collections;
import java.util.Locale;
import java.util.Map;

Expand All @@ -50,13 +51,20 @@ public class NodeVisitorWithTypeTracking extends NodeVisitorStub {
private final GraphQLSchema schema;
private final Map<String, FragmentDefinition> fragmentsByName;
private final ConditionalNodes conditionalNodes = new ConditionalNodes();

public NodeVisitorWithTypeTracking(QueryVisitor preOrderCallback, QueryVisitor postOrderCallback, Map<String, Object> variables, GraphQLSchema schema, Map<String, FragmentDefinition> fragmentsByName) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is public but marked as @internal

private final QueryTraversalOptions options;

public NodeVisitorWithTypeTracking(QueryVisitor preOrderCallback,
QueryVisitor postOrderCallback,
Map<String, Object> variables,
GraphQLSchema schema,
Map<String, FragmentDefinition> fragmentsByName,
QueryTraversalOptions options) {
this.preOrderCallback = preOrderCallback;
this.postOrderCallback = postOrderCallback;
this.variables = variables;
this.schema = schema;
this.fragmentsByName = fragmentsByName;
this.options = options;
}

@Override
Expand Down Expand Up @@ -156,12 +164,17 @@ public TraversalControl visitField(Field field, TraverserContext<Node> context)
boolean isTypeNameIntrospectionField = fieldDefinition == schema.getIntrospectionTypenameFieldDefinition();
GraphQLFieldsContainer fieldsContainer = !isTypeNameIntrospectionField ? (GraphQLFieldsContainer) unwrapAll(parentEnv.getOutputType()) : null;
GraphQLCodeRegistry codeRegistry = schema.getCodeRegistry();
Map<String, Object> argumentValues = ValuesResolver.getArgumentValues(codeRegistry,
fieldDefinition.getArguments(),
field.getArguments(),
CoercedVariables.of(variables),
GraphQLContext.getDefault(),
Locale.getDefault());
Map<String, Object> argumentValues;
if (options.isCoerceFieldArguments()) {
argumentValues = ValuesResolver.getArgumentValues(codeRegistry,
fieldDefinition.getArguments(),
field.getArguments(),
CoercedVariables.of(variables),
GraphQLContext.getDefault(),
Locale.getDefault());
} else {
argumentValues = Collections.emptyMap();
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main change

QueryVisitorFieldEnvironment environment = new QueryVisitorFieldEnvironmentImpl(isTypeNameIntrospectionField,
field,
fieldDefinition,
Expand Down
35 changes: 30 additions & 5 deletions src/main/java/graphql/analysis/QueryTransformer.java
97AF
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,22 @@ public class QueryTransformer {
private final GraphQLSchema schema;
private final Map<String, FragmentDefinition> fragmentsByName;
private final Map<String, Object> variables;

private final GraphQLCompositeType rootParentType;
private final QueryTraversalOptions options;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QueryTransformer also uses the NodeTracking code and hence this can apply here as well - why not



private QueryTransformer(GraphQLSchema schema,
Node root,
GraphQLCompositeType rootParentType,
Map<String, FragmentDefinition> fragmentsByName,
Map<String, Object> variables) {
Map<String, Object> variables,
QueryTraversalOptions options) {
this.schema = assertNotNull(schema, () -> "schema can't be null");
this.variables = assertNotNull(variables, () -> "variables can't be null");
this.root = assertNotNull(root, () -> "root can't be null");
this.rootParentType = assertNotNull(rootParentType);
this.fragmentsByName = assertNotNull(fragmentsByName, () -> "fragmentsByName can't be null");
this.options = assertNotNull(options, () -> "options can't be null");
}

/**
Expand All @@ -65,12 +67,17 @@ private QueryTransformer(GraphQLSchema schema,
*/
public Node transform(QueryVisitor queryVisitor) {
QueryVisitor noOp = new QueryVisitorStub();
NodeVisitorWithTypeTracking nodeVisitor = new NodeVisitorWithTypeTracking(queryVisitor, noOp, variables, schema, fragmentsByName);
NodeVisitorWithTypeTracking nodeVisitor = new NodeVisitorWithTypeTracking(queryVisitor,
noOp,
variables,
schema,
fragmentsByName,
options);

Map<Class<?>, Object> rootVars = new LinkedHashMap<>();
rootVars.put(QueryTraversalContext.class, new QueryTraversalContext(rootParentType, null, null, GraphQLContext.getDefault()));

TraverserVisitor<Node> nodeTraverserVisitor = new TraverserVisitor<Node>() {
TraverserVisitor<Node> nodeTraverserVisitor = new TraverserVisitor<>() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny fix up - not need for generic


@Override
public TraversalControl enter(TraverserContext<Node> context) {
Expand Down Expand Up @@ -98,6 +105,7 @@ public static class Builder {
private Node root;
private GraphQLCompositeType rootParentType;
private Map<String, FragmentDefinition> fragmentsByName;
private QueryTraversalOptions options = QueryTraversalOptions.defaultOptions();


/**
Expand Down Expand Up @@ -160,8 +168,25 @@ public Builder fragmentsByName(Map<String, FragmentDefinition> fragmentsByName)
return this;
}

/**
* Sets the options to use while traversing
*
* @param options the options to use
* @return this builder
*/
public Builder options(QueryTraversalOptions options) {
this.options = assertNotNull(options, () -> "options can't be null");
return this;
}

public QueryTransformer build() {
return new QueryTransformer(schema, root, rootParentType, fragmentsByName, variables);
return new QueryTransformer(
schema,
root,
rootParentType,
fragmentsByName,
variables,
options);
}
}
}
31 changes: 31 additions & 0 deletions src/main/java/graphql/analysis/QueryTraversalOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package graphql.analysis;

import graphql.PublicApi;

/**
* This options object controls how {@link QueryTraverser} works
*/
@PublicApi
public class QueryTraversalOptions {

private final boolean coerceFieldArguments;

private QueryTraversalOptions(boolean coerceFieldArguments) {
this.coerceFieldArguments = coerceFieldArguments;
}

/**
* @return true if field arguments should be coerced. This is true by default.
*/
public boolean isCoerceFieldArguments() {
return coerceFieldArguments;
}

public static QueryTraversalOptions defaultOptions() {
return new QueryTraversalOptions(true);
}

public QueryTraversalOptions coerceFieldArguments(boolean coerceFieldArguments) {
return new QueryTraversalOptions(coerceFieldArguments);
}
}
61 changes: 53 additions & 8 deletions src/main/java/graphql/analysis/QueryTraverser.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,42 +49,52 @@ public class QueryTraverser {
private CoercedVariables coercedVariables;

private final GraphQLCompositeType rootParentType;
private final QueryTraversalOptions options;

private QueryTraverser(GraphQLSchema schema,
Document document,
String operation,
CoercedVariables coercedVariables) {
CoercedVariables coercedVariables,
QueryTraversalOptions options
) {
this.schema = schema;
NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, operation);
this.fragmentsByName = getOperationResult.fragmentsByName;
this.roots = singletonList(getOperationResult.operationDefinition);
this.rootParentType = getRootTypeFromOperation(getOperationResult.operationDefinition);
this.coercedVariables = coercedVariables;
this.options = options;
}

private QueryTraverser(GraphQLSchema schema,
Document document,
String operation,
RawVariables rawVariables) {
RawVariables rawVariables,
QueryTraversalOptions options
) {
this.schema = schema;
NodeUtil.GetOperationResult getOperationResult = NodeUtil.getOperation(document, operation);
List<VariableDefinition> variableDefinitions = getOperationResult.operationDefinition.getVariableDefinitions();
this.fragmentsByName = getOperationResult.fragmentsByName;
this.roots = singletonList(getOperationResult.operationDefinition);
this.rootParentType = getRootTypeFromOperation(getOperationResult.operationDefinition);
this.coercedVariables = ValuesResolver.coerceVariableValues(schema, variableDefinitions, rawVariables, GraphQLContext.getDefault(), Locale.getDefault());
this.options = options;
}

private QueryTraverser(GraphQLSchema schema,
Node root,
GraphQLCompositeType rootParentType,
Map<String, FragmentDefinition> fragmentsByName,
CoercedVariables coercedVariables) {
CoercedVariables coercedVariables,
QueryTraversalOptions options
) {
this.schema = schema;
this.roots = Collections.singleton(root);
this.rootParentType = rootParentType;
this.fragmentsByName = fragmentsByName;
this.coercedVariables = coercedVariables;
this.options = options;
}

public Object visitDepthFirst(QueryVisitor queryVisitor) {
Expand Down Expand Up @@ -191,7 +201,12 @@ private Object visitImpl(QueryVisitor visitFieldCallback, Boolean preOrder) {
}

NodeTraverser nodeTraverser = new NodeTraverser(rootVars, this::childrenOf);
NodeVisitorWithTypeTracking nodeVisitorWithTypeTracking = new NodeVisitorWithTypeTracking(preOrderCallback, postOrderCallback, coercedVariables.toMap(), schema, fragmentsByName);
NodeVisitorWithTypeTracking nodeVisitorWithTypeTracking = new NodeVisitorWithTypeTracking(preOrderCallback,
postOrderCallback,
coercedVariables.toMap(),
schema,
fragmentsByName,
options);
return nodeTraverser.depthFirst(nodeVisitorWithTypeTracking, roots);
}

Expand All @@ -210,6 +225,7 @@ public static class Builder {
private Node root;
private GraphQLCompositeType rootParentType;
private Map<String, FragmentDefinition> fragmentsByName;
private QueryTraversalOptions options = QueryTraversalOptions.defaultOptions();


/**
Expand Down Expand Up @@ -313,24 +329,53 @@ public Builder fragmentsByName(Map<String, FragmentDefinition> fragmentsByName)
return this;
}

/**
* Sets the options to use while traversing
*
* @param options the options to use
* @return this builder
*/
public Builder options(QueryTraversalOptions options) {
this.options = assertNotNull(options, () -> "options can't be null");
return this;
}

/**
* @return a built {@link QueryTraverser} object
*/
public QueryTraverser build() {
checkState();
if (document != null) {
if (rawVariables != null) {
return new QueryTraverser(schema, document, operation, rawVariables);
return new QueryTraverser(schema,
document,
operation,
rawVariables,
options);
}
return new QueryTraverser(schema, document, operation, coercedVariables);
return new QueryTraverser(schema,
document,
operation,
coercedVariables,
options);
} else {
if (rawVariables != null) {
// When traversing with an arbitrary root, there is no variable definition context available
// Thus, the variables must have already been coerced
// Retaining this builder for backwards compatibility
return new QueryTraverser(schema, root, rootParentType, fragmentsByName, CoercedVariables.of(rawVariables.toMap()));
return new QueryTraverser(schema,
root,
rootParentType,
fragmentsByName,
CoercedVariables.of(rawVariables.toMap()),
options);
}
return new QueryTraverser(schema, root, rootParentType, fragmentsByName, coercedVariables);
return new QueryTraverser(schema,
root,
rootParentType,
fragmentsByName,
coercedVariables,
options);
}
}

Expand Down
57 changes: 57 additions & 0 deletions src/test/groovy/graphql/analysis/QueryTransformerTest.groovy
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package graphql.analysis

import graphql.TestUtil
import graphql.execution.CoercedVariables
import graphql.language.Document
import graphql.language.Field
import graphql.language.NodeUtil
Expand Down Expand Up @@ -448,4 +449,60 @@ class QueryTransformerTest extends Specification {
printAstCompact(newNode) == "{__typename ...on A{aX}...on B{b}}"

}

def "can coerce field arguments or not"() {
def sdl = """
input Test{ x: String!}
type Query{ testInput(input: Test!): String}
type Mutation{ testInput(input: Test!): String}
"""

def schema = TestUtil.schema(sdl)

def query = createQuery('''
mutation a($test: Test!) {
testInput(input: $test)
}''')


def fieldArgMap = [:]
def queryVisitorStub = new QueryVisitorStub() {
@Override
void visitField(QueryVisitorFieldEnvironment queryVisitorFieldEnvironment) {
super.visitField(queryVisitorFieldEnvironment)
fieldArgMap = queryVisitorFieldEnvironment.getArguments()
}
}

when:
QueryTraverser.newQueryTraverser()
.schema(schema)
.document(query)
.coercedVariables(CoercedVariables.of([test: [x: "X"]]))
.build()
.visitPreOrder(queryVisitorStub)

then:

fieldArgMap == [input: [x:"X"]]

when:
fieldArgMap = null


def options = QueryTraversalOptions.defaultOptions()
.coerceFieldArguments(false)
QueryTraverser.newQueryTraverser()
.schema(schema)
.document(query)
.coercedVariables(CoercedVariables.of([test: [x: "X"]]))
.options(options)
.build()
.visitPreOrder(queryVisitorStub)


then:
fieldArgMap == [:] // empty map
}

}
20 changes: 20 additions & 0 deletions src/test/groovy/graphql/analysis/QueryTraversalOptionsTest.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package graphql.analysis

import spock.lang.Specification

class QueryTraversalOptionsTest extends Specification {

def "defaulting works as expected"() {
when:
def options = QueryTraversalOptions.defaultOptions()

then:
options.isCoerceFieldArguments()

when:
options = QueryTraversalOptions.defaultOptions().coerceFieldArguments(false)

then:
!options.isCoerceFieldArguments()
}
}
Loading
0