AssertValidName throws on a name presumably generated by the library itself

396 views Asked by At

This is using a quite intricate entity collection which mainly consist of JPA entities, but which also include proxies and entities fetched from APIs. I've only really annotated the base class I'm trying to fetch with @GraphQLQuery annotations, but it goes into the hierarchy below that, and at some point finds a property which is a static HashMap<String, String[]> with hardcoded contents. (I've learned not to ask...) At this point it throws this exception and everything stops:

graphql.AssertException: Name must be non-null, non-empty and match [_A-Za-z][_0-9A-Za-z]* - was 'Map_String_String[]Scalar'
at graphql.Assert.assertValidName(Assert.java:58)
at graphql.schema.GraphQLScalarType.<init>(GraphQLScalarType.java:50)
at graphql.schema.GraphQLScalarType.<init>(GraphQLScalarType.java:45)
at io.leangen.graphql.util.Scalars.graphQLMapScalar(Scalars.java:325)
at io.leangen.graphql.generator.mapping.common.ObjectScalarAdapter.toGraphQLType(ObjectScalarAdapter.java:20)
at io.leangen.graphql.generator.mapping.common.ObjectScalarAdapter.toGraphQLType(ObjectScalarAdapter.java:16)
at io.leangen.graphql.generator.mapping.common.CachingMapper.toGraphQLType(CachingMapper.java:30)
at io.leangen.graphql.generator.OperationMapper.toGraphQLType(OperationMapper.java:179)
at io.leangen.graphql.generator.OperationMapper.toGraphQLType(OperationMapper.java:165)
at io.leangen.graphql.generator.OperationMapper.toGraphQLField(OperationMapper.java:138)
at io.leangen.graphql.generator.mapping.common.ObjectTypeMapper.lambda$getFields$3(ObjectTypeMapper.java:88)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1625)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at io.leangen.graphql.generator.mapping.common.ObjectTypeMapper.getFields(ObjectTypeMapper.java:89)
at io.leangen.graphql.generator.mapping.common.ObjectTypeMapper.toGraphQLType(ObjectTypeMapper.java:41)
at io.leangen.graphql.generator.mapping.common.ObjectTypeMapper.toGraphQLType(ObjectTypeMapper.java:33)
at io.leangen.graphql.generator.mapping.common.CachingMapper.toGraphQLType(CachingMapper.java:30)
at io.leangen.graphql.generator.OperationMapper.toGraphQLType(OperationMapper.java:179)
at io.leangen.graphql.generator.OperationMapper.toGraphQLType(OperationMapper.java:165)
at io.leangen.graphql.generator.OperationMapper.toGraphQLField(OperationMapper.java:138)
at io.leangen.graphql.generator.mapping.common.ObjectTypeMapper.lambda$getFields$3(ObjectTypeMapper.java:88)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1625)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at io.leangen.graphql.generator.mapping.common.ObjectTypeMapper.getFields(ObjectTypeMapper.java:89)
at io.leangen.graphql.generator.mapping.common.ObjectTypeMapper.toGraphQLType(ObjectTypeMapper.java:41)
at io.leangen.graphql.generator.mapping.common.ObjectTypeMapper.toGraphQLType(ObjectTypeMapper.java:33)
at io.leangen.graphql.generator.mapping.common.CachingMapper.toGraphQLType(CachingMapper.java:30)
at io.leangen.graphql.generator.OperationMapper.toGraphQLType(OperationMapper.java:179)
at io.leangen.graphql.generator.OperationMapper.toGraphQLType(OperationMapper.java:165)
at io.leangen.graphql.generator.OperationMapper.toGraphQLField(OperationMapper.java:138)
at io.leangen.graphql.generator.OperationMapper.lambda$generateQueries$0(OperationMapper.java:91)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at io.leangen.graphql.generator.OperationMapper.generateQueries(OperationMapper.java:92)
at io.leangen.graphql.generator.OperationMapper.<init>(OperationMapper.java:75)
at io.leangen.graphql.GraphQLSchemaGenerator.generate(GraphQLSchemaGenerator.java:868)
at com.ist.exam.graphql.services.GraphQLService.init(GraphQLService.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:349)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:300)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:133)
... 39 more

I have no idea how this name is generated, but it comes out as "Map_String_String[]Scalar", which then throws on assertValidName.

I would annotate the property with @GraphQLQuery(name="somethingClever") , but at this point we're inside a library shared throughout the company, and changes made here have to go through a change request and stuff.

I tried to exclude this proxy package with withBasePackages, but that seemed to have no effect. This is the method trying to create the schema:

GraphQLSchema schema = new GraphQLSchemaGenerator()
                    .withBasePackages("entities")
                    .withResolverBuilders(new AnnotatedResolverBuilder())
                    .withOperationsFromSingleton(examService)
                    .withValueMapperFactory(new JacksonValueMapperFactory())
                    .generate();
            graphQL = GraphQL.newGraphQL(schema).build();

Has anyone seen either of these problems before? Can I blacklist a package instead of whitelisting it? e.g. withBasePackages("!proxies")

1

There are 1 answers

0
kaqqao On BEST ANSWER

It's a bug, as an invalid name should never be generated. I'll fix this immediately for the next release of GraphQL SPQR (the current version is 0.9.9 at the time of writing).

As for how it ends up being Map_String_String[]Scalar, it's rather involving... Maps are exceptionally tricky, as there's nothing like a map in GraphQL. The options are basically to treat the map as a list of typed key-value pairs or just as an unknown dynamic structure (the so called JSON scalar). SPQR enables both approaches, but treats maps as complex (JSON) scalars by default. To maintain some idea of what's inside, it will generate a differently named scalar for each underlying Java type. In doing so, it has to generate a unique name for each. In your case, it finds a Map<String, String[]> which generates an invalid name that you see - Map_String_String[]Scalar.

You could easily work around this problem by registering a custom TypeMapper that would catch this case, or all complex scalars for that matter, and give them all the same name instead of a unique one.

E.g.

public class CustomObjectScalarMapper extends ObjectScalarMapper {
    private static final String OBJECT_SCALAR_NAME = "ObjectScalar";

    @Override
    protected String getTypeName(AnnotatedType type, BuildContext buildContext) {
        return OBJECT_SCALAR_NAME;
    }

    @Override
    protected String getInputTypeName(AnnotatedType type, BuildContext buildContext) {
        return OBJECT_SCALAR_NAME;
    }
}

And replace the existing ObjectScalarMapper via:

generator.withTypeMappers((conf, current) -> current.replace(ObjectScalarMapper.class, new CustomObjectScalarMapper()))

With this, all complex scalars will be called ObjectScalar and you won't have the issue.

If you wish to be more granular, override the supports method as well.


A small remark, your generator config is needlessly setting a bunch of things to their default values.

GraphQLSchema schema = new GraphQLSchemaGenerator()
                    .withBasePackages("entities")
                    .withResolverBuilders(new AnnotatedResolverBuilder())
                    .withOperationsFromSingleton(examService)
                    .withValueMapperFactory(new JacksonValueMapperFactory())
                    .generate();

is equivalent to

GraphQLSchema schema = new GraphQLSchemaGenerator()
                    .withBasePackages("entities")
                    .withOperationsFromSingleton(examService)
                    .generate();