You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2021/05/13 17:00:10 UTC

[sling-org-apache-sling-graphql-core] 01/01: SLING-10375 - Explicitly define the supported directives in GraphQL Core

This is an automated email from the ASF dual-hosted git repository.

radu pushed a commit to branch issue/SLING-10375
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git

commit f2df009897b32571fa400dfa14ba6bc22cd5ecbb
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Thu May 13 18:58:52 2021 +0200

    SLING-10375 - Explicitly define the supported directives in GraphQL Core
    
    * added the custom directives supported by this module to the
    TypeDefinitionRegistry
    * documented the directives in the README
---
 README.md                                          |  74 +++---
 .../sling/graphql/core/directives/Directives.java  | 103 ++++++++
 .../graphql/core/engine/DefaultQueryExecutor.java  | 284 +++++++++++----------
 src/test/resources/failing-fetcher-schema.txt      |   7 -
 src/test/resources/failing-schema.txt              |   7 -
 .../resources/failing-type-resolver-schema.txt     |   7 -
 .../apps/graphql/test/one/GQLschema.jsp            |  14 -
 src/test/resources/paginated-humans-schema.txt     |  14 -
 src/test/resources/scalars-schema.txt              |   9 +-
 src/test/resources/test-schema-selected-foryou.txt |   9 +-
 src/test/resources/test-schema.txt                 |  18 +-
 11 files changed, 294 insertions(+), 252 deletions(-)

diff --git a/README.md b/README.md
index e3d0c57..27e9c0f 100644
--- a/README.md
+++ b/README.md
@@ -20,9 +20,6 @@ need to use their APIs directly.
 The [GraphQL sample website](https://github.com/apache/sling-samples/tree/master/org.apache.sling.graphql.samples.website)
 provides usage examples and demonstrates using GraphQL queries (and Handlebars templates) on both the server and
 client sides.
-
-> As I write this, work is ongoing at [SLING-9550](https://issues.apache.org/jira/browse/SLING-9550) to implement custom 
-> GraphQL Scalars
  
 ## Supported GraphQL endpoint styles
 
@@ -87,24 +84,23 @@ schemas dynamically, taking request selectors into account.
 Unless you have specific needs not covered by this mechanism, there's no need to implement your
 own `SchemaProvider` services.
 
-## SlingDataFetcher selection with Schema Directives
-
-The GraphQL schemas used by this module can be enhanced using
-[schema directives](http://spec.graphql.org/June2018/#sec-Language.Directives)
-(see also the [Apollo docs](https://www.apollographql.com/docs/graphql-tools/schema-directives/) for how those work)
-that select specific `SlingDataFetcher` services to return the appropriate data.
-
-A default data fetcher is used for types and fields which have no such directive.
+## SlingDataFetcher selection using the `@fetcher` directive
 
-Here's a simple example, the test code has more:
+The following `@fetcher` directive is automatically defined by this module:
 
     # This directive maps fields to our Sling data fetchers
     directive @fetcher(
-        name : String,
+        name : String!,
         options : String = "",
         source : String = ""
     ) on FIELD_DEFINITION
 
+A field using the `@fetcher` directive allows selecting a specific `SlingDataFetcher` service to return the appropriate data.
+
+Fileds which do not have such a directive will be retrieved using the default data fetcher.
+
+Here's a simple example, the test code has more:
+
     type Query {
       withTestingSelector : TestData @fetcher(name:"test/pipe")
     }
@@ -123,21 +119,20 @@ which have Java package names that start with `org.apache.sling`.
 The `<options>` and `<source>` arguments of the directive can be used by the
 `SlingDataFetcher` services to influence their behavior.
 
-## SlingTypeResolver selection with Schema Directives
+## SlingTypeResolver selection using the `@resolver` directive
 
-The GraphQL schemas used by this module can be enhanced using
-[schema directives](http://spec.graphql.org/June2018/#sec-Language.Directives)
-(see also the [Apollo docs](https://www.apollographql.com/docs/graphql-tools/schema-directives/) for how those work)
-that select specific `SlingTypeResolver` services to return the appropriate GraphQL object type using Unions.
-
-Here's a simple example, the test code has more:
+The following `@resolver` directive is automatically defined by this module:
 
     # This directive maps the corresponding type resolver to a given Union
     directive @resolver(
-        name: String, 
+        name: String!, 
         options: String = "", 
         source: String = ""
-    ) on UNION
+    ) on UNION | INTERFACE
+
+A `Union` or `Interface` type can provide a `@resolver` directive, to select a specific `SlingTypeResolver` service to return the appropriate GraphQL object type.
+
+Here's a simple example, the test code has more:
 
     union TestUnion @resolver(name : "test/resolver", source : "TestUnion") = Type_1 | Type_2 | Type_3 | Type_4
 
@@ -151,18 +146,35 @@ which have Java package names that start with `org.apache.sling`.
 The `<options>` and `<source>` arguments of the directive can be used by the
 `SlingTypeResolver` services to influence their behavior.
 
-## Result Set Pagination
+## Result Set Pagination using the `@connection` and `@fetcher` directives
+
+This module implements support for the [Relay Cursor Connections](https://relay.dev/graphql/connections.htm)
+specification, via the `@connection` directive, coupled with a `@fetcher` directive. The `@connection` directive
+has the following definition:
+
+    directive @connection(
+      for: String!
+    ) on FIELD_DEFINITION
+
+With this in mind, your schema that supports pagination can look like:
 
-The [GenericConnection](./src/main/java/org/apache/sling/graphql/core/helpers/pagination/GenericConnection.java) class provides support
-for paginated results, following the [Relay Cursor Connections](https://relay.dev/graphql/connections.htm) specification.
+    type Query {
+        paginatedHumans (after : String, limit : Int) : HumanConnection @connection(for: "Human") @fetcher(name:"humans/connection")
+    }
+
+    type Human {
+        id: ID!
+        name: String!
+        address: String
+    }
 
-With this utility class, you just need to supply an `Iterator` on your data, a function to generate a string that represents the cursor
-for a given object, and optional parameters to control the page start and length.
+The [GenericConnection](./src/main/java/org/apache/sling/graphql/core/helpers/pagination/GenericConnection.java) class,
+together with the [`org.apache.sling.graphql.api.pagination`](./src/main/java/org/apache/sling/graphql/api/pagination) API
+provide support for paginated results. With this utility class, you just need to supply an `Iterator` on your data, a
+function to generate a string that represents the cursor for a given object, and optional parameters to control the
+page start and length.
 
-The [QueryDataFetcherComponent](./src/test/java/org/apache/sling/graphql/core/mocks/QueryDataFetcherComponent.java) test class has a 
-concrete example. The below code is sufficient to produce a paginated result according to the Relay spec, assuming the GraphQL schema
-contains the required types. We are working on a schema directive to generate those connection types automatically, but for now they
-can be added manually to a schema, like [the one used in this test](./src/test/resources/initial-content/apps/graphql/test/one/GQLschema.jsp).
+The [QueryDataFetcherComponent](./src/test/java/org/apache/sling/graphql/core/mocks/QueryDataFetcherComponent.java) provides a usage example: 
 
     // fake test data simulating a query
     final List<Resource> data = new ArrayList<>();
diff --git a/src/main/java/org/apache/sling/graphql/core/directives/Directives.java b/src/main/java/org/apache/sling/graphql/core/directives/Directives.java
new file mode 100644
index 0000000..a21f878
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/core/directives/Directives.java
@@ -0,0 +1,103 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.graphql.core.directives;
+
+import graphql.introspection.Introspection;
+import graphql.language.Description;
+import graphql.language.DirectiveDefinition;
+import graphql.language.DirectiveLocation;
+import graphql.language.InputValueDefinition;
+import graphql.language.NonNullType;
+import graphql.language.StringValue;
+import graphql.language.TypeName;
+
+public class Directives {
+
+    public static final DirectiveDefinition CONNECTION = DirectiveDefinition.newDirectiveDefinition()
+            .name("connection")
+            .directiveLocation(DirectiveLocation.newDirectiveLocation().name(Introspection.DirectiveLocation.FIELD_DEFINITION.name()).build())
+            .description(new Description("Marks a connection type according to the Relay specification.", null, false))
+            .inputValueDefinition(
+                    InputValueDefinition.newInputValueDefinition()
+                            .name("for")
+                            .description(new Description("The type for which the connection is created.", null, false))
+                            .type(NonNullType.newNonNullType(TypeName.newTypeName("String").build()).build())
+                            .build()
+            )
+            .build();
+
+    public static final DirectiveDefinition FETCHER = DirectiveDefinition.newDirectiveDefinition()
+            .name("fetcher")
+            .directiveLocation(DirectiveLocation.newDirectiveLocation().name(Introspection.DirectiveLocation.FIELD_DEFINITION.name()).build())
+            .description(new Description("Maps a field to a SlingDataDetcher.", null, false))
+            .inputValueDefinition(
+                    InputValueDefinition.newInputValueDefinition()
+                            .name("name")
+                            .description(new Description("The name with which the SlingDataFetcher was registered.", null, false))
+                            .type(NonNullType.newNonNullType(TypeName.newTypeName("String").build()).build())
+                            .build()
+            )
+            .inputValueDefinition(
+                    InputValueDefinition.newInputValueDefinition()
+                            .name("options")
+                            .description(new Description("Options passed to the SlingDataFetcher.", null, false))
+                            .type(TypeName.newTypeName("String").build())
+                            .defaultValue(new StringValue(""))
+                            .build()
+            )
+            .inputValueDefinition(
+                    InputValueDefinition.newInputValueDefinition()
+                            .name("source")
+                            .description(new Description("Source information passed to the SlingDataFetcher.", null, false))
+                            .type(TypeName.newTypeName("String").build())
+                            .defaultValue(new StringValue(""))
+                            .build()
+            )
+            .build();
+
+    public static final DirectiveDefinition RESOLVER = DirectiveDefinition.newDirectiveDefinition()
+            .name("resolver")
+            .directiveLocation(DirectiveLocation.newDirectiveLocation().name(Introspection.DirectiveLocation.INTERFACE.name()).build())
+            .directiveLocation(DirectiveLocation.newDirectiveLocation().name(Introspection.DirectiveLocation.UNION.name()).build())
+            .description(new Description("Maps a type to a SlingTypeResolver.", null, false))
+            .inputValueDefinition(
+                    InputValueDefinition.newInputValueDefinition()
+                            .name("name")
+                            .description(new Description("The name with which the SlingTypeResolver was registered.", null, false))
+                            .type(NonNullType.newNonNullType(TypeName.newTypeName("String").build()).build())
+                            .build()
+            )
+            .inputValueDefinition(
+                    InputValueDefinition.newInputValueDefinition()
+                            .name("options")
+                            .description(new Description("Options passed to the SlingTypeResolver.", null, false))
+                            .type(TypeName.newTypeName("String").build())
+                            .defaultValue(new StringValue(""))
+                            .build()
+            )
+            .inputValueDefinition(
+                    InputValueDefinition.newInputValueDefinition()
+                            .name("source")
+                            .description(new Description("Source information passed to the SlingTypeResolver.", null, false))
+                            .type(TypeName.newTypeName("String").build())
+                            .defaultValue(new StringValue(""))
+                            .build()
+            )
+            .build();
+}
diff --git a/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java b/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
index 843f281..bf8d803 100644
--- a/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
+++ b/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
@@ -37,6 +37,7 @@ import org.apache.sling.graphql.api.SlingGraphQLException;
 import org.apache.sling.graphql.api.SlingTypeResolver;
 import org.apache.sling.graphql.api.engine.QueryExecutor;
 import org.apache.sling.graphql.api.engine.ValidationResult;
+import org.apache.sling.graphql.core.directives.Directives;
 import org.apache.sling.graphql.core.hash.SHA256Hasher;
 import org.apache.sling.graphql.core.scalars.SlingScalarsProvider;
 import org.apache.sling.graphql.core.schema.RankedSchemaProviders;
@@ -137,28 +138,6 @@ public class DefaultQueryExecutor implements QueryExecutor {
         int schemaCacheSize() default 128;
     }
 
-    private class ExecutionContext {
-        final GraphQLSchema schema;
-        final ExecutionInput input;
-
-        ExecutionContext(@NotNull String query, @NotNull Map<String, Object> variables, @NotNull Resource queryResource, @NotNull String[] selectors) 
-        throws ScriptException {
-            final String schemaSdl = prepareSchemaDefinition(schemaProvider, queryResource, selectors);
-            if (schemaSdl == null) {
-                throw new SlingGraphQLException(String.format("Cannot get a schema for resource %s and selectors %s.", queryResource,
-                        Arrays.toString(selectors)));
-            }
-            LOGGER.debug("Resource {} maps to GQL schema {}", queryResource.getPath(), schemaSdl);
-            final TypeDefinitionRegistry typeDefinitionRegistry = getTypeDefinitionRegistry(schemaSdl, queryResource, selectors);
-            schema = buildSchema(typeDefinitionRegistry, queryResource);
-            input = ExecutionInput.newExecutionInput()
-                    .query(query)
-                    .variables(variables)
-                    .build();
-
-        }
-    }
-
     @Activate
     public void activate(Config config) {
         int schemaCacheSize = config.schemaCacheSize();
@@ -232,119 +211,6 @@ public class DefaultQueryExecutor implements QueryExecutor {
         }
     }
 
-    private RuntimeWiring buildWiring(TypeDefinitionRegistry typeRegistry, Iterable<GraphQLScalarType> scalars, Resource r) {
-        List<ObjectTypeDefinition> types = typeRegistry.getTypes(ObjectTypeDefinition.class);
-        RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();
-        for (ObjectTypeDefinition type : types) {
-            builder.type(type.getName(), typeWiring -> {
-                for (FieldDefinition field : type.getFieldDefinitions()) {
-                    try {
-                        DataFetcher<Object> fetcher = getDataFetcher(field, r);
-                        if (fetcher != null) {
-                            typeWiring.dataFetcher(field.getName(), fetcher);
-                        }
-                    } catch (SlingGraphQLException e) {
-                        throw e;
-                    } catch (Exception e) {
-                        throw new SlingGraphQLException("Exception while building wiring.", e);
-                    }
-                }
-                handleConnectionTypes(type, typeRegistry);
-                return typeWiring;
-            });
-        }
-        scalars.forEach(builder::scalar);
-        List<UnionTypeDefinition> unionTypes = typeRegistry.getTypes(UnionTypeDefinition.class);
-        for (UnionTypeDefinition type : unionTypes) {
-            wireTypeResolver(builder, type, r);
-        }
-        List<InterfaceTypeDefinition> interfaceTypes = typeRegistry.getTypes(InterfaceTypeDefinition.class);
-        for (InterfaceTypeDefinition type : interfaceTypes) {
-            wireTypeResolver(builder, type, r);
-        }
-        return builder.build();
-    }
-
-    private <T extends TypeDefinition<T>> void wireTypeResolver(RuntimeWiring.Builder builder, TypeDefinition<T> type, Resource r) {
-        try {
-            TypeResolver resolver = getTypeResolver(type, r);
-            if (resolver != null) {
-                builder.type(type.getName(), typeWriting -> typeWriting.typeResolver(resolver));
-            }
-        } catch (SlingGraphQLException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new SlingGraphQLException("Exception while building wiring.", e);
-        }
-    }
-
-    private String getDirectiveArgumentValue(Directive d, String name) {
-        final Argument a = d.getArgument(name);
-        if (a != null && a.getValue() instanceof StringValue) {
-            return ((StringValue) a.getValue()).getValue();
-        }
-        return null;
-    }
-
-    private @NotNull String validateFetcherName(String name) {
-        if (SlingDataFetcherSelector.nameMatchesPattern(name)) {
-            return name;
-        }
-        throw new SlingGraphQLException(String.format("Invalid fetcher name %s, does not match %s",
-                name, SlingDataFetcherSelector.FETCHER_NAME_PATTERN));
-    }
-
-    private @NotNull String validateResolverName(String name) {
-        if (SlingTypeResolverSelector.nameMatchesPattern(name)) {
-            return name;
-        }
-        throw new SlingGraphQLException(String.format("Invalid type resolver name %s, does not match %s",
-                name, SlingTypeResolverSelector.RESOLVER_NAME_PATTERN));
-    }
-
-    private DataFetcher<Object> getDataFetcher(FieldDefinition field, Resource currentResource) {
-        DataFetcher<Object> result = null;
-        final Directive d = field.getDirective(FETCHER_DIRECTIVE);
-        if (d != null) {
-            final String name = validateFetcherName(getDirectiveArgumentValue(d, FETCHER_NAME));
-            final String options = getDirectiveArgumentValue(d, FETCHER_OPTIONS);
-            final String source = getDirectiveArgumentValue(d, FETCHER_SOURCE);
-            SlingDataFetcher<Object> f = dataFetcherSelector.getSlingFetcher(name);
-            if (f != null) {
-                result = new SlingDataFetcherWrapper<>(f, currentResource, options, source);
-            }
-        }
-        return result;
-    }
-
-    private <T extends TypeDefinition<T>> TypeResolver getTypeResolver(TypeDefinition<T> typeDefinition, Resource currentResource) {
-        TypeResolver resolver = null;
-        final Directive d = typeDefinition.getDirective(RESOLVER_DIRECTIVE);
-        if (d != null) {
-            final String name = validateResolverName(getDirectiveArgumentValue(d, RESOLVER_NAME));
-            final String options = getDirectiveArgumentValue(d, RESOLVER_OPTIONS);
-            final String source = getDirectiveArgumentValue(d, RESOLVER_SOURCE);
-            SlingTypeResolver<Object> r = typeResolverSelector.getSlingTypeResolver(name);
-            if (r != null) {
-                resolver = new SlingTypeResolverWrapper(r, currentResource, options, source);
-            }
-        }
-        return resolver;
-    }
-
-    private @Nullable String prepareSchemaDefinition(@NotNull SchemaProvider schemaProvider,
-                                                     @NotNull org.apache.sling.api.resource.Resource resource,
-                                                     @NotNull String[] selectors) throws ScriptException {
-        try {
-            return schemaProvider.getSchema(resource, selectors);
-        } catch (Exception e) {
-            final ScriptException up = new ScriptException("Schema provider failed");
-            up.initCause(e);
-            LOGGER.info("Schema provider Exception", up);
-            throw up;
-        }
-    }
-
     TypeDefinitionRegistry getTypeDefinitionRegistry(@NotNull String sdl, @NotNull Resource currentResource, @NotNull String[] selectors) {
         readLock.lock();
         String newHash = SHA256Hasher.getHash(sdl);
@@ -365,6 +231,12 @@ public class DefaultQueryExecutor implements QueryExecutor {
                 if (!newHash.equals(oldHash)) {
                     resourceToHashMap.put(resourceToHashMapKey, newHash);
                     TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
+                    typeRegistry.add(Directives.CONNECTION);
+                    typeRegistry.add(Directives.FETCHER);
+                    typeRegistry.add(Directives.RESOLVER);
+                    for (ObjectTypeDefinition typeDefinition : typeRegistry.getTypes(ObjectTypeDefinition.class)) {
+                        handleConnectionTypes(typeDefinition, typeRegistry);
+                    }
                     hashToSchemaMap.put(newHash, typeRegistry);
                     return typeRegistry;
                 }
@@ -380,12 +252,6 @@ public class DefaultQueryExecutor implements QueryExecutor {
         }
     }
 
-    private GraphQLSchema buildSchema(@NotNull TypeDefinitionRegistry typeRegistry, @NotNull Resource currentResource) {
-        Iterable<GraphQLScalarType> scalars = scalarsProvider.getCustomScalars(typeRegistry.scalars());
-        RuntimeWiring runtimeWiring = buildWiring(typeRegistry, scalars, currentResource);
-        return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
-    }
-
     private String getCacheKey(@NotNull Resource resource, @NotNull String[] selectors) {
         return resource.getPath() + ":" + String.join(".", selectors);
     }
@@ -459,4 +325,140 @@ public class DefaultQueryExecutor implements QueryExecutor {
         }
     }
 
+    private class ExecutionContext {
+        final GraphQLSchema schema;
+        final ExecutionInput input;
+
+        ExecutionContext(@NotNull String query, @NotNull Map<String, Object> variables, @NotNull Resource queryResource, @NotNull String[] selectors)
+                throws ScriptException {
+            final String schemaSdl = prepareSchemaDefinition(schemaProvider, queryResource, selectors);
+            if (schemaSdl == null) {
+                throw new SlingGraphQLException(String.format("Cannot get a schema for resource %s and selectors %s.", queryResource,
+                        Arrays.toString(selectors)));
+            }
+            LOGGER.debug("Resource {} maps to GQL schema {}", queryResource.getPath(), schemaSdl);
+            final TypeDefinitionRegistry typeDefinitionRegistry = getTypeDefinitionRegistry(schemaSdl, queryResource, selectors);
+            Iterable<GraphQLScalarType> scalars = scalarsProvider.getCustomScalars(typeDefinitionRegistry.scalars());
+            RuntimeWiring runtimeWiring = buildWiring(typeDefinitionRegistry, scalars, queryResource);
+            schema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
+            input = ExecutionInput.newExecutionInput()
+                    .query(query)
+                    .variables(variables)
+                    .build();
+
+        }
+
+        private RuntimeWiring buildWiring(TypeDefinitionRegistry typeRegistry, Iterable<GraphQLScalarType> scalars, Resource r) {
+            List<ObjectTypeDefinition> types = typeRegistry.getTypes(ObjectTypeDefinition.class);
+            RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();
+            for (ObjectTypeDefinition type : types) {
+                builder.type(type.getName(), typeWiring -> {
+                    for (FieldDefinition field : type.getFieldDefinitions()) {
+                        try {
+                            DataFetcher<Object> fetcher = getDataFetcher(field, r);
+                            if (fetcher != null) {
+                                typeWiring.dataFetcher(field.getName(), fetcher);
+                            }
+                        } catch (SlingGraphQLException e) {
+                            throw e;
+                        } catch (Exception e) {
+                            throw new SlingGraphQLException("Exception while building wiring.", e);
+                        }
+                    }
+                    return typeWiring;
+                });
+            }
+            scalars.forEach(builder::scalar);
+            List<UnionTypeDefinition> unionTypes = typeRegistry.getTypes(UnionTypeDefinition.class);
+            for (UnionTypeDefinition type : unionTypes) {
+                wireTypeResolver(builder, type, r);
+            }
+            List<InterfaceTypeDefinition> interfaceTypes = typeRegistry.getTypes(InterfaceTypeDefinition.class);
+            for (InterfaceTypeDefinition type : interfaceTypes) {
+                wireTypeResolver(builder, type, r);
+            }
+            return builder.build();
+        }
+
+        private <T extends TypeDefinition<T>> void wireTypeResolver(RuntimeWiring.Builder builder, TypeDefinition<T> type, Resource r) {
+            try {
+                TypeResolver resolver = getTypeResolver(type, r);
+                if (resolver != null) {
+                    builder.type(type.getName(), typeWriting -> typeWriting.typeResolver(resolver));
+                }
+            } catch (SlingGraphQLException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new SlingGraphQLException("Exception while building wiring.", e);
+            }
+        }
+
+        private DataFetcher<Object> getDataFetcher(FieldDefinition field, Resource currentResource) {
+            DataFetcher<Object> result = null;
+            final Directive d = field.getDirective(FETCHER_DIRECTIVE);
+            if (d != null) {
+                final String name = validateFetcherName(getDirectiveArgumentValue(d, FETCHER_NAME));
+                final String options = getDirectiveArgumentValue(d, FETCHER_OPTIONS);
+                final String source = getDirectiveArgumentValue(d, FETCHER_SOURCE);
+                SlingDataFetcher<Object> f = dataFetcherSelector.getSlingFetcher(name);
+                if (f != null) {
+                    result = new SlingDataFetcherWrapper<>(f, currentResource, options, source);
+                }
+            }
+            return result;
+        }
+
+        private <T extends TypeDefinition<T>> TypeResolver getTypeResolver(TypeDefinition<T> typeDefinition, Resource currentResource) {
+            TypeResolver resolver = null;
+            final Directive d = typeDefinition.getDirective(RESOLVER_DIRECTIVE);
+            if (d != null) {
+                final String name = validateResolverName(getDirectiveArgumentValue(d, RESOLVER_NAME));
+                final String options = getDirectiveArgumentValue(d, RESOLVER_OPTIONS);
+                final String source = getDirectiveArgumentValue(d, RESOLVER_SOURCE);
+                SlingTypeResolver<Object> r = typeResolverSelector.getSlingTypeResolver(name);
+                if (r != null) {
+                    resolver = new SlingTypeResolverWrapper(r, currentResource, options, source);
+                }
+            }
+            return resolver;
+        }
+
+        private String getDirectiveArgumentValue(Directive d, String name) {
+            final Argument a = d.getArgument(name);
+            if (a != null && a.getValue() instanceof StringValue) {
+                return ((StringValue) a.getValue()).getValue();
+            }
+            return null;
+        }
+
+        private @NotNull String validateFetcherName(String name) {
+            if (SlingDataFetcherSelector.nameMatchesPattern(name)) {
+                return name;
+            }
+            throw new SlingGraphQLException(String.format("Invalid fetcher name %s, does not match %s",
+                    name, SlingDataFetcherSelector.FETCHER_NAME_PATTERN));
+        }
+
+        private @NotNull String validateResolverName(String name) {
+            if (SlingTypeResolverSelector.nameMatchesPattern(name)) {
+                return name;
+            }
+            throw new SlingGraphQLException(String.format("Invalid type resolver name %s, does not match %s",
+                    name, SlingTypeResolverSelector.RESOLVER_NAME_PATTERN));
+        }
+
+        private @Nullable String prepareSchemaDefinition(@NotNull SchemaProvider schemaProvider,
+                                                         @NotNull org.apache.sling.api.resource.Resource resource,
+                                                         @NotNull String[] selectors) throws ScriptException {
+            try {
+                return schemaProvider.getSchema(resource, selectors);
+            } catch (Exception e) {
+                final ScriptException up = new ScriptException("Schema provider failed");
+                up.initCause(e);
+                LOGGER.info("Schema provider Exception", up);
+                throw up;
+            }
+        }
+    }
+
 }
diff --git a/src/test/resources/failing-fetcher-schema.txt b/src/test/resources/failing-fetcher-schema.txt
index a559d0f..e5642e0 100644
--- a/src/test/resources/failing-fetcher-schema.txt
+++ b/src/test/resources/failing-fetcher-schema.txt
@@ -15,13 +15,6 @@
 # * specific language governing permissions and limitations
 # * under the License.
 
-# This directive maps fields to our Sling data fetchers
-directive @fetcher(
-    name : String,
-    options : String = "",
-    source : String = ""
-) on FIELD_DEFINITION
-
 # Schema used to test invalid fetcher names
 type Query {
     currentResource : SlingResource @fetcher(name:"echoNS/echo")
diff --git a/src/test/resources/failing-schema.txt b/src/test/resources/failing-schema.txt
index a559d0f..e5642e0 100644
--- a/src/test/resources/failing-schema.txt
+++ b/src/test/resources/failing-schema.txt
@@ -15,13 +15,6 @@
 # * specific language governing permissions and limitations
 # * under the License.
 
-# This directive maps fields to our Sling data fetchers
-directive @fetcher(
-    name : String,
-    options : String = "",
-    source : String = ""
-) on FIELD_DEFINITION
-
 # Schema used to test invalid fetcher names
 type Query {
     currentResource : SlingResource @fetcher(name:"echoNS/echo")
diff --git a/src/test/resources/failing-type-resolver-schema.txt b/src/test/resources/failing-type-resolver-schema.txt
index 7451b3a..14b60e2 100644
--- a/src/test/resources/failing-type-resolver-schema.txt
+++ b/src/test/resources/failing-type-resolver-schema.txt
@@ -15,13 +15,6 @@
 # * specific language governing permissions and limitations
 # * under the License.
 
-# This directive maps fields to our Sling data fetchers
-directive @resolver(
-    name : String,
-    options : String = "",
-    source : String = ""
-) on UNION
-
 # GraphQL Schema used for our tests
 type Query {
     currentResource : SlingResource @fetcher(name:"echoNS/echo")
diff --git a/src/test/resources/initial-content/apps/graphql/test/one/GQLschema.jsp b/src/test/resources/initial-content/apps/graphql/test/one/GQLschema.jsp
index fca94ba..405d626 100644
--- a/src/test/resources/initial-content/apps/graphql/test/one/GQLschema.jsp
+++ b/src/test/resources/initial-content/apps/graphql/test/one/GQLschema.jsp
@@ -17,20 +17,6 @@
 * under the License.
 --%>
 
-# This directive maps fields to our Sling data fetchers
-directive @fetcher(
-    name : String,
-    options : String = "",
-    source : String = ""
-) on FIELD_DEFINITION
-
-# This directive will generate the additional types for a Connection,
-# according to the Relay Connections specification from
-# https://relay.dev/graphql/connections.htm
-directive @connection(
-    for : String
-) on FIELD_DEFINITION
-
 type Query {
   oneSchemaResource : SlingResource @fetcher(name:"test/pipe" source:"$")
   oneSchemaQuery : SlingResourceConnection @connection(for: "SlingResource") @fetcher(name:"test/query")
diff --git a/src/test/resources/paginated-humans-schema.txt b/src/test/resources/paginated-humans-schema.txt
index 3fecc3d..19ee626 100644
--- a/src/test/resources/paginated-humans-schema.txt
+++ b/src/test/resources/paginated-humans-schema.txt
@@ -15,20 +15,6 @@
 # * specific language governing permissions and limitations
 # * under the License.
 
-# This directive maps fields to our Sling data fetchers
-directive @fetcher(
-    name : String,
-    options : String = "",
-    source : String = ""
-) on FIELD_DEFINITION
-
-# This directive will generate the additional types for a Connection,
-# according to the Relay Connections specification from
-# https://relay.dev/graphql/connections.htm
-directive @connection(
-    for : String
-) on FIELD_DEFINITION
-
 type Query {
     paginatedHumans (after : String, limit : Int) : HumanConnection @connection(for: "Human") @fetcher(name:"humans/connection")
 }
diff --git a/src/test/resources/scalars-schema.txt b/src/test/resources/scalars-schema.txt
index 75851ff..fb5735f 100644
--- a/src/test/resources/scalars-schema.txt
+++ b/src/test/resources/scalars-schema.txt
@@ -15,13 +15,6 @@
 # * specific language governing permissions and limitations
 # * under the License.
 
-# This directive maps fields to our Sling data fetchers
-directive @fetcher(
-    name : String,
-    options : String = "",
-    source : String = ""
-) on FIELD_DEFINITION
-
 scalar URL
 scalar UppercaseString
 
@@ -32,4 +25,4 @@ type Query {
 type AddressInfo {
     hostname: UppercaseString
     url: URL
-}
\ No newline at end of file
+}
diff --git a/src/test/resources/test-schema-selected-foryou.txt b/src/test/resources/test-schema-selected-foryou.txt
index 0c22a9d..d34d190 100644
--- a/src/test/resources/test-schema-selected-foryou.txt
+++ b/src/test/resources/test-schema-selected-foryou.txt
@@ -15,13 +15,6 @@
 # * specific language governing permissions and limitations
 # * under the License.
 
-# This directive maps fields to our Sling data fetchers
-directive @fetcher(
-    name : String,
-    options : String = "",
-    source : String = ""
-) on FIELD_DEFINITION
-
 # GraphQL Schema used for our tests
 # when specific request selectors are used
 type Query {
@@ -31,4 +24,4 @@ type Query {
 type SlingResource { 
     fortyTwo: Int @fetcher(name:"test/fortyTwo")
     path: Int @fetcher(name:"test/fortyTwo")
-}
\ No newline at end of file
+}
diff --git a/src/test/resources/test-schema.txt b/src/test/resources/test-schema.txt
index e7fb1b0..02dc7d5 100644
--- a/src/test/resources/test-schema.txt
+++ b/src/test/resources/test-schema.txt
@@ -15,21 +15,9 @@
 # * specific language governing permissions and limitations
 # * under the License.
 
-# This directive maps fields to our Sling data fetchers
-directive @fetcher(
-    name : String,
-    options : String = "",
-    source : String = ""
-) on FIELD_DEFINITION
-
-# This directive to maps types
-directive @resolver(
-    name : String,
-    options : String = "",
-    source : String = ""
-) on UNION | INTERFACE
-
-# GraphQL Schema used for our tests
+"""
+GraphQL Schema used for our tests
+"""
 type Query {
     currentResource : SlingResource @fetcher(name:"echoNS/echo")