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

#400: Add support for GraphQL union type #1469

Merged
merged 2 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public static Annotations getAnnotationsForArray(org.jboss.jandex.Type typeInCol
return new Annotations(annotationMap);
}

//
//

/**
* Used when we are creating operation and arguments for these operations
Expand Down Expand Up @@ -580,6 +580,7 @@ private static Map<DotName, AnnotationInstance> getAnnotationsWithFilter(org.jbo
public static final DotName INPUT = DotName.createSimple("org.eclipse.microprofile.graphql.Input");
public static final DotName TYPE = DotName.createSimple("org.eclipse.microprofile.graphql.Type");
public static final DotName INTERFACE = DotName.createSimple("org.eclipse.microprofile.graphql.Interface");
public static final DotName UNION = DotName.createSimple("io.smallrye.graphql.api.Union");
public static final DotName ENUM = DotName.createSimple("org.eclipse.microprofile.graphql.Enum");
public static final DotName ID = DotName.createSimple("org.eclipse.microprofile.graphql.Id");
public static final DotName DESCRIPTION = DotName.createSimple("org.eclipse.microprofile.graphql.Description");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.smallrye.graphql.schema.creator.type.InputTypeCreator;
import io.smallrye.graphql.schema.creator.type.InterfaceCreator;
import io.smallrye.graphql.schema.creator.type.TypeCreator;
import io.smallrye.graphql.schema.creator.type.UnionCreator;
import io.smallrye.graphql.schema.helper.BeanValidationDirectivesHelper;
import io.smallrye.graphql.schema.helper.Directives;
import io.smallrye.graphql.schema.helper.GroupHelper;
Expand Down Expand Up @@ -64,6 +65,7 @@ public class SchemaBuilder {
private final ReferenceCreator referenceCreator;
private final OperationCreator operationCreator;
private final DirectiveTypeCreator directiveTypeCreator;
private final UnionCreator unionCreator;

/**
* This builds the Schema from Jandex
Expand Down Expand Up @@ -98,6 +100,7 @@ private SchemaBuilder(TypeAutoNameStrategy autoNameStrategy) {
typeCreator = new TypeCreator(referenceCreator, fieldCreator, operationCreator);
interfaceCreator = new InterfaceCreator(referenceCreator, fieldCreator, operationCreator);
directiveTypeCreator = new DirectiveTypeCreator(referenceCreator);
unionCreator = new UnionCreator(referenceCreator);
}

private Schema generateSchema() {
Expand Down Expand Up @@ -130,7 +133,7 @@ private Schema generateSchema() {
// Add all custom datafetchers
addDataFetchers(schema);

// Reset the maps.
// Reset the maps.
referenceCreator.clear();

return schema;
Expand Down Expand Up @@ -162,6 +165,9 @@ private void addTypesToSchema(Schema schema) {
// Add the interface types
createAndAddToSchema(ReferenceType.INTERFACE, interfaceCreator, schema::addInterface);

// Add the union types
createAndAddToSchema(ReferenceType.UNION, unionCreator, schema::addUnion);

// Add the enum types
createAndAddToSchema(ReferenceType.ENUM, enumCreator, schema::addEnum);
}
Expand All @@ -185,6 +191,11 @@ private void addOutstandingTypesToSchema(Schema schema) {
keepGoing = true;
}

// See if there is any unions we missed
if (findOutstandingAndAddToSchema(ReferenceType.UNION, unionCreator, schema::containsUnion, schema::addUnion)) {
keepGoing = true;
}

// See if there is any enums we missed
if (findOutstandingAndAddToSchema(ReferenceType.ENUM, enumCreator, schema::containsEnum,
schema::addEnum)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@

/**
* Here we create references to things that might not yet exist.
*
*
* We store all references to be created later.
*
*
* @author Phillip Kruger ([email protected])
*/
public class ReferenceCreator {
Expand All @@ -46,12 +46,14 @@ public class ReferenceCreator {
private final Queue<Reference> typeReferenceQueue = new ArrayDeque<>();
private final Queue<Reference> enumReferenceQueue = new ArrayDeque<>();
private final Queue<Reference> interfaceReferenceQueue = new ArrayDeque<>();
private final Queue<Reference> unionReferenceQueue = new ArrayDeque<>();

// Some maps we populate during scanning
private final Map<String, Reference> inputReferenceMap = new HashMap<>();
private final Map<String, Reference> typeReferenceMap = new HashMap<>();
private final Map<String, Reference> enumReferenceMap = new HashMap<>();
private final Map<String, Reference> interfaceReferenceMap = new HashMap<>();
private final Map<String, Reference> unionReferenceMap = new HashMap<>();

private final TypeAutoNameStrategy autoNameStrategy;

Expand All @@ -68,16 +70,18 @@ public void clear() {
typeReferenceMap.clear();
enumReferenceMap.clear();
interfaceReferenceMap.clear();
unionReferenceMap.clear();

inputReferenceQueue.clear();
typeReferenceQueue.clear();
enumReferenceQueue.clear();
interfaceReferenceQueue.clear();
unionReferenceQueue.clear();
}

/**
* Get the values for a certain type
*
*
* @param referenceType the type
* @return the references
*/
Expand All @@ -87,7 +91,7 @@ public Queue<Reference> values(ReferenceType referenceType) {

/**
* Get the Type auto name strategy
*
*
* @return the strategy as supplied
*/
public TypeAutoNameStrategy getTypeAutoNameStrategy() {
Expand All @@ -96,7 +100,7 @@ public TypeAutoNameStrategy getTypeAutoNameStrategy() {

/**
* Get a reference to a field type for an adapter on a field
*
*
* @param direction the direction
* @param fieldType the java type
* @param annotations annotation on this operations method
Expand All @@ -111,7 +115,7 @@ public Reference createReferenceForAdapter(Type fieldType,
/**
* Get a reference to a field type for an operation Direction is OUT on a field (and IN on an argument) In the case
* of operations, there is no fields (only methods)
*
*
* @param fieldType the java type
* @param annotationsForMethod annotation on this operations method
* @return a reference to the type
Expand All @@ -123,7 +127,7 @@ public Reference createReferenceForOperationField(Type fieldType, Annotations an
/**
* Get a reference to a argument type for an operation Direction is IN on an argument (and OUT on a field) In the
* case of operation, there is no field (only methods)
*
*
* @param argumentType the java type
* @param annotationsForThisArgument annotations on this argument
* @return a reference to the argument
Expand All @@ -145,9 +149,9 @@ public Reference createReferenceForSourceArgument(Type argumentType, Annotations

/**
* Get a reference to a field (method response) on an interface
*
*
* Interfaces is only usable on Type, so the direction in OUT.
*
*
* @param methodType the method response type
* @param annotationsForThisMethod annotations on this method
* @return a reference to the type
Expand All @@ -159,9 +163,9 @@ public Reference createReferenceForInterfaceField(Type methodType, Annotations a

/**
* Get a reference to a Field Type for a InputType or Type.
*
*
* We need both the type and the getter/setter method as both is applicable.
*
*
* @param direction in or out
* @param fieldType the field type
* @param methodType the method type
Expand All @@ -180,7 +184,7 @@ public Reference createReferenceForPojoField(Type fieldType,
/**
* This method create a reference to type that might not yet exist. It also store to be created later, if we do not
* already know about it.
*
*
* @param direction the direction (in or out)
* @param classInfo the Java class
* @param createAdapedToType create the type in the schema
Expand All @@ -197,7 +201,7 @@ private Reference createReference(Direction direction,

ReferenceType referenceType = getCorrectReferenceType(classInfo, annotationsForClass, direction);

if (referenceType.equals(ReferenceType.INTERFACE)) {
if (referenceType.equals(ReferenceType.INTERFACE) || referenceType.equals(ReferenceType.UNION)) {
// Also check that we create all implementations
Collection<ClassInfo> knownDirectImplementors = ScanningContext.getIndex()
.getAllKnownImplementors(classInfo.name());
Expand Down Expand Up @@ -229,7 +233,7 @@ private Reference createReference(Direction direction,

createReference(direction, impl, createAdapedToType, createAdapedWithType,
parametrizedTypeArgumentsReferencesImpl,
true);
referenceType.equals(ReferenceType.INTERFACE));
}
}

Expand Down Expand Up @@ -276,8 +280,20 @@ private Reference createReference(Direction direction,
private static boolean isInterface(ClassInfo classInfo, Annotations annotationsForClass) {
boolean isJavaInterface = Classes.isInterface(classInfo);
if (isJavaInterface) {
if (annotationsForClass.containsOneOfTheseAnnotations(Annotations.TYPE, Annotations.INPUT)) {
// This should be mapped to a type/input and not an interface
if (annotationsForClass.containsOneOfTheseAnnotations(Annotations.TYPE, Annotations.INPUT, Annotations.UNION)) {
// This should be mapped to a type/input/union and not an interface
return false;
}
return true;
}
return false;
}

private static boolean isUnion(ClassInfo classInfo, Annotations annotationsForClass) {
boolean isJavaInterface = Classes.isInterface(classInfo);
if (isJavaInterface) {
if (annotationsForClass.containsOneOfTheseAnnotations(Annotations.TYPE, Annotations.INPUT, Annotations.INTERFACE)) {
// This should be mapped to a type/input/interface and not a union
return false;
}
return true;
Expand Down Expand Up @@ -468,6 +484,8 @@ private Map<String, Reference> getReferenceMap(ReferenceType referenceType) {
return inputReferenceMap;
case INTERFACE:
return interfaceReferenceMap;
case UNION:
return unionReferenceMap;
case TYPE:
return typeReferenceMap;
default:
Expand All @@ -483,6 +501,8 @@ private Queue<Reference> getReferenceQueue(ReferenceType referenceType) {
return inputReferenceQueue;
case INTERFACE:
return interfaceReferenceQueue;
case UNION:
return unionReferenceQueue;
case TYPE:
return typeReferenceQueue;
default:
Expand All @@ -493,6 +513,8 @@ private Queue<Reference> getReferenceQueue(ReferenceType referenceType) {
private static ReferenceType getCorrectReferenceType(ClassInfo classInfo, Annotations annotations, Direction direction) {
if (isInterface(classInfo, annotations)) {
return ReferenceType.INTERFACE;
} else if (isUnion(classInfo, annotations)) {
return ReferenceType.UNION;
} else if (Classes.isEnum(classInfo)) {
return ReferenceType.ENUM;
} else if (direction.equals(Direction.IN)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ public Type create(ClassInfo classInfo, Reference reference) {
// Fields
addFields(type, classInfo, reference);

// Interfaces
addInterfaces(type, classInfo, reference);
// Interfaces/Unions
addPolymorphicTypes(type, classInfo, reference);

// Operations
addOperations(type, classInfo);
Expand All @@ -85,19 +85,25 @@ private void addDirectives(Type type, ClassInfo classInfo) {
type.setDirectiveInstances(directives.buildDirectiveInstances(classInfo::classAnnotation));
}

private void addInterfaces(Type type, ClassInfo classInfo, Reference reference) {
private void addPolymorphicTypes(Type type, ClassInfo classInfo, Reference reference) {
List<org.jboss.jandex.Type> interfaceNames = classInfo.interfaceTypes();
for (org.jboss.jandex.Type interfaceType : interfaceNames) {
String interfaceFullName = interfaceType.name().toString();
// Ignore java interfaces (like Serializable)
if (InterfaceCreator.canAddInterfaceIntoScheme(interfaceType.name().toString())) {
// TODO: should this check be renamed now that it is used for both union and interface checks?
if (InterfaceCreator.canAddInterfaceIntoScheme(interfaceFullName)) {
ClassInfo interfaceInfo = ScanningContext.getIndex().getClassByName(interfaceType.name());
if (interfaceInfo != null) {
Annotations annotationsForInterface = Annotations.getAnnotationsForClass(interfaceInfo);
Reference interfaceRef = referenceCreator.createReferenceForInterfaceField(interfaceType,
annotationsForInterface, reference);
type.addInterface(interfaceRef);
// add all parent interfaces recursively as GraphQL schema requires it
addInterfaces(type, interfaceInfo, reference);
if (annotationsForInterface.containsOneOfTheseAnnotations(Annotations.UNION)) {
type.addUnion(interfaceRef);
} else {
type.addInterface(interfaceRef);
// add all parent interfaces recursively as GraphQL schema requires it
addPolymorphicTypes(type, interfaceInfo, reference);
}
}
}
}
Expand Down Expand Up @@ -134,6 +140,17 @@ protected Map<String, Operation> toOperations(Map<DotName, List<MethodParameterI
}
}
}
for (Reference u : type.getUnionMemberships()) {
String className = u.getClassName();
if (sourceFields.containsKey(DotName.createSimple(className))) {
List<MethodParameterInfo> methodParameterInfos = sourceFields.get(DotName.createSimple(className));
for (MethodParameterInfo methodParameterInfo : methodParameterInfos) {
MethodInfo methodInfo = methodParameterInfo.method();
Operation o = operationCreator.createOperation(methodInfo, OperationType.QUERY, type);
operations.put(o.getName(), o);
}
}
}
return operations;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.smallrye.graphql.schema.creator.type;

import java.util.Optional;

import org.jboss.jandex.ClassInfo;
import org.jboss.logging.Logger;

import io.smallrye.graphql.schema.Annotations;
import io.smallrye.graphql.schema.creator.ReferenceCreator;
import io.smallrye.graphql.schema.helper.DescriptionHelper;
import io.smallrye.graphql.schema.helper.TypeNameHelper;
import io.smallrye.graphql.schema.model.Reference;
import io.smallrye.graphql.schema.model.ReferenceType;
import io.smallrye.graphql.schema.model.UnionType;

public class UnionCreator implements Creator<UnionType> {

private static final Logger LOG = Logger.getLogger(UnionCreator.class.getName());

private final ReferenceCreator referenceCreator;

public UnionCreator(ReferenceCreator referenceCreator) {
this.referenceCreator = referenceCreator;
}

@Override
public UnionType create(ClassInfo classInfo, Reference reference) {
LOG.debug("Creating union from " + classInfo.name().toString());

Annotations annotations = Annotations.getAnnotationsForClass(classInfo);

// Name
String name = TypeNameHelper.getAnyTypeName(classInfo,
annotations,
referenceCreator.getTypeAutoNameStrategy(),
ReferenceType.UNION,
reference.getClassParametrizedTypes());

// Description
Optional<String> maybeDescription = DescriptionHelper.getDescriptionForType(annotations);

return new UnionType(classInfo.name().toString(), name, maybeDescription.orElse(null));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

/**
* Type of reference
*
*
* Because we refer to types before they might exist, we need an indication of the type
*
*
* @author Phillip Kruger ([email protected])
*/
public enum ReferenceType {
INPUT,
TYPE,
ENUM,
INTERFACE,
UNION,
SCALAR
}
}
Loading