question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Annotated methods returing a String are showing as a collision with unannotated methods returning a String

See original GitHub issue

I am using graphql-spqr in a Spring Boot app via the Spring Boot Starter. The Spring Boot Starter currently references 0.9.9 so I included 0.10.0 in my build.gradle to see if this issue was fixed, but it still persisted in 0.10.0.

Anyway, I have a bunch of entity classes, eg:

import javax.validation.constraints.NotBlank;

public class Entity1 {
    public String getField() { return "field value"; }
    public int getOtherField() { return 1; }
}

public class Entity2 {
  @NotBlank
  public String getValidatedField() { return "value"; }
}

These are used in a service annotated with @GraphQLApi, eg:

@GraphQLApi
public class EntityService {

  @GraphQLQuery(name = "entity1")
  public Entity1 getEntity1() {
    return new Entity1();
  }

 @GraphQLQuery(name = "entity2")
  public Entity2 getEntity2() {
    return new Entity2();
  }
}

This all works great. However, the following warning is printed to the console on application start:

2019-07-05 13:20:15.910  WARN 31447 --- [           main] i.l.graphql.generator.OperationMapper    : Potential type name collision detected: 'String' bound to multiple types: java.lang.String (loaded by the bootstrap class loader) and @javax.validation.constraints.NotBlank(message="{javax.validation.constraints.NotBlank.message}", payload={}, groups={}) java.lang.String (loaded by the bootstrap class loader). Assign unique names using the appropriate annotations or override the TypeInfoGenerator. For details and solutions see https://github.com/leangen/graphql-spqr/wiki/Errors#non-unique-type-name. If this warning is a false positive, please report it: https://github.com/leangen/graphql-spqr/issues.

I spent some time attempting to debug why this occurs, but with no success. The closest I could figure was that “java.lang.String with an annotation” and “java.lang.String” both match the “String” GraphQL type, but when the Validator calls isMappingAllowed, the comparison fails because it considers “java.lang.String with an annotation” and “java.lang.String” to be different.

Is there a way to ignore annotations that you have no control over and cannot add@GraphQLIgnore to? Or is this another case to consider for Issue #232

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:8 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
kaqqaocommented, Jul 8, 2019

For the meantime, you could make a comparator like this:

public class IgnoredAnnotationsComparator implements Comparator<AnnotatedType> {

    private final Set<Class<? extends Annotation>> ignoredAnnotations;

    public IgnoredAnnotationsComparator(Class<? extends Annotation>... ignoredAnnotations) {
        Set<Class<? extends Annotation>> ann = new HashSet<>();
        Collections.addAll(ann, ignoredAnnotations);
        this.ignoredAnnotations = Collections.unmodifiableSet(ann);
    }

    @Override
    public int compare(AnnotatedType o1, AnnotatedType o2) {
        Class<?> r1 = ClassUtils.getRawType(o1.getType());
        Class<?> r2 = ClassUtils.getRawType(o2.getType());
        return (r1.equals(r2) && annotationsMatch(o1, o2)) ? 0 : -1;
    }

    private boolean annotationsMatch(AnnotatedType o1, AnnotatedType o2) {
        Set<? extends Class<? extends Annotation>> a1 = Arrays.stream(o1.getAnnotations())
                .map(Annotation::annotationType)
                .collect(Collectors.toSet());
        Set<? extends Class<? extends Annotation>> a2 = Arrays.stream(o2.getAnnotations())
                .map(Annotation::annotationType)
                .collect(Collectors.toSet());

        return a1.stream().noneMatch(ann -> !a2.contains(ann) && !ignoredAnnotations.contains(ann))
                && a2.stream().noneMatch(ann -> !a1.contains(ann) && !ignoredAnnotations.contains(ann));
    }
}

When comparing types, it will check that they only differ in ignored annotations. You can then register it via:

generator.withTypeComparator(new IgnoredAnnotationsComparator(NotBlank.class))

This is of course way too cumbersome… and will likely change soon… but it is something you can do if you want to.

0reactions
seanfcommented, Dec 10, 2019

An update on the subject of SPQR with Scala:

I got my NonNullSchemaTransformer to work by first converting the Java Types into another tree structure (I called it TypeInfo) which always wraps nodes in Required or Optional to help with the tree shape problem above. In actual fact, I have five node types(!): ListTypeInfo, RequiredNonLeafTypeInfo, OptionalNonLeafTypeInfo, RequiredLeafTypeInfo, OptionalLeafTypeInfo.

Then SchemaTransformer.transformArgument and SchemaTransformer.transformField recursively descend into the GraphQLType (from GraphQLArgument.getType or GraphQLFieldDefinition.getType) together with the corresponding TypeInfo (converted from GraphQLArgument.getBaseType.getType or Operation.getJavaType.getType) at each level of recursion. It’s certainly not pretty, but it seems to work.

Finally (I hope) I had to create an ArgumentInjector to pre-empt InputValueDeserializer, ensuring that I never receive a null for an Option parameter. Might have to revisit that if we ever need to distinguish between null and undefined as the schema evolves.

In summary:

  • I’m pleased with how configurable SPQR is, given that I’ve needed to plug in a TypeMapper, a TypeAdapter, a ResolverInterceptor, a SchemaTransformer and an ArgumentInjector.
  • GraphQL-Java could use some pluggability when it comes to handling Iterables, given that it assumes all GraphQL lists are either native arrays or java.lang.Iterables.
  • ResolverInterceptor is a lifesaver, along with the example AuthInterceptor in InterceptorTest. Thanks @kaqqao !
  • My SchemaTransformer worries me a bit. It doesn’t handle GraphQL unions or Scala Maps yet (don’t need them yet) but it’s already a fair amount of code, so it could be a maintenance problem.
  • The other mappers, adapters and injectors were all nice and straightforward (other than a Scala type inference surprise when calling ResolutionEnvironment.convertOutput from my ScalaOptionTypeAdapter).
Read more comments on GitHub >

github_iconTop Results From Across the Web

error about returning a string from a String method [duplicate]
So I tried to break it down in these two methods, but it is saying that it needs to return a string and...
Read more >
Mypy Documentation - Read the Docs
This chapter introduces some core concepts of mypy, including function annotations, the typing module, stub files, and more. If you're looking ...
Read more >
Real world advice for writing maintainable Go programs
In short, named return values are a symptom of a clever piece of code which should be reviewed with suspicion. If the method...
Read more >
Biopython Tutorial and Cookbook
In this case consider the low-level SimpleFastaParser and FastqGeneralIterator parsers which return just a tuple of strings for each record (see ...
Read more >
How to return string or array of strings from JAVA code to C ...
Hi I am callin java method from C function using JNI. I want to return string or array of strings from java method....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found