neo4j-graphql / neo4j-graphql-java

Neo4j Labs Project: Pure JVM translation for GraphQL queries and mutations to Neo4j's Cypher
Apache License 2.0
105 stars 49 forks source link

Custom QueryHandler? #304

Closed rickardoberg closed 1 year ago

rickardoberg commented 1 year ago

I need to replace the default QueryHandler with my own, and am struggling to figure out how to do it. I can disabled the default one easily, but implementing a new version of it doesn't seem to be possible. Going by QueryHandler as a template, the Factory needs the Neo4jTypeDefinitionRegistry, but this is a private field in SchemaBuilder. There's also no way to register my own implementation in the "handlers" list, so not sure how to get invoked.

If it should indeed be possible to turn off the default QueryHandler+Factory, then what is the correct way to plug in a custom one?

Andy2003 commented 1 year ago

Currently, such customizations are not possible. But I could imagine corresponding extension points. You are welcome to create a PR, which I will then take a look at.

rickardoberg commented 1 year ago

Alright, so I think I managed to a reasonable workaround. Here's the code to create the schema. I let the QueryHandler be enabled but ignoring the types (marked with @entity directive) I want to override. `
String schema = new String(Resources.getResource("neo4j/graphql/schema2.gql").orElseThrow().openStream().readAllBytes(), StandardCharsets.UTF_8);

        TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser().parse(schema);

        // Find @entity types
        List<TypeDefinition> entityTypes = typeDefinitionRegistry.types().values().stream()
                .filter(td -> td.hasDirective(GraphQLModel.Directives.entity.name()))
                .toList();
        List<String> entityTypeNames = entityTypes.stream()
                .filter(td -> td.hasDirective(GraphQLModel.Directives.entity.name()))
                .map(td -> td.getName())
                .toList();

        TypeDefinitionRegistry neo4jTypeDefinitionRegistry = getNeo4jEnhancements();
        SchemaConfig schemaConfig = new SchemaConfig(new SchemaConfig.CRUDConfig(true, entityTypeNames), new SchemaConfig.CRUDConfig(false, Collections.emptyList()));

        RuntimeWiring.Builder runtimeWiringBuilder = RuntimeWiring.newRuntimeWiring();
        GraphQLCodeRegistry.Builder codeRegistryBuilder = GraphQLCodeRegistry.newCodeRegistry();
        SchemaBuilder schemaBuilder = new SchemaBuilder(typeDefinitionRegistry, schemaConfig);
        schemaBuilder.augmentTypes();
        schemaBuilder.registerScalars(runtimeWiringBuilder);
        schemaBuilder.registerTypeNameResolver(runtimeWiringBuilder);
        schemaBuilder.registerDataFetcher(codeRegistryBuilder, null, typeDefinitionRegistry);

        AugmentationHandler entityHandler = new EntityQueryHandler.Factory(schemaConfig, typeDefinitionRegistry, neo4jTypeDefinitionRegistry);

        typeDefinitionRegistry
                .getTypes(ImplementingTypeDefinition.class)
                .forEach(entityHandler::augmentType);

        typeDefinitionRegistry.getType("Query").map(ObjectTypeDefinition.class::cast)
                .map(ObjectTypeDefinition::getFieldDefinitions)
                .ifPresent(l -> l.forEach(fd ->
                {
                    DataFetcher<org.neo4j.graphql.Cypher> cypher = entityHandler.createDataFetcher(AugmentationHandler.OperationType.QUERY, fd);
                    if (cypher != null) {
                        codeRegistryBuilder.dataFetcher(FieldCoordinates.coordinates("Query", fd.getName()), cypher);
                    }
                }));

        GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(
                typeDefinitionRegistry,
                runtimeWiringBuilder.codeRegistry(codeRegistryBuilder).build()
        );

        return graphQLSchema;

` It's not pretty, and a way to have my custom handler be more integrated like the existing one would be better, for example to avoid having to read the Neo4j types, but at least it seems to be working.