1+ package graphql .analysis ;
2+
3+ import graphql .PublicApi ;
4+ import graphql .execution .CoercedVariables ;
5+ import graphql .language .Document ;
6+ import graphql .schema .GraphQLSchema ;
7+
8+ import java .util .LinkedHashMap ;
9+ import java .util .Map ;
10+
11+ import static graphql .Assert .assertNotNull ;
12+ import static java .util .Optional .ofNullable ;
13+
14+ /**
15+ * This can calculate the complexity of an operation using the specified {@link FieldComplexityCalculator} you pass
16+ * into it.
17+ */
18+ @ PublicApi
19+ public class QueryComplexityCalculator {
20+
21+ private final FieldComplexityCalculator fieldComplexityCalculator ;
22+ private final GraphQLSchema schema ;
23+ private final Document document ;
24+ private final String operationName ;
25+ private final CoercedVariables variables ;
26+
27+ public QueryComplexityCalculator (Builder builder ) {
28+ this .fieldComplexityCalculator = assertNotNull (builder .fieldComplexityCalculator , () -> "fieldComplexityCalculator can't be null" );
29+ this .schema = assertNotNull (builder .schema , () -> "schema can't be null" );
30+ this .document = assertNotNull (builder .document , () -> "document can't be null" );
31+ this .variables = assertNotNull (builder .variables , () -> "variables can't be null" );
32+ this .operationName = builder .operationName ;
33+ }
34+
35+
36+ public int calculate () {
37+ Map <QueryVisitorFieldEnvironment , Integer > valuesByParent = calculateByParents ();
38+ return valuesByParent .getOrDefault (null , 0 );
39+ }
40+
41+ /**
42+ * @return a map that shows the field complexity for each field level in the operation
43+ */
44+ public Map <QueryVisitorFieldEnvironment , Integer > calculateByParents () {
45+ QueryTraverser queryTraverser = QueryTraverser .newQueryTraverser ()
46+ .schema (this .schema )
47+ .document (this .document )
48+ .operationName (this .operationName )
49+ .coercedVariables (this .variables )
50+ .build ();
51+
52+
53+ Map <QueryVisitorFieldEnvironment , Integer > valuesByParent = new LinkedHashMap <>();
54+ queryTraverser .visitPostOrder (new QueryVisitorStub () {
55+ @ Override
56+ public void visitField (QueryVisitorFieldEnvironment env ) {
57+ int childComplexity = valuesByParent .getOrDefault (env , 0 );
58+ int value = calculateComplexity (env , childComplexity );
59+
60+ QueryVisitorFieldEnvironment parentEnvironment = env .getParentEnvironment ();
61+ valuesByParent .compute (parentEnvironment , (key , oldValue ) -> {
62+ Integer currentValue = ofNullable (oldValue ).orElse (0 );
63+ return currentValue + value ;
64+ }
65+ );
66+ }
67+ });
68+
69+ return valuesByParent ;
70+ }
71+
72+ private int calculateComplexity (QueryVisitorFieldEnvironment queryVisitorFieldEnvironment , int childComplexity ) {
73+ if (queryVisitorFieldEnvironment .isTypeNameIntrospectionField ()) {
74+ return 0 ;
75+ }
76+ FieldComplexityEnvironment fieldComplexityEnvironment = convertEnv (queryVisitorFieldEnvironment );
77+ return fieldComplexityCalculator .calculate (fieldComplexityEnvironment , childComplexity );
78+ }
79+
80+ private FieldComplexityEnvironment convertEnv (QueryVisitorFieldEnvironment queryVisitorFieldEnvironment ) {
81+ FieldComplexityEnvironment parentEnv = null ;
82+ if (queryVisitorFieldEnvironment .getParentEnvironment () != null ) {
83+ parentEnv = convertEnv (queryVisitorFieldEnvironment .getParentEnvironment ());
84+ }
85+ return new FieldComplexityEnvironment (
86+ queryVisitorFieldEnvironment .getField (),
87+ queryVisitorFieldEnvironment .getFieldDefinition (),
88+ queryVisitorFieldEnvironment .getFieldsContainer (),
89+ queryVisitorFieldEnvironment .getArguments (),
90+ parentEnv
91+ );
92+ }
93+
94+ public static Builder newCalculator () {
95+ return new Builder ();
96+ }
97+
98+ public static class Builder {
99+ private FieldComplexityCalculator fieldComplexityCalculator ;
100+ private GraphQLSchema schema ;
101+ private Document document ;
102+ private String operationName ;
103+ private CoercedVariables variables = CoercedVariables .emptyVariables ();
104+
105+ public Builder schema (GraphQLSchema graphQLSchema ) {
106+ this .schema = graphQLSchema ;
107+ return this ;
108+ }
109+
110+ public Builder fieldComplexityCalculator (FieldComplexityCalculator complexityCalculator ) {
111+ this .fieldComplexityCalculator = complexityCalculator ;
112+ return this ;
113+ }
114+
115+ public Builder document (Document document ) {
116+ this .document = document ;
117+ return this ;
118+ }
119+
120+ public Builder operationName (String operationName ) {
121+ this .operationName = operationName ;
122+ return this ;
123+ }
124+
125+ public Builder variables (CoercedVariables variables ) {
126+ this .variables = variables ;
127+ return this ;
128+ }
129+
130+ public QueryComplexityCalculator build () {
131+ return new QueryComplexityCalculator (this );
132+ }
133+ }
134+ }
0 commit comments