Search in sources :

Example 1 with AppliedDirective

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>&#064;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);
    }
}
Also used : AppliedDirective(com.graphql_java_generator.plugin.language.AppliedDirective) FieldTypeAST(com.graphql_java_generator.plugin.language.FieldTypeAST) ArrayList(java.util.ArrayList) FieldImpl(com.graphql_java_generator.plugin.language.impl.FieldImpl) Field(com.graphql_java_generator.plugin.language.Field) ObjectType(com.graphql_java_generator.plugin.language.impl.ObjectType) InterfaceType(com.graphql_java_generator.plugin.language.impl.InterfaceType) InterfaceType(com.graphql_java_generator.plugin.language.impl.InterfaceType) Type(com.graphql_java_generator.plugin.language.Type) ObjectType(com.graphql_java_generator.plugin.language.impl.ObjectType) HashSet(java.util.HashSet)

Example 2 with AppliedDirective

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;
}
Also used : AppliedDirective(com.graphql_java_generator.plugin.language.AppliedDirective) Argument(graphql.language.Argument) ArrayList(java.util.ArrayList) AppliedDirectiveImpl(com.graphql_java_generator.plugin.language.impl.AppliedDirectiveImpl)

Aggregations

AppliedDirective (com.graphql_java_generator.plugin.language.AppliedDirective)2 ArrayList (java.util.ArrayList)2 Field (com.graphql_java_generator.plugin.language.Field)1 FieldTypeAST (com.graphql_java_generator.plugin.language.FieldTypeAST)1 Type (com.graphql_java_generator.plugin.language.Type)1 AppliedDirectiveImpl (com.graphql_java_generator.plugin.language.impl.AppliedDirectiveImpl)1 FieldImpl (com.graphql_java_generator.plugin.language.impl.FieldImpl)1 InterfaceType (com.graphql_java_generator.plugin.language.impl.InterfaceType)1 ObjectType (com.graphql_java_generator.plugin.language.impl.ObjectType)1 Argument (graphql.language.Argument)1 HashSet (java.util.HashSet)1