All Downloads are FREE. Search and download functionalities are using the official Maven repository.

graphql.schema.idl.SchemaDecompiler Maven / Gradle / Ivy

The newest version!
package graphql.schema.idl;

import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLEnumValueDefinition;
import graphql.schema.GraphQLInputObjectType;
import graphql.schema.GraphQLInputType;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLNonNull;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLUnionType;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

/**
 * This can decompile an in memory GraphQL schema back to a logical schema definition
 */
public class SchemaDecompiler {

    /**
     * Options to use when decompiling a schema
     */
    public static class Options {
        private final boolean includeIntrospectionTypes;

        private final boolean includeScalars;

        private Options(boolean includeIntrospectionTypes, boolean includeScalars) {
            this.includeIntrospectionTypes = includeIntrospectionTypes;
            this.includeScalars = includeScalars;
        }

        public boolean isIncludeIntrospectionTypes() {
            return includeIntrospectionTypes;
        }

        public boolean isIncludeScalars() {
            return includeScalars;
        }

        public static Options defaultOptions() {
            return new Options(false, false);
        }

        /**
         * This will allow you to include introspection types that are contained in a schema
         *
         * @param flag whether to include them
         *
         * @return options
         */
        public Options includeIntrospectionTypes(boolean flag) {
            return new Options(flag, this.includeScalars);
        }

        /**
         * This will allow you to include scalar types that are contained in a schema
         *
         * @param flag whether to include them
         *
         * @return options
         */
        public Options includeScalarTypes(boolean flag) {
            return new Options(this.includeIntrospectionTypes, flag);
        }
    }

    private final Map> printers = new LinkedHashMap<>();
    private final Options options;

    public SchemaDecompiler() {
        this(Options.defaultOptions());
    }

    public SchemaDecompiler(Options options) {
        this.options = options;
        printers.put(GraphQLSchema.class, schemaPrinter());
        printers.put(GraphQLObjectType.class, objectPrinter());
        printers.put(GraphQLEnumType.class, enumPrinter());
        printers.put(GraphQLScalarType.class, scalarPrinter());
        printers.put(GraphQLInterfaceType.class, interfacePrinter());
        printers.put(GraphQLUnionType.class, unionPrinter());
        printers.put(GraphQLInputObjectType.class, inputObjectPrinter());
    }

    /**
     * This can decompile an in memory GraphQL schema back to a logical schema definition
     *
     * @param schema the schema in play
     *
     * @return the logical schema definition
     */
    public String decompile(GraphQLSchema schema) {
        StringWriter sw = new StringWriter();
        PrintWriter out = new PrintWriter(sw);

        printer(schema.getClass()).print(out, schema);

        List typesAsList = new ArrayList<>(schema.getAllTypesAsList());
        typesAsList.sort(Comparator.comparing(GraphQLType::getName));

        printType(out, typesAsList, GraphQLInputType.class);
        printType(out, typesAsList, GraphQLInterfaceType.class);
        printType(out, typesAsList, GraphQLUnionType.class);
        printType(out, typesAsList, GraphQLObjectType.class);
        printType(out, typesAsList, GraphQLEnumType.class);
        printType(out, typesAsList, GraphQLScalarType.class);

        return sw.toString();
    }
    private interface TypePrinter {

        void print(PrintWriter out, T type);

    }

    private boolean isIntrospectionType(GraphQLType type) {
        return !options.isIncludeIntrospectionTypes() && type.getName().startsWith("__");
    }

    private TypePrinter scalarPrinter() {
        return (out, type) -> {
            if (!options.isIncludeScalars()) {
                return;
            }
            if (!ScalarInfo.isStandardScalar(type)) {
                out.format("scalar %s\n\n", type.getName());
            }
        };
    }

    private TypePrinter enumPrinter() {
        return (out, type) -> {
            if (isIntrospectionType(type)) {
                return;
            }
            out.format("enum %s {\n", type.getName());
            for (GraphQLEnumValueDefinition enumValueDefinition : type.getValues()) {
                out.format("   %s\n", enumValueDefinition.getName());
            }
            out.format("}\n\n");
        };
    }

    private TypePrinter interfacePrinter() {
        return (out, type) -> {
            if (isIntrospectionType(type)) {
                return;
            }
            out.format("interface %s {\n", type.getName());
            type.getFieldDefinitions().forEach(fd ->
                    out.format("   %s%s : %s\n",
                            fd.getName(), argsString(fd.getArguments()), typeString(fd.getType())));
            out.format("}\n\n");
        };
    }

    private TypePrinter unionPrinter() {
        return (out, type) -> {
            if (isIntrospectionType(type)) {
                return;
            }
            out.format("union %s = ", type.getName());
            List types = type.getTypes();
            for (int i = 0; i < types.size(); i++) {
                GraphQLOutputType objectType = types.get(i);
                if (i > 0) {
                    out.format(" | ");
                }
                out.format("%s", objectType.getName());
            }
            out.format("}\n\n");
        };
    }


    private TypePrinter objectPrinter() {
        return (out, type) -> {
            if (isIntrospectionType(type)) {
                return;
            }
            out.format("type %s {\n", type.getName());
            type.getFieldDefinitions().forEach(fd ->
                    out.format("   %s%s : %s\n",
                            fd.getName(), argsString(fd.getArguments()), typeString(fd.getType())));
            out.format("}\n\n");
        };
    }


    private TypePrinter inputObjectPrinter() {
        return (out, type) -> {
            if (isIntrospectionType(type)) {
                return;
            }
            out.format("input %s {\n", type.getName());
            type.getFieldDefinitions().forEach(fd ->
                    out.format("   %s : %s\n",
                            fd.getName(), typeString(fd.getType())));
            out.format("}\n\n");
        };
    }

    private TypePrinter schemaPrinter() {
        return (out, type) -> {
            out.format("schema {\n");
            GraphQLObjectType queryType = type.getQueryType();
            GraphQLObjectType mutationType = type.getMutationType();
            if (queryType != null) {
                out.format("   query : %s\n", queryType.getName());
            }
            if (mutationType != null) {
                out.format("   mutation : %s\n", mutationType.getName());
            }
            out.format("}\n\n");
        };
    }

    String typeString(GraphQLType rawType) {
        StringBuilder sb = new StringBuilder();
        Stack stack = new Stack<>();

        GraphQLType type = rawType;
        while (true) {
            if (type instanceof GraphQLNonNull) {
                type = ((GraphQLNonNull) type).getWrappedType();
                stack.push("!");
            } else if (type instanceof GraphQLList) {
                type = ((GraphQLList) type).getWrappedType();
                sb.append("[");
                stack.push("]");
            } else {
                sb.append(type.getName());
                break;
            }
        }
        while (!stack.isEmpty()) {
            sb.append(stack.pop());
        }
        return sb.toString();

    }

    String argsString(List arguments) {
        int count = 0;
        StringBuilder sb = new StringBuilder();
        for (GraphQLArgument argument : arguments) {
            if (count == 0) {
                sb.append("(");
            } else {
                sb.append(", ");
            }
            sb.append(argument.getName()).append(" : ").append(typeString(argument.getType()));
            Object defaultValue = argument.getDefaultValue();
            if (defaultValue != null) {
                sb.append(" = ");
                if (defaultValue instanceof Number) {
                    sb.append(defaultValue);
                } else {
                    sb.append('"').append(defaultValue).append('"');
                }
            }
            count++;
        }
        if (count > 0) {
            sb.append(")");
        }
        return sb.toString();
    }

    @SuppressWarnings("unchecked")
    private  TypePrinter printer(Class clazz) {
        TypePrinter typePrinter = printers.computeIfAbsent(clazz,
                k -> (out, type) -> out.println("Type not implemented : " + type)
        );
        return (TypePrinter) typePrinter;
    }

    public String decompile(GraphQLType type) {
        StringWriter sw = new StringWriter();
        PrintWriter out = new PrintWriter(sw);

        printType(out, type);

        return sw.toString();
    }

    private void printType(PrintWriter out, List typesAsList, Class typeClazz) {
        typesAsList.stream()
                .filter(type -> type.getClass().equals(typeClazz))
                .forEach(type -> printType(out, type));
    }

    private void printType(PrintWriter out, GraphQLType type) {
        TypePrinter printer = printer(type.getClass());
        printer.print(out, type);
    }
}