Unwrap Optional types for schema inspection

Prior to this commit, return types declared by controller handlers that
were wrapped with `Optional` were not considered during the schema
analysis and would result in false positives in the schema inspection
report, stating that relevant fields were unmapped.
This is especially relevant for controller handlers returning optional
values from Spring Data repositories.

This commit ensures that `java.util.Optional` types are unwrapped and
that wrapped types are considered during the analysis.

Fixes gh-875
This commit is contained in:
Brian Clozel
2024-01-12 12:27:43 +01:00
parent b326771f9e
commit e054fe13cf
2 changed files with 35 additions and 5 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2023 the original author or authors.
* Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import graphql.schema.DataFetcher;
@@ -181,7 +182,7 @@ final class SchemaMappingInspector {
resolvableType = nestForList(resolvableType, (parent == this.schema.getSubscriptionType()));
}
else {
resolvableType = nestIfReactive(resolvableType);
resolvableType = nestIfWrappedType(resolvableType);
}
// Type already inspected?
@@ -230,15 +231,21 @@ final class SchemaMappingInspector {
if (type == ResolvableType.NONE) {
return type;
}
type = nestIfReactive(type);
type = nestIfWrappedType(type);
if (logger.isDebugEnabled() && type.getGenerics().length != 1) {
logger.debug("Expected Connection type to have a generic parameter: " + type);
}
return type.getNested(2);
}
private ResolvableType nestIfReactive(ResolvableType type) {
private ResolvableType nestIfWrappedType(ResolvableType type) {
Class<?> clazz = type.resolve(Object.class);
if (Optional.class.isAssignableFrom(clazz)) {
if (logger.isDebugEnabled() && type.getGeneric(0).resolve() == null) {
logger.debug("Expected Optional type to have a generic parameter: " + type);
}
return type.getNested(2);
}
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(clazz);
if (adapter != null) {
if (logger.isDebugEnabled() && adapter.isNoValue()) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2023 the original author or authors.
* Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import graphql.schema.FieldCoordinates;
@@ -101,6 +102,23 @@ class SchemaMappingInspectorTests {
assertThatReport(report).hasUnmappedFieldCount(1).containsUnmappedFields("Book", "missing");
}
@Test
void reportWorksForQueryWithOptional() {
String schema = """
type Query {
optionalBook: Book
}
type Book {
id: ID
name: String
missing: Boolean
}
""";
SchemaReport report = inspectSchema(schema, BookController.class);
assertThatReport(report).hasUnmappedFieldCount(1).containsUnmappedFields("Book", "missing");
}
@Test
void reportWorksForQueryWithConnection() {
String schema = """
@@ -595,6 +613,11 @@ class SchemaMappingInspectorTests {
return new Book();
}
@QueryMapping
public Optional<Book> optionalBook() {
return Optional.of(new Book());
}
@SchemaMapping
public Author author(Book book) {
return new Author();