1717
1818import com .google .auto .service .AutoService ;
1919import com .squareup .javapoet .TypeName ;
20+ import feign .graphql .GraphqlField ;
2021import feign .graphql .GraphqlQuery ;
2122import feign .graphql .GraphqlSchema ;
2223import feign .graphql .Scalar ;
24+ import feign .graphql .Toggle ;
2325import graphql .language .Document ;
2426import graphql .language .Field ;
25- import graphql .language .FieldDefinition ;
26- import graphql .language .ListType ;
27- import graphql .language .NonNullType ;
2827import graphql .language .ObjectTypeDefinition ;
2928import graphql .language .OperationDefinition ;
3029import graphql .language .SelectionSet ;
31- import graphql .language .Type ;
3230import graphql .language .VariableDefinition ;
3331import graphql .parser .Parser ;
3432import graphql .schema .GraphQLSchema ;
3937import java .util .List ;
4038import java .util .Map ;
4139import java .util .Set ;
40+ import java .util .TreeSet ;
41+ import java .util .function .Supplier ;
4242import javax .annotation .processing .AbstractProcessor ;
4343import javax .annotation .processing .Filer ;
4444import javax .annotation .processing .Messager ;
4848import javax .annotation .processing .SupportedAnnotationTypes ;
4949import javax .annotation .processing .SupportedSourceVersion ;
5050import javax .lang .model .SourceVersion ;
51+ import javax .lang .model .element .Element ;
5152import javax .lang .model .element .ExecutableElement ;
5253import javax .lang .model .element .PackageElement ;
5354import javax .lang .model .element .TypeElement ;
5455import javax .lang .model .type .DeclaredType ;
56+ import javax .lang .model .type .MirroredTypeException ;
57+ import javax .lang .model .type .MirroredTypesException ;
5558import javax .lang .model .type .TypeKind ;
5659import javax .lang .model .type .TypeMirror ;
5760import javax .tools .Diagnostic ;
@@ -117,6 +120,9 @@ private void processInterface(TypeElement typeElement) {
117120 var validator = new QueryValidator (messager );
118121 var generator = new TypeGenerator (filer , messager , registry , typeMapper , targetPackage );
119122
123+ var classFieldAnnotations = extractFieldAnnotations (typeElement );
124+ var classConfig = resolveClassConfig (schemaAnnotation , classFieldAnnotations );
125+
120126 for (var enclosed : typeElement .getEnclosedElements ()) {
121127 if (!(enclosed instanceof ExecutableElement method )) {
122128 continue ;
@@ -126,6 +132,9 @@ private void processInterface(TypeElement typeElement) {
126132 continue ;
127133 }
128134
135+ var methodConfig = resolveMethodConfig (method , queryAnnotation , classConfig );
136+ generator .setAnnotationConfig (methodConfig );
137+
129138 processMethod (
130139 method ,
131140 queryAnnotation ,
@@ -242,9 +251,9 @@ private void processMethod(
242251 if (rootType != null ) {
243252 var rootField = findRootField (operation .getSelectionSet ());
244253 if (rootField != null && rootField .getSelectionSet () != null ) {
245- var rootFieldDef = findFieldDefinition (rootType , rootField .getName ());
254+ var rootFieldDef = GraphqlTypeMapper . findFieldDefinition (rootType , rootField .getName ());
246255 if (rootFieldDef != null ) {
247- var fieldTypeName = unwrapTypeName (rootFieldDef .getType ());
256+ var fieldTypeName = GraphqlTypeMapper . unwrapTypeName (rootFieldDef .getType ());
248257 var fieldObjectType =
249258 registry .getType (fieldTypeName , ObjectTypeDefinition .class ).orElse (null );
250259 if (fieldObjectType != null ) {
@@ -297,77 +306,39 @@ private Field findRootField(SelectionSet selectionSet) {
297306 return null ;
298307 }
299308
300- private FieldDefinition findFieldDefinition (ObjectTypeDefinition typeDef , String fieldName ) {
301- for (var fd : typeDef .getFieldDefinitions ()) {
302- if (fd .getName ().equals (fieldName )) {
303- return fd ;
304- }
305- }
306- return null ;
307- }
308-
309309 private ObjectTypeDefinition getRootType (
310310 OperationDefinition operation , TypeDefinitionRegistry registry ) {
311- var rootTypeName =
311+ var operationName =
312312 switch (operation .getOperation ()) {
313- case MUTATION ->
314- registry
315- .schemaDefinition ()
316- .flatMap (
317- sd ->
318- sd .getOperationTypeDefinitions ().stream ()
319- .filter (otd -> otd .getName ().equals ("mutation" ))
320- .findFirst ())
321- .map (otd -> otd .getTypeName ().getName ())
322- .orElse ("Mutation" );
323- case SUBSCRIPTION ->
324- registry
325- .schemaDefinition ()
326- .flatMap (
327- sd ->
328- sd .getOperationTypeDefinitions ().stream ()
329- .filter (otd -> otd .getName ().equals ("subscription" ))
330- .findFirst ())
331- .map (otd -> otd .getTypeName ().getName ())
332- .orElse ("Subscription" );
333- default ->
334- registry
335- .schemaDefinition ()
336- .flatMap (
337- sd ->
338- sd .getOperationTypeDefinitions ().stream ()
339- .filter (otd -> otd .getName ().equals ("query" ))
340- .findFirst ())
341- .map (otd -> otd .getTypeName ().getName ())
342- .orElse ("Query" );
313+ case MUTATION -> "mutation" ;
314+ case SUBSCRIPTION -> "subscription" ;
315+ default -> "query" ;
343316 };
317+ var fallback = Character .toUpperCase (operationName .charAt (0 )) + operationName .substring (1 );
318+ var rootTypeName =
319+ registry
320+ .schemaDefinition ()
321+ .flatMap (
322+ sd ->
323+ sd .getOperationTypeDefinitions ().stream ()
324+ .filter (otd -> otd .getName ().equals (operationName ))
325+ .findFirst ())
326+ .map (otd -> otd .getTypeName ().getName ())
327+ .orElse (fallback );
344328 return registry .getType (rootTypeName , ObjectTypeDefinition .class ).orElse (null );
345329 }
346330
347331 private String findGraphqlInputType (
348332 String javaParamTypeName , List <VariableDefinition > variableDefs ) {
349333 for (var varDef : variableDefs ) {
350- var graphqlTypeName = unwrapTypeName (varDef .getType ());
334+ var graphqlTypeName = GraphqlTypeMapper . unwrapTypeName (varDef .getType ());
351335 if (graphqlTypeName .equals (javaParamTypeName )) {
352336 return graphqlTypeName ;
353337 }
354338 }
355339 return javaParamTypeName ;
356340 }
357341
358- private String unwrapTypeName (Type <?> type ) {
359- if (type instanceof NonNullType nullType ) {
360- return unwrapTypeName (nullType .getType ());
361- }
362- if (type instanceof ListType listType ) {
363- return unwrapTypeName (listType .getType ());
364- }
365- if (type instanceof graphql .language .TypeName name ) {
366- return name .getName ();
367- }
368- return "String" ;
369- }
370-
371342 private static final Set <String > JAVA_BUILT_INS =
372343 Set .of (
373344 "String" ,
@@ -434,6 +405,129 @@ private TypeMirror unwrapListTypeMirror(TypeMirror typeMirror) {
434405 return typeMirror ;
435406 }
436407
408+ private TypeAnnotationConfig resolveClassConfig (
409+ GraphqlSchema annotation ,
410+ Map <String , TypeAnnotationConfig .FieldAnnotations > classFieldAnnotations ) {
411+ var fqns = extractClassFqns (annotation ::typeAnnotations );
412+ var rawAnnotations = annotation .rawTypeAnnotations ();
413+ var usesFqns = extractClassFqns (annotation ::uses );
414+ var nonNullFqns = extractClassFqns (annotation ::nonNullTypeAnnotations );
415+ var nonNullRaw = annotation .nonNullRawTypeAnnotations ();
416+ var config =
417+ TypeAnnotationConfig .resolve (
418+ fqns ,
419+ rawAnnotations ,
420+ annotation .useOptional (),
421+ classFieldAnnotations ,
422+ nonNullFqns ,
423+ nonNullRaw );
424+ if (usesFqns .isEmpty ()) {
425+ return config ;
426+ }
427+ var mergedImports = new TreeSet <>(config .imports ());
428+ for (var fqn : usesFqns ) {
429+ if (!fqn .startsWith ("java.lang." )) {
430+ mergedImports .add (fqn );
431+ }
432+ }
433+ return new TypeAnnotationConfig (
434+ mergedImports ,
435+ config .annotations (),
436+ config .useOptional (),
437+ config .fieldAnnotations (),
438+ config .nonNullAnnotations ());
439+ }
440+
441+ private static List <String > extractClassFqns (Supplier <Class <?>[]> accessor ) {
442+ try {
443+ var classes = accessor .get ();
444+ return java .util .Arrays .stream (classes ).map (Class ::getCanonicalName ).toList ();
445+ } catch (MirroredTypesException e ) {
446+ return e .getTypeMirrors ().stream ().map (TypeMirror ::toString ).toList ();
447+ }
448+ }
449+
450+ private TypeAnnotationConfig resolveMethodConfig (
451+ ExecutableElement method , GraphqlQuery annotation , TypeAnnotationConfig classConfig ) {
452+ var methodFqns = extractClassFqns (annotation ::typeAnnotations );
453+ var methodRaw = annotation .rawTypeAnnotations ();
454+ var methodToggle = annotation .useOptional ();
455+
456+ var useOptional =
457+ methodToggle == Toggle .INHERIT ? classConfig .useOptional () : methodToggle == Toggle .TRUE ;
458+
459+ var methodFieldAnnotations = extractFieldAnnotations (method );
460+ var fieldAnnotations =
461+ TypeAnnotationConfig .FieldAnnotations .merge (
462+ classConfig .fieldAnnotations (), methodFieldAnnotations );
463+
464+ var methodNonNullFqns = extractClassFqns (annotation ::nonNullTypeAnnotations );
465+ var methodNonNullRaw = annotation .nonNullRawTypeAnnotations ();
466+ boolean hasMethodNonNull = !methodNonNullFqns .isEmpty () || methodNonNullRaw .length > 0 ;
467+ var resolvedNonNull = hasMethodNonNull ? null : classConfig .nonNullAnnotations ();
468+
469+ boolean hasMethodAnnotations = !methodFqns .isEmpty () || methodRaw .length > 0 ;
470+ if (!hasMethodAnnotations && !hasMethodNonNull ) {
471+ if (useOptional == classConfig .useOptional ()
472+ && fieldAnnotations .equals (classConfig .fieldAnnotations ())) {
473+ return classConfig ;
474+ }
475+ var mergedImports = new TreeSet <>(classConfig .imports ());
476+ for (var fa : fieldAnnotations .values ()) {
477+ mergedImports .addAll (fa .imports ());
478+ }
479+ return new TypeAnnotationConfig (
480+ mergedImports ,
481+ classConfig .annotations (),
482+ useOptional ,
483+ fieldAnnotations ,
484+ classConfig .nonNullAnnotations ());
485+ }
486+
487+ var nonNullFqns = hasMethodNonNull ? methodNonNullFqns : List .<String >of ();
488+ var nonNullRaw = hasMethodNonNull ? methodNonNullRaw : new String [0 ];
489+ var config =
490+ TypeAnnotationConfig .resolve (
491+ methodFqns , methodRaw , useOptional , fieldAnnotations , nonNullFqns , nonNullRaw );
492+
493+ if (resolvedNonNull != null && !resolvedNonNull .isEmpty ()) {
494+ var mergedImports = new TreeSet <>(config .imports ());
495+ mergedImports .addAll (classConfig .imports ());
496+ return new TypeAnnotationConfig (
497+ mergedImports , config .annotations (), useOptional , fieldAnnotations , resolvedNonNull );
498+ }
499+
500+ return config ;
501+ }
502+
503+ private Map <String , TypeAnnotationConfig .FieldAnnotations > extractFieldAnnotations (
504+ Element method ) {
505+ var fieldAnnotations = new HashMap <String , TypeAnnotationConfig .FieldAnnotations >();
506+ var graphqlFields = method .getAnnotationsByType (GraphqlField .class );
507+ for (var gf : graphqlFields ) {
508+ var fqns = extractClassFqns (gf ::typeAnnotations );
509+ var typeOverride = extractFieldTypeOverride (gf );
510+ var resolved =
511+ TypeAnnotationConfig .FieldAnnotations .resolve (
512+ fqns , gf .rawTypeAnnotations (), typeOverride );
513+ fieldAnnotations .put (gf .name (), resolved );
514+ }
515+ return fieldAnnotations ;
516+ }
517+
518+ private String extractFieldTypeOverride (GraphqlField annotation ) {
519+ try {
520+ var cls = annotation .type ();
521+ if (cls == Void .class ) {
522+ return null ;
523+ }
524+ return cls .getCanonicalName ();
525+ } catch (MirroredTypeException e ) {
526+ var fqn = e .getTypeMirror ().toString ();
527+ return "java.lang.Void" .equals (fqn ) ? null : fqn ;
528+ }
529+ }
530+
437531 private String getPackageName (TypeElement typeElement ) {
438532 var enclosing = typeElement .getEnclosingElement ();
439533 while (enclosing != null && !(enclosing instanceof PackageElement )) {
0 commit comments