Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schema generation fails in the presence of a java.time.OffsetTime attribute #260

Closed
oscararias opened this issue May 9, 2019 · 2 comments

Comments

@oscararias
Copy link

oscararias commented May 9, 2019

Hello,

I have this simple POJO:

public class Campaign {
    private Long id;
    private OffsetTime hour;
    // Constructors, getters and setters here
}

Which is used as return type of a simple GraphqlQuery annotated method

    @GraphQLQuery
    public Campaign campaign(@GraphQLId Long id) {
        // code here
    }

SPQR doesn't seem to be able to handle the OffsetTime attribute and throws this exception:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'graphQLSchema' defined in class path resource [io/leangen/graphql/spqr/spring/autoconfigure/SpqrAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [graphql.schema.GraphQLSchema]: Factory method 'graphQLSchema' threw exception; nested exception is io.leangen.graphql.metadata.exceptions.TypeMappingException: Argument arg0 of operation "supported" has different types in different resolver methods. Types found: [java.time.temporal.TemporalField, java.time.temporal.TemporalUnit]. If this is intentional, and you wish GraphQL SPQR to infer the most common super type automatically, see https://github.com/leangen/graphql-spqr/wiki/Errors#operation-with-multiple-resolver-methods-of-different-types
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:627)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:607)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1321)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1160)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:849)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)
	at com.mundor.sms.SmsApplication.main(SmsApplication.java:10)
	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.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [graphql.schema.GraphQLSchema]: Factory method 'graphQLSchema' threw exception; nested exception is io.leangen.graphql.metadata.exceptions.TypeMappingException: Argument arg0 of operation "supported" has different types in different resolver methods. Types found: [java.time.temporal.TemporalField, java.time.temporal.TemporalUnit]. If this is intentional, and you wish GraphQL SPQR to infer the most common super type automatically, see https://github.com/leangen/graphql-spqr/wiki/Errors#operation-with-multiple-resolver-methods-of-different-types
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622)
	... 24 common frames omitted

I wonder if there is any workaround for this issue, if I am missing some kind of configuration or if it can be related to the usage of the spring boot starter.

Thanks in advance

@kaqqao kaqqao closed this as completed in fbd3643 May 12, 2019
@kaqqao
Copy link
Member

kaqqao commented May 12, 2019

You're right, OffsetTime did not work out of the box. Will in the next version.

In general, if you run into a type that doesn't work out of the box, you must register a custom TypeMapper to support it.

For scalars (like OffsetTime), it's rather trivial:

public class OffsetTimeMapper implements TypeMapper {

    private final GraphQLScalarType GraphQLOffsetTime = Scalars.temporalScalar(OffsetTime.class, "OffsetTime", "a time with a UTC offset",
            OffsetTime::parse, i -> OffsetTime.ofInstant(i, ZoneOffset.UTC));
    
    @Override
    public GraphQLOutputType toGraphQLType(AnnotatedType javaType, OperationMapper operationMapper, Set<Class<? extends TypeMapper>> mappersToSkip, BuildContext buildContext) {
        return GraphQLOffsetTime;
    }

    @Override
    public GraphQLInputType toGraphQLInputType(AnnotatedType javaType, OperationMapper operationMapper, Set<Class<? extends TypeMapper>> mappersToSkip, BuildContext buildContext) {
        return GraphQLOffsetTime;
    }

    @Override
    public boolean supports(AnnotatedType type) {
        return ClassUtils.isSuperClass(OffsetTime.class, type);
    }
}

Register via:

generator.withTypeMappers(new OffsetTimeMapper());

kaqqao added a commit that referenced this issue May 13, 2019
@oscararias
Copy link
Author

Hi!

Thanks for your answer, I tested my use case with the last snapshot and it works perfectly. Looking forward to the next release.

However, I run into a small problem with your proposed custom TypeMapper implementation since ClassUtils.isSupperClass does not exist for v0.9.9. I modified the code simply replacing that call by what's now in master.

@Override
public boolean supports(AnnotatedType type) {
    return OffsetTime.class.isAssignableFrom(GenericTypeReflector.erase(type.getType()));
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants