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

bug: DefaultInputObjectMapper.getFieldType(declaredField, TargetClass) does not work for collections with bounded generic implementations #1385

Open
lbusch25 opened this issue Jan 11, 2023 · 1 comment
Labels
bug Something isn't working stale

Comments

@lbusch25
Copy link

lbusch25 commented Jan 11, 2023

Expected behavior

Given an implementing class with a bounded generic type:

@Builder
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class TaskProperties<F extends TaskField> implements Serializable {

    @Serial
    private static final long serialVersionUID = 1115187246981180834L;

    private List<F> fields;
}

And the below schema implementation:

type Query {
    getFakeGeneric: TaskProperties
}

type Mutation {
    testGenericFieldTypeNotFound(taskProperties: TaskPropertyInput!): TaskProperties
}

type TaskProperties {
    fields: [TaskField]
}

type TaskField {
    fieldName: String
    value: Object #corresponds to a FieldType implementation - can be wrapped Java type or custom
    type: FieldType
}

input TaskPropertyInput {
    fields: [TaskConfigurationInput]
}

input TaskConfigurationInput {
    fieldName: String
    value: Object
    type: FieldType
}

The TaskPropertyInput should correctly map to the TaskProperties java class during DGS's deserialization of the input object. Instead an error is thrown. Note that this same object can be resolved correctly as a return type, and that it works via REST mapping (with the default ObjectMapper implementation).

Actual behavior

The below exception is thrown around identifying class F:

org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.util.LinkedHashMap<?, ?>] to type [@javax.validation.constraints.NotNull @com.netflix.graphql.dgs.InputArgument com.lawsonbusch.inputdemo.domain.TaskProperties<com.lawsonbusch.inputdemo.domain.TaskConfigurationField<?>>] for value '{fields=[{fieldName=test field, value=string value, type=STRING}]}'; nested exception is java.lang.ClassNotFoundException: F
	at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47) ~[spring-core-5.3.23.jar:5.3.23]
	at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:192) ~[spring-core-5.3.23.jar:5.3.23]
	at com.netflix.graphql.dgs.internal.method.AbstractInputArgumentResolver.convertValue(AbstractInputArgumentResolver.kt:93) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.internal.method.AbstractInputArgumentResolver.resolveArgument(AbstractInputArgumentResolver.kt:49) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.internal.method.ArgumentResolverComposite.resolveArgument(ArgumentResolverComposite.kt:39) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.internal.DataFetcherInvoker.get(DataFetcherInvoker.kt:62) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at graphql.schema.DataFetcherFactories.lambda$wrapDataFetcher$2(DataFetcherFactories.java:37) ~[graphql-java-19.3.jar:na]
	at graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation.lambda$instrumentDataFetcher$0(DataLoaderDispatcherInstrumentation.java:86) ~[graphql-java-19.3.jar:na]
	at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:282) ~[graphql-java-19.3.jar:na]
	at graphql.execution.ExecutionStrategy.resolveFieldWithInfo(ExecutionStrategy.java:211) ~[graphql-java-19.3.jar:na]
	at graphql.execution.ExecutionStrategy.resolveField(ExecutionStrategy.java:183) ~[graphql-java-19.3.jar:na]
	at graphql.execution.AsyncSerialExecutionStrategy.lambda$execute$1(AsyncSerialExecutionStrategy.java:47) ~[graphql-java-19.3.jar:na]
	at graphql.execution.Async.eachSequentiallyImpl(Async.java:191) ~[graphql-java-19.3.jar:na]
	at graphql.execution.Async.eachSequentially(Async.java:180) ~[graphql-java-19.3.jar:na]
	at graphql.execution.AsyncSerialExecutionStrategy.execute(AsyncSerialExecutionStrategy.java:42) ~[graphql-java-19.3.jar:na]
	at graphql.execution.Execution.executeOperation(Execution.java:159) ~[graphql-java-19.3.jar:na]
	at graphql.execution.Execution.execute(Execution.java:105) ~[graphql-java-19.3.jar:na]
	at graphql.GraphQL.execute(GraphQL.java:645) ~[graphql-java-19.3.jar:na]
	at graphql.GraphQL.lambda$parseValidateAndExecute$11(GraphQL.java:564) ~[graphql-java-19.3.jar:na]
	at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1187) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2309) ~[na:na]
	at graphql.GraphQL.parseValidateAndExecute(GraphQL.java:559) ~[graphql-java-19.3.jar:na]
	at graphql.GraphQL.executeAsync(GraphQL.java:527) ~[graphql-java-19.3.jar:na]
	at com.netflix.graphql.dgs.internal.BaseDgsQueryExecutor.baseExecute(BaseDgsQueryExecutor.kt:127) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.internal.DefaultDgsQueryExecutor.execute(DefaultDgsQueryExecutor.kt:79) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.mvc.DgsRestController$graphql$executionResult$1.invoke(DgsRestController.kt:206) ~[graphql-dgs-spring-webmvc-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.mvc.DgsRestController$graphql$executionResult$1.invoke(DgsRestController.kt:204) ~[graphql-dgs-spring-webmvc-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.internal.utils.TimeTracer.logTime(TimeTracer.kt:24) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.mvc.DgsRestController.graphql(DgsRestController.kt:204) ~[graphql-dgs-spring-webmvc-5.5.0.jar:5.5.0]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:681) ~[tomcat-embed-core-9.0.65.jar:4.0.FR]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.65.jar:4.0.FR]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
Caused by: java.lang.ClassNotFoundException: F
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) ~[na:na]
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[na:na]
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[na:na]
	at java.base/java.lang.Class.forName0(Native Method) ~[na:na]
	at java.base/java.lang.Class.forName(Class.java:375) ~[na:na]
	at com.netflix.graphql.dgs.internal.DefaultInputObjectMapper.mapToJavaObject(DefaultInputObjectMapper.kt:125) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.internal.method.InputObjectMapperConverter.convert(InputObjectMapperConverter.kt:40) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41) ~[spring-core-5.3.23.jar:5.3.23]
	... 78 common frames omitted

Steps to reproduce

I have created a public repo on my github profile that contains a test project, with instructions on how to run the project (basic maven build and run from IntelliJ). This project also has example queries in the ReadMe.md and java docs that described the expected errors and known workarounds.

Note that if the generic type F is replaced with a concrete implementation, an exception is still thrown.

@lbusch25 lbusch25 added the bug Something isn't working label Jan 11, 2023
@kilink
Copy link
Member

kilink commented Feb 21, 2023

I think as designed, InputObjectMapper cannot work for your use case, as it deals with classes instead of types; e.g., the type variable F can only be resolved with the method parameter type info, which is lost by the time the InputObjectMapper methods are called.

I am working on overhauling the InputObjectMapper code to simplify it, so I can try to tackle this problem as part of that, but it will require a redesign of the APIs to accept a TypeDescriptor at the very least.

@github-actions github-actions bot added the stale label Feb 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working stale
Projects
None yet
Development

No branches or pull requests

2 participants