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:09 UTC

[sling-org-apache-sling-graphql-core] branch issue/SLING-10375 created (now f2df009)

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

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


      at f2df009  SLING-10375 - Explicitly define the supported directives in GraphQL Core

This branch includes the following new commits:

     new f2df009  SLING-10375 - Explicitly define the supported directives in GraphQL Core

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


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

Posted by ra...@apache.org.
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")