use of com.graphql_java_generator.plugin.language.AppliedDirective in project graphql-maven-plugin-project by graphql-java-generator.
the class AddRelayConnections method addEdgeConnectionAndApplyNodeInterface.
/**
* This method searches for all interfaces, then type fields that have been marked by the RelayConnection directive,
* then, for each type, say <I>Xxx</I>:
* <UL>
* <LI>Creates the XxxEdge type</LI>
* <LI>Creates the XxxConnection type</LI>
* <LI>Add the <I>Node</I> interface as implemented by the Xxx type</LI>
* <LI>Update each field that is marked with the <I>@RelayConnection</I> interface, to change its type by the
* TypeConnection type instead</I>
* </UL>
*/
void addEdgeConnectionAndApplyNodeInterface() {
// A first step is to control that the schema is correct:
// * Standard case: a type's field has the @RelayConnection directive, and this field doesn't come from an
// interface.
// * More complex case: an interface's field has the @RelayConnection directive. We'll have to loop into
// each interface or type that implements this interface, and check that the relevant has the @RelayConnection
// directive. If not, a warning is issued.
// * Erroneous case: an interface or a type's field has the @RelayConnection directive, this field comes from an
// implemented interface, and the relevant field's interface doesn't have this directive.
//
// To do this:
//
// Step 1: identify all fields that have been marked with the @RelayConnection directive
// Step 2: for each of these fields, whether it is owned by an object or an interface, check if it inherits from
// an interface field. If yes, add the check that the field in the implemented interface is also marked with the
// @RelayConnection directive. If no, raise an error.
// Step 3: for fields of an interface that is marked with the @RelayConnection directive, checks that the
// implemented fields (that this: field if the same name in types that implement this interface) are also marked
// with the @RelayConnection directive. If not, a warning is issued, but the field is still added to the list of
// fields that implements the @RelayConnection directive
// Step 4: Identify the list of types and interfaces for which the Edge and Connection and Node interface should
// be done.
// Step 5: Actually implement the edges, connections and mark these types/interfaces with the Node interface
// Step 6: Update every field that implements the RelayConnection and change its type by the relevant
// XxxConnection object. The list of field to update is: all fields marked by the @RelayConnection, or that
// implements a field that is marked by the directive (see step3). This @RelayConnection directive must also be
// removed from the final schema
List<Field> fields = new ArrayList<>();
int nbErrors = 0;
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Step 1: identify all fields in types and interfaces that have been marked with the @RelayConnection directive
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Stream.concat(documentParser.getObjectTypes().stream(), documentParser.getInterfaceTypes().stream()).forEach((type) -> {
for (Field f : type.getFields()) {
// Is this field marked by the @RelayConnection directive?
for (AppliedDirective d : f.getAppliedDirectives()) {
if (d.getDirective().getName().equals("RelayConnection")) {
// It must be a list
if (!(f.getFieldTypeAST().getListDepth() > 0)) {
throw new RuntimeException("The " + f.getOwningType().getName() + "." + f.getName() + " field has the @RelayConnection directive applied, but is not a list. The @RelayConnection directive may only be applied on lists.");
}
// InputType may not have relay connection fields
if (((ObjectType) f.getOwningType()).isInputType()) {
throw new RuntimeException("The " + f.getOwningType().getName() + "." + f.getName() + " field has the @RelayConnection directive applied. But input type may not have fields to which the @RelayConnection directive is applied.");
}
//
// Everything is Ok. Let's go
fields.add(f);
break;
}
}
// for(AppliedDirective)
}
// for(Field))
});
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for (Field f : fields) {
// the current looping field
for (Field fieldInheritedFrom : getFieldInheritedFrom(f)) {
// This field must be marked by the @RelayConnection directive. So/and it must exist in the fields list
if (!fields.contains(fieldInheritedFrom)) {
logger.error("The field " + f.getName() + " of the " + (f.getOwningType() instanceof InterfaceType ? "interface" : "type") + " " + f.getOwningType().getName() + " has the directive @RelayConnection applied. But it inherits from the interface " + fieldInheritedFrom.getOwningType().getName() + ", in which this field doesn't have the directive @RelayConnection applied");
nbErrors += 1;
}
}
// for(getFieldInheritedFrom())
}
if (nbErrors > 0) {
throw new RuntimeException(nbErrors + " error(s) was(were) found in this schema, linked with the @RelayConnection schema. Please check the logged errors.");
}
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Step 3: for fields of an interface that is marked with the @RelayConnection directive, checks that the
// implemented fields (that this: field if the same name in types that implement this interface) are also marked
// with the @RelayConnection directive. If not, a warning is issued, but the field is still added to the list of
// fields that implements the @RelayConnection directive
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
List<Field> fieldsToAdd = new ArrayList<>();
for (Field field : fields) {
if (field.getOwningType() instanceof InterfaceType) {
for (Field inheritedField : getInheritedFields(field)) {
boolean found = false;
for (AppliedDirective d : inheritedField.getAppliedDirectives()) {
if (d.getDirective().getName().equals("RelayConnection")) {
found = true;
break;
}
}
if (!found) {
// We've found an object's field, inherited from an interface in which it is marked by the
// @RelayConnection directive. But this object's field is not marked with this directive. It's
// strange, but is generally Ok. So we display a warning. And we add this field to the list of
// field that must implement the relay connection.
logger.warn("The field " + inheritedField.getOwningType().getName() + "." + inheritedField.getName() + " implements (directly or indirectly) the " + field.getOwningType().getName() + "." + field.getName() + " field, but does not have the @RelayConnection directive");
// As we may not update a list, while we're looping in it, we create another list, that we'll be
// added afterward.
fieldsToAdd.add(inheritedField);
}
// if (!found)
}
// for(getInheritedFields)
}
// if
}
// for(fields)
fields.addAll(fieldsToAdd);
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Step 4: Identify the list of types and interfaces for which the Edge and Connection and Node interface should
// be done.
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Set<String> connectionTypeNames = new HashSet<>();
for (Field f : fields) {
connectionTypeNames.add(f.getGraphQLTypeSimpleName());
}
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for (String typeName : connectionTypeNames) {
Type type = documentParser.getType(typeName);
addNodeInterfaceToType(type);
generateConnectionType(type);
generateEdgeType(type);
}
// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for (Field f : fields) {
FieldTypeAST fieldTypeAST = FieldTypeAST.builder().graphQLTypeSimpleName(f.getGraphQLTypeSimpleName() + "Connection").mandatory(false).build();
((FieldImpl) f).setFieldTypeAST(fieldTypeAST);
}
}
use of com.graphql_java_generator.plugin.language.AppliedDirective in project graphql-maven-plugin-project by graphql-java-generator.
the class DocumentParser method readAppliedDirectives.
/**
* Reads a GraphQL directive that has been applied to an item of the GraphQL schema. The relevant directive
* definition should already have been read before (see {@link #readDirectiveDefinition(DirectiveDefinition)}).
*
* @param directives
* @return
*/
List<AppliedDirective> readAppliedDirectives(List<graphql.language.Directive> directives) {
List<AppliedDirective> ret = new ArrayList<>();
if (directives != null) {
for (graphql.language.Directive nodeDirective : directives) {
AppliedDirectiveImpl d = new AppliedDirectiveImpl();
d.setDirective(getDirectiveDefinition(nodeDirective.getName()));
// Let's read its arguments
if (nodeDirective.getArguments() != null) {
for (Argument a : nodeDirective.getArguments()) {
// We store the graphql.language.Value as we receive it. We may not have parsed the relevant
// Object to check its field, and obviously, we can"t instanciate any object or enum yet, as we
// dont't even generated any code.
d.getArgumentValues().put(a.getName(), a.getValue());
}
}
ret.add(d);
}
// for
}
return ret;
}
Aggregations