Improve class mapping in SchemaMappingInspector
This commit is contained in:
@@ -280,21 +280,26 @@ For unions, the inspection iterates over member types and tries to find the corr
|
||||
classes. For interfaces, the inspection iterates over implementation types and looks
|
||||
for the corresponding classes.
|
||||
|
||||
By default, corresponding `Class` can be found if the class name matches that of the
|
||||
GraphQL union member of interface implementation type, _and_ the `Class` is located in
|
||||
the same package (and/or outer class) as the return type of the controller method for the
|
||||
union or interface. In addition, if `ClassNameTypeResolver` is configured as a
|
||||
By default, corresponding Java classes can be detected out-of-the-box in the following cases:
|
||||
|
||||
- The ``Class``'s simple name matches the GraphQL union member of interface implementation
|
||||
type name, _and_ the `Class` is located in the same package as the return type of the
|
||||
controller method, or controller class, mapped to the union or interface field.
|
||||
- The `Class` is inspected in other parts of the schema where the mapped field is of a
|
||||
concrete union member or interface implementation type.
|
||||
- You have registered a
|
||||
xref:request-execution.adoc#execution.graphqlsource.default-type-resolver[TypeResolver]
|
||||
with explicit class mapping registrations, those are also checked.
|
||||
that has explicit `Class` to GraphQL type mappings .
|
||||
|
||||
If a union member or an interface implementation type is listed as skipped, you have
|
||||
the following additional options:
|
||||
In none the above help, and GraphQL types are reported as skipped in the schema inspection
|
||||
report, you can make the following customizations:
|
||||
|
||||
- Register a function to resolve the `Class` name for a given GraphQL type to account
|
||||
for class naming conventions.
|
||||
- Register a `ClassResolver` with any custom resolution logic.
|
||||
- Explicitly map a GraphQL type name to a Java class or classes.
|
||||
- Configure a function that customizes how a GraphQL type name is adapted to a simple
|
||||
`Class` name. This can help with a specific Java class naming conventions.
|
||||
- Provide a `ClassNameTypeResolver` to map a GraphQL type a Java classes.
|
||||
|
||||
Use the following for such customizations:
|
||||
For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -302,10 +307,8 @@ GraphQlSource.Builder builder = ...
|
||||
|
||||
builder.schemaResources(..)
|
||||
.inspectSchemaMappings(
|
||||
initializer -> initializer.classNameFunction(type -> type.getName() + "Impl")
|
||||
report -> {
|
||||
logger.debug(report);
|
||||
})
|
||||
initializer -> initializer.classMapping("Author", Author.class)
|
||||
logger::debug);
|
||||
----
|
||||
|
||||
|
||||
|
||||
@@ -238,10 +238,7 @@ final class DefaultSchemaResourceGraphQlSourceBuilder
|
||||
// Add explicit mappings from ClassNameTypeResolver's
|
||||
runtimeWiring.getTypeResolvers().values().stream().distinct().forEach((resolver) -> {
|
||||
if (resolver instanceof ClassNameTypeResolver cntr) {
|
||||
Map<Class<?>, String> mappings = cntr.getMappings();
|
||||
if (!mappings.isEmpty()) {
|
||||
initializer.classResolver(SchemaMappingInspector.ClassResolver.create(mappings));
|
||||
}
|
||||
cntr.getMappings().forEach((aClass, name) -> initializer.classMapping(name, aClass));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -302,20 +302,43 @@ public final class SchemaMappingInspector {
|
||||
public interface Initializer {
|
||||
|
||||
/**
|
||||
* Provide a function to derive the simple class name that corresponds to a
|
||||
* GraphQL union member type, or a GraphQL interface implementation type.
|
||||
* This is then used to find a Java class in the same package as that of
|
||||
* the return type of the controller method for the interface or union.
|
||||
* <p>The default, {@link GraphQLObjectType#getName()} is used
|
||||
* Provide an explicit mapping between a GraphQL type name and the Java
|
||||
* class(es) that represent it at runtime to help inspect union member
|
||||
* and interface implementation types when those associations cannot be
|
||||
* discovered otherwise.
|
||||
* <p>Out of the box, there a several ways through which schema inspection
|
||||
* can locate such types automatically:
|
||||
* <ul>
|
||||
* <li>Java class representations are located in the same package as the
|
||||
* type returned from the controller method for a union or interface field,
|
||||
* and their {@link Class#getSimpleName() simple class names} match GraphQL
|
||||
* type names, possibly with the help of a {@link #classNameFunction}.
|
||||
* <li>Java class representations are located in the same package as the
|
||||
* declaring class of the controller method for a union or interface field.
|
||||
* <li>Controller methods return the Java class representations of schema
|
||||
* fields for concrete union member or interface implementation types.
|
||||
* </ul>
|
||||
* @param graphQlTypeName the name of a GraphQL Object type
|
||||
* @param aClass one or more Java class representations
|
||||
* @return the same initializer instance
|
||||
*/
|
||||
Initializer classMapping(String graphQlTypeName, Class<?>... aClass);
|
||||
|
||||
/**
|
||||
* Help to derive the {@link Class#getSimpleName() simple class name} for
|
||||
* the Java representation of a GraphQL union member or interface implementing
|
||||
* type. For more details, see {@link #classMapping(String, Class[])}.
|
||||
* <p>By default, {@link GraphQLObjectType#getName()} is used.
|
||||
* @param function the function to use
|
||||
* @return the same initializer instance
|
||||
*/
|
||||
Initializer classNameFunction(Function<GraphQLObjectType, String> function);
|
||||
|
||||
/**
|
||||
* Add a custom {@link ClassResolver} to use to find the Java class for a
|
||||
* GraphQL union member type, or a GraphQL interface implementation type.
|
||||
* @param resolver the resolver to add
|
||||
* Alternative to {@link #classMapping(String, Class[])} with a custom
|
||||
* {@link ClassResolver} to find the Java class(es) for a GraphQL union
|
||||
* member or interface implementation type.
|
||||
* @param resolver the resolver to use to find associated Java classes
|
||||
* @return the same initializer instance
|
||||
*/
|
||||
Initializer classResolver(ClassResolver resolver);
|
||||
@@ -345,14 +368,6 @@ public final class SchemaMappingInspector {
|
||||
*/
|
||||
List<Class<?>> resolveClass(GraphQLObjectType objectType, GraphQLNamedOutputType interfaceOrUnionType);
|
||||
|
||||
|
||||
/**
|
||||
* Create a resolver from the given mappings.
|
||||
* @param mappings from Class to GraphQL type name
|
||||
*/
|
||||
static ClassResolver create(Map<Class<?>, String> mappings) {
|
||||
return new MappingClassResolver(mappings);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -365,6 +380,8 @@ public final class SchemaMappingInspector {
|
||||
|
||||
private final List<ClassResolver> classResolvers = new ArrayList<>();
|
||||
|
||||
private final MultiValueMap<String, Class<?>> classMappings = new LinkedMultiValueMap<>();
|
||||
|
||||
@Override
|
||||
public Initializer classNameFunction(Function<GraphQLObjectType, String> function) {
|
||||
this.classNameFunction = function;
|
||||
@@ -377,14 +394,20 @@ public final class SchemaMappingInspector {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Initializer classMapping(String graphQlTypeName, Class<?>... classes) {
|
||||
for (Class<?> aClass : classes) {
|
||||
this.classMappings.add(graphQlTypeName, aClass);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchemaReport inspect(GraphQLSchema schema, Map<String, Map<String, DataFetcher>> fetchers) {
|
||||
|
||||
ReflectionClassResolver reflectionResolver =
|
||||
ReflectionClassResolver.create(schema, fetchers, this.classNameFunction);
|
||||
|
||||
List<ClassResolver> resolvers = new ArrayList<>(this.classResolvers);
|
||||
resolvers.add(reflectionResolver);
|
||||
resolvers.add(new MappingClassResolver(this.classMappings));
|
||||
resolvers.add(ReflectionClassResolver.create(schema, fetchers, this.classNameFunction));
|
||||
|
||||
InterfaceUnionLookup lookup = InterfaceUnionLookup.create(schema, resolvers);
|
||||
|
||||
@@ -399,15 +422,15 @@ public final class SchemaMappingInspector {
|
||||
*/
|
||||
private static final class MappingClassResolver implements ClassResolver {
|
||||
|
||||
private final MultiValueMap<String, Class<?>> map = new LinkedMultiValueMap<>();
|
||||
private final MultiValueMap<String, Class<?>> mappings = new LinkedMultiValueMap<>();
|
||||
|
||||
MappingClassResolver(Map<Class<?>, String> mappings) {
|
||||
mappings.forEach((key, value) -> this.map.add(value, key));
|
||||
MappingClassResolver(MultiValueMap<String, Class<?>> mappings) {
|
||||
this.mappings.putAll(mappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<?>> resolveClass(GraphQLObjectType objectType, GraphQLNamedOutputType interfaceOrUnionType) {
|
||||
return this.map.getOrDefault(objectType.getName(), Collections.emptyList());
|
||||
return this.mappings.getOrDefault(objectType.getName(), Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,7 +501,7 @@ public final class SchemaMappingInspector {
|
||||
if (PACKAGE_PREDICATE.test(clazz.getPackageName())) {
|
||||
addClassPrefix(outputTypeName, clazz, classPrefixes);
|
||||
}
|
||||
else if (dataFetcher instanceof SelfDescribingDataFetcher<?> selfDescribing) {
|
||||
if (dataFetcher instanceof SelfDescribingDataFetcher<?> selfDescribing) {
|
||||
if (selfDescribing.getReturnType().getSource() instanceof MethodParameter param) {
|
||||
addClassPrefix(outputTypeName, param.getDeclaringClass(), classPrefixes);
|
||||
}
|
||||
|
||||
@@ -17,13 +17,11 @@
|
||||
package org.springframework.graphql.execution;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.graphql.data.method.annotation.QueryMapping;
|
||||
import org.springframework.graphql.execution.SchemaMappingInspector.ClassResolver;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
/**
|
||||
@@ -103,10 +101,8 @@ public class SchemaMappingInspectorInterfaceTests extends SchemaMappingInspector
|
||||
@Test
|
||||
void classNameTypeResolver() {
|
||||
|
||||
Map<Class<?>, String> mappings = Map.of(CarImpl.class, "Car");
|
||||
|
||||
SchemaReport report = inspectSchema(schema,
|
||||
initializer -> initializer.classResolver(ClassResolver.create(mappings)),
|
||||
initializer -> initializer.classMapping("Car", CarImpl.class),
|
||||
VehicleController.class);
|
||||
|
||||
assertThatReport(report)
|
||||
|
||||
@@ -17,14 +17,12 @@
|
||||
package org.springframework.graphql.execution;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.graphql.data.method.annotation.Argument;
|
||||
import org.springframework.graphql.data.method.annotation.QueryMapping;
|
||||
import org.springframework.graphql.execution.SchemaMappingInspector.ClassResolver;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
/**
|
||||
@@ -129,10 +127,8 @@ public class SchemaMappingInspectorUnionTests extends SchemaMappingInspectorTest
|
||||
@Test
|
||||
void classNameTypeResolver() {
|
||||
|
||||
Map<Class<?>, String> mappings = Map.of(PhotoImpl.class, "Photo");
|
||||
|
||||
SchemaReport report = inspectSchema(schema,
|
||||
initializer -> initializer.classResolver(ClassResolver.create(mappings)),
|
||||
initializer -> initializer.classMapping("Photo", PhotoImpl.class),
|
||||
SearchController.class);
|
||||
|
||||
assertThatReport(report)
|
||||
|
||||
Reference in New Issue
Block a user