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")