Skip to content

Wrapping a projected payload in optional returns IllegalStateException #506

@frehov

Description

@frehov

I believe this might be a bug, and as the title suggest, by wrapping an optional in a projected payload interface it will fail with an IllegalStateException.

We have some queries that allow for omitting the arguments. If this is contrary to the graphql spec, we'll adjust our usage insted. But it would be beneficial to be able to wrap any argument in optional and still have them resolve.

When wrapping a projected payload interface in an optional, the ProjectedPayloadMethodArgumentResolver is not checking if there is an interface inside an optional type, which then fails with the following error:

2022-10-14 15:17:56,931 [DEBUG] o.s.g.d.m.HandlerMethod -- Could not resolve parameter [0] in public java.util.List<no.nav.nom.contract.nom.api.graphql.OrgEnhetResult> no.nav.nom.apps.api.graphql.controller.GraphQLOrgEnhetController.organisasjonsenheter(java.util.Optional<no.nav.nom.apps.api.graphql.common.projections.OrgEnheterSearch>,graphql.schema.DataFetchingEnvironment): No primary or single unique constructor found for interface no.nav.nom.apps.api.graphql.common.projections.OrgEnheterSearch
2022-10-14 15:17:56,953 [ERROR] o.s.g.e.ExceptionResolversExceptionHandler -- Unresolved IllegalStateException for executionId c5666fb2-5c5e-c803-0bde-0060fd82488f
java.lang.IllegalStateException: No primary or single unique constructor found for interface no.nav.nom.apps.api.graphql.common.projections.OrgEnheterSearch
	at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:267)
	at org.springframework.graphql.data.GraphQlArgumentBinder.createValue(GraphQlArgumentBinder.java:248)
	at org.springframework.graphql.data.GraphQlArgumentBinder.bind(GraphQlArgumentBinder.java:163)
	at org.springframework.graphql.data.method.annotation.support.ArgumentMethodArgumentResolver.resolveArgument(ArgumentMethodArgumentResolver.java:58)
	at org.springframework.graphql.data.method.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:83)
	at org.springframework.graphql.data.method.annotation.support.DataFetcherHandlerMethod.getMethodArgumentValues(DataFetcherHandlerMethod.java:170)
	at org.springframework.graphql.data.method.annotation.support.DataFetcherHandlerMethod.invoke(DataFetcherHandlerMethod.java:115)
	at org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer$SchemaMappingDataFetcher.get(AnnotatedControllerConfigurer.java:514)
	at org.springframework.graphql.execution.ContextDataFetcherDecorator.lambda$get$0(ContextDataFetcherDecorator.java:74)
	at org.springframework.graphql.execution.ReactorContextManager.invokeCallable(ReactorContextManager.java:104)
	at org.springframework.graphql.execution.ContextDataFetcherDecorator.get(ContextDataFetcherDecorator.java:73)
	at org.springframework.boot.actuate.metrics.graphql.GraphQlMetricsInstrumentation.lambda$instrumentDataFetcher$1(GraphQlMetricsInstrumentation.java:98)
	at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:282)
	at graphql.execution.ExecutionStrategy.resolveFieldWithInfo(ExecutionStrategy.java:211)
	at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:59)
	at graphql.execution.Execution.executeOperation(Execution.java:159)
	at graphql.execution.Execution.execute(Execution.java:105)
	at graphql.GraphQL.execute(GraphQL.java:645)
	at graphql.GraphQL.lambda$parseValidateAndExecute$11(GraphQL.java:564)
	at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1187)
	at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2309)
	at graphql.GraphQL.parseValidateAndExecute(GraphQL.java:559)
	at graphql.GraphQL.executeAsync(GraphQL.java:527)
	...
	at java.base/java.lang.Thread.run(Thread.java:833)

The controller method in question is mapped like this by the AnnotatedControllerConfigurer

Query.organisasjonsenheter => organisasjonsenheter(java.util.Optional<no.nav.nom.apps.api.graphql.common.projections.OrgEnheterSearch>,graphql.schema.DataFetchingEnvironment)

the projected payload we're using

@ProjectedPayload
public interface OrgEnheterSearch {
    Set<String> getAgressoIder();
    String getOrgNiv();

    default Set<String> resolveAgressoIder() {
        if(getAgressoIder() == null) {
            return emptySet();
        }
        return getAgressoIder();
    }

    static OrgEnheterSearch emptyOrgEnheterSearch() {
        return new OrgEnheterSearch() {
            @Override
            public Set<String> getAgressoIder() {
                return emptySet();
            }

            @Override
            public String getOrgNiv() {
                return null;
            }
        };
    }
}

query in question.

query {
    organisasjonsenheter(where: {agressoIder: ["testId1","notFoundIdent"]}) {
        id
        code
    }
}

optional query without arguments

query {
    organisasjonsenheter {
        id
        code
    }
}

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions