• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Ekryd / sortgraphql / 2150

08 Apr 2024 05:09PM CUT coverage: 98.082%. Remained the same
2150

push

circleci

web-flow
Update dependency commons-io:commons-io to v2.16.1

818 of 834 relevant lines covered (98.08%)

0.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.8
/sorter/src/main/java/sortgraphql/sort/SchemaPrinter.java
1
package sortgraphql.sort;
2

3
import static graphql.Directives.DeprecatedDirective;
4
import static graphql.introspection.Introspection.DirectiveLocation.ARGUMENT_DEFINITION;
5
import static graphql.introspection.Introspection.DirectiveLocation.ENUM_VALUE;
6
import static graphql.introspection.Introspection.DirectiveLocation.FIELD_DEFINITION;
7
import static graphql.introspection.Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION;
8
import static graphql.util.EscapeUtil.escapeJsonString;
9
import static java.util.Arrays.asList;
10
import static java.util.stream.Collectors.joining;
11
import static java.util.stream.Collectors.toCollection;
12
import static java.util.stream.Collectors.toList;
13

14
import graphql.GraphQLContext;
15
import graphql.PublicApi;
16
import graphql.execution.ValuesResolver;
17
import graphql.language.AbstractDescribedNode;
18
import graphql.language.AstPrinter;
19
import graphql.language.Description;
20
import graphql.schema.GraphQLArgument;
21
import graphql.schema.GraphQLDirective;
22
import graphql.schema.GraphQLEnumType;
23
import graphql.schema.GraphQLEnumValueDefinition;
24
import graphql.schema.GraphQLFieldDefinition;
25
import graphql.schema.GraphQLInputObjectField;
26
import graphql.schema.GraphQLInputObjectType;
27
import graphql.schema.GraphQLInputType;
28
import graphql.schema.GraphQLInterfaceType;
29
import graphql.schema.GraphQLNamedSchemaElement;
30
import graphql.schema.GraphQLNamedType;
31
import graphql.schema.GraphQLObjectType;
32
import graphql.schema.GraphQLOutputType;
33
import graphql.schema.GraphQLScalarType;
34
import graphql.schema.GraphQLSchema;
35
import graphql.schema.GraphQLSchemaElement;
36
import graphql.schema.GraphQLType;
37
import graphql.schema.GraphQLTypeUtil;
38
import graphql.schema.GraphQLUnionType;
39
import graphql.schema.GraphqlTypeComparatorEnvironment;
40
import graphql.schema.InputValueWithState;
41
import graphql.schema.idl.ScalarInfo;
42
import graphql.schema.visibility.GraphqlFieldVisibility;
43
import java.io.PrintWriter;
44
import java.io.StringWriter;
45
import java.util.ArrayList;
46
import java.util.Comparator;
47
import java.util.List;
48
import java.util.Locale;
49
import java.util.Optional;
50
import java.util.function.Consumer;
51
import java.util.function.Predicate;
52
import java.util.stream.Collectors;
53

54
/** This can print an in memory GraphQL schema back to a logical schema definition */
55
@PublicApi
56
public class SchemaPrinter {
57
  //
58
  // we use this so that we get the simple "@deprecated" as text and not a full exploded
59
  // text with arguments (but only when we auto add this)
60
  //
61
  private static final GraphQLDirective DeprecatedDirective4Printing =
1✔
62
      GraphQLDirective.newDirective()
1✔
63
          .name("deprecated")
1✔
64
          .validLocations(FIELD_DEFINITION, ENUM_VALUE, ARGUMENT_DEFINITION, INPUT_FIELD_DEFINITION)
1✔
65
          .build();
1✔
66

67
  private final Options options;
68

69
  public SchemaPrinter(Options options) {
1✔
70
    this.options = options;
1✔
71
  }
1✔
72

73
  /**
74
   * This can print an in memory GraphQL schema back to a logical schema definition
75
   *
76
   * @param schema the schema in play
77
   * @return the logical schema definition
78
   */
79
  public String print(GraphQLSchema schema) {
80
    var sw = new StringWriter();
1✔
81
    var out = new PrintWriter(sw);
1✔
82

83
    var visibility = schema.getCodeRegistry().getFieldVisibility();
1✔
84

85
    printSchema(out, schema);
1✔
86

87
    List<GraphQLNamedType> typesAsList =
1✔
88
        schema.getAllTypesAsList().stream()
1✔
89
            .sorted(Comparator.comparing(GraphQLNamedType::getName))
1✔
90
            .collect(toCollection(ArrayList::new));
1✔
91

92
    typesAsList =
1✔
93
        removeMatchingItems(
1✔
94
            typesAsList,
95
            matchesTypeName(schema.getQueryType()),
1✔
96
            type -> printObject(out, (GraphQLObjectType) type, visibility));
1✔
97
    typesAsList =
1✔
98
        removeMatchingItems(
1✔
99
            typesAsList,
100
            matchesTypeName(schema.getMutationType()),
1✔
101
            type -> printObject(out, (GraphQLObjectType) type, visibility));
1✔
102
    typesAsList =
1✔
103
        removeMatchingItems(
1✔
104
            typesAsList,
105
            matchesTypeName(schema.getSubscriptionType()),
1✔
106
            type -> printObject(out, (GraphQLObjectType) type, visibility));
1✔
107

108
    typesAsList =
1✔
109
        removeMatchingItems(
1✔
110
            typesAsList,
111
            matchesClass(GraphQLScalarType.class),
1✔
112
            type -> printScalar(out, (GraphQLScalarType) type));
1✔
113
    typesAsList =
1✔
114
        removeMatchingItems(
1✔
115
            typesAsList,
116
            matchesClass(GraphQLInterfaceType.class),
1✔
117
            type -> printInterface(out, (GraphQLInterfaceType) type, visibility));
1✔
118
    typesAsList =
1✔
119
        removeMatchingItems(
1✔
120
            typesAsList,
121
            matchesClass(GraphQLUnionType.class),
1✔
122
            type -> printUnion(out, (GraphQLUnionType) type));
1✔
123
    typesAsList =
1✔
124
        removeMatchingItems(
1✔
125
            typesAsList,
126
            matchesClass(GraphQLInputObjectType.class),
1✔
127
            type -> printInput(out, (GraphQLInputObjectType) type, visibility));
1✔
128
    typesAsList =
1✔
129
        removeMatchingItems(
1✔
130
            typesAsList,
131
            matchesClass(GraphQLObjectType.class),
1✔
132
            type -> printObject(out, (GraphQLObjectType) type, visibility));
1✔
133
    typesAsList.stream()
1✔
134
        .filter(matchesClass(GraphQLEnumType.class))
1✔
135
        .forEach(type -> printEnum(out, (GraphQLEnumType) type));
1✔
136

137
    var result = sw.toString();
1✔
138
    if (result.endsWith("\n\n")) {
1✔
139
      result = result.substring(0, result.length() - 1);
1✔
140
    }
141
    return result;
1✔
142
  }
143

144
  private void printSchema(PrintWriter out, GraphQLSchema schema) {
145
    if (needsSchemaPrinted(schema)) {
1✔
146
      printSchemaElement(out, schema);
1✔
147
    }
148

149
    if (options.isIncludeDirectiveDefinitions()) {
1✔
150
      var directives = getSchemaDirectives(schema);
1✔
151
      if (!directives.isEmpty()) {
1✔
152
        out.append(directiveDefinitions(directives));
1✔
153
      }
154
    } else if (options.isIncludeDefinedDirectiveDefinitions()) {
1✔
155
      var directives =
1✔
156
          getSchemaDirectives(schema).stream()
1✔
157
              .filter(
1✔
158
                  directive ->
159
                      directive.getDefinition() != null
1✔
160
                          && directive.getDefinition().getSourceLocation() != null)
1✔
161
              .collect(Collectors.toList());
1✔
162
      if (!directives.isEmpty()) {
1✔
163
        out.append(directiveDefinitions(directives));
1✔
164
      }
165
    }
166
  }
1✔
167

168
  private boolean needsSchemaPrinted(GraphQLSchema schema) {
169
    var queryType = schema.getQueryType();
1✔
170
    var mutationType = schema.getMutationType();
1✔
171
    var subscriptionType = schema.getSubscriptionType();
1✔
172

173
    // when serializing a GraphQL schema using the type system language, a
174
    // schema definition should be omitted if only uses the default root type names.
175
    var needsSchemaPrinted = options.isIncludeSchemaDefinition();
1✔
176

177
    if (!needsSchemaPrinted) {
1✔
178
      if (queryType != null && !queryType.getName().equals("Query")) {
1✔
179
        needsSchemaPrinted = true;
1✔
180
      }
181
      if (mutationType != null && !mutationType.getName().equals("Mutation")) {
1✔
182
        needsSchemaPrinted = true;
1✔
183
      }
184
      if (subscriptionType != null && !subscriptionType.getName().equals("Subscription")) {
1✔
185
        needsSchemaPrinted = true;
1✔
186
      }
187
    }
188
    return needsSchemaPrinted;
1✔
189
  }
190

191
  private void printSchemaElement(PrintWriter out, GraphQLSchema schema) {
192
    // Too much work to replace this deprecation
193
    //noinspection deprecation
194
    var schemaDirectives =
1✔
195
        schema.getSchemaDirectives().stream()
1✔
196
            .sorted(Comparator.comparing(GraphQLDirective::getName))
1✔
197
            .collect(toList());
1✔
198

199
    out.format(
1✔
200
            "schema%s{",
201
            schemaDirectives.isEmpty()
1✔
202
                ? " "
1✔
203
                : directivesString(GraphQLSchemaElement.class, schemaDirectives))
1✔
204
        .append("\n");
1✔
205

206
    var queryType = schema.getQueryType();
1✔
207
    if (queryType != null) {
1✔
208
      out.format("  query: %s", queryType.getName()).append("\n");
1✔
209
    }
210

211
    var mutationType = schema.getMutationType();
1✔
212
    if (mutationType != null) {
1✔
213
      out.format("  mutation: %s", mutationType.getName()).append("\n");
1✔
214
    }
215

216
    var subscriptionType = schema.getSubscriptionType();
1✔
217
    if (subscriptionType != null) {
1✔
218
      out.format("  subscription: %s", subscriptionType.getName()).append("\n");
1✔
219
    }
220

221
    out.append("}\n\n");
1✔
222
  }
1✔
223

224
  private void printScalar(PrintWriter out, GraphQLScalarType type) {
225
    if (!options.isIncludeScalars() || ScalarInfo.isGraphqlSpecifiedScalar(type)) {
1✔
226
      return;
1✔
227
    }
228
    if (!options.getNodeDescriptionFilter().test(type.getDefinition())) {
1✔
229
      return;
1✔
230
    }
231
    printComments(out, type, "");
1✔
232
    out.format(
1✔
233
            "scalar %s%s",
234
            type.getName(), directivesString(GraphQLScalarType.class, type.getDirectives()))
1✔
235
        .append("\n\n");
1✔
236
  }
1✔
237

238
  private void printInterface(
239
      PrintWriter out, GraphQLInterfaceType type, GraphqlFieldVisibility visibility) {
240
    if (isIntrospectionType(type)) {
1✔
241
      return;
×
242
    }
243
    if (!options.getNodeDescriptionFilter().test(type.getDefinition())) {
1✔
244
      return;
1✔
245
    }
246
    printComments(out, type, "");
1✔
247
    if (type.getInterfaces().isEmpty()) {
1✔
248
      out.format(
1✔
249
          "interface %s%s",
250
          type.getName(),
1✔
251
          type.getDirectives().isEmpty()
1✔
252
              ? " "
1✔
253
              : directivesString(GraphQLInterfaceType.class, type.getDirectives()));
1✔
254
    } else {
255

256
      var environment =
257
          GraphqlTypeComparatorEnvironment.newEnvironment()
1✔
258
              .parentType(GraphQLInterfaceType.class)
1✔
259
              .elementType(GraphQLOutputType.class)
1✔
260
              .build();
1✔
261
      var implementsComparator = options.getComparatorRegistry().getComparator(environment);
1✔
262

263
      var interfaceNames =
1✔
264
          type.getInterfaces().stream().sorted(implementsComparator).map(GraphQLNamedType::getName);
1✔
265
      out.format(
1✔
266
          "interface %s implements %s%s",
267
          type.getName(),
1✔
268
          interfaceNames.collect(joining(" & ")),
1✔
269
          type.getDirectives().isEmpty()
1✔
270
              ? " "
1✔
271
              : directivesString(GraphQLInterfaceType.class, type.getDirectives()));
1✔
272
    }
273

274
    var environment =
275
        GraphqlTypeComparatorEnvironment.newEnvironment()
1✔
276
            .parentType(GraphQLInterfaceType.class)
1✔
277
            .elementType(GraphQLFieldDefinition.class)
1✔
278
            .build();
1✔
279
    var comparator = options.getComparatorRegistry().getComparator(environment);
1✔
280

281
    printFieldDefinitions(out, comparator, visibility.getFieldDefinitions(type));
1✔
282
    out.append("\n\n");
1✔
283
  }
1✔
284

285
  private void printUnion(PrintWriter out, GraphQLUnionType type) {
286
    if (isIntrospectionType(type)) {
1✔
287
      return;
×
288
    }
289
    if (!options.getNodeDescriptionFilter().test(type.getDefinition())) {
1✔
290
      return;
1✔
291
    }
292

293
    var environment =
294
        GraphqlTypeComparatorEnvironment.newEnvironment()
1✔
295
            .parentType(GraphQLUnionType.class)
1✔
296
            .elementType(GraphQLOutputType.class)
1✔
297
            .build();
1✔
298
    var comparator = options.getComparatorRegistry().getComparator(environment);
1✔
299

300
    printComments(out, type, "");
1✔
301
    out.format(
1✔
302
        "union %s%s = ",
303
        type.getName(), directivesString(GraphQLUnionType.class, type.getDirectives()));
1✔
304
    var types = type.getTypes().stream().sorted(comparator).collect(toList());
1✔
305
    for (var i = 0; i < types.size(); i++) {
1✔
306
      var objectType = types.get(i);
1✔
307
      if (i > 0) {
1✔
308
        out.append(" | ");
1✔
309
      }
310
      out.append(objectType.getName());
1✔
311
    }
312
    out.append("\n\n");
1✔
313
  }
1✔
314

315
  private void printInput(
316
      PrintWriter out, GraphQLInputObjectType type, GraphqlFieldVisibility visibility) {
317
    if (isIntrospectionType(type)) {
1✔
318
      return;
×
319
    }
320
    if (!options.getNodeDescriptionFilter().test(type.getDefinition())) {
1✔
321
      return;
1✔
322
    }
323
    printComments(out, type, "");
1✔
324
    var environment =
325
        GraphqlTypeComparatorEnvironment.newEnvironment()
1✔
326
            .parentType(GraphQLInputObjectType.class)
1✔
327
            .elementType(GraphQLInputObjectField.class)
1✔
328
            .build();
1✔
329
    var comparator = options.getComparatorRegistry().getComparator(environment);
1✔
330

331
    out.format(
1✔
332
        "input %s%s",
333
        type.getName(),
1✔
334
        type.getDirectives().isEmpty()
1✔
335
            ? " "
1✔
336
            : directivesString(GraphQLInputObjectType.class, type.getDirectives()));
1✔
337
    var inputObjectFields = visibility.getFieldDefinitions(type);
1✔
338
    if (!inputObjectFields.isEmpty()) {
1✔
339
      out.append("{\n");
1✔
340
      inputObjectFields.stream()
1✔
341
          .filter(options.getIncludeSchemaElement())
1✔
342
          .sorted(comparator)
1✔
343
          .forEach(
1✔
344
              fd -> {
345
                printComments(out, fd, "  ");
1✔
346
                out.format("  %s: %s", fd.getName(), typeString(fd.getType()));
1✔
347
                if (fd.hasSetDefaultValue()) {
1✔
348
                  var defaultValue = fd.getInputFieldDefaultValue();
1✔
349
                  var astValue = printAst(defaultValue, fd.getType());
1✔
350
                  out.format(" = %s", astValue);
1✔
351
                }
352
                out.format(directivesString(GraphQLInputObjectField.class, fd.getDirectives()));
1✔
353
                out.append("\n");
1✔
354
              });
1✔
355
      out.append("}");
1✔
356
    }
357
    out.append("\n\n");
1✔
358
  }
1✔
359

360
  private void printObject(
361
      PrintWriter out, GraphQLObjectType type, GraphqlFieldVisibility visibility) {
362
    if (isIntrospectionType(type)) {
1✔
363
      return;
1✔
364
    }
365
    if (!options.getNodeDescriptionFilter().test(type.getDefinition())) {
1✔
366
      return;
1✔
367
    }
368
    printComments(out, type, "");
1✔
369
    if (type.getInterfaces().isEmpty()) {
1✔
370
      out.format(
1✔
371
          "type %s%s",
372
          type.getName(),
1✔
373
          type.getDirectives().isEmpty()
1✔
374
              ? " "
1✔
375
              : directivesString(GraphQLObjectType.class, type.getDirectives()));
1✔
376
    } else {
377

378
      var environment =
379
          GraphqlTypeComparatorEnvironment.newEnvironment()
1✔
380
              .parentType(GraphQLObjectType.class)
1✔
381
              .elementType(GraphQLOutputType.class)
1✔
382
              .build();
1✔
383
      var implementsComparator = options.getComparatorRegistry().getComparator(environment);
1✔
384

385
      var interfaceNames =
1✔
386
          type.getInterfaces().stream().sorted(implementsComparator).map(GraphQLNamedType::getName);
1✔
387
      out.format(
1✔
388
          "type %s implements %s%s",
389
          type.getName(),
1✔
390
          interfaceNames.collect(joining(" & ")),
1✔
391
          type.getDirectives().isEmpty()
1✔
392
              ? " "
1✔
393
              : directivesString(GraphQLObjectType.class, type.getDirectives()));
1✔
394
    }
395

396
    var environment =
397
        GraphqlTypeComparatorEnvironment.newEnvironment()
1✔
398
            .parentType(GraphQLObjectType.class)
1✔
399
            .elementType(GraphQLFieldDefinition.class)
1✔
400
            .build();
1✔
401
    var comparator = options.getComparatorRegistry().getComparator(environment);
1✔
402

403
    printFieldDefinitions(out, comparator, visibility.getFieldDefinitions(type));
1✔
404
    out.append("\n\n");
1✔
405
  }
1✔
406

407
  private void printEnum(PrintWriter out, GraphQLEnumType type) {
408
    if (isIntrospectionType(type)) {
1✔
409
      return;
1✔
410
    }
411
    if (!options.getNodeDescriptionFilter().test(type.getDefinition())) {
1✔
412
      return;
1✔
413
    }
414

415
    var environment =
416
        GraphqlTypeComparatorEnvironment.newEnvironment()
1✔
417
            .parentType(GraphQLEnumType.class)
1✔
418
            .elementType(GraphQLEnumValueDefinition.class)
1✔
419
            .build();
1✔
420
    var comparator = options.getComparatorRegistry().getComparator(environment);
1✔
421

422
    printComments(out, type, "");
1✔
423
    out.format(
1✔
424
        "enum %s%s", type.getName(), directivesString(GraphQLEnumType.class, type.getDirectives()));
1✔
425
    var values = type.getValues().stream().sorted(comparator).collect(toList());
1✔
426
    if (!values.isEmpty()) {
1✔
427
      out.append(" {\n");
1✔
428
      for (var enumValueDefinition : values) {
1✔
429
        printComments(out, enumValueDefinition, "  ");
1✔
430
        var enumValueDirectives = enumValueDefinition.getDirectives();
1✔
431
        if (enumValueDefinition.isDeprecated()) {
1✔
432
          enumValueDirectives = addDeprecatedDirectiveIfNeeded(enumValueDirectives);
1✔
433
        }
434
        out.format(
1✔
435
                "  %s%s",
436
                enumValueDefinition.getName(),
1✔
437
                directivesString(GraphQLEnumValueDefinition.class, enumValueDirectives))
1✔
438
            .append("\n");
1✔
439
      }
1✔
440
      out.append("}");
1✔
441
    }
442
    out.append("\n\n");
1✔
443
  }
1✔
444

445
  private Predicate<GraphQLNamedType> matchesClass(Class<?> clazz) {
446
    return type ->
1✔
447
        clazz.isAssignableFrom(type.getClass()) && options.getIncludeSchemaElement().test(type);
1✔
448
  }
449

450
  private Predicate<GraphQLNamedType> matchesTypeName(GraphQLObjectType namedType) {
451
    if (namedType == null) {
1✔
452
      return testType -> false;
1✔
453
    }
454
    var name = namedType.getName();
1✔
455
    return testType ->
1✔
456
        testType.getName().equals(name) && options.getIncludeSchemaElement().test(testType);
1✔
457
  }
458

459
  private <T> List<T> removeMatchingItems(
460
      List<T> list, Predicate<T> filter, Consumer<T> removedItemsFn) {
461
    List<T> returnValue = new ArrayList<>();
1✔
462
    for (var item : list) {
1✔
463
      if (filter.test(item)) {
1✔
464
        removedItemsFn.accept(item);
1✔
465
      } else {
466
        returnValue.add(item);
1✔
467
      }
468
    }
1✔
469
    return returnValue;
1✔
470
  }
471

472
  private boolean isIntrospectionType(GraphQLNamedType type) {
473
    return !options.isIncludeIntrospectionTypes() && type.getName().startsWith("__");
1✔
474
  }
475

476
  private void printFieldDefinitions(
477
      PrintWriter out,
478
      Comparator<? super GraphQLSchemaElement> comparator,
479
      List<GraphQLFieldDefinition> fieldDefinitions) {
480
    if (fieldDefinitions.isEmpty()) {
1✔
481
      return;
×
482
    }
483

484
    out.append("{\n");
1✔
485
    fieldDefinitions.stream()
1✔
486
        .filter(options.getIncludeSchemaElement())
1✔
487
        .filter(fd -> options.getNodeDescriptionFilter().test(fd.getDefinition()))
1✔
488
        .sorted(comparator)
1✔
489
        .forEach(
1✔
490
            fd -> {
491
              printComments(out, fd, "  ");
1✔
492
              var fieldDirectives = fd.getDirectives();
1✔
493
              if (fd.isDeprecated()) {
1✔
494
                fieldDirectives = addDeprecatedDirectiveIfNeeded(fieldDirectives);
×
495
              }
496

497
              out.format(
1✔
498
                      "  %s%s: %s%s",
499
                      fd.getName(),
1✔
500
                      argsString(GraphQLFieldDefinition.class, fd.getArguments()),
1✔
501
                      typeString(fd.getType()),
1✔
502
                      directivesString(GraphQLFieldDefinition.class, fieldDirectives))
1✔
503
                  .append("\n");
1✔
504
            });
1✔
505
    out.append("}");
1✔
506
  }
1✔
507

508
  private static String printAst(InputValueWithState value, GraphQLInputType type) {
509
    return AstPrinter.printAst(
1✔
510
        ValuesResolver.valueToLiteral(
1✔
511
            value, type, GraphQLContext.getDefault(), Locale.getDefault()));
1✔
512
  }
513

514
  private List<GraphQLDirective> getSchemaDirectives(GraphQLSchema schema) {
515
    return schema.getDirectives().stream()
1✔
516
        .filter(options.getIncludeDirective())
1✔
517
        .filter(options.getIncludeSchemaElement())
1✔
518
        .filter(d -> options.getNodeDescriptionFilter().test(d.getDefinition()))
1✔
519
        .sorted(Comparator.comparing(GraphQLDirective::getName))
1✔
520
        .collect(toList());
1✔
521
  }
522

523
  String typeString(GraphQLType rawType) {
524
    return GraphQLTypeUtil.simplePrint(rawType);
1✔
525
  }
526

527
  String argsString(Class<? extends GraphQLSchemaElement> parent, List<GraphQLArgument> arguments) {
528
    var hasDescriptions = arguments.stream().anyMatch(this::hasDescription);
1✔
529
    var halfPrefix = hasDescriptions ? "  " : "";
1✔
530
    var prefix = hasDescriptions ? "    " : "";
1✔
531
    var count = 0;
1✔
532
    var sb = new StringBuilder();
1✔
533

534
    var environment =
535
        GraphqlTypeComparatorEnvironment.newEnvironment()
1✔
536
            .parentType(parent)
1✔
537
            .elementType(GraphQLArgument.class)
1✔
538
            .build();
1✔
539
    var comparator = options.getComparatorRegistry().getComparator(environment);
1✔
540

541
    arguments =
1✔
542
        arguments.stream()
1✔
543
            .sorted(comparator)
1✔
544
            .filter(options.getIncludeSchemaElement())
1✔
545
            .collect(toList());
1✔
546
    for (var argument : arguments) {
1✔
547
      if (count == 0) {
1✔
548
        sb.append("(");
1✔
549
      } else {
550
        sb.append(", ");
1✔
551
      }
552
      if (hasDescriptions) {
1✔
553
        sb.append("\n");
1✔
554
      }
555
      sb.append(printComments(argument, prefix));
1✔
556

557
      sb.append(prefix)
1✔
558
          .append(argument.getName())
1✔
559
          .append(": ")
1✔
560
          .append(typeString(argument.getType()));
1✔
561
      if (argument.hasSetDefaultValue()) {
1✔
562
        var defaultValue = argument.getArgumentDefaultValue();
1✔
563
        sb.append(" = ");
1✔
564
        sb.append(printAst(defaultValue, argument.getType()));
1✔
565
      }
566

567
      argument.getDirectives().stream()
1✔
568
          .filter(options.getIncludeSchemaElement())
1✔
569
          .map(this::directiveString)
1✔
570
          .filter(it -> !it.isEmpty())
1✔
571
          .forEach(directiveString -> sb.append(" ").append(directiveString));
1✔
572

573
      count++;
1✔
574
    }
1✔
575
    if (count > 0) {
1✔
576
      if (hasDescriptions) {
1✔
577
        sb.append("\n");
1✔
578
      }
579
      sb.append(halfPrefix).append(")");
1✔
580
    }
581
    return sb.toString();
1✔
582
  }
583

584
  String directivesString(
585
      Class<? extends GraphQLSchemaElement> parent, List<GraphQLDirective> directives) {
586
    directives =
1✔
587
        directives.stream()
1✔
588
            // @deprecated is special - we always print it if something is deprecated
589
            .filter(
1✔
590
                directive ->
591
                    options.getIncludeDirective().test(directive)
1✔
592
                        || isDeprecatedDirective(directive))
1✔
593
            .filter(options.getIncludeSchemaElement())
1✔
594
            .collect(toList());
1✔
595

596
    if (directives.isEmpty()) {
1✔
597
      return "";
1✔
598
    }
599
    var sb = new StringBuilder();
1✔
600
    if (hasDirectiveOnOwnLine(parent)) {
1✔
601
      sb.append("\n");
1✔
602
    } else if (parent != GraphQLSchemaElement.class) {
1✔
603
      sb.append(" ");
1✔
604
    }
605

606
    var environment =
607
        GraphqlTypeComparatorEnvironment.newEnvironment()
1✔
608
            .parentType(parent)
1✔
609
            .elementType(GraphQLDirective.class)
1✔
610
            .build();
1✔
611
    var comparator = options.getComparatorRegistry().getComparator(environment);
1✔
612

613
    directives = directives.stream().sorted(comparator).collect(toList());
1✔
614
    for (var i = 0; i < directives.size(); i++) {
1✔
615
      var directive = directives.get(i);
1✔
616
      sb.append(directiveString(directive));
1✔
617
      if (hasDirectiveOnOwnLine(parent)) {
1✔
618
        sb.append("\n");
1✔
619
      } else if (i < directives.size() - 1) {
1✔
620
        sb.append(" ");
1✔
621
      }
622
    }
623
    return sb.toString();
1✔
624
  }
625

626
  private boolean hasDirectiveOnOwnLine(Class<? extends GraphQLSchemaElement> parent) {
627
    return parent == GraphQLObjectType.class
1✔
628
        || parent == GraphQLInterfaceType.class
629
        || parent == GraphQLSchemaElement.class
630
        || parent == GraphQLInputObjectType.class;
631
  }
632

633
  private String directiveString(GraphQLDirective directive) {
634
    if (!options.getIncludeSchemaElement().test(directive)) {
1✔
635
      return "";
×
636
    }
637
    // @deprecated is special - we always print it if something is deprecated
638
    if (!(options.getIncludeDirective().test(directive) || isDeprecatedDirective(directive))) {
1✔
639
      return "";
×
640
    }
641

642
    var sb = new StringBuilder();
1✔
643
    sb.append("@").append(directive.getName());
1✔
644

645
    List<GraphQLArgument> args = getSortedDirectiveArgument(directive);
1✔
646
    if (!args.isEmpty()) {
1✔
647
      sb.append("(");
1✔
648
      for (var i = 0; i < args.size(); i++) {
1✔
649
        var arg = args.get(i);
1✔
650
        String argValue = null;
1✔
651
        if (arg.hasSetValue()) {
1✔
652
          argValue = printAst(arg.toAppliedArgument().getArgumentValue(), arg.getType());
1✔
653
        } else if (arg.hasSetDefaultValue()) {
×
654
          argValue = printAst(arg.getArgumentDefaultValue(), arg.getType());
×
655
        }
656
        if (!isNullOrEmpty(argValue)) {
1✔
657
          sb.append(arg.getName());
1✔
658
          sb.append(": ");
1✔
659
          sb.append(argValue);
1✔
660
          if (i < args.size() - 1) {
1✔
661
            sb.append(", ");
1✔
662
          }
663
        }
664
      }
665
      sb.append(")");
1✔
666
    }
667
    return sb.toString();
1✔
668
  }
669

670
  private List<GraphQLArgument> getSortedDirectiveArgument(GraphQLDirective directive) {
671
    var environment =
672
        GraphqlTypeComparatorEnvironment.newEnvironment()
1✔
673
            .parentType(GraphQLDirective.class)
1✔
674
            .elementType(GraphQLArgument.class)
1✔
675
            .build();
1✔
676
    var comparator = options.getComparatorRegistry().getComparator(environment);
1✔
677

678
    var args = directive.getArguments();
1✔
679
    args =
1✔
680
        args.stream()
1✔
681
            .filter(arg -> arg.hasSetValue() && !sameAsDefaultValue(arg))
1✔
682
            .sorted(comparator)
1✔
683
            .collect(toList());
1✔
684
    return args;
1✔
685
  }
686

687
  private boolean sameAsDefaultValue(GraphQLArgument arg) {
688
    if (arg.hasSetValue() && arg.hasSetDefaultValue()) {
1✔
689
      var argValue = arg.toAppliedArgument().getArgumentValue().getValue();
1✔
690
      var defaultValue = arg.getArgumentDefaultValue().getValue();
1✔
691
      //noinspection DataFlowIssue
692
      return argValue.toString().equals(defaultValue.toString());
1✔
693
    }
694
    return false;
1✔
695
  }
696

697
  private boolean isDeprecatedDirective(GraphQLDirective directive) {
698
    return directive.getName().equals(DeprecatedDirective.getName());
1✔
699
  }
700

701
  private boolean hasDeprecatedDirective(List<GraphQLDirective> directives) {
702
    return directives.stream().filter(this::isDeprecatedDirective).count() == 1;
1✔
703
  }
704

705
  private List<GraphQLDirective> addDeprecatedDirectiveIfNeeded(List<GraphQLDirective> directives) {
706
    if (!hasDeprecatedDirective(directives)) {
1✔
707
      directives = new ArrayList<>(directives);
×
708
      directives.add(DeprecatedDirective4Printing);
×
709
    }
710
    return directives;
1✔
711
  }
712

713
  private String directiveDefinitions(List<GraphQLDirective> directives) {
714
    var sb = new StringBuilder();
1✔
715
    directives.stream()
1✔
716
        .filter(options.getIncludeSchemaElement())
1✔
717
        .forEach(
1✔
718
            directive -> {
719
              sb.append(directiveDefinition(directive));
1✔
720
              sb.append("\n");
1✔
721
            });
1✔
722
    if (!directives.isEmpty()) {
1✔
723
      sb.append("\n");
1✔
724
    }
725
    return sb.toString();
1✔
726
  }
727

728
  private String directiveDefinition(GraphQLDirective directive) {
729
    var sb = new StringBuilder();
1✔
730

731
    var sw = new StringWriter();
1✔
732
    printComments(new PrintWriter(sw), directive, "");
1✔
733

734
    sb.append(sw);
1✔
735

736
    sb.append("directive @").append(directive.getName());
1✔
737

738
    var environment =
739
        GraphqlTypeComparatorEnvironment.newEnvironment()
1✔
740
            .parentType(GraphQLDirective.class)
1✔
741
            .elementType(GraphQLArgument.class)
1✔
742
            .build();
1✔
743
    var comparator = options.getComparatorRegistry().getComparator(environment);
1✔
744

745
    var args = directive.getArguments();
1✔
746
    args =
1✔
747
        args.stream()
1✔
748
            .filter(options.getIncludeSchemaElement())
1✔
749
            .sorted(comparator)
1✔
750
            .collect(toList());
1✔
751

752
    sb.append(argsString(GraphQLDirective.class, args));
1✔
753

754
    if (directive.isRepeatable()) {
1✔
755
      sb.append(" repeatable");
1✔
756
    }
757

758
    sb.append(" on ");
1✔
759

760
    var locations =
1✔
761
        directive.validLocations().stream().map(Enum::name).collect(Collectors.joining(" | "));
1✔
762
    sb.append(locations);
1✔
763

764
    return sb.toString();
1✔
765
  }
766

767
  private String printComments(GraphQLNamedSchemaElement graphQLType, String prefix) {
768
    var sw = new StringWriter();
1✔
769
    var pw = new PrintWriter(sw);
1✔
770
    printComments(pw, graphQLType, prefix);
1✔
771
    return sw.toString();
1✔
772
  }
773

774
  private void printComments(
775
      PrintWriter out, GraphQLNamedSchemaElement graphQLType, String prefix) {
776

777
    var documentation = getDocumentation(graphQLType);
1✔
778
    if (documentation.isNullOrEmpty()) {
1✔
779
      return;
1✔
780
    }
781

782
    printMultiLineHashDescription(out, prefix, documentation.getComments());
1✔
783

784
    documentation
1✔
785
        .getDescription()
1✔
786
        .map(description -> asList(description.split("\n")))
1✔
787
        .filter(lines -> !lines.isEmpty())
1✔
788
        .ifPresent(
1✔
789
            lines -> {
790
              if (options.isDescriptionsAsHashComments()) {
1✔
791
                printMultiLineHashDescription(out, prefix, lines);
1✔
792
              } else if (lines.size() > 1) {
1✔
793
                printMultiLineDescription(out, prefix, lines);
1✔
794
              } else {
795
                printSingleLineDescription(out, prefix, lines.get(0));
1✔
796
              }
797
            });
1✔
798
  }
1✔
799

800
  private void printMultiLineHashDescription(PrintWriter out, String prefix, List<String> lines) {
801
    lines.forEach(l -> out.printf("%s#%s", prefix, l).append("\n"));
1✔
802
  }
1✔
803

804
  private void printMultiLineDescription(PrintWriter out, String prefix, List<String> lines) {
805
    out.printf("%s\"\"\"", prefix).append("\n");
1✔
806
    lines.forEach(l -> out.printf("%s%s", prefix, l).append("\n"));
1✔
807
    out.printf("%s\"\"\"", prefix).append("\n");
1✔
808
  }
1✔
809

810
  private void printSingleLineDescription(PrintWriter out, String prefix, String s) {
811
    // See: https://github.com/graphql/graphql-spec/issues/148
812
    var desc = escapeJsonString(s);
1✔
813
    out.printf("%s\"%s\"", prefix, desc).append("\n");
1✔
814
  }
1✔
815

816
  private boolean hasDescription(GraphQLNamedSchemaElement descriptionHolder) {
817
    var description = getDocumentation(descriptionHolder);
1✔
818
    return !description.isNullOrEmpty();
1✔
819
  }
820

821
  private DescriptionAndComments getDocumentation(GraphQLNamedSchemaElement type) {
822
    var returnValue = new DescriptionAndComments();
1✔
823

824
    AbstractDescribedNode<?> definition = (AbstractDescribedNode<?>) type.getDefinition();
1✔
825

826
    if (definition != null) {
1✔
827
      returnValue.comments(definition.getComments());
1✔
828
    }
829

830
    Optional.ofNullable(definition)
1✔
831
        .map(AbstractDescribedNode::getDescription)
1✔
832
        .map(Description::getContent)
1✔
833
        .or(() -> Optional.ofNullable(type.getDescription()))
1✔
834
        .filter(d -> !d.isBlank())
1✔
835
        .ifPresent(returnValue::description);
1✔
836

837
    return returnValue;
1✔
838
  }
839

840
  private static boolean isNullOrEmpty(String s) {
841
    return s == null || s.isEmpty();
1✔
842
  }
843
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc