diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ecf6987..39fccf4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,29 +1,90 @@ name: Release -on: - workflow_dispatch +permissions: + contents: write + packages: write +on: + workflow_dispatch: + inputs: + releaseType: + type: choice + description: "Release type" + required: true + default: minor + options: + - patch + - minor + - major jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Release - uses: qcastel/github-actions-maven-release@v1.11.1 - with: - release-branch-name: "master" - maven-args: "-P sonatype" - git-release-bot-name: "release-bot" - git-release-bot-email: "release-bot@fleetpin.co.nz" - - gpg-enabled: "true" - gpg-key-id: ${{ secrets.GPG_KEY_ID }} - gpg-key: ${{ secrets.GPG_KEY }} - - maven-repo-server-id: sonatype - maven-repo-server-username: ${{ secrets.MVN_REPO_PRIVATE_REPO_USER }} - maven-repo-server-password: ${{ secrets.MVN_REPO_PRIVATE_REPO_PASSWORD }} - - access-token: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v4 + + - uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: 21 + distribution: zulu + - uses: whelk-io/maven-settings-xml-action@v22 + with: + servers: > + [ + { "id": "sonatype", "username": "${{ secrets.MVN_REPO_PRIVATE_REPO_USER }}", "password": "${{ secrets.MVN_REPO_PRIVATE_REPO_PASSWORD }}" } + ] + - name: set name + run: | + git config --global user.name "release-bot"; + git config --global user.email "release-bot@fleetpin.co.nz"; + + - name: add key + run: | + echo "${{ secrets.GPG_KEY }}" | base64 -d > private.key + gpg --batch --import ./private.key + rm ./private.key + gpg --list-secret-keys --keyid-format LONG + + - name: Get current development version + id: get_version + run: | + VERSION=$( mvn help:evaluate -Dexpression=project.version -q -DforceStdout | sed 's/-SNAPSHOT//' ) + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Generate versions + id: generate_versions + uses: WyriHaximus/github-action-next-semvers@v1.2.1 + with: + version: ${{ steps.get_version.outputs.version }} + + - name: Pick release version + id: pick_release_version + run: | + VERSION=$( + case ${{ github.event.inputs.releaseType }} in + ("minor") echo "${{ steps.generate_versions.outputs.minor }}" ;; + ("major") echo "${{ steps.generate_versions.outputs.major }}" ;; + ("patch") echo "${{ steps.generate_versions.outputs.patch }}" ;; + esac + ) + echo "version=$VERSION" >> $GITHUB_OUTPUT + + + - name: prepare + run: | + mvn release:prepare -Dusername=${{ secrets.GITHUB_TOKEN }} \ + -DreleaseVersion=${{ steps.pick_release_version.outputs.version }} \ + -DdevelopmentVersion=${{ steps.pick_release_version.outputs.version }}-SNAPSHOT \ + -P sonatype + + - name: release + run: | + mvn release:perform -Dusername=${{ secrets.GITHUB_TOKEN }} \ + -DreleaseVersion=${{ steps.pick_release_version.outputs.version }} \ + -DdevelopmentVersion=${{ steps.pick_release_version.outputs.version }}-SNAPSHOT \ + -Darguments="-DskipTests" \ + -P sonatype diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6290649..61bac3b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,21 +1,22 @@ name: Test -on: +on: push: jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/cache@v1 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Build with Maven - run: mvn -B test --file pom.xml + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: 21 + distribution: zulu + - name: Build with Maven + run: mvn -B test --file pom.xml diff --git a/.gitignore b/.gitignore index 7f52411..0369bb8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target/ /.classpath /.project -.settings \ No newline at end of file +.settings +.idea/* + diff --git a/README.md b/README.md index 44171fe..8b5d6ed 100644 --- a/README.md +++ b/README.md @@ -337,12 +337,42 @@ public class AssetRestrict implements RestrictType { ## Directives These are similar to GraphQL directives but just implemented on the java model -You define a custom annotation and add the `@Directive` to it -This annotation in then passed into the DirectiveCaller allowing you to add options to the annotation if need be +You define a custom annotation and add the `@Directive` to it. +The directive annotation must contain an array of DirectiveLocations which will be used in the GraphQL definition. +Any function defined in the annotation will be placed on the schema definition as an argument. ```java @Retention(RUNTIME) -@Directive(AdminOnly.AdminOnlyDirective.class) +@Directive( { Introspection.DirectiveLocation.FIELD_DEFINITION } ) +public @interface CustomDirective { + String input(); +} +``` +This directive can now be placed where set: +```java +@Query +@CustomDirective(input = "Custom Directive Contents") +public static String sayHello() { + return "Hello world"; +} +``` +Which will then end up on the schema like so +```graphql +directive @CustomDirective(input: String!) on FIELD_DEFINITION + +type Query { + sayHello: String! @CustomDirective(input: "Custom Directive Contents") +} +``` + +## DataFetcherWrapper +Similar to the setup of a Directive the DataFetcherWrapper is created as an +annotation. This annotation is then passed into the DirectiveCaller allowing +you to add options to the annotation if need be + +```java +@Retention(RUNTIME) +@DataFetcherWrapper(AdminOnly.AdminOnlyDirective.class) public @interface AdminOnly { ... } diff --git a/pom.xml b/pom.xml index 491e29d..e572a4c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,16 +1,33 @@ + 4.0.0 com.fleetpin graphql-builder - 1.0.2-SNAPSHOT + 3.1.0-SNAPSHOT GraphQL Builder Builds a graphql schema from a model using reflection - https://github.com/fleetpin/graphql-builder + https://github.com/ashley-taylor/graphql-builder - 5.6.0 + 5.11.2 UTF-8 + 2.18.0 + 1.17.0 + 1.2.1 + 22.3 @@ -27,9 +44,9 @@ - https://github.com/fleetpin/graphql-builder - scm:git:https://github.com/fleetpin/graphql-builder.git - scm:git:https://github.com/fleetpin/graphql-builder.git + https://github.com/ashley-taylor/graphql-builder + scm:git:https://github.com/ashley-taylor/graphql-builder.git + scm:git:https://github.com/ashley-taylor/graphql-builder.git HEAD @@ -56,56 +73,146 @@ org.apache.maven.plugins maven-compiler-plugin - 11 - 11 + 21 + 21 -parameters org.apache.maven.plugins maven-release-plugin - 2.5.3 + 3.1.1 + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.10.1 + + + attach-javadocs + + jar + + + + + + com.hubspot.maven.plugins + prettier-maven-plugin + 0.22 + + 1.4.0 + 160 + 4 + true + true + true + + + + validate + + + + org.pitest + pitest-maven + ${pitest.version} + + + org.pitest + pitest-junit5-plugin + ${pitest-junit5-plugin.version} + + + + 4 + false + false + + STRONGER + + + + + com.mycila + license-maven-plugin + 4.6 + + + +
src/license/license.txt
+
+
+
+
+ - com.graphql-java - graphql-java - 18.1 + com.graphql-java + graphql-java + ${graphql.version} + + + com.graphql-java + graphql-java-extended-scalars + 21.0 + test org.reflections reflections - 0.9.11 + 0.10.2 + + + jakarta.annotation + jakarta.annotation-api + 3.0.0 com.fasterxml.jackson.core jackson-databind - 2.12.6.1 + ${jackson.version} + test com.fasterxml.jackson.module jackson-module-parameter-names - 2.10.1 - + ${jackson.version} + test com.fasterxml.jackson.datatype jackson-datatype-jdk8 - 2.10.1 - + ${jackson.version} + test com.fasterxml.jackson.datatype jackson-datatype-jsr310 - 2.10.1 + ${jackson.version} + test io.reactivex.rxjava3 rxjava - 3.0.0-RC7 + 3.1.9 + test @@ -114,6 +221,14 @@ ${junit.jupiter.version} test + + + org.skyscreamer + jsonassert + 1.5.3 + test + + @@ -122,35 +237,10 @@ sonatype - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.1.1 - - - attach-javadocs - - jar - - - - org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.2.7 sign-artifacts @@ -164,7 +254,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.7 + 1.7.0 true sonatype diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..dbc10d2 --- /dev/null +++ b/renovate.json @@ -0,0 +1,3 @@ +{ + "forkProcessing": "enabled" +} diff --git a/src/license/license.txt b/src/license/license.txt new file mode 100644 index 0000000..3208ac9 --- /dev/null +++ b/src/license/license.txt @@ -0,0 +1,9 @@ +Licensed 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. \ No newline at end of file diff --git a/src/main/java/com/fleetpin/graphql/builder/Authorizer.java b/src/main/java/com/fleetpin/graphql/builder/Authorizer.java index 87bf50a..f6480f4 100644 --- a/src/main/java/com/fleetpin/graphql/builder/Authorizer.java +++ b/src/main/java/com/fleetpin/graphql/builder/Authorizer.java @@ -9,9 +9,6 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder; -public interface Authorizer { - -} +public interface Authorizer {} diff --git a/src/main/java/com/fleetpin/graphql/builder/AuthorizerSchema.java b/src/main/java/com/fleetpin/graphql/builder/AuthorizerSchema.java index dc7fedc..2eff123 100644 --- a/src/main/java/com/fleetpin/graphql/builder/AuthorizerSchema.java +++ b/src/main/java/com/fleetpin/graphql/builder/AuthorizerSchema.java @@ -9,59 +9,67 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder; +import static com.fleetpin.graphql.builder.EntityUtil.isContext; + +import graphql.GraphQLContext; +import graphql.GraphqlErrorException; +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; - -import graphql.schema.DataFetcher; +import java.util.function.Function; +import java.util.stream.Collectors; public class AuthorizerSchema { - - + private final DataFetcherRunner dataFetcherRunner; private final Set basePackages; private final Map targets; - private AuthorizerSchema(Set basePackages, Map targets) { + private AuthorizerSchema(DataFetcherRunner dataFetcherRunner, Set basePackages, Map targets) { + this.dataFetcherRunner = dataFetcherRunner; this.basePackages = basePackages; this.targets = targets; } - public static AuthorizerSchema build(Set basePackage, Set> authorizers) throws ReflectiveOperationException { + public static AuthorizerSchema build(DataFetcherRunner dataFetcherRunner, Set basePackage, Set> authorizers) + throws ReflectiveOperationException { Map targets = new HashMap<>(); - for(var type: authorizers) { + for (var type : authorizers) { Authorizer auth = type.getDeclaredConstructor().newInstance(); targets.put(type.getPackageName(), auth); } - return new AuthorizerSchema(basePackage, targets); + return new AuthorizerSchema(dataFetcherRunner, basePackage, targets); } public Authorizer getAuthorizer(Class type) { String name = type.getPackageName(); Authorizer auth = null; - while(true) { + while (true) { auth = targets.get(name); - if(auth == null) { - if(basePackages.contains(name)) { + if (auth == null) { + if (basePackages.contains(name)) { return null; } - if(name.indexOf('.') == -1) { + if (name.indexOf('.') == -1) { throw new RuntimeException("Referencing class outside base package " + type); } name = name.substring(0, name.lastIndexOf('.')); - }else { + } else { return auth; } } @@ -69,160 +77,191 @@ public Authorizer getAuthorizer(Class type) { public DataFetcher wrap(DataFetcher fetcher, Method method) { Authorizer wrapper = getAuthorizer(method.getDeclaringClass()); - if(wrapper == null) { - return fetcher; + if (wrapper == null) { + return fetcher; } Set parameterNames = new HashSet<>(); - - for(var parameter: method.getParameters()) { + + for (var parameter : method.getParameters()) { parameterNames.add(parameter.getName()); } - - + int longest = 0; - + Method[] targets = wrapper.getClass().getMethods(); - for(Method target: targets) { + for (Method target : targets) { boolean valid = false; valid |= target.getReturnType() == Boolean.TYPE; - - - if(target.getReturnType().isAssignableFrom(CompletableFuture.class)) { + + if (target.getReturnType().isAssignableFrom(CompletableFuture.class)) { Type genericType = ((ParameterizedType) target.getGenericReturnType()).getActualTypeArguments()[0]; - if(Boolean.class.isAssignableFrom((Class) genericType)) { + if (Boolean.class.isAssignableFrom((Class) genericType)) { valid = true; } } - if(!valid) { + if (!valid) { continue; } - if(target.getDeclaringClass().equals(Object.class)) { + if (target.getDeclaringClass().equals(Object.class)) { continue; } int matched = 0; - for(var parameter: target.getParameters()) { - if(parameterNames.contains(parameter.getName())) { + for (var parameter : target.getParameters()) { + if (parameterNames.contains(parameter.getName())) { matched++; } } - if(matched > longest) { + if (matched > longest) { longest = matched; } } - + List toRun = new ArrayList<>(); - for(Method target: targets) { - - + for (Method target : targets) { boolean valid = false; valid |= target.getReturnType() == Boolean.TYPE; - - - if(target.getReturnType().isAssignableFrom(CompletableFuture.class)) { + + if (target.getReturnType().isAssignableFrom(CompletableFuture.class)) { Type genericType = ((ParameterizedType) target.getGenericReturnType()).getActualTypeArguments()[0]; - if(Boolean.class.isAssignableFrom((Class) genericType)) { + if (Boolean.class.isAssignableFrom((Class) genericType)) { valid = true; } } - if(!valid) { + if (!valid) { continue; } - if(target.getDeclaringClass().equals(Object.class)) { + if (target.getDeclaringClass().equals(Object.class)) { continue; } - + int matched = 0; - for(var parameter: target.getParameters()) { - if(parameterNames.contains(parameter.getName())) { + for (var parameter : target.getParameters()) { + if (parameterNames.contains(parameter.getName())) { matched++; } } - if(matched == longest) { + if (matched == longest) { toRun.add(target); } } - - if(toRun.isEmpty()) { + + if (toRun.isEmpty()) { throw new RuntimeException("No authorizer found for " + method); } - - - - - - - - return env -> { - for(Method authorizer: toRun) { - Object[] args = new Object[authorizer.getParameterCount()]; - - for(int i = 0; i < args.length; i++) { - if(authorizer.getParameterTypes()[i].isAssignableFrom(env.getClass())) { - args[i] = env; - }else if(authorizer.getParameterTypes()[i].isAssignableFrom(env.getContext().getClass())) { - args[i] = env.getContext(); - }else { - args[i] = env.getArgument(authorizer.getParameters()[i].getName()); + + List> authRunners = toRun + .stream() + .>map(authorizer -> { + var count = authorizer.getParameterCount(); + + List> mappers = Arrays + .asList(authorizer.getParameters()) + .stream() + .map(parameter -> buildResolver(parameter.getName(), parameter.getType(), parameter.getAnnotations())) + .collect(Collectors.toList()); + + DataFetcher authFetcher = env -> { + Object[] args = new Object[count]; + + for (int i = 0; i < args.length; i++) { + args[i] = mappers.get(i).apply(env); } - } + + return authorizer.invoke(wrapper, args); + }; + return dataFetcherRunner.manage(authorizer, authFetcher); + }) + .collect(Collectors.toList()); + + return env -> { + for (var authorizer : authRunners) { try { - Object allow = authorizer.invoke(wrapper, args); - if(allow instanceof Boolean) { - if((Boolean) allow) { - return fetcher.get(env); - }else { - throw new RuntimeException("Invalid access"); + Object allow = authorizer.get(env); + + if (allow instanceof Boolean) { + if ((Boolean) allow) { + return fetcher.get(env); + } else { + throw GraphqlErrorException.newErrorException().message("unauthorized").errorClassification(ErrorType.UNAUTHORIZED).build(); } - }else { + } else { //only other type that passes checks above CompletableFuture allowed = (CompletableFuture) allow; - - return allowed.handle((r, e) -> { - if(e != null) { - if(e.getCause() instanceof Exception) { - e = e.getCause(); - } - if(e instanceof RuntimeException) { - throw (RuntimeException) e; - } - throw new RuntimeException(e); - } - if(r) { - try { - return fetcher.get(env); - } catch (Throwable e1) { - if(e1.getCause() instanceof Exception) { - e1 = e1.getCause(); + + return allowed + .handle((r, e) -> { + if (e != null) { + if (e.getCause() instanceof Exception) { + e = e.getCause(); + } + if (e instanceof RuntimeException) { + throw (RuntimeException) e; } - if(e1 instanceof RuntimeException) { - throw (RuntimeException) e1; + throw new RuntimeException(e); + } + if (r) { + try { + return fetcher.get(env); + } catch (Throwable e1) { + if (e1.getCause() instanceof Exception) { + e1 = e1.getCause(); + } + if (e1 instanceof RuntimeException) { + throw (RuntimeException) e1; + } + throw new RuntimeException(e1); } - throw new RuntimeException(e1); - } - }else { - throw new RuntimeException("Invalid access"); - } - }).thenCompose(a -> { - if(a instanceof CompletableFuture) { - return (CompletableFuture) a; - }else { - return CompletableFuture.completedFuture(a); - } - - }); + } else { + throw new RuntimeException("Invalid access"); + } + }) + .thenCompose(a -> { + if (a instanceof CompletableFuture) { + return (CompletableFuture) a; + } else { + return CompletableFuture.completedFuture(a); + } + }); } - }catch (InvocationTargetException e) { - if(e.getCause() instanceof Exception) { + } catch (InvocationTargetException e) { + if (e.getCause() instanceof Exception) { throw (Exception) e.getCause(); - }else { + } else { throw e; } } } return fetcher.get(env); - }; } + private Function buildResolver(String name, Class type, Annotation[] annotations) { + if (isContext(type, annotations)) { + if (type.isAssignableFrom(DataFetchingEnvironment.class)) { + return env -> env; + } + if (type.isAssignableFrom(GraphQLContext.class)) { + return env -> env.getGraphQlContext(); + } + return env -> { + var localContext = env.getLocalContext(); + if (localContext != null && type.isAssignableFrom(localContext.getClass())) { + return localContext; + } + + var context = env.getContext(); + if (context != null && type.isAssignableFrom(context.getClass())) { + return context; + } + + context = env.getGraphQlContext().get(name); + if (context != null && type.isAssignableFrom(context.getClass())) { + return context; + } + throw new RuntimeException("Context object " + name + " not found"); + }; + } + return env -> env.getArgument(name); + } } diff --git a/src/main/java/com/fleetpin/graphql/builder/DataFetcherRunner.java b/src/main/java/com/fleetpin/graphql/builder/DataFetcherRunner.java new file mode 100644 index 0000000..1b13479 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/DataFetcherRunner.java @@ -0,0 +1,8 @@ +package com.fleetpin.graphql.builder; + +import graphql.schema.DataFetcher; +import java.lang.reflect.Method; + +public interface DataFetcherRunner { + public DataFetcher manage(Method method, DataFetcher fetcher); +} diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectiveCaller.java b/src/main/java/com/fleetpin/graphql/builder/DirectiveCaller.java index 9ad61e9..73b3bd9 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectiveCaller.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectiveCaller.java @@ -9,15 +9,12 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder; -import java.lang.annotation.Annotation; - import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; +import java.lang.annotation.Annotation; -public interface DirectiveCaller extends DirectiveOperation{ +public interface DirectiveCaller extends DirectiveOperation { public Object process(T annotation, DataFetchingEnvironment env, DataFetcher fetcher) throws Exception; - } diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectiveOperation.java b/src/main/java/com/fleetpin/graphql/builder/DirectiveOperation.java index 7504649..e278c2c 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectiveOperation.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectiveOperation.java @@ -9,19 +9,15 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder; import java.lang.annotation.Annotation; - /** - * Implementations are either + * Implementations are either * DirectiveOperator is used to wrap a method call and modify it. Can be used for things like restrictions * SchemaDirective is used to add directive information to the graphql schema - * + * * */ -public interface DirectiveOperation { - -} +public interface DirectiveOperation {} diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java b/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java new file mode 100644 index 0000000..18d429d --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java @@ -0,0 +1,89 @@ +package com.fleetpin.graphql.builder; + +import com.fleetpin.graphql.builder.annotations.Directive; +import graphql.introspection.Introspection; +import graphql.schema.*; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; + +public class DirectiveProcessor { + + private final GraphQLDirective directive; + private final Map> builders; + + public DirectiveProcessor(GraphQLDirective directive, Map> builders) { + this.directive = directive; + this.builders = builders; + } + + public static DirectiveProcessor build(EntityProcessor entityProcessor, Class directive) { + var builder = GraphQLDirective.newDirective().name(directive.getSimpleName()); + var validLocations = directive.getAnnotation(Directive.class).value(); + // loop through and add valid locations + for (Introspection.DirectiveLocation location : validLocations) { + builder.validLocation(location); + } + + // Check for repeatable tag in annotation and add it + builder.repeatable(directive.getAnnotation(Directive.class).repeatable()); + + // Go through each argument and add name/type to directive + var methods = directive.getDeclaredMethods(); + Map> builders = new HashMap<>(); + for (Method method : methods) { + if (method.getParameterCount() != 0) { + continue; + } + var name = method.getName(); + + GraphQLArgument.Builder argument = GraphQLArgument.newArgument(); + argument.name(name); + + // Get the type of the argument from the return type of the method + TypeMeta innerMeta = new TypeMeta(null, method.getReturnType(), method.getGenericReturnType()); + var argumentType = entityProcessor.getEntity(innerMeta).getInputType(innerMeta, method.getAnnotations()); + argument.type(argumentType); + + // Add the argument to the directive builder to be used for declaration + builder.argument(argument); + + // Add a builder to the builders list (in order to populate applied directives) + builders.put( + name, + object -> { + try { + return GraphQLAppliedDirectiveArgument.newArgument().name(name).type(argumentType).valueProgrammatic(method.invoke(object)).build(); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + ); + } + return new DirectiveProcessor(builder.build(), builders); + } + + public void apply(Annotation annotation, Consumer builder) throws InvocationTargetException, IllegalAccessException { + var methods = annotation.annotationType().getDeclaredMethods(); + + // Create a new AppliedDirective which we will populate with the set values + var arguments = GraphQLAppliedDirective.newDirective(); + arguments.name(directive.getName()); + + // To get the value we loop through each method and get the method name and value + for (Method m : methods) { + // Using the builder created earlier populate the values of each method. + arguments.argument(builders.get(m.getName()).apply(annotation)); + } + + // Add the argument to the Builder + builder.accept(arguments.build()); + } + + public GraphQLDirective getDirective() { + return this.directive; + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java index 266ab44..ce6e59e 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java @@ -1,5 +1,3 @@ -package com.fleetpin.graphql.builder; - /* * Licensed 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 @@ -11,216 +9,210 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ +package com.fleetpin.graphql.builder; - - +import com.fleetpin.graphql.builder.annotations.DataFetcherWrapper; +import com.fleetpin.graphql.builder.annotations.Directive; +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import graphql.schema.GraphQLAppliedDirective; +import graphql.schema.GraphQLDirective; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.WeakHashMap; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; - import org.reactivestreams.Publisher; -import com.fleetpin.graphql.builder.annotations.Directive; - -import graphql.schema.DataFetcher; -import graphql.schema.DataFetchingEnvironment; -import graphql.schema.GraphQLAppliedDirective; -import graphql.schema.GraphQLDirective; -import io.reactivex.rxjava3.core.Flowable; - class DirectivesSchema { private final Collection> global; private final Map, DirectiveCaller> targets; - private final Map, SDLDirective> schemaDirective; - private Map, SDLProcessor> sdlProcessors; + private final Collection> directives; + private Map, DirectiveProcessor> directiveProcessors; - private DirectivesSchema(Collection> global, Map, DirectiveCaller> targets, Map, SDLDirective> schemaDirective) { + private DirectivesSchema( + Collection> global, + Map, DirectiveCaller> targets, + Collection> directives + ) { this.global = global; this.targets = targets; - this.schemaDirective = schemaDirective; + this.directives = directives; } + //TODO:mess of exceptions - public static DirectivesSchema build(List> globalDirectives, Set> dierctiveTypes) throws ReflectiveOperationException { + public static DirectivesSchema build(List> globalDirectives, Set> directiveTypes) throws ReflectiveOperationException { Map, DirectiveCaller> targets = new HashMap<>(); - Map, SDLDirective> graphqlDirective = new HashMap<>(); - for(Class directiveType: dierctiveTypes) { - if(!directiveType.isAnnotationPresent(Directive.class)) { + + Collection> allDirectives = new ArrayList<>(); + for (Class directiveType : directiveTypes) { + if (directiveType.isAnnotationPresent(DataFetcherWrapper.class)) { + Class> caller = directiveType.getAnnotation(DataFetcherWrapper.class).value(); + if (DirectiveCaller.class.isAssignableFrom(caller)) { + // TODO error for no zero args constructor + var callerInstance = (DirectiveCaller) caller.getConstructor().newInstance(); + targets.put((Class) directiveType, callerInstance); + } continue; } - if(!directiveType.isAnnotation()) { - //TODO:better error management - throw new RuntimeException("@Directive Annotation must only be placed on annotations"); - } - - var directive = directiveType.getAnnotation(Directive.class); - Class> caller = directive.value(); - //TODO: if target implents other things this won't lineup right - Class target = (Class) ((ParameterizedType) caller.getGenericInterfaces()[0]).getActualTypeArguments()[0]; - if(!target.equals(directiveType)) { - //TODO:better errors - throw new RuntimeException("Annotation missmatch"); + if (!directiveType.isAnnotationPresent(Directive.class)) { + continue; } - - - if(DirectiveCaller.class.isAssignableFrom(caller)) { - //TODO error for no zero args constructor - var callerInstance = (DirectiveCaller) caller.getConstructor().newInstance(); - targets.put((Class) directiveType, callerInstance); - }else { - var callerInstance = (SDLDirective) caller.getConstructor().newInstance(); - graphqlDirective.put((Class) directiveType, callerInstance); + if (!directiveType.isAnnotation()) { + throw new RuntimeException("@Directive Annotation must only be placed on annotations"); } + allDirectives.add((Class) directiveType); } - - return new DirectivesSchema(globalDirectives, targets, graphqlDirective); + + return new DirectivesSchema(globalDirectives, targets, allDirectives); } + private DirectiveCaller get(Annotation annotation) { return targets.get(annotation.annotationType()); } + private DataFetcher wrap(DirectiveCaller directive, T annotation, DataFetcher fetcher) { return env -> { return directive.process(annotation, env, fetcher); }; } - + public Stream getSchemaDirective() { - return sdlProcessors.values().stream().map(f -> f.getDirective()); + return directiveProcessors.values().stream().map(DirectiveProcessor::getDirective); } - + private DataFetcher wrap(RestrictTypeFactory directive, DataFetcher fetcher) { //TODO: hate having this cache here would love to scope against the env object but nothing to hook into dataload caused global leak Map> cache = Collections.synchronizedMap(new WeakHashMap<>()); - - - return env -> { - return cache.computeIfAbsent(env, key -> directive.create(key).thenApply(t -> t)).thenCompose(restrict -> { - try { - Object response = fetcher.get(env); - if(response instanceof CompletionStage) { - return ((CompletionStage) response).thenCompose(r -> applyRestrict(restrict, r )); - } - return applyRestrict(restrict, response); - } catch (Exception e) { - if(e instanceof RuntimeException) { - throw (RuntimeException) e; + + return env -> + cache + .computeIfAbsent(env, key -> directive.create(key).thenApply(t -> t)) + .thenCompose(restrict -> { + try { + Object response = fetcher.get(env); + if (response instanceof CompletionStage) { + return ((CompletionStage) response).thenCompose(r -> applyRestrict(restrict, r)); + } + return applyRestrict(restrict, response); + } catch (Exception e) { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } + throw new RuntimeException(e); } - throw new RuntimeException(e); - } - }); - }; + }); } public boolean target(Method method, TypeMeta meta) { - - for(var global: this.global) { + for (var global : this.global) { //TODO: extract class - if(global.extractType().isAssignableFrom(meta.getType())) { + if (global.extractType().isAssignableFrom(meta.getType())) { return true; } } - for(Annotation annotation: method.getAnnotations()) { - if(get(annotation) != null) { + for (Annotation annotation : method.getAnnotations()) { + if (get(annotation) != null) { return true; } } return false; } - public DataFetcher wrap(Method method, TypeMeta meta, DataFetcher fetcher) { - for(var g: global) { - if(g.extractType().isAssignableFrom(meta.getType())) { + + public DataFetcher wrap(Method method, TypeMeta meta, DataFetcher fetcher) { + for (var g : global) { + if (g.extractType().isAssignableFrom(meta.getType())) { fetcher = wrap(g, fetcher); } } - for(Annotation annotation: method.getAnnotations()) { + for (Annotation annotation : method.getAnnotations()) { DirectiveCaller directive = (DirectiveCaller) get(annotation); - if(directive != null) { - fetcher = wrap(directive, annotation, fetcher); + if (directive != null) { + fetcher = wrap(directive, annotation, fetcher); } } return fetcher; } - + private CompletableFuture applyRestrict(RestrictType restrict, Object response) { - if(response instanceof List) { - return restrict.filter((List)response); - }else if(response instanceof Publisher) { - return CompletableFuture.completedFuture(Flowable.fromPublisher((Publisher) response).flatMap(entry -> { - return Flowable.fromCompletionStage(restrict.allow(entry)).filter(t -> t == Boolean.TRUE).map(t -> entry); - })); - }else if(response instanceof Optional) { + if (response instanceof List) { + return restrict.filter((List) response); + } else if (response instanceof Publisher) { + return CompletableFuture.completedFuture(new FilteredPublisher((Publisher) response, restrict)); + } else if (response instanceof Optional) { var optional = (Optional) response; - if(optional.isEmpty()) { + if (optional.isEmpty()) { return CompletableFuture.completedFuture(response); } var target = optional.get(); - if(target instanceof List) { - return restrict.filter((List)target); - }else { - return restrict.allow(target).thenApply(allow -> { - if(allow == Boolean.TRUE) { + if (target instanceof List) { + return restrict.filter((List) target); + } else { + return restrict + .allow(target) + .thenApply(allow -> { + if (allow == Boolean.TRUE) { + return response; + } else { + return Optional.empty(); + } + }); + } + } else { + return restrict + .allow(response) + .thenApply(allow -> { + if (allow == Boolean.TRUE) { return response; - }else { - return Optional.empty(); + } else { + return null; } }); - } - }else { - return restrict.allow(response).thenApply(allow -> { - if(allow == Boolean.TRUE) { - return response; - }else { - return null; - } - }); } } - + private static CompletableFuture> all(List> toReturn) { - return CompletableFuture.allOf(toReturn.toArray(CompletableFuture[]::new)) - .thenApply(__ -> toReturn.stream().map(m -> { - try { - return m.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - }).collect(Collectors.toList())); + return CompletableFuture + .allOf(toReturn.toArray(CompletableFuture[]::new)) + .thenApply(__ -> + toReturn + .stream() + .map(m -> { + try { + return m.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toList()) + ); } - public void addSchemaDirective(AnnotatedElement element, Class location, Consumer builder) { - for(Annotation annotation: element.getAnnotations()) { - var processor = this.sdlProcessors.get(annotation.annotationType()); - if(processor != null) { - processor.apply(annotation, location, builder); + for (Annotation annotation : element.getAnnotations()) { + var processor = this.directiveProcessors.get(annotation.annotationType()); + if (processor != null) { + try { + processor.apply(annotation, builder); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException("Could not process applied directive: " + location.getName()); + } } } - } - public void processSDL(EntityProcessor entityProcessor) { - - Map, SDLProcessor> sdlProcessors = new HashMap<>(); - - this.schemaDirective.forEach((k, v) -> { - sdlProcessors.put(k, SDLProcessor.build(entityProcessor, v)); - }); - this.sdlProcessors = sdlProcessors; - + + public void processDirectives(EntityProcessor ep) { // Replacement of processSDL + Map, DirectiveProcessor> directiveProcessors = new HashMap<>(); + + this.directives.forEach(dir -> directiveProcessors.put(dir, DirectiveProcessor.build(ep, dir))); + this.directiveProcessors = directiveProcessors; } - } diff --git a/src/main/java/com/fleetpin/graphql/builder/DurationCoercing.java b/src/main/java/com/fleetpin/graphql/builder/DurationCoercing.java deleted file mode 100644 index f6391ca..0000000 --- a/src/main/java/com/fleetpin/graphql/builder/DurationCoercing.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed 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 com.fleetpin.graphql.builder; - -import java.time.Duration; - -import graphql.schema.Coercing; -import graphql.schema.CoercingParseLiteralException; -import graphql.schema.CoercingParseValueException; -import graphql.schema.CoercingSerializeException; - -public class DurationCoercing implements Coercing { - - @Override - public Duration serialize(Object dataFetcherResult) throws CoercingSerializeException { - return convertImpl(dataFetcherResult); - } - - @Override - public Duration parseValue(Object input) throws CoercingParseValueException { - return convertImpl(input); - } - - @Override - public Duration parseLiteral(Object input) throws CoercingParseLiteralException { - return convertImpl(input); - } - - - private Duration convertImpl(Object input) { - if (input instanceof Duration) { - return (Duration) input; - } else if (input instanceof String) { - return Duration.parse((String) input); - } - return null; - } - - -} diff --git a/src/main/java/com/fleetpin/graphql/builder/EntityHolder.java b/src/main/java/com/fleetpin/graphql/builder/EntityHolder.java new file mode 100644 index 0000000..d585319 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/EntityHolder.java @@ -0,0 +1,291 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import com.fleetpin.graphql.builder.TypeMeta.Flag; +import com.fleetpin.graphql.builder.annotations.Id; +import com.fleetpin.graphql.builder.mapper.InputTypeBuilder; +import graphql.Scalars; +import graphql.schema.GraphQLInputType; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLNamedInputType; +import graphql.schema.GraphQLNamedOutputType; +import graphql.schema.GraphQLNamedType; +import graphql.schema.GraphQLNonNull; +import graphql.schema.GraphQLOutputType; +import graphql.schema.GraphQLTypeReference; +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; + +public abstract class EntityHolder { + + private GraphQLNamedOutputType type; + private GraphQLNamedInputType inputType; + private InputTypeBuilder resolver; + + final GraphQLOutputType getType(TypeMeta meta, Annotation[] annotations) { + if (type == null) { + type = new GraphQLTypeReference(EntityUtil.getName(meta)); + type = buildType(); + } + GraphQLOutputType toReturn = getTypeInner(annotations); + return toType(meta, toReturn); + } + + private GraphQLOutputType toType(TypeMeta meta, GraphQLOutputType toReturn) { + boolean required = true; + for (var flag : meta.getFlags()) { + if (flag == Flag.OPTIONAL) { + required = false; + } + if (flag == Flag.ARRAY) { + if (required) { + toReturn = GraphQLNonNull.nonNull(toReturn); + } + toReturn = GraphQLList.list(toReturn); + required = true; + } + } + if (required) { + toReturn = GraphQLNonNull.nonNull(toReturn); + } + return toReturn; + } + + public final GraphQLNamedOutputType getInnerType(TypeMeta meta) { + if (type == null) { + type = new GraphQLTypeReference(EntityUtil.getName(meta)); + type = buildType(); + } + return type; + } + + private GraphQLNamedOutputType getTypeInner(Annotation[] annotations) { + if (annotations == null) { + return type; + } + for (Annotation an : annotations) { + if (an.annotationType().equals(Id.class)) { + return Scalars.GraphQLID; + } + } + return type; + } + + protected abstract GraphQLNamedOutputType buildType(); + + public final GraphQLInputType getInputType(TypeMeta meta, Annotation[] annotations) { + if (inputType == null) { + inputType = new GraphQLTypeReference(buildInputName()); + inputType = buildInput(); + } + GraphQLInputType toReturn = getInputTypeInner(annotations); + + boolean required = true; + for (var flag : meta.getFlags()) { + if (flag == Flag.OPTIONAL) { + required = false; + } + if (flag == Flag.ARRAY) { + if (required) { + toReturn = GraphQLNonNull.nonNull(toReturn); + } + toReturn = GraphQLList.list(toReturn); + required = true; + } + } + if (required) { + toReturn = GraphQLNonNull.nonNull(toReturn); + } + return toReturn; + } + + private GraphQLInputType getInputTypeInner(Annotation[] annotations) { + for (Annotation an : annotations) { + if (an.annotationType().equals(Id.class)) { + return Scalars.GraphQLID; + } + } + return inputType; + } + + protected abstract GraphQLNamedInputType buildInput(); + + protected abstract String buildInputName(); + + public Stream types() { + List types = new ArrayList<>(2); + if (type != null) { + types.add(type); + } + if (inputType != null && inputType != type) { + types.add(inputType); + } + return types.stream(); + } + + protected abstract InputTypeBuilder buildResolver(); + + public final InputTypeBuilder getResolver(TypeMeta meta) { + if (resolver == null) { + resolver = resolverPointer(); + resolver = buildResolver(); + } + var flags = new ArrayList<>(meta.getFlags()); + Collections.reverse(flags); + return process(meta.getTypes().iterator(), flags.iterator(), resolver); + } + + private InputTypeBuilder resolverPointer() { + return (obj, graphQLContext, locale) -> this.resolver.convert(obj, graphQLContext, locale); + } + + private static InputTypeBuilder process(Iterator> iterator, Iterator flags, InputTypeBuilder resolver) { + if (iterator.hasNext()) { + Flag flag = null; + if (flags.hasNext()) { + flag = flags.next(); + } + var type = iterator.next(); + + if (Optional.class.isAssignableFrom(type)) { + return processOptional(iterator, flags, resolver); + } + + if (flag == Flag.OPTIONAL) { + return processNull(type, iterator, flags, resolver); + } + + if (List.class.isAssignableFrom(type)) { + return processCollection(ArrayList::new, iterator, flags, resolver); + } + + if (Set.class.isAssignableFrom(type)) { + return processCollection(size -> new LinkedHashSet<>((int) (size / 0.75 + 1)), iterator, flags, resolver); + } + + if (type.isArray()) { + return processArray(type, iterator, flags, resolver); + } + + if (iterator.hasNext()) { + throw new RuntimeException("Unsupported type " + type); + } + + if (type.isEnum()) { + return processEnum((Class) type); + } + return resolver; + } + throw new RuntimeException("No type"); + } + + private static InputTypeBuilder processEnum(Class type) { + var constants = type.getEnumConstants(); + var map = new HashMap(); + for (var c : constants) { + map.put(c.name(), c); + } + + return (obj, context, locale) -> { + if (type.isInstance(obj)) { + return obj; + } + return map.get(obj); + }; + } + + private static InputTypeBuilder processOptional(Iterator> iterator, Iterator flags, InputTypeBuilder resolver) { + var mapper = process(iterator, flags, resolver); + return (obj, context, locale) -> { + if (obj instanceof Optional) { + if (((Optional) obj).isEmpty()) { + return obj; + } else { + obj = ((Optional) obj).get(); + } + } + if (obj == null) { + return Optional.empty(); + } + return Optional.of(mapper.convert(obj, context, locale)); + }; + } + + private static InputTypeBuilder processNull(Class type, Iterator> iterator, Iterator flags, InputTypeBuilder resolver) { + var classes = new ArrayList>(); + classes.add(type); + iterator.forEachRemaining(classes::add); + + var mapper = process(classes.iterator(), flags, resolver); + return (obj, context, locale) -> { + if (obj == null) { + return null; + } + return mapper.convert(obj, context, locale); + }; + } + + private static InputTypeBuilder processArray(Class type, Iterator> iterator, Iterator flags, InputTypeBuilder resolver) { + var component = type.getComponentType(); + if (component.isPrimitive()) { + throw new RuntimeException("Do not support primitive array"); + } + + var mapper = process(iterator, flags, resolver); + return (obj, context, locale) -> { + if (obj instanceof Collection) { + var collection = (Collection) obj; + Object[] toReturn = (Object[]) Array.newInstance(component, collection.size()); + int i = 0; + for (var c : collection) { + toReturn[i++] = mapper.convert(c, context, locale); + } + return toReturn; + } else { + throw new RuntimeException("Expected a Collection got " + obj.getClass()); + } + }; + } + + private static InputTypeBuilder processCollection( + Function create, + Iterator> iterator, + Iterator flags, + InputTypeBuilder resolver + ) { + var mapper = process(iterator, flags, resolver); + return (obj, context, locale) -> { + if (obj instanceof Collection) { + var collection = (Collection) obj; + var toReturn = create.apply(collection.size()); + for (var c : collection) { + toReturn.add(mapper.convert(c, context, locale)); + } + return toReturn; + } else { + throw new RuntimeException("Expected a Collection got " + obj.getClass()); + } + }; + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/EntityProcessor.java b/src/main/java/com/fleetpin/graphql/builder/EntityProcessor.java index 29d0efe..0deb63c 100644 --- a/src/main/java/com/fleetpin/graphql/builder/EntityProcessor.java +++ b/src/main/java/com/fleetpin/graphql/builder/EntityProcessor.java @@ -1,503 +1,182 @@ -package com.fleetpin.graphql.builder; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.Arrays; -import java.util.Map; -import java.util.function.Consumer; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.GraphQLDeprecated; -import com.fleetpin.graphql.builder.annotations.GraphQLDescription; -import com.fleetpin.graphql.builder.annotations.GraphQLIgnore; -import com.fleetpin.graphql.builder.annotations.InputIgnore; -import com.fleetpin.graphql.builder.annotations.Scalar; -import com.fleetpin.graphql.builder.annotations.SchemaOption; - -import graphql.Scalars; -import graphql.schema.Coercing; -import graphql.schema.DataFetcher; -import graphql.schema.FieldCoordinates; -import graphql.schema.GraphQLAppliedDirective; -import graphql.schema.GraphQLCodeRegistry; -import graphql.schema.GraphQLEnumType; -import graphql.schema.GraphQLFieldDefinition; -import graphql.schema.GraphQLInputObjectField; -import graphql.schema.GraphQLInputObjectType; -import graphql.schema.GraphQLInterfaceType; -import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLObjectType.Builder; -import graphql.schema.GraphQLScalarType; -import graphql.schema.GraphQLType; -import graphql.schema.GraphQLTypeReference; -import graphql.schema.idl.TypeRuntimeWiring; - -class EntityProcessor { - - private final Map additionalTypes; - private final GraphQLCodeRegistry.Builder codeRegistry; - private final DirectivesSchema directives; - - - public EntityProcessor(Map additionalTypes, GraphQLCodeRegistry.Builder codeRegistry, DirectivesSchema diretives) { - this.additionalTypes = additionalTypes; - this.codeRegistry = codeRegistry; - this.directives = diretives; - } - - - public static Class extraOptionalType(Type type) { - if(type instanceof Class) { - return (Class) type; - }else if(type instanceof ParameterizedType){ - return extraOptionalType(((ParameterizedType) type).getActualTypeArguments()[0]); - } - throw new RuntimeException("extraction failure for " + type.getClass()); - } - - private void addType(TypeMeta meta, boolean input) { - Class type = meta.getType(); - Type genericType = meta.getGenericType(); - if(genericType == null) { - genericType = type; - } - try { - if(type.isAnnotationPresent(Scalar.class)) { - GraphQLScalarType.Builder scalarType = GraphQLScalarType.newScalar(); - String typeName = getName(meta); - scalarType.name(typeName); - - var description = type.getAnnotation(GraphQLDescription.class); - if(description != null) { - scalarType.description(description.value()); - } - - Class coerecing = type.getAnnotation(Scalar.class).value(); - scalarType.coercing(coerecing.getDeclaredConstructor().newInstance()); - - addDirectives(type, type, scalarType::withAppliedDirective); - var built = scalarType.build(); - if(additionalTypes.put(built.getName(), built) != null) { - throw new RuntimeException(built.getName() + "defined more than once"); - } - } - - if(type.isAnnotationPresent(Entity.class)) { - //special handling - if(type.isEnum()) { - graphql.schema.GraphQLEnumType.Builder enumType = GraphQLEnumType.newEnum(); - String typeName = getName(meta); - enumType.name(typeName); - - var description = type.getAnnotation(GraphQLDescription.class); - if(description != null) { - enumType.description(description.value()); - } - - Object[] enums = type.getEnumConstants(); - for(Object e: enums) { - Enum a = (Enum) e; - if(type.getDeclaredField(e.toString()).isAnnotationPresent(GraphQLIgnore.class)) { - continue; - } - enumType.value(a.name(), a); - } - addDirectives(type, type, enumType::withAppliedDirective); - GraphQLEnumType built = enumType.build(); - if(additionalTypes.put(built.getName(), built) != null) { - throw new RuntimeException(built.getName() + "defined more than once"); - } - return; - } - - SchemaOption schemaType = SchemaOption.BOTH; - Entity graphTypeAnnotation = type.getAnnotation(Entity.class); - if(graphTypeAnnotation != null) { - schemaType = graphTypeAnnotation.value(); - } - - Builder graphType = GraphQLObjectType.newObject(); - String typeName = getName(meta); - graphType.name(typeName); - - - - GraphQLInterfaceType.Builder interfaceBuilder = GraphQLInterfaceType.newInterface(); - interfaceBuilder.name(typeName); - - GraphQLInputObjectType.Builder graphInputType = GraphQLInputObjectType.newInputObject(); - if(schemaType == SchemaOption.INPUT) { - graphInputType.name(typeName); - }else { - graphInputType.name(typeName + "Input"); - } - - { - GraphQLInputObjectField.Builder field = GraphQLInputObjectField.newInputObjectField(); - field.name("__typename"); - field.type(Scalars.GraphQLString); - graphInputType.field(field); - } - { - var description = type.getAnnotation(GraphQLDescription.class); - if(description != null) { - graphType.description(description.value()); - graphInputType.description(description.value()); - interfaceBuilder.description(description.value()); - } - } - - - TypeRuntimeWiring.Builder runtime = new TypeRuntimeWiring.Builder(); - runtime.typeName(typeName); - for(Method method: type.getMethods()) { - try { - if(method.isSynthetic()) { - continue; - } - if(method.getDeclaringClass().equals(Object.class)) { - continue; - } - if(method.isAnnotationPresent(GraphQLIgnore.class)) { - continue; - } - //will also be on implementing class - if(Modifier.isAbstract(method.getModifiers()) || method.getDeclaringClass().isInterface()) { - continue; - } - if(Modifier.isStatic(method.getModifiers())) { - continue; - }else { - //getter type - if(!input && method.getName().matches("(get|is)[A-Z].*")) { - String name; - if(method.getName().startsWith("get")) { - name = method.getName().substring("get".length(), "get".length() + 1).toLowerCase() + method.getName().substring("get".length() + 1); - }else { - name = method.getName().substring("is".length(), "is".length() + 1).toLowerCase() + method.getName().substring("is".length() + 1); - } - - GraphQLFieldDefinition.Builder field = GraphQLFieldDefinition.newFieldDefinition(); - field.name(name); - addDirectives(method, type, field::withAppliedDirective); - var deprecated = method.getAnnotation(GraphQLDeprecated.class); - if(deprecated != null) { - field.deprecate(deprecated.value()); - } - var description = method.getAnnotation(GraphQLDescription.class); - if(description != null) { - field.description(description.value()); - } - - TypeMeta innerMeta = new TypeMeta(this, meta, method.getReturnType(), method.getGenericReturnType()); - field.type(SchemaBuilder.getType(innerMeta, method.getAnnotations())); - graphType.field(field); - interfaceBuilder.field(field); - - if(method.getParameterCount() > 0 || directives.target(method, innerMeta)) { - codeRegistry.dataFetcher(FieldCoordinates.coordinates(typeName, name), buildDirectiveWrapper(directives, method, innerMeta)); - } - }else if(input && method.getName().matches("set[A-Z].*")) { - if(method.getParameterCount() == 1 && !method.isAnnotationPresent(InputIgnore.class)) { - String name = method.getName().substring("set".length(), "set".length() + 1).toLowerCase() + method.getName().substring("set".length() + 1); - GraphQLInputObjectField.Builder field = GraphQLInputObjectField.newInputObjectField(); - field.name(name); - addDirectives(method, type, field::withAppliedDirective); - TypeMeta innerMeta = new TypeMeta(this, meta, method.getParameterTypes()[0], method.getGenericParameterTypes()[0]); - field.type(SchemaBuilder.getInputType(innerMeta, method.getParameterAnnotations()[0])); - graphInputType.field(field); - } - } - } - }catch(RuntimeException e) { - e.printStackTrace(); - throw new RuntimeException("Failed to process method " + method, e); - } - } - - boolean unmappedGenerics = meta.hasUnmappedGeneric(); - boolean interfaceable = type.isInterface() || Modifier.isAbstract(type.getModifiers()); - if(!input && (interfaceable || unmappedGenerics)) { - addDirectives(type, type, interfaceBuilder::withAppliedDirective); - GraphQLInterfaceType built = interfaceBuilder.build(); - if(additionalTypes.put(built.getName(), built) != null) { - throw new RuntimeException(built.getName() + "defined more than once"); - } - - codeRegistry.typeResolver(built.getName(), env -> { - if(type.isInstance(env.getObject())) { - - var name = typeNameLookup(env.getObject()); - var t = additionalTypes.get(name); - if(!(t instanceof GraphQLObjectType)) { - t = additionalTypes.get(name + "_DIRECT"); - } - try { - return (GraphQLObjectType) t; - }catch (ClassCastException e) { - throw e; - } - } - return null; - }); - if(interfaceable) { - return; - } - } - if(unmappedGenerics) { - graphType.withInterface(GraphQLTypeReference.typeRef(typeName)); - graphType.name(typeName + "_DIRECT"); - } - Class parent = type.getSuperclass(); - while(!input && parent != null) { - if(parent.isAnnotationPresent(Entity.class)) { - TypeMeta innerMeta = new TypeMeta(this, meta, parent, type.getGenericSuperclass()); - String interfaceName = process(innerMeta); - graphType.withInterface(GraphQLTypeReference.typeRef(interfaceName)); - - if(!parent.equals(type.getGenericSuperclass())) { - innerMeta = new TypeMeta(this, meta, parent, parent); - interfaceName = process(innerMeta); - graphType.withInterface(GraphQLTypeReference.typeRef(interfaceName)); - } - - var genericMeta = new TypeMeta(this, null, parent, parent); - if(!getName(innerMeta).equals(getName(genericMeta))) { - interfaceName = process(genericMeta); - graphType.withInterface(GraphQLTypeReference.typeRef(interfaceName)); - } - - } - parent = parent.getSuperclass(); - } - //generics - if(!input) { - TypeMeta innerMeta = new TypeMeta(this, meta, type, type); - if(!getName(innerMeta).equals(typeName)) { - String interfaceName = process(innerMeta); - graphType.withInterface(GraphQLTypeReference.typeRef(interfaceName)); - } - innerMeta = new TypeMeta(this, null, type, type); - if(!getName(innerMeta).equals(typeName)) { - String interfaceName = process(innerMeta); - graphType.withInterface(GraphQLTypeReference.typeRef(interfaceName)); - } - } - - if(!input && (schemaType == SchemaOption.BOTH || schemaType == SchemaOption.TYPE)) { - addDirectives(type, type, graphType::withAppliedDirective); - GraphQLObjectType built = graphType.build(); - if(additionalTypes.put(built.getName(), built) != null) { - throw new RuntimeException(built.getName() + "defined more than once"); - } - codeRegistry.typeResolver(built.getName(), env -> { - if(type.isInstance(env.getObject())) { - return built; - } - return null; - }); - } - if(input && (schemaType == SchemaOption.BOTH || schemaType == SchemaOption.INPUT)) { - addDirectives(type, type, graphInputType::withAppliedDirective); - GraphQLInputObjectType inputBuild = graphInputType.build(); - if(additionalTypes.put(inputBuild.getName(), inputBuild) != null) { - throw new RuntimeException(inputBuild.getName() + " defined more than once"); - } - } - } - }catch (ReflectiveOperationException | RuntimeException e) { - throw new RuntimeException("Failed to build schema for class " + type, e); - } - } - - - - - private void addDirectives(AnnotatedElement element, Class location, Consumer builder) { - this.directives.addSchemaDirective(element, location, builder); - } - - - - private static DataFetcher buildDirectiveWrapper(DirectivesSchema diretives, Method method, TypeMeta meta) { - DataFetcher fetcher = env -> { - Object[] args = new Object[method.getParameterCount()]; - for(int i = 0; i < args.length; i++) { - - if(method.getParameterTypes()[i].isAssignableFrom(env.getClass())) { - args[i] = env; - }else if(method.getParameterTypes()[i].isAssignableFrom(env.getContext().getClass())) { - args[i] = env.getContext(); - }else { - Object obj = env.getArgument(method.getParameters()[i].getName()); - args[i] = obj; - } - } - try { - return method.invoke(env.getSource(), args); - }catch (Exception e) { - System.out.println(method); - System.out.println((Object) env.getSource()); - System.out.println(Arrays.toString(args)); - if(e.getCause() instanceof Exception) { - throw (Exception) e.getCause(); - }else { - throw e; - } - - } - }; - - fetcher = diretives.wrap(method, meta, fetcher); - return fetcher; - - } - - - private String getName(TypeMeta meta) { - var type = meta.getType(); - - String name = null; - - if(type.isEnum()) { - name = type.getSimpleName(); - } - if(type.isAnnotationPresent(Scalar.class)) { - name = type.getSimpleName(); - } - if(type.isAnnotationPresent(Entity.class)) { - name = type.getSimpleName(); - } - var genericType = meta.getGenericType(); - - for(int i = 0; i < type.getTypeParameters().length; i++) { - if(genericType instanceof ParameterizedType) { - var t = ((ParameterizedType) genericType).getActualTypeArguments()[i]; - if(t instanceof Class) { - String extra = ((Class) t).getSimpleName(); - name += "_" + extra; - - }else if(t instanceof TypeVariable){ - var variable = (TypeVariable) t; - Class extra = meta.resolveToType(variable); - if(extra != null) { - name += "_" + extra.getSimpleName(); - } - } - }else { - Class extra = meta.resolveToType(type.getTypeParameters()[i]); - if(extra != null) { - name += "_" + extra.getSimpleName(); - } - } - } - return name; - } - - private String typeNameLookup(Object obj) { - var type = obj.getClass(); - String name = null; - - if(type.isEnum()) { - name = type.getSimpleName(); - } - if(type.isAnnotationPresent(Scalar.class)) { - name = type.getSimpleName(); - } - if(type.isAnnotationPresent(Entity.class)) { - name = type.getSimpleName(); - } - - for(var t: type.getTypeParameters()) { - t.getTypeName(); - for(var method: type.getMethods()) { - var methodType = method.getGenericReturnType(); - if(methodType instanceof TypeVariable) { - var typeVariable = ((TypeVariable) methodType); - if(typeVariable.equals(t)) { - //maybe we should dig through private fields first - try { - name += "_" + typeNameLookup(method.invoke(obj)); - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Could not infre type with regard to generics."); - } //TODO: might have arguments might be a future which would make impossible to resolve - } - } - } - } - - return name; - } - - public String process(TypeMeta meta) { - - TypeMeta rawMeta = new TypeMeta(this, null,meta.getType(), meta.getType()); - String rawName = getName(rawMeta); - - if(rawName != null && !this.additionalTypes.containsKey(rawName)) { - this.additionalTypes.put(rawName, null); // so we don't go around in circles if depend on self - addType(rawMeta, false); - } - - String name = getName(meta); - if(name != null && !this.additionalTypes.containsKey(name)) { - this.additionalTypes.put(name, null); // so we don't go around in circles if depend on self - addType(meta, false); - } - return name; - } - - private String getNameInput(TypeMeta meta) { - var type = meta.getType(); - String name = null; - if(type.isEnum()) { - name = type.getSimpleName(); - } - - if(type.isAnnotationPresent(Scalar.class)) { - name = type.getSimpleName(); - } - - if(type.isAnnotationPresent(Entity.class)) { - if(type.getAnnotation(Entity.class).value() == SchemaOption.BOTH) { - name = type.getSimpleName() + "Input"; - }else { - name = type.getSimpleName(); - } - } - - var genericType = meta.getGenericType(); - - if(genericType instanceof ParameterizedType) { - var parameterizedTypes = ((ParameterizedType) genericType).getActualTypeArguments(); - - for(var t: parameterizedTypes) { - if(t instanceof Class) { - String extra = ((Class) t).getSimpleName(); - name += "_" + extra; - - } - } - } - - return name; - } - - - public String processInput(TypeMeta meta) { - String name = getNameInput(meta); - if(name != null && !this.additionalTypes.containsKey(name)) { - this.additionalTypes.put(name, null); // so we don't go around in circles if depend on self - addType(meta, true); - } - return name; - } - - - -} +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import com.fleetpin.graphql.builder.annotations.Scalar; +import com.fleetpin.graphql.builder.annotations.Union; +import com.fleetpin.graphql.builder.mapper.InputTypeBuilder; +import graphql.Scalars; +import graphql.schema.GraphQLAppliedDirective; +import graphql.schema.GraphQLCodeRegistry; +import graphql.schema.GraphQLInputType; +import graphql.schema.GraphQLOutputType; +import graphql.schema.GraphQLScalarType; +import graphql.schema.GraphQLType; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class EntityProcessor { + + private final DirectivesSchema directives; + + private final Map entities; + private final MethodProcessor methodProcessor; + + EntityProcessor(DataFetcherRunner dataFetcherRunner, List scalars, DirectivesSchema diretives) { + this.methodProcessor = new MethodProcessor(dataFetcherRunner, this, diretives); + this.entities = new HashMap<>(); + addDefaults(); + addScalars(scalars); + + this.directives = diretives; + } + + private void addDefaults() { + put(Boolean.class, new ScalarEntity(Scalars.GraphQLBoolean)); + put(Boolean.TYPE, new ScalarEntity(Scalars.GraphQLBoolean)); + + put(Double.class, new ScalarEntity(Scalars.GraphQLFloat)); + put(Double.TYPE, new ScalarEntity(Scalars.GraphQLFloat)); + + put(Integer.class, new ScalarEntity(Scalars.GraphQLInt)); + put(Integer.TYPE, new ScalarEntity(Scalars.GraphQLInt)); + + put(String.class, new ScalarEntity(Scalars.GraphQLString)); + } + + private void addScalars(List scalars) { + for (var scalar : scalars) { + var coercing = scalar.getCoercing(); + var type = coercing.getClass(); + for (var method : type.getMethods()) { + if (method.isSynthetic()) { + continue; + } + if ("parseValue".equals(method.getName())) { + var returnType = method.getReturnType(); + if (returnType.equals(Long.class)) { + put(Long.TYPE, new ScalarEntity(scalar)); + } else if (returnType.equals(Byte.class)) { + put(Byte.TYPE, new ScalarEntity(scalar)); + } else if (returnType.equals(Character.class)) { + put(Character.TYPE, new ScalarEntity(scalar)); + } else if (returnType.equals(Float.class)) { + put(Float.TYPE, new ScalarEntity(scalar)); + } else if (returnType.equals(Short.class)) { + put(Short.TYPE, new ScalarEntity(scalar)); + } + put(returnType, new ScalarEntity(scalar)); + break; + } + } + } + } + + private void put(Class type, ScalarEntity entity) { + var name = EntityUtil.getName(new TypeMeta(null, type, type)); + entities.put(name, entity); + } + + Set getAdditionalTypes() { + return entities.values().stream().flatMap(s -> s.types()).collect(Collectors.toSet()); + } + + public EntityHolder getEntity(Class type) { + return getEntity(new TypeMeta(null, type, type)); + } + + EntityHolder getEntity(TypeMeta meta) { + String name = EntityUtil.getName(meta); + return entities.computeIfAbsent( + name, + __ -> { + Class type = meta.getType(); + Type genericType = meta.getGenericType(); + if (genericType == null) { + genericType = type; + } + try { + if (type.isAnnotationPresent(Scalar.class)) { + return new ScalarEntity(directives, meta); + } + if (type.isEnum()) { + return new EnumEntity(directives, meta); + } else { + return new ObjectEntity(this, meta); + } + } catch (ReflectiveOperationException | RuntimeException e) { + throw new RuntimeException("Failed to build schema for class " + type, e); + } + } + ); + } + + public GraphQLOutputType getType(TypeMeta meta, Annotation[] annotations) { + for (var annotation : annotations) { + if (annotation instanceof Union) { + var union = (Union) annotation; + return getUnionType(meta, union); + } + } + + return getEntity(meta).getType(meta, annotations); + } + + private GraphQLOutputType getUnionType(TypeMeta meta, Union union) { + var name = UnionType.name(union); + + return entities + .computeIfAbsent( + name, + __ -> { + try { + return new UnionType(this, union); + } catch (RuntimeException e) { + throw new RuntimeException("Failed to build schema for union " + union, e); + } + } + ) + .getType(meta, null); + } + + public GraphQLInputType getInputType(TypeMeta meta, Annotation[] annotations) { + return getEntity(meta).getInputType(meta, annotations); + } + + void addSchemaDirective(AnnotatedElement element, Class location, Consumer builder) { + this.directives.addSchemaDirective(element, location, builder); + } + + public InputTypeBuilder getResolver(TypeMeta meta) { + return getEntity(meta).getResolver(meta); + } + + public InputTypeBuilder getResolver(Class type) { + var meta = new TypeMeta(null, type, type); + return getEntity(meta).getResolver(meta); + } + + GraphQLCodeRegistry.Builder getCodeRegistry() { + return this.methodProcessor.getCodeRegistry(); + } + + MethodProcessor getMethodProcessor() { + return methodProcessor; + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/EntityUtil.java b/src/main/java/com/fleetpin/graphql/builder/EntityUtil.java new file mode 100644 index 0000000..f63efdf --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/EntityUtil.java @@ -0,0 +1,172 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import com.fleetpin.graphql.builder.annotations.Context; +import com.fleetpin.graphql.builder.annotations.GraphQLIgnore; +import com.fleetpin.graphql.builder.annotations.GraphQLName; +import com.fleetpin.graphql.builder.annotations.InputIgnore; +import graphql.GraphQLContext; +import graphql.schema.DataFetchingEnvironment; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Optional; + +class EntityUtil { + + static String getName(TypeMeta meta) { + var type = meta.getType(); + + var genericType = meta.getGenericType(); + + var name = buildUpName(meta, type, genericType); + if (meta.isDirect()) { + name += "_DIRECT"; + } + + return name; + } + + private static String buildUpName(TypeMeta meta, Class type, Type genericType) { + String name = type.getSimpleName(); + + for (int i = 0; i < type.getTypeParameters().length; i++) { + if (genericType instanceof ParameterizedType) { + var t = ((ParameterizedType) genericType).getActualTypeArguments()[i]; + if (t instanceof Class) { + String extra = ((Class) t).getSimpleName(); + name += "_" + extra; + } else if (t instanceof TypeVariable) { + var variable = (TypeVariable) t; + Class extra = meta.resolveToType(variable); + if (extra != null) { + name += "_" + extra.getSimpleName(); + } + } else if (t instanceof ParameterizedType pType) { + var rawType = pType.getRawType(); + if (rawType instanceof Class rawClass) { + var extra = buildUpName(meta, rawClass, pType); + name += "_" + extra; + } else { + throw new RuntimeException("Generics are more complex that logic currently can handle"); + } + } + } else { + Class extra = meta.resolveToType(type.getTypeParameters()[i]); + if (extra != null) { + name += "_" + extra.getSimpleName(); + } + } + } + return name; + } + + public static Optional getter(Method method) { + if (method.isSynthetic()) { + return Optional.empty(); + } + if (method.getDeclaringClass().equals(Object.class)) { + return Optional.empty(); + } + if (method.isAnnotationPresent(GraphQLIgnore.class)) { + return Optional.empty(); + } + // will also be on implementing class + if (Modifier.isAbstract(method.getModifiers()) || method.getDeclaringClass().isInterface()) { + return Optional.empty(); + } + if (Modifier.isStatic(method.getModifiers())) { + return Optional.empty(); + } else if (method.getName().matches("(get|is)[A-Z].*")) { + String name; + if (method.getName().startsWith("get")) { + name = method.getName().substring("get".length(), "get".length() + 1).toLowerCase() + method.getName().substring("get".length() + 1); + } else { + name = method.getName().substring("is".length(), "is".length() + 1).toLowerCase() + method.getName().substring("is".length() + 1); + } + return Optional.of(getName(name, method)); + } + return Optional.empty(); + } + + public static Optional setter(Method method) { + if (method.isSynthetic()) { + return Optional.empty(); + } + if (method.getDeclaringClass().equals(Object.class)) { + return Optional.empty(); + } + if (method.isAnnotationPresent(GraphQLIgnore.class)) { + return Optional.empty(); + } + // will also be on implementing class + if (Modifier.isAbstract(method.getModifiers()) || method.getDeclaringClass().isInterface()) { + return Optional.empty(); + } + if (Modifier.isStatic(method.getModifiers())) { + return Optional.empty(); + } else if (method.getName().matches("set[A-Z].*")) { + if (method.getParameterCount() == 1 && !method.isAnnotationPresent(InputIgnore.class)) { + String name = method.getName().substring("set".length(), "set".length() + 1).toLowerCase() + method.getName().substring("set".length() + 1); + return Optional.of(getName(name, method)); + } + } + return Optional.empty(); + } + + static String getName(String fallback, AnnotatedElement... annotated) { + for (var anno : annotated) { + if (anno.isAnnotationPresent(GraphQLName.class)) { + return anno.getAnnotation(GraphQLName.class).value(); + } + } + return fallback; + } + + static boolean isContext(Class class1, Annotation[] annotations) { + for (var annotation : annotations) { + if (annotation instanceof Context) { + return true; + } + } + return ( + class1.isAssignableFrom(GraphQLContext.class) || class1.isAssignableFrom(DataFetchingEnvironment.class) || class1.isAnnotationPresent(Context.class) + ); + } + + static T getAnnotation(Class type, Class annotation) { + var response = type.getAnnotation(annotation); + if (response != null) { + return response; + } + + if (type.getSuperclass() != null) { + response = getAnnotation(type.getSuperclass(), annotation); + if (response != null) { + return response; + } + } + + for (var parent : type.getInterfaces()) { + response = getAnnotation(parent, annotation); + if (response != null) { + return response; + } + } + return null; + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/EnumEntity.java b/src/main/java/com/fleetpin/graphql/builder/EnumEntity.java new file mode 100644 index 0000000..b05bc20 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/EnumEntity.java @@ -0,0 +1,78 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import static graphql.schema.GraphQLEnumValueDefinition.newEnumValueDefinition; + +import com.fleetpin.graphql.builder.annotations.GraphQLDescription; +import com.fleetpin.graphql.builder.annotations.GraphQLIgnore; +import com.fleetpin.graphql.builder.mapper.InputTypeBuilder; +import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLNamedInputType; +import graphql.schema.GraphQLNamedOutputType; + +public class EnumEntity extends EntityHolder { + + private final GraphQLEnumType enumType; + + public EnumEntity(DirectivesSchema directives, TypeMeta meta) throws ReflectiveOperationException { + graphql.schema.GraphQLEnumType.Builder enumType = GraphQLEnumType.newEnum(); + String typeName = EntityUtil.getName(meta); + enumType.name(typeName); + + var type = meta.getType(); + + var description = type.getAnnotation(GraphQLDescription.class); + if (description != null) { + enumType.description(description.value()); + } + + Object[] enums = type.getEnumConstants(); + for (Object e : enums) { + Enum a = (Enum) e; + var field = type.getDeclaredField(e.toString()); + if (field.isAnnotationPresent(GraphQLIgnore.class)) { + continue; + } + var name = EntityUtil.getName(a.name(), field); + var valueDef = newEnumValueDefinition().name(a.name()).value(a); + var desc = field.getAnnotation(GraphQLDescription.class); + if (desc != null) { + valueDef.description(desc.value()); + } + + enumType.value(valueDef.build()); + } + directives.addSchemaDirective(type, type, enumType::withAppliedDirective); + this.enumType = enumType.build(); + } + + @Override + protected GraphQLNamedInputType buildInput() { + return enumType; + } + + @Override + protected GraphQLNamedOutputType buildType() { + return enumType; + } + + @Override + protected String buildInputName() { + return enumType.getName(); + } + + @Override + protected InputTypeBuilder buildResolver() { + return null; + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/SDLDirective.java b/src/main/java/com/fleetpin/graphql/builder/ErrorType.java similarity index 61% rename from src/main/java/com/fleetpin/graphql/builder/SDLDirective.java rename to src/main/java/com/fleetpin/graphql/builder/ErrorType.java index 7194c1d..16e7e67 100644 --- a/src/main/java/com/fleetpin/graphql/builder/SDLDirective.java +++ b/src/main/java/com/fleetpin/graphql/builder/ErrorType.java @@ -9,22 +9,10 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder; -import java.lang.annotation.Annotation; -import java.util.List; - -import graphql.introspection.Introspection.DirectiveLocation; - -public interface SDLDirective extends DirectiveOperation{ - - public List validLocations(); - - public K build(T annotation, Class location); - - public default boolean repeatable() { - return false; - } +import graphql.ErrorClassification; +enum ErrorType implements ErrorClassification { + UNAUTHORIZED, } diff --git a/src/main/java/com/fleetpin/graphql/builder/FilteredPublisher.java b/src/main/java/com/fleetpin/graphql/builder/FilteredPublisher.java new file mode 100644 index 0000000..afe8cc9 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/FilteredPublisher.java @@ -0,0 +1,92 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +class FilteredPublisher implements Publisher { + + private final RestrictType restrict; + private final Publisher publisher; + + public FilteredPublisher(Publisher publisher, RestrictType restrict) { + this.publisher = publisher; + this.restrict = restrict; + } + + @Override + public void subscribe(Subscriber subscriber) { + publisher.subscribe( + new Subscriber() { + private CompletableFuture current = CompletableFuture.completedFuture(null); + private Subscription s; + + @Override + public void onSubscribe(Subscription s) { + this.s = s; + subscriber.onSubscribe(s); + } + + @Override + public void onNext(T t) { + synchronized (this) { + current = current.thenCompose(__ -> restrict.allow(t)).whenComplete(process(subscriber, t)); + } + } + + private BiConsumer process(Subscriber subscriber, T t) { + return (allow, error) -> { + if (error != null) { + subscriber.onError(error); + return; + } + if (allow) { + subscriber.onNext(t); + } else { + s.request(1); + } + }; + } + + @Override + public void onError(Throwable t) { + synchronized (this) { + current = + current.whenComplete((__, error) -> { + if (error != null) { + subscriber.onError(error); + } + subscriber.onError(t); + }); + } + } + + @Override + public void onComplete() { + synchronized (this) { + current = + current.whenComplete((__, error) -> { + if (error != null) { + subscriber.onError(error); + } + subscriber.onComplete(); + }); + } + } + } + ); + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/GraphqlLongCoercing.java b/src/main/java/com/fleetpin/graphql/builder/GraphqlLongCoercing.java deleted file mode 100644 index 89b7a35..0000000 --- a/src/main/java/com/fleetpin/graphql/builder/GraphqlLongCoercing.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.fleetpin.graphql.builder; - -import static graphql.Assert.assertNotNull; - -import java.math.BigDecimal; -import java.math.BigInteger; - -import graphql.Internal; -import graphql.language.IntValue; -import graphql.language.Value; -import graphql.schema.Coercing; -import graphql.schema.CoercingParseLiteralException; -import graphql.schema.CoercingParseValueException; -import graphql.schema.CoercingSerializeException; - - -public class GraphqlLongCoercing implements Coercing { - - - private Long convertImpl(Object input) { - if (input instanceof Long) { - return (Long) input; - } else if (isNumberIsh(input)) { - BigDecimal value; - try { - value = new BigDecimal(input.toString()); - } catch (NumberFormatException e) { - return null; - } - try { - return value.longValueExact(); - } catch (ArithmeticException e) { - return null; - } - } else { - return null; - } - } - - @Override - public Long serialize(Object input) { - Long result = convertImpl(input); - if (result == null) { - throw new CoercingSerializeException( - "Expected type 'Long' but was '" + typeName(input) + "'." - ); - } - return result; - } - - @Override - public Long parseValue(Object input) { - Long result = convertImpl(input); - if (result == null) { - throw new CoercingParseValueException( - "Expected type 'Int' but was '" + typeName(input) + "'." - ); - } - return result; - } - - @Override - public Long parseLiteral(Object input) { - if (!(input instanceof IntValue)) { - throw new CoercingParseLiteralException( - "Expected AST type 'IntValue' but was '" + typeName(input) + "'." - ); - } - BigInteger value = ((IntValue) input).getValue(); - return value.longValue(); - } - - - private boolean isNumberIsh(Object input) { - return input instanceof Number || input instanceof String; - } - - private String typeName(Object input) { - if (input == null) { - return "null"; - } - - return input.getClass().getSimpleName(); - } -} diff --git a/src/main/java/com/fleetpin/graphql/builder/InputBuilder.java b/src/main/java/com/fleetpin/graphql/builder/InputBuilder.java new file mode 100644 index 0000000..ff00057 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/InputBuilder.java @@ -0,0 +1,289 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.GraphQLDescription; +import com.fleetpin.graphql.builder.annotations.GraphQLIgnore; +import com.fleetpin.graphql.builder.annotations.InputIgnore; +import com.fleetpin.graphql.builder.annotations.OneOf; +import com.fleetpin.graphql.builder.annotations.SchemaOption; +import com.fleetpin.graphql.builder.mapper.ConstructorFieldBuilder; +import com.fleetpin.graphql.builder.mapper.ConstructorFieldBuilder.RecordMapper; +import com.fleetpin.graphql.builder.mapper.InputTypeBuilder; +import com.fleetpin.graphql.builder.mapper.ObjectFieldBuilder; +import com.fleetpin.graphql.builder.mapper.ObjectFieldBuilder.FieldMapper; +import com.fleetpin.graphql.builder.mapper.OneOfBuilder; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInputObjectType.Builder; +import graphql.schema.GraphQLNamedInputType; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; + +public abstract class InputBuilder { + + protected final EntityProcessor entityProcessor; + protected final TypeMeta meta; + + public InputBuilder(EntityProcessor entityProcessor, TypeMeta meta) { + this.entityProcessor = entityProcessor; + this.meta = meta; + } + + protected String buildName() { + var schemaType = SchemaOption.BOTH; + Entity graphTypeAnnotation = meta.getType().getAnnotation(Entity.class); + if (graphTypeAnnotation != null) { + schemaType = graphTypeAnnotation.value(); + } + + String typeName = EntityUtil.getName(meta); + + String inputName = typeName; + if (schemaType != SchemaOption.INPUT) { + inputName += "Input"; + } + return inputName; + } + + public GraphQLNamedInputType buildInput() { + GraphQLInputObjectType.Builder graphInputType = GraphQLInputObjectType.newInputObject(); + var type = meta.getType(); + + graphInputType.name(buildName()); + + { + var description = type.getAnnotation(GraphQLDescription.class); + if (description != null) { + graphInputType.description(description.value()); + } + } + + processFields(graphInputType); + + entityProcessor.addSchemaDirective(type, type, graphInputType::withAppliedDirective); + return graphInputType.build(); + } + + abstract void processFields(Builder graphInputType); + + protected abstract InputTypeBuilder resolve(); + + public static class OneOfInputBuilder extends InputBuilder { + + public OneOfInputBuilder(EntityProcessor entityProcessor, TypeMeta meta) { + super(entityProcessor, meta); + } + + @Override + void processFields(Builder graphInputType) { + var oneOf = meta.getType().getAnnotation(OneOf.class); + for (var oneOfType : oneOf.value()) { + var name = oneOfType.name(); + GraphQLInputObjectField.Builder field = GraphQLInputObjectField.newInputObjectField(); + field.name(name); + if (!oneOfType.description().isEmpty()) { + field.description(oneOfType.description()); + } + TypeMeta innerMeta = new TypeMeta(meta, oneOfType.type(), oneOfType.type()); + innerMeta.optional(); + var type = entityProcessor.getEntity(innerMeta).getInputType(innerMeta, new Annotation[0]); + field.type(type); + graphInputType.field(field); + } + } + + @Override + protected InputTypeBuilder resolve() { + var oneOf = meta.getType().getAnnotation(OneOf.class); + return new OneOfBuilder(entityProcessor, meta.getType(), oneOf); + } + } + + public static class ObjectType extends InputBuilder { + + public ObjectType(EntityProcessor entityProcessor, TypeMeta meta) { + super(entityProcessor, meta); + } + + @Override + void processFields(Builder graphInputType) { + for (Method method : meta.getType().getMethods()) { + try { + var name = EntityUtil.setter(method); + if (name.isPresent()) { + GraphQLInputObjectField.Builder field = GraphQLInputObjectField.newInputObjectField(); + field.name(name.get()); + entityProcessor.addSchemaDirective(method, meta.getType(), field::withAppliedDirective); + TypeMeta innerMeta = new TypeMeta(meta, method.getParameterTypes()[0], method.getGenericParameterTypes()[0], method.getParameters()[0]); + var entity = entityProcessor.getEntity(innerMeta); + var inputType = entity.getInputType(innerMeta, method.getParameterAnnotations()[0]); + field.type(inputType); + + var description = method.getAnnotation(GraphQLDescription.class); + if (description != null) { + field.description(description.value()); + } + + graphInputType.field(field); + } + } catch (RuntimeException e) { + throw new RuntimeException("Failed to process method " + method, e); + } + } + } + + @Override + public InputTypeBuilder resolve() { + var fieldMappers = new ArrayList(); + + for (Method method : meta.getType().getMethods()) { + try { + var name = EntityUtil.setter(method); + if (name.isPresent()) { + TypeMeta innerMeta = new TypeMeta(meta, method.getParameterTypes()[0], method.getGenericParameterTypes()[0], method.getParameters()[0]); + fieldMappers.add(FieldMapper.build(entityProcessor, innerMeta, name.get(), method)); + } + } catch (RuntimeException e) { + throw new RuntimeException("Failed to process method " + method, e); + } + } + + return new ObjectFieldBuilder(meta.getType(), fieldMappers); + } + } + + public static class ObjectConstructorType extends InputBuilder { + + private final Constructor constructor; + + public ObjectConstructorType(EntityProcessor entityProcessor, TypeMeta meta, Constructor constructor) { + super(entityProcessor, meta); + this.constructor = constructor; + } + + @Override + void processFields(Builder graphInputType) { + for (var parameter : constructor.getParameters()) { + try { + GraphQLInputObjectField.Builder field = GraphQLInputObjectField.newInputObjectField(); + field.name(parameter.getName()); + entityProcessor.addSchemaDirective(parameter, meta.getType(), field::withAppliedDirective); + TypeMeta innerMeta = new TypeMeta(meta, parameter.getType(), parameter.getParameterizedType(), parameter); + var entity = entityProcessor.getEntity(innerMeta); + var inputType = entity.getInputType(innerMeta, parameter.getAnnotations()); + + var description = parameter.getAnnotation(GraphQLDescription.class); + if (description != null) { + field.description(description.value()); + } + field.type(inputType); + graphInputType.field(field); + } catch (RuntimeException e) { + throw new RuntimeException("Failed to process method " + parameter, e); + } + } + } + + @Override + public InputTypeBuilder resolve() { + var fieldMappers = new ArrayList(); + + for (var parameter : constructor.getParameters()) { + TypeMeta innerMeta = new TypeMeta(meta, parameter.getType(), parameter.getParameterizedType(), parameter); + var resolver = entityProcessor.getResolver(innerMeta); + fieldMappers.add(new RecordMapper(parameter.getName(), parameter.getType(), resolver)); + } + + return new ConstructorFieldBuilder(meta.getType(), fieldMappers); + } + } + + public static class Record extends InputBuilder { + + public Record(EntityProcessor entityProcessor, TypeMeta meta) { + super(entityProcessor, meta); + } + + @Override + void processFields(Builder graphInputType) { + for (var field : this.meta.getType().getDeclaredFields()) { + try { + if (field.isSynthetic()) { + continue; + } + if (field.isAnnotationPresent(GraphQLIgnore.class)) { + continue; + } + if (Modifier.isStatic(field.getModifiers())) { + continue; + } else { + // getter type + if (!field.isAnnotationPresent(InputIgnore.class)) { + String name = EntityUtil.getName(field.getName(), field); + GraphQLInputObjectField.Builder fieldBuilder = GraphQLInputObjectField.newInputObjectField(); + fieldBuilder.name(name); + entityProcessor.addSchemaDirective(field, meta.getType(), fieldBuilder::withAppliedDirective); + TypeMeta innerMeta = new TypeMeta(meta, field.getType(), field.getGenericType(), field); + var entity = entityProcessor.getEntity(innerMeta); + var inputType = entity.getInputType(innerMeta, field.getAnnotations()); + + var description = field.getAnnotation(GraphQLDescription.class); + if (description != null) { + fieldBuilder.description(description.value()); + } + + fieldBuilder.type(inputType); + graphInputType.field(fieldBuilder); + } + } + } catch (RuntimeException e) { + throw new RuntimeException("Failed to process method " + field, e); + } + } + } + + @Override + protected InputTypeBuilder resolve() { + try { + var fieldMappers = new ArrayList(); + + for (var field : this.meta.getType().getDeclaredFields()) { + if (field.isSynthetic()) { + continue; + } + if (field.isAnnotationPresent(GraphQLIgnore.class)) { + continue; + } + if (Modifier.isStatic(field.getModifiers())) { + continue; + } else { + // getter type + if (!field.isAnnotationPresent(InputIgnore.class)) { + TypeMeta innerMeta = new TypeMeta(meta, field.getType(), field.getGenericType(), field); + var resolver = entityProcessor.getResolver(innerMeta); + var name = EntityUtil.getName(field.getName(), field); + fieldMappers.add(new RecordMapper(name, field.getType(), resolver)); + } + } + } + return new ConstructorFieldBuilder(meta.getType(), fieldMappers); + } catch (RuntimeException e) { + throw new RuntimeException("Failed to process " + this.meta.getType(), e); + } + } + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/InstantCoercing.java b/src/main/java/com/fleetpin/graphql/builder/InstantCoercing.java deleted file mode 100644 index bfe409b..0000000 --- a/src/main/java/com/fleetpin/graphql/builder/InstantCoercing.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed 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 com.fleetpin.graphql.builder; - -import java.time.Instant; - -import graphql.schema.Coercing; -import graphql.schema.CoercingParseLiteralException; -import graphql.schema.CoercingParseValueException; -import graphql.schema.CoercingSerializeException; - -public class InstantCoercing implements Coercing { - - @Override - public Instant serialize(Object dataFetcherResult) throws CoercingSerializeException { - return convertImpl(dataFetcherResult); - } - - @Override - public Instant parseValue(Object input) throws CoercingParseValueException { - return convertImpl(input); - } - - @Override - public Instant parseLiteral(Object input) throws CoercingParseLiteralException { - return convertImpl(input); - } - - - private Instant convertImpl(Object input) { - if (input instanceof Instant) { - return (Instant) input; - } else if (input instanceof String) { - return Instant.parse((String) input); - } - if (input instanceof Long) { - return Instant.ofEpochMilli((long) input); - } - return null; - } - - -} diff --git a/src/main/java/com/fleetpin/graphql/builder/LocalDateCoercing.java b/src/main/java/com/fleetpin/graphql/builder/LocalDateCoercing.java deleted file mode 100644 index b3d0f0f..0000000 --- a/src/main/java/com/fleetpin/graphql/builder/LocalDateCoercing.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed 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 com.fleetpin.graphql.builder; - -import java.time.LocalDate; - -import graphql.schema.Coercing; -import graphql.schema.CoercingParseLiteralException; -import graphql.schema.CoercingParseValueException; -import graphql.schema.CoercingSerializeException; - -public class LocalDateCoercing implements Coercing { - - @Override - public LocalDate serialize(Object dataFetcherResult) throws CoercingSerializeException { - return convertImpl(dataFetcherResult); - } - - @Override - public LocalDate parseValue(Object input) throws CoercingParseValueException { - return convertImpl(input); - } - - @Override - public LocalDate parseLiteral(Object input) throws CoercingParseLiteralException { - return convertImpl(input); - } - - - private LocalDate convertImpl(Object input) { - if (input instanceof LocalDate) { - return (LocalDate) input; - } else if (input instanceof String) { - return LocalDate.parse((String) input); - } - return null; - } - - -} diff --git a/src/main/java/com/fleetpin/graphql/builder/LocalDateTimeCoercing.java b/src/main/java/com/fleetpin/graphql/builder/LocalDateTimeCoercing.java deleted file mode 100644 index 1bfc48e..0000000 --- a/src/main/java/com/fleetpin/graphql/builder/LocalDateTimeCoercing.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed 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 com.fleetpin.graphql.builder; - -import java.time.LocalDateTime; - -import graphql.schema.Coercing; -import graphql.schema.CoercingParseLiteralException; -import graphql.schema.CoercingParseValueException; -import graphql.schema.CoercingSerializeException; - -public class LocalDateTimeCoercing implements Coercing { - - @Override - public LocalDateTime serialize(Object dataFetcherResult) throws CoercingSerializeException { - return convertImpl(dataFetcherResult); - } - - @Override - public LocalDateTime parseValue(Object input) throws CoercingParseValueException { - return convertImpl(input); - } - - @Override - public LocalDateTime parseLiteral(Object input) throws CoercingParseLiteralException { - return convertImpl(input); - } - - - private LocalDateTime convertImpl(Object input) { - if (input instanceof LocalDateTime) { - return (LocalDateTime) input; - } else if (input instanceof String) { - return LocalDateTime.parse((String) input); - } - return null; - } - - -} diff --git a/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java b/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java new file mode 100644 index 0000000..8bef984 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java @@ -0,0 +1,249 @@ +package com.fleetpin.graphql.builder; + +import static com.fleetpin.graphql.builder.EntityUtil.isContext; + +import com.fleetpin.graphql.builder.annotations.Directive; +import com.fleetpin.graphql.builder.annotations.GraphQLDeprecated; +import com.fleetpin.graphql.builder.annotations.GraphQLDescription; +import com.fleetpin.graphql.builder.annotations.Mutation; +import com.fleetpin.graphql.builder.annotations.Query; +import com.fleetpin.graphql.builder.annotations.Subscription; +import graphql.GraphQLContext; +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import graphql.schema.FieldCoordinates; +import graphql.schema.GraphQLAppliedDirective; +import graphql.schema.GraphQLAppliedDirectiveArgument; +import graphql.schema.GraphQLArgument; +import graphql.schema.GraphQLCodeRegistry; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLFieldDefinition.Builder; +import graphql.schema.GraphQLObjectType; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.function.Function; + +class MethodProcessor { + + private final DataFetcherRunner dataFetcherRunner; + private final EntityProcessor entityProcessor; + private final DirectivesSchema diretives; + + private final GraphQLCodeRegistry.Builder codeRegistry; + + private final GraphQLObjectType.Builder graphQuery; + private final GraphQLObjectType.Builder graphMutations; + private final GraphQLObjectType.Builder graphSubscriptions; + + public MethodProcessor(DataFetcherRunner dataFetcherRunner, EntityProcessor entityProcessor, DirectivesSchema diretives) { + this.dataFetcherRunner = dataFetcherRunner; + this.entityProcessor = entityProcessor; + this.diretives = diretives; + this.codeRegistry = GraphQLCodeRegistry.newCodeRegistry(); + + this.graphQuery = GraphQLObjectType.newObject(); + graphQuery.name("Query"); + this.graphMutations = GraphQLObjectType.newObject(); + graphMutations.name("Mutations"); + this.graphSubscriptions = GraphQLObjectType.newObject(); + graphSubscriptions.name("Subscriptions"); + } + + void process(AuthorizerSchema authorizer, Method method) throws ReflectiveOperationException { + if (!Modifier.isStatic(method.getModifiers())) { + throw new RuntimeException("End point must be a static method"); + } + FieldCoordinates coordinates; + GraphQLObjectType.Builder object; + if (method.isAnnotationPresent(Query.class)) { + coordinates = FieldCoordinates.coordinates("Query", EntityUtil.getName(method.getName(), method)); + object = graphQuery; + } else if (method.isAnnotationPresent(Mutation.class)) { + coordinates = FieldCoordinates.coordinates("Mutations", EntityUtil.getName(method.getName(), method)); + object = graphMutations; + } else if (method.isAnnotationPresent(Subscription.class)) { + coordinates = FieldCoordinates.coordinates("Subscriptions", EntityUtil.getName(method.getName(), method)); + object = graphSubscriptions; + } else { + return; + } + + object.field(process(authorizer, coordinates, null, method)); + } + + Builder process(AuthorizerSchema authorizer, FieldCoordinates coordinates, TypeMeta parentMeta, Method method) + throws InvocationTargetException, IllegalAccessException { + GraphQLFieldDefinition.Builder field = GraphQLFieldDefinition.newFieldDefinition(); + + entityProcessor.addSchemaDirective(method, method.getDeclaringClass(), field::withAppliedDirective); + + var deprecated = method.getAnnotation(GraphQLDeprecated.class); + if (deprecated != null) { + field.deprecate(deprecated.value()); + } + + var description = method.getAnnotation(GraphQLDescription.class); + if (description != null) { + field.description(description.value()); + } + + field.name(coordinates.getFieldName()); + + TypeMeta meta = new TypeMeta(parentMeta, method.getReturnType(), method.getGenericReturnType(), method); + var type = entityProcessor.getType(meta, method.getAnnotations()); + field.type(type); + for (int i = 0; i < method.getParameterCount(); i++) { + var parameter = method.getParameters()[i]; + GraphQLArgument.Builder argument = GraphQLArgument.newArgument(); + if (isContext(parameter.getType(), parameter.getAnnotations())) { + continue; + } + + TypeMeta inputMeta = new TypeMeta(null, parameter.getType(), method.getGenericParameterTypes()[i], parameter); + argument.type(entityProcessor.getInputType(inputMeta, method.getParameterAnnotations()[i])); // TODO:dirty cast + + description = parameter.getAnnotation(GraphQLDescription.class); + if (description != null) { + argument.description(description.value()); + } + + for (Annotation annotation : parameter.getAnnotations()) { + // Check to see if the annotation is a directive + if (!annotation.annotationType().isAnnotationPresent(Directive.class)) { + continue; + } + var annotationType = annotation.annotationType(); + // Get the values out of the directive annotation + var methods = annotationType.getDeclaredMethods(); + + // Get the applied directive and add it to the argument + var appliedDirective = getAppliedDirective(annotation, annotationType, methods); + argument.withAppliedDirective(appliedDirective); + } + + argument.name(EntityUtil.getName(parameter.getName(), parameter)); + // TODO: argument.defaultValue(defaultValue) + field.argument(argument); + } + + DataFetcher fetcher = buildFetcher(diretives, authorizer, method, meta); + codeRegistry.dataFetcher(coordinates, fetcher); + return field; + } + + private GraphQLAppliedDirective getAppliedDirective(Annotation annotation, Class annotationType, Method[] methods) + throws IllegalAccessException, InvocationTargetException { + var appliedDirective = new GraphQLAppliedDirective.Builder().name(annotationType.getSimpleName()); + for (var definedMethod : methods) { + var name = definedMethod.getName(); + var value = definedMethod.invoke(annotation); + if (value == null) { + continue; + } + + TypeMeta innerMeta = new TypeMeta(null, definedMethod.getReturnType(), definedMethod.getGenericReturnType()); + var argumentType = entityProcessor.getEntity(innerMeta).getInputType(innerMeta, definedMethod.getAnnotations()); + appliedDirective.argument(GraphQLAppliedDirectiveArgument.newArgument().name(name).type(argumentType).valueProgrammatic(value).build()); + } + return appliedDirective.build(); + } + + private DataFetcher buildFetcher(DirectivesSchema diretives, AuthorizerSchema authorizer, Method method, TypeMeta meta) { + DataFetcher fetcher = buildDataFetcher(meta, method); + fetcher = diretives.wrap(method, meta, fetcher); + + if (authorizer != null) { + fetcher = authorizer.wrap(fetcher, method); + } + return fetcher; + } + + private DataFetcher buildDataFetcher(TypeMeta meta, Method method) { + Function[] resolvers = new Function[method.getParameterCount()]; + + method.setAccessible(true); + + for (int i = 0; i < resolvers.length; i++) { + var parameter = method.getParameters()[i]; + Class type = parameter.getType(); + var name = EntityUtil.getName(parameter.getName(), parameter); + var generic = method.getGenericParameterTypes()[i]; + var argMeta = new TypeMeta(meta, type, generic, parameter); + resolvers[i] = buildResolver(name, argMeta, parameter.getAnnotations()); + } + + DataFetcher fetcher = env -> { + try { + Object[] args = new Object[resolvers.length]; + for (int i = 0; i < resolvers.length; i++) { + args[i] = resolvers[i].apply(env); + } + return method.invoke(env.getSource(), args); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof Exception) { + throw (Exception) e.getCause(); + } else { + throw e; + } + } catch (Exception e) { + throw e; + } + }; + + return dataFetcherRunner.manage(method, fetcher); + } + + private Function buildResolver(String name, TypeMeta argMeta, Annotation[] annotations) { + if (isContext(argMeta.getType(), annotations)) { + var type = argMeta.getType(); + if (type.isAssignableFrom(DataFetchingEnvironment.class)) { + return env -> env; + } + if (type.isAssignableFrom(GraphQLContext.class)) { + return env -> env.getGraphQlContext(); + } + return env -> { + var localContext = env.getLocalContext(); + if (localContext != null && type.isAssignableFrom(localContext.getClass())) { + return localContext; + } + + var context = env.getContext(); + if (context != null && type.isAssignableFrom(context.getClass())) { + return context; + } + + context = env.getGraphQlContext().get(name); + if (context != null && type.isAssignableFrom(context.getClass())) { + return context; + } + throw new RuntimeException("Context object " + name + " not found"); + }; + } + + var resolver = entityProcessor.getResolver(argMeta); + + return env -> { + var arg = env.getArgument(name); + return resolver.convert(arg, env.getGraphQlContext(), env.getLocale()); + }; + } + + public GraphQLCodeRegistry.Builder getCodeRegistry() { + return codeRegistry; + } + + GraphQLObjectType.Builder getGraphQuery() { + return graphQuery; + } + + GraphQLObjectType.Builder getGraphMutations() { + return graphMutations; + } + + GraphQLObjectType.Builder getGraphSubscriptions() { + return graphSubscriptions; + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/MonthDayCoercing.java b/src/main/java/com/fleetpin/graphql/builder/MonthDayCoercing.java deleted file mode 100644 index 152cdfa..0000000 --- a/src/main/java/com/fleetpin/graphql/builder/MonthDayCoercing.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fleetpin.graphql.builder; - -import java.time.MonthDay; - -import graphql.schema.Coercing; -import graphql.schema.CoercingParseLiteralException; -import graphql.schema.CoercingParseValueException; -import graphql.schema.CoercingSerializeException; - -public class MonthDayCoercing implements Coercing { - - @Override - public MonthDay serialize(Object dataFetcherResult) throws CoercingSerializeException { - return convertImpl(dataFetcherResult); - } - - @Override - public MonthDay parseValue(Object input) throws CoercingParseValueException { - return convertImpl(input); - } - - @Override - public MonthDay parseLiteral(Object input) throws CoercingParseLiteralException { - return convertImpl(input); - } - - - private MonthDay convertImpl(Object input) { - if (input instanceof MonthDay) { - return (MonthDay) input; - } else if (input instanceof String) { - return MonthDay.parse((String) input); - } - return null; - } -} \ No newline at end of file diff --git a/src/main/java/com/fleetpin/graphql/builder/ObjectEntity.java b/src/main/java/com/fleetpin/graphql/builder/ObjectEntity.java new file mode 100644 index 0000000..cffab6b --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/ObjectEntity.java @@ -0,0 +1,79 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import com.fleetpin.graphql.builder.annotations.GraphQLCreator; +import com.fleetpin.graphql.builder.annotations.OneOf; +import com.fleetpin.graphql.builder.mapper.InputTypeBuilder; +import graphql.schema.GraphQLNamedInputType; +import graphql.schema.GraphQLNamedOutputType; + +public class ObjectEntity extends EntityHolder { + + private InputBuilder inputBuilder; + + private TypeBuilder typeBuilder; + + public ObjectEntity(EntityProcessor entityProcessor, TypeMeta meta) { + if (meta.getType().isRecord()) { + typeBuilder = new TypeBuilder.Record(entityProcessor, meta); + } else { + typeBuilder = new TypeBuilder.ObjectType(entityProcessor, meta); + } + + if (meta.getType().isAnnotationPresent(OneOf.class)) { + inputBuilder = new InputBuilder.OneOfInputBuilder(entityProcessor, meta); + } else if (meta.getType().isRecord()) { + inputBuilder = new InputBuilder.Record(entityProcessor, meta); + } else { + var constructors = meta.getType().getDeclaredConstructors(); + if (constructors.length == 1) { + var constructor = constructors[0]; + if (constructor.getParameterCount() > 0) { + inputBuilder = new InputBuilder.ObjectConstructorType(entityProcessor, meta, constructor); + return; + } + } + for (var constructor : constructors) { + if (constructor.isAnnotationPresent(GraphQLCreator.class)) { + inputBuilder = new InputBuilder.ObjectConstructorType(entityProcessor, meta, constructor); + return; + } + } + inputBuilder = new InputBuilder.ObjectType(entityProcessor, meta); + } + } + + @Override + protected GraphQLNamedInputType buildInput() { + return inputBuilder.buildInput(); + } + + @Override + public InputTypeBuilder buildResolver() { + return inputBuilder.resolve(); + } + + @Override + protected GraphQLNamedOutputType buildType() { + try { + return typeBuilder.buildType(); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + protected String buildInputName() { + return inputBuilder.buildName(); + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/RestrictType.java b/src/main/java/com/fleetpin/graphql/builder/RestrictType.java index 0a24a90..b234d5a 100644 --- a/src/main/java/com/fleetpin/graphql/builder/RestrictType.java +++ b/src/main/java/com/fleetpin/graphql/builder/RestrictType.java @@ -9,7 +9,6 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder; import java.util.ArrayList; @@ -17,28 +16,26 @@ import java.util.concurrent.CompletableFuture; public interface RestrictType { - public CompletableFuture allow(T obj); - + public default CompletableFuture> filter(List list) { - boolean[] keep = new boolean[list.size()]; - + CompletableFuture[] all = new CompletableFuture[list.size()]; - for(int i = 0; i < list.size(); i++) { + for (int i = 0; i < list.size(); i++) { int offset = i; all[i] = allow(list.get(i)).thenAccept(allow -> keep[offset] = allow); } - return CompletableFuture.allOf(all).thenApply(__ -> { - List toReturn = new ArrayList<>(); - for(int i = 0; i < list.size(); i++) { - if(keep[i]) { - toReturn.add(list.get(i)); + return CompletableFuture + .allOf(all) + .thenApply(__ -> { + List toReturn = new ArrayList<>(); + for (int i = 0; i < list.size(); i++) { + if (keep[i]) { + toReturn.add(list.get(i)); + } } - } - return toReturn; - }); + return toReturn; + }); } - - } diff --git a/src/main/java/com/fleetpin/graphql/builder/RestrictTypeFactory.java b/src/main/java/com/fleetpin/graphql/builder/RestrictTypeFactory.java index 577de13..05e4be2 100644 --- a/src/main/java/com/fleetpin/graphql/builder/RestrictTypeFactory.java +++ b/src/main/java/com/fleetpin/graphql/builder/RestrictTypeFactory.java @@ -9,22 +9,20 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder; +import graphql.schema.DataFetchingEnvironment; import java.lang.reflect.ParameterizedType; import java.util.concurrent.CompletableFuture; -import graphql.schema.DataFetchingEnvironment; - public interface RestrictTypeFactory { public CompletableFuture> create(DataFetchingEnvironment context); default Class extractType() { - for(var inter: getClass().getGenericInterfaces()) { - if(inter instanceof ParameterizedType) { + for (var inter : getClass().getGenericInterfaces()) { + if (inter instanceof ParameterizedType) { var param = (ParameterizedType) inter; - if(RestrictTypeFactory.class.equals(param.getRawType())) { + if (RestrictTypeFactory.class.equals(param.getRawType())) { return (Class) param.getActualTypeArguments()[0]; } } diff --git a/src/main/java/com/fleetpin/graphql/builder/SDLProcessor.java b/src/main/java/com/fleetpin/graphql/builder/SDLProcessor.java deleted file mode 100644 index 68db9d9..0000000 --- a/src/main/java/com/fleetpin/graphql/builder/SDLProcessor.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.fleetpin.graphql.builder; - -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; - -import com.fleetpin.graphql.builder.annotations.GraphQLIgnore; -import com.fleetpin.graphql.builder.annotations.InputIgnore; - -import graphql.schema.GraphQLAppliedDirective; -import graphql.schema.GraphQLAppliedDirectiveArgument; -import graphql.schema.GraphQLArgument; -import graphql.schema.GraphQLDirective; - -/** - * SDL directives are applied directly to the schema. They can be used to export - * extra information about methods By default graphql will not export this - * information but it can be enabled with new - * IntrospectionWithDirectivesSupport() - */ -public class SDLProcessor { - - private final SDLDirective factory; - private final GraphQLDirective directive; - private final List> builders; - - public SDLProcessor(SDLDirective factory, GraphQLDirective directive, List> builders) { - this.factory = (SDLDirective) factory; - this.directive = directive; - this.builders = builders; - } - - public static SDLProcessor build(EntityProcessor entityProcessor, SDLDirective directive) { - - for(var inter: directive.getClass().getAnnotatedInterfaces()) { - if(inter.getType() instanceof ParameterizedType) { - var type = (ParameterizedType) inter.getType(); - if(type.getRawType() instanceof Class) { - var rawType = (Class) type.getRawType(); - if(SDLDirective.class.isAssignableFrom(rawType)) { - var annotation = (Class) type.getActualTypeArguments()[0]; - var arguments = (Class) type.getActualTypeArguments()[1]; - - var builder = GraphQLDirective.newDirective().name(annotation.getSimpleName()); - for(var location: directive.validLocations()) { - builder.validLocation(location); - } - builder.repeatable(directive.repeatable()); - List> builders = new ArrayList<>(); - for(Method method: arguments.getMethods()) { - try { - if(method.isSynthetic()) { - continue; - } - if(method.getDeclaringClass().equals(Object.class)) { - continue; - } - if(method.isAnnotationPresent(GraphQLIgnore.class)) { - continue; - } - //will also be on implementing class - if(Modifier.isAbstract(method.getModifiers()) || method.getDeclaringClass().isInterface()) { - continue; - } - if(Modifier.isStatic(method.getModifiers())) { - continue; - }else { - if(method.getName().matches("(get|is)[A-Z].*") && method.getParameterCount() == 0 ) { - String name; - if(method.getName().startsWith("get")) { - name = method.getName().substring("get".length(), "get".length() + 1).toLowerCase() + method.getName().substring("get".length() + 1); - }else { - name = method.getName().substring("is".length(), "is".length() + 1).toLowerCase() + method.getName().substring("is".length() + 1); - } - GraphQLArgument.Builder argument = GraphQLArgument.newArgument(); - argument.name(name); - TypeMeta innerMeta = new TypeMeta(entityProcessor, null, method.getReturnType(), method.getGenericReturnType()); - var argumentType = SchemaBuilder.getInputType(innerMeta, method.getAnnotations()); - argument.type(argumentType); - builder.argument(argument); - builders.add(object -> { - try { - return GraphQLAppliedDirectiveArgument.newArgument().name(name).type(argumentType).valueProgrammatic(method.invoke(object)).build(); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - }); - } - } - }catch(RuntimeException e) { - throw new RuntimeException("Failed to process method " + method, e); - } - } - return new SDLProcessor(directive, builder.build(), builders); - } - } - } - } - return null; - } - - public void apply(Annotation annotation, Class location, Consumer builder) { - var built = factory.build(annotation, location); - if (built != null) { - // need to call the methods building out the arguments - var arguments = GraphQLAppliedDirective.newDirective(); - arguments.name(directive.getName()); - for(var b: this.builders) { - arguments.argument(b.apply(built)); - } - builder.accept(arguments.build()); - } - - } - - public GraphQLDirective getDirective() { - return directive; - } - -} diff --git a/src/main/java/com/fleetpin/graphql/builder/ScalarEntity.java b/src/main/java/com/fleetpin/graphql/builder/ScalarEntity.java new file mode 100644 index 0000000..d1c0422 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/ScalarEntity.java @@ -0,0 +1,68 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import com.fleetpin.graphql.builder.annotations.GraphQLDescription; +import com.fleetpin.graphql.builder.annotations.Scalar; +import com.fleetpin.graphql.builder.mapper.InputTypeBuilder; +import graphql.schema.Coercing; +import graphql.schema.GraphQLNamedInputType; +import graphql.schema.GraphQLNamedOutputType; +import graphql.schema.GraphQLScalarType; + +public class ScalarEntity extends EntityHolder { + + private final GraphQLScalarType scalar; + + public ScalarEntity(GraphQLScalarType scalar) { + this.scalar = scalar; + } + + public ScalarEntity(DirectivesSchema directives, TypeMeta meta) throws ReflectiveOperationException { + GraphQLScalarType.Builder scalarType = GraphQLScalarType.newScalar(); + String typeName = EntityUtil.getName(meta); + scalarType.name(typeName); + + var type = meta.getType(); + + var description = type.getAnnotation(GraphQLDescription.class); + if (description != null) { + scalarType.description(description.value()); + } + + Class coerecing = type.getAnnotation(Scalar.class).value(); + scalarType.coercing(coerecing.getDeclaredConstructor().newInstance()); + + directives.addSchemaDirective(type, type, scalarType::withAppliedDirective); + this.scalar = scalarType.build(); + } + + @Override + protected GraphQLNamedInputType buildInput() { + return scalar; + } + + @Override + protected GraphQLNamedOutputType buildType() { + return scalar; + } + + @Override + protected String buildInputName() { + return scalar.getName(); + } + + @Override + public InputTypeBuilder buildResolver() { + return scalar.getCoercing()::parseValue; + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java index 7c754e6..24bf2fc 100644 --- a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java +++ b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java @@ -9,526 +9,184 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder; -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationTargetException; +import com.fleetpin.graphql.builder.annotations.*; +import graphql.schema.GraphQLScalarType; +import graphql.schema.GraphQLSchema; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.MonthDay; -import java.time.YearMonth; -import java.time.ZoneId; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.Set; - import org.reflections.Reflections; -import org.reflections.scanners.MethodAnnotationsScanner; -import org.reflections.scanners.SubTypesScanner; -import org.reflections.scanners.TypeAnnotationsScanner; - -import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; -import com.fleetpin.graphql.builder.TypeMeta.Flag; -import com.fleetpin.graphql.builder.annotations.Context; -import com.fleetpin.graphql.builder.annotations.Directive; -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.GraphQLDeprecated; -import com.fleetpin.graphql.builder.annotations.GraphQLDescription; -import com.fleetpin.graphql.builder.annotations.Id; -import com.fleetpin.graphql.builder.annotations.Mutation; -import com.fleetpin.graphql.builder.annotations.Query; -import com.fleetpin.graphql.builder.annotations.Restrict; -import com.fleetpin.graphql.builder.annotations.Restricts; -import com.fleetpin.graphql.builder.annotations.Scalar; -import com.fleetpin.graphql.builder.annotations.SchemaOption; -import com.fleetpin.graphql.builder.annotations.Subscription; - -import graphql.GraphQLContext; -import graphql.Scalars; -import graphql.schema.DataFetcher; -import graphql.schema.DataFetchingEnvironment; -import graphql.schema.FieldCoordinates; -import graphql.schema.GraphQLArgument; -import graphql.schema.GraphQLCodeRegistry; -import graphql.schema.GraphQLFieldDefinition; -import graphql.schema.GraphQLInputType; -import graphql.schema.GraphQLList; -import graphql.schema.GraphQLNonNull; -import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLObjectType.Builder; -import graphql.schema.GraphQLOutputType; -import graphql.schema.GraphQLScalarType; -import graphql.schema.GraphQLSchema; -import graphql.schema.GraphQLType; -import graphql.schema.GraphQLTypeReference; +import org.reflections.scanners.Scanners; public class SchemaBuilder { - public final static ObjectMapper MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).registerModule(new ParameterNamesModule()) - .registerModule(new Jdk8Module()) - .registerModule(new JavaTimeModule()) - .disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS).disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS).disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS) - .setVisibility(PropertyAccessor.FIELD, Visibility.ANY); - - private static final GraphQLScalarType INSTANT_SCALAR = GraphQLScalarType.newScalar().name("DateTime").coercing(new InstantCoercing()).build(); - private static final GraphQLScalarType DATE_SCALAR = GraphQLScalarType.newScalar().name("Date").coercing(new LocalDateCoercing()).build(); - private static final GraphQLScalarType DURATION_SCALAR = GraphQLScalarType.newScalar().name("Duration").coercing(new DurationCoercing()).build(); - private static final GraphQLScalarType ZONE_ID_SCALAR = GraphQLScalarType.newScalar().name("Timezone").coercing(new ZoneIdCoercing()).build(); - private static final GraphQLScalarType MONTH_DAY_SCALAR = GraphQLScalarType.newScalar().name("MonthDay").coercing(new MonthDayCoercing()).build(); - private static final GraphQLScalarType YEAR_MONTH_SCALAR = GraphQLScalarType.newScalar().name("YearMonth").coercing(new YearMonthCoercing()).build(); - private static final GraphQLScalarType LONG_SCALAR = GraphQLScalarType.newScalar().name("Long").coercing(new GraphqlLongCoercing()).build(); - - private final DirectivesSchema diretives; - private final AuthorizerSchema authorizer; - - private final GraphQLCodeRegistry.Builder codeRegistry; - private final Map additionalTypes; - - private final Builder graphQuery; - private final Builder graphMutations; - private final Builder graphSubscriptions; - + private final DirectivesSchema directives; + private final AuthorizerSchema authorizer; private final EntityProcessor entityProcessor; - - private SchemaBuilder(DirectivesSchema diretives, AuthorizerSchema authorizer) { - this.diretives = diretives; + private SchemaBuilder(DataFetcherRunner dataFetcherRunner, List scalars, DirectivesSchema directives, AuthorizerSchema authorizer) { + this.directives = directives; this.authorizer = authorizer; - - this.graphQuery = GraphQLObjectType.newObject(); - graphQuery.name("Query"); - this.graphMutations = GraphQLObjectType.newObject(); - graphMutations.name("Mutations"); - this.graphSubscriptions = GraphQLObjectType.newObject(); - graphSubscriptions.name("Subscriptions"); - this.additionalTypes = new HashMap<>(); - this.codeRegistry = GraphQLCodeRegistry.newCodeRegistry(); - - this.entityProcessor = new EntityProcessor(additionalTypes, codeRegistry, diretives); - - - diretives.processSDL(entityProcessor); + this.entityProcessor = new EntityProcessor(dataFetcherRunner, scalars, directives); + + directives.processDirectives(entityProcessor); } - - private SchemaBuilder process(Set endPoints) throws ReflectiveOperationException { - for(var method: endPoints) { - if(!Modifier.isStatic(method.getModifiers())) { - throw new RuntimeException("End point must be a static method"); - } - //TODO:query vs mutation - GraphQLFieldDefinition.Builder field = GraphQLFieldDefinition.newFieldDefinition(); - - var deprecated = method.getAnnotation(GraphQLDeprecated.class); - if(deprecated != null) { - field.deprecate(deprecated.value()); - } - - var description = method.getAnnotation(GraphQLDescription.class); - if(description != null) { - field.description(description.value()); - } - - field.name(method.getName()); - - TypeMeta meta = new TypeMeta(entityProcessor, null, method.getReturnType(), method.getGenericReturnType()); - field.type(getType(meta, method.getAnnotations())); - for(int i = 0; i < method.getParameterCount(); i++) { - GraphQLArgument.Builder argument = GraphQLArgument.newArgument(); - if(isContext(method.getParameterTypes()[i])) { - continue; - } - - TypeMeta inputMeta = new TypeMeta(this.entityProcessor, null, method.getParameterTypes()[i], method.getGenericParameterTypes()[i]); - argument.type(getInputType(inputMeta, method.getParameterAnnotations()[i]));//TODO:dirty cast - argument.name(method.getParameters()[i].getName()); - //TODO: argument.defaultValue(defaultValue) - field.argument(argument); - } - diretives.addSchemaDirective(method, method.getDeclaringClass(), field::withAppliedDirective); - if(method.isAnnotationPresent(Query.class)) { - graphQuery.field(field); - - DataFetcher fetcher = buildFetcher(diretives, authorizer, method, meta); - codeRegistry.dataFetcher(graphQuery.build(), field.build(), fetcher); - }else if(method.isAnnotationPresent(Mutation.class)) { - graphMutations.field(field); - DataFetcher fetcher = buildFetcher(diretives, authorizer, method, meta); - codeRegistry.dataFetcher(FieldCoordinates.coordinates("Mutations", method.getName()), fetcher); - }else if(method.isAnnotationPresent(Subscription.class)) { - graphSubscriptions.field(field); - DataFetcher fetcher = buildFetcher(diretives, authorizer, method, meta); - codeRegistry.dataFetcher(FieldCoordinates.coordinates("Subscriptions", method.getName()), fetcher); - } - + private SchemaBuilder processTypes(Set> types) { + for (var type : types) { + TypeMeta meta = new TypeMeta(null, type, type); + var annotation = type.getAnnotation(Entity.class); + if (annotation.value() != SchemaOption.INPUT) { + this.entityProcessor.getEntity(meta).getInnerType(meta); + } } return this; } - - private SchemaBuilder processTypes(Set> types) { - for(var type: types) { - TypeMeta meta = new TypeMeta(this.entityProcessor, null, type, type); - - var annotation = type.getAnnotation(Entity.class); - if(annotation.value() != SchemaOption.INPUT) { - this.entityProcessor.process(meta); - } - + + private SchemaBuilder process(HashSet endPoints) throws ReflectiveOperationException { + var methodProcessor = this.entityProcessor.getMethodProcessor(); + for (var method : endPoints) { + methodProcessor.process(authorizer, method); } + return this; } - private GraphQLSchema build(Set> schemaConfiguration) { - codeRegistry.typeResolver("ID", env -> { - return null; - }); - var builder = GraphQLSchema.newSchema().codeRegistry(codeRegistry.build()).additionalTypes(new HashSet<>(additionalTypes.values())); - - var query = graphQuery.build(); + private graphql.schema.GraphQLSchema.Builder build(Set> schemaConfiguration) { + var methods = entityProcessor.getMethodProcessor(); + + var builder = GraphQLSchema.newSchema().codeRegistry(methods.getCodeRegistry().build()).additionalTypes(entityProcessor.getAdditionalTypes()); + + var query = methods.getGraphQuery().build(); builder.query(query); - - var mutations = graphMutations.build(); - if(!mutations.getFields().isEmpty()) { + + var mutations = methods.getGraphMutations().build(); + if (!mutations.getFields().isEmpty()) { builder.mutation(mutations); } - var subscriptions = graphSubscriptions.build(); - if(!subscriptions.getFields().isEmpty()) { + var subscriptions = methods.getGraphSubscriptions().build(); + if (!subscriptions.getFields().isEmpty()) { builder.subscription(subscriptions); } - - - builder.build(); - - diretives.getSchemaDirective().forEach(directive -> builder.additionalDirective(directive)); - - - for(var schema: schemaConfiguration) { - this.diretives.addSchemaDirective(schema, schema, builder::withSchemaAppliedDirective); + + directives.getSchemaDirective().forEach(directive -> builder.additionalDirective(directive)); + + for (var schema : schemaConfiguration) { + this.directives.addSchemaDirective(schema, schema, builder::withSchemaAppliedDirective); } - - return builder.build(); + return builder; + } + public static GraphQLSchema build(String... classPath) { + return builder(classPath).build(); } - private static boolean isContext(Class class1) { - return class1.isAssignableFrom(GraphQLContext.class) || class1.isAssignableFrom(DataFetchingEnvironment.class) || class1.isAnnotationPresent(Context.class); + public static GraphQLSchema.Builder builder(String... classpath) { + var builder = builder(); + for (var path : classpath) { + builder.classpath(path); + } + return builder.build(); } - private static DataFetcher buildFetcher(DirectivesSchema diretives, AuthorizerSchema authorizer, Method method, TypeMeta meta) { - DataFetcher fetcher = env -> { - try { - Object[] args = new Object[method.getParameterCount()]; - for(int i = 0; i < args.length; i++) { - Class type = method.getParameterTypes()[i]; - if(type.isAssignableFrom(env.getClass())) { - args[i] = env; - }else if(type.isAssignableFrom(env.getContext().getClass())) { - args[i] = env.getContext(); - }else { - Object obj = env.getArgument(method.getParameters()[i].getName()); - //if they don't match use json to make them - - if(obj instanceof List) { - var genericType = method.getGenericParameterTypes()[i]; - args[i] = MAPPER.convertValue(obj, new TypeReference() { - @Override - public Type getType() { - return genericType; - } - }); - }else if(type.isInstance(obj) ) { - args[i] = obj; - }else { - if(Optional.class.isAssignableFrom(type)) { - if(obj == null) { - args[i] = Optional.empty(); - }else { - var genericType = method.getGenericParameterTypes()[i]; - var t = ((ParameterizedType) genericType).getActualTypeArguments()[0]; - args[i] = Optional.of(MAPPER.convertValue(obj, new TypeReference() { - @Override - public Type getType() { - return t; - } - })); - } - }else { - var t = method.getParameters()[i].getParameterizedType(); - args[i] = MAPPER.convertValue(obj, new TypeReference() { - @Override - public Type getType() { - return t; - } - }); - } - } - } - } + public static Builder builder() { + return new Builder(); + } - return method.invoke(null, args); - }catch (InvocationTargetException e) { - e.printStackTrace(); - if(e.getCause() instanceof Exception) { - throw (Exception) e.getCause(); - }else { - throw e; - } + public static class Builder { - }catch (Exception e) { - e.printStackTrace(); - throw e; - } - }; - fetcher = diretives.wrap(method, meta, fetcher); - - if(authorizer != null) { - fetcher = authorizer.wrap(fetcher, method); - } - return fetcher; - } + private DataFetcherRunner dataFetcherRunner = (method, fetcher) -> fetcher; + private List classpaths = new ArrayList<>(); + private List scalars = new ArrayList<>(); + private Builder() {} - - static GraphQLOutputType getType(TypeMeta meta, Annotation[] annotations) { - - - GraphQLOutputType toReturn = getTypeInner(meta.getType(), annotations, meta.getName()); - boolean required = true; - for(var flag: meta.getFlags()) { - if(flag == Flag.OPTIONAL) { - required = false; - } - if(flag == Flag.ARRAY) { - if(required) { - toReturn = GraphQLNonNull.nonNull(toReturn); - } - toReturn = GraphQLList.list(toReturn); - required = true; - } + public Builder dataFetcherRunner(DataFetcherRunner dataFetcherRunner) { + this.dataFetcherRunner = dataFetcherRunner; + return this; } - if(required) { - toReturn = GraphQLNonNull.nonNull(toReturn); - } - return toReturn; - } - - - private static GraphQLOutputType getTypeInner(Class type, Annotation[] annotations, String name) { - for(Annotation an: annotations) { - if(an.annotationType().equals(Id.class)) { - return Scalars.GraphQLID; - } + public Builder classpath(String classpath) { + this.classpaths.add(classpath); + return this; } - if(type.equals(Boolean.class) || type.equals(Boolean.TYPE)) { - return Scalars.GraphQLBoolean; - }else if(type.equals(Float.class) || type.equals(Float.TYPE)) { - return Scalars.GraphQLFloat; - }else if(type.equals(Double.class) || type.equals(Double.TYPE)) { - return Scalars.GraphQLFloat; - }else if(type.equals(Integer.class) || type.equals(Integer.TYPE)) { - return Scalars.GraphQLInt; - }else if(type.equals(Long.class) || type.equals(Integer.TYPE)) { - return LONG_SCALAR; - }else if(type.equals(Long.class) || type.equals(Long.TYPE)) { - return Scalars.GraphQLInt; - }else if(type.equals(Short.class) || type.equals(Short.TYPE)) { - return Scalars.GraphQLInt; - }else if(type.equals(String.class)) { - return Scalars.GraphQLString; - }else if(type.equals(Instant.class)) { - return INSTANT_SCALAR; - }else if(type.equals(LocalDate.class)) { - return DATE_SCALAR; - }else if(type.equals(ZoneId.class)) { - return ZONE_ID_SCALAR; - }else if(type.equals(Duration.class)) { - return DURATION_SCALAR; - }else if(type.equals(MonthDay.class)) { - return MONTH_DAY_SCALAR; - }else if(type.equals(YearMonth.class)) { - return YEAR_MONTH_SCALAR; - } - - - if(type.isEnum()) { - return GraphQLTypeReference.typeRef(name); - } - - if(type.isAnnotationPresent(Entity.class)) { - return GraphQLTypeReference.typeRef(name); + public Builder scalar(GraphQLScalarType scalar) { + this.scalars.add(scalar); + return this; } - - if(type.isAnnotationPresent(Scalar.class)) { - return GraphQLTypeReference.typeRef(name); - } - - throw new RuntimeException("Unsupport type " + type); - } - - - static GraphQLInputType getInputType(TypeMeta meta, Annotation[] annotations) { - - GraphQLInputType toReturn = getInputTypeInner(meta.getType(), annotations, meta.getInputName()); - - boolean required = true; - for(var flag: meta.getFlags()) { - if(flag == Flag.OPTIONAL) { - required = false; - } - if(flag == Flag.ARRAY) { - if(required) { - toReturn = GraphQLNonNull.nonNull(toReturn); + + public GraphQLSchema.Builder build() { + try { + Reflections reflections = new Reflections(classpaths, Scanners.SubTypes, Scanners.MethodsAnnotated, Scanners.TypesAnnotated); + Set> authorizers = reflections.getSubTypesOf(Authorizer.class); + //want to make everything split by package + AuthorizerSchema authorizer = AuthorizerSchema.build(dataFetcherRunner, new HashSet<>(classpaths), authorizers); + + Set> schemaConfiguration = reflections.getSubTypesOf(SchemaConfiguration.class); + + Set> directivesTypes = reflections.getTypesAnnotatedWith(Directive.class); + directivesTypes.addAll(reflections.getTypesAnnotatedWith(DataFetcherWrapper.class)); + + Set> restrict = reflections.getTypesAnnotatedWith(Restrict.class); + Set> restricts = reflections.getTypesAnnotatedWith(Restricts.class); + List> globalRestricts = new ArrayList<>(); + + for (var r : restrict) { + Restrict annotation = EntityUtil.getAnnotation(r, Restrict.class); + var factoryClass = annotation.value(); + var factory = factoryClass.getConstructor().newInstance(); + if (!factory.extractType().isAssignableFrom(r)) { + throw new RuntimeException( + "Restrict annotation does match class applied to targets" + factory.extractType() + " but was on class " + r + ); + } + globalRestricts.add(factory); } - toReturn = GraphQLList.list(toReturn); - required = true; - } - } - if(required) { - toReturn = GraphQLNonNull.nonNull(toReturn); - } - return toReturn; - } - - private static GraphQLInputType getInputTypeInner(Class type, Annotation[] annotations, String name) { - for(Annotation an: annotations) { - if(an.annotationType().equals(Id.class)) { - return Scalars.GraphQLID; - } - } + for (var r : restricts) { + Restricts annotations = EntityUtil.getAnnotation(r, Restricts.class); + for (Restrict annotation : annotations.value()) { + var factoryClass = annotation.value(); + var factory = factoryClass.getConstructor().newInstance(); - if(type.equals(Boolean.class) || type.equals(Boolean.TYPE)) { - return Scalars.GraphQLBoolean; - }else if(type.equals(Float.class) || type.equals(Float.TYPE)) { - return Scalars.GraphQLFloat; - }else if(type.equals(Double.class) || type.equals(Double.TYPE)) { - return Scalars.GraphQLFloat; - }else if(type.equals(Integer.class) || type.equals(Integer.TYPE)) { - return Scalars.GraphQLInt; - }else if(type.equals(Long.class) || type.equals(Long.TYPE)) { - return LONG_SCALAR; - }else if(type.equals(Short.class) || type.equals(Short.TYPE)) { - return Scalars.GraphQLInt; - }else if(type.equals(String.class)) { - return Scalars.GraphQLString; - }else if(type.equals(Instant.class)) { - return INSTANT_SCALAR; - }else if(type.equals(LocalDate.class)) { - return DATE_SCALAR; - }else if(type.equals(ZoneId.class)) { - return ZONE_ID_SCALAR; - }else if(type.equals(Duration.class)) { - return DURATION_SCALAR; - }else if(type.equals(MonthDay.class)) { - return MONTH_DAY_SCALAR; - }else if(type.equals(YearMonth.class)) { - return YEAR_MONTH_SCALAR; - } + if (!factory.extractType().isAssignableFrom(r)) { + throw new RuntimeException( + "Restrict annotation does match class applied to targets" + factory.extractType() + " but was on class " + r + ); + } + globalRestricts.add(factory); + } + } - - - - if(type.isEnum()) { - return GraphQLTypeReference.typeRef(name); - } - - if(type.isAnnotationPresent(Scalar.class)) { - return GraphQLTypeReference.typeRef(name); - } - - - if(type.isAnnotationPresent(Entity.class)) { - return GraphQLTypeReference.typeRef(name); - } - - throw new RuntimeException("Unsupport type " + type); - } + DirectivesSchema directivesSchema = DirectivesSchema.build(globalRestricts, directivesTypes); // Entry point for directives - public static GraphQLSchema build(String... classPath) throws ReflectiveOperationException { - - - Reflections reflections = new Reflections(classPath, new SubTypesScanner(), new MethodAnnotationsScanner(), new TypeAnnotationsScanner()); - Set> authorizers = reflections.getSubTypesOf(Authorizer.class); - //want to make everything split by package - AuthorizerSchema authorizer = AuthorizerSchema.build(new HashSet<>(Arrays.asList(classPath)), authorizers); - - Set> schemaConfiguration = reflections.getSubTypesOf(SchemaConfiguration.class); - - - Set> dierctivesTypes = reflections.getTypesAnnotatedWith(Directive.class); - - Set> restrict = reflections.getTypesAnnotatedWith(Restrict.class); - Set> restricts = reflections.getTypesAnnotatedWith(Restricts.class); - List> globalRestricts = new ArrayList<>(); - - for(var r: restrict) { - Restrict annotation = r.getAnnotation(Restrict.class); - var factoryClass = annotation.value(); - var factory = factoryClass.getConstructor().newInstance(); - if(!factory.extractType().isAssignableFrom(r)) { - throw new RuntimeException("Restrict annotation does match class applied to targets" + factory.extractType() + " but was on class " + r); - } - globalRestricts.add(factory); - } - - for(var r: restricts) { - Restricts annotations = r.getAnnotation(Restricts.class); - for (Restrict annotation: annotations.value()) { - var factoryClass = annotation.value(); - var factory = factoryClass.getConstructor().newInstance(); - - if(!factory.extractType().isAssignableFrom(r)) { - throw new RuntimeException("Restrict annotation does match class applied to targets" + factory.extractType() + " but was on class " + r); - } - globalRestricts.add(factory); - } - } - - DirectivesSchema diretivesSchema = DirectivesSchema.build(globalRestricts, dierctivesTypes); - - Set> types = reflections.getTypesAnnotatedWith(Entity.class); - - Set> scalars = reflections.getTypesAnnotatedWith(Scalar.class); - - var mutations = reflections.getMethodsAnnotatedWith(Mutation.class); - var subscriptions = reflections.getMethodsAnnotatedWith(Subscription.class); - var queries = reflections.getMethodsAnnotatedWith(Query.class); - - var endPoints = new HashSet<>(mutations); - endPoints.addAll(subscriptions); - endPoints.addAll(queries); - - types.removeIf(t -> t.getDeclaredAnnotation(Entity.class) == null); - types.removeIf(t -> t.isAnonymousClass()); - scalars.removeIf(t -> t.isAnonymousClass()); - - return new SchemaBuilder(diretivesSchema, authorizer).process(endPoints).processTypes(types).build(schemaConfiguration); - } + Set> types = reflections.getTypesAnnotatedWith(Entity.class); + + var mutations = reflections.getMethodsAnnotatedWith(Mutation.class); + var subscriptions = reflections.getMethodsAnnotatedWith(Subscription.class); + var queries = reflections.getMethodsAnnotatedWith(Query.class); + var endPoints = new HashSet<>(mutations); + endPoints.addAll(subscriptions); + endPoints.addAll(queries); + types.removeIf(t -> t.getDeclaredAnnotation(Entity.class) == null); + types.removeIf(t -> t.isAnonymousClass()); - + return new SchemaBuilder(dataFetcherRunner, scalars, directivesSchema, authorizer) + .processTypes(types) + .process(endPoints) + .build(schemaConfiguration); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + } } diff --git a/src/main/java/com/fleetpin/graphql/builder/SchemaConfiguration.java b/src/main/java/com/fleetpin/graphql/builder/SchemaConfiguration.java index 7a2240b..4eb2412 100644 --- a/src/main/java/com/fleetpin/graphql/builder/SchemaConfiguration.java +++ b/src/main/java/com/fleetpin/graphql/builder/SchemaConfiguration.java @@ -1,9 +1,18 @@ -package com.fleetpin.graphql.builder; - -/** - * Used to add extra information to the schema. Currently only supports directives. That are inferred using the annotation - * - */ -public interface SchemaConfiguration { - -} +/* + * Licensed 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 com.fleetpin.graphql.builder; + +/** + * Used to add extra information to the schema. Currently only supports directives. That are inferred using the annotation + * + */ +public interface SchemaConfiguration {} diff --git a/src/main/java/com/fleetpin/graphql/builder/TypeBuilder.java b/src/main/java/com/fleetpin/graphql/builder/TypeBuilder.java new file mode 100644 index 0000000..7d35b9c --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/TypeBuilder.java @@ -0,0 +1,232 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.GraphQLDescription; +import com.fleetpin.graphql.builder.annotations.GraphQLIgnore; +import graphql.schema.FieldCoordinates; +import graphql.schema.GraphQLInterfaceType; +import graphql.schema.GraphQLNamedOutputType; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLObjectType.Builder; +import graphql.schema.GraphQLTypeReference; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +public abstract class TypeBuilder { + + protected final EntityProcessor entityProcessor; + protected final TypeMeta meta; + + public TypeBuilder(EntityProcessor entityProcessor, TypeMeta meta) { + this.entityProcessor = entityProcessor; + this.meta = meta; + } + + public GraphQLNamedOutputType buildType() throws ReflectiveOperationException { + Builder graphType = GraphQLObjectType.newObject(); + String typeName = EntityUtil.getName(meta); + graphType.name(typeName); + + GraphQLInterfaceType.Builder interfaceBuilder = GraphQLInterfaceType.newInterface(); + interfaceBuilder.name(typeName); + var type = meta.getType(); + { + var description = type.getAnnotation(GraphQLDescription.class); + if (description != null) { + graphType.description(description.value()); + interfaceBuilder.description(description.value()); + } + } + + processFields(typeName, graphType, interfaceBuilder); + + boolean unmappedGenerics = meta.hasUnmappedGeneric(); + + if (unmappedGenerics) { + var name = EntityUtil.getName(meta.notDirect()); + + graphType.withInterface(GraphQLTypeReference.typeRef(name)); + if (meta.isDirect()) { + interfaceBuilder.withInterface(GraphQLTypeReference.typeRef(name)); + } + } + Class parent = type.getSuperclass(); + while (parent != null) { + if (parent.isAnnotationPresent(Entity.class)) { + TypeMeta innerMeta = new TypeMeta(meta, parent, type.getGenericSuperclass()); + GraphQLInterfaceType interfaceName = (GraphQLInterfaceType) entityProcessor.getEntity(innerMeta).getInnerType(innerMeta); + addInterface(graphType, interfaceBuilder, interfaceName); + + if (!parent.equals(type.getGenericSuperclass())) { + innerMeta = new TypeMeta(meta, parent, parent); + interfaceName = (GraphQLInterfaceType) entityProcessor.getEntity(innerMeta).getInnerType(innerMeta); + addInterface(graphType, interfaceBuilder, interfaceName); + } + + var genericMeta = new TypeMeta(null, parent, parent); + if (!EntityUtil.getName(innerMeta).equals(EntityUtil.getName(genericMeta))) { + interfaceName = (GraphQLInterfaceType) entityProcessor.getEntity(genericMeta).getInnerType(genericMeta); + addInterface(graphType, interfaceBuilder, interfaceName); + } + } + parent = parent.getSuperclass(); + } + // generics + TypeMeta innerMeta = new TypeMeta(meta, type, type); + if (!EntityUtil.getName(innerMeta).equals(typeName)) { + var interfaceName = entityProcessor.getEntity(innerMeta).getInnerType(innerMeta); + graphType.withInterface(GraphQLTypeReference.typeRef(interfaceName.getName())); + interfaceBuilder.withInterface(GraphQLTypeReference.typeRef(interfaceName.getName())); + } + innerMeta = new TypeMeta(null, type, type); + if (!EntityUtil.getName(innerMeta).equals(typeName)) { + var interfaceName = entityProcessor.getEntity(innerMeta).getInnerType(innerMeta); + graphType.withInterface(GraphQLTypeReference.typeRef(interfaceName.getName())); + interfaceBuilder.withInterface(GraphQLTypeReference.typeRef(interfaceName.getName())); + } + + boolean interfaceable = type.isInterface() || Modifier.isAbstract(type.getModifiers()); + if (!meta.isDirect() && (interfaceable || unmappedGenerics)) { + entityProcessor.addSchemaDirective(type, type, interfaceBuilder::withAppliedDirective); + GraphQLInterfaceType built = interfaceBuilder.build(); + + entityProcessor + .getCodeRegistry() + .typeResolver( + built.getName(), + env -> { + if (type.isInstance(env.getObject())) { + var meta = new TypeMeta(null, env.getObject().getClass(), env.getObject().getClass()); + var t = entityProcessor.getEntity(meta).getInnerType(null); + if (!(t instanceof GraphQLObjectType)) { + t = entityProcessor.getEntity(meta.direct()).getInnerType(null); + } + try { + return (GraphQLObjectType) t; + } catch (ClassCastException e) { + throw e; + } + } + return null; + } + ); + + if (unmappedGenerics && !meta.isDirect()) { + var directType = meta.direct(); + entityProcessor.getEntity(directType).getInnerType(directType); + } + return built; + } + + entityProcessor.addSchemaDirective(type, type, graphType::withAppliedDirective); + var built = graphType.build(); + entityProcessor + .getCodeRegistry() + .typeResolver( + built.getName(), + env -> { + if (type.isInstance(env.getObject())) { + return built; + } + return null; + } + ); + return built; + } + + private void addInterface(Builder graphType, GraphQLInterfaceType.Builder interfaceBuilder, GraphQLInterfaceType interfaceName) { + graphType.withInterface(interfaceName); + for (var inner : interfaceName.getInterfaces()) { + graphType.withInterface(GraphQLTypeReference.typeRef(inner.getName())); + interfaceBuilder.withInterface(GraphQLTypeReference.typeRef(inner.getName())); + } + interfaceBuilder.withInterface(interfaceName); + } + + protected abstract void processFields(String typeName, Builder graphType, graphql.schema.GraphQLInterfaceType.Builder interfaceBuilder) + throws ReflectiveOperationException; + + public static class ObjectType extends TypeBuilder { + + public ObjectType(EntityProcessor entityProcessor, TypeMeta meta) { + super(entityProcessor, meta); + } + + @Override + protected void processFields(String typeName, Builder graphType, graphql.schema.GraphQLInterfaceType.Builder interfaceBuilder) + throws ReflectiveOperationException { + var type = meta.getType(); + for (Method method : type.getMethods()) { + try { + var name = EntityUtil.getter(method); + if (name.isEmpty()) { + continue; + } + var f = entityProcessor.getMethodProcessor().process(null, FieldCoordinates.coordinates(typeName, name.get()), meta, method); + graphType.field(f); + interfaceBuilder.field(f); + } catch (RuntimeException e) { + throw new RuntimeException("Failed to process method " + method, e); + } + } + } + } + + public static class Record extends TypeBuilder { + + public Record(EntityProcessor entityProcessor, TypeMeta meta) { + super(entityProcessor, meta); + } + + @Override + protected void processFields(String typeName, Builder graphType, graphql.schema.GraphQLInterfaceType.Builder interfaceBuilder) + throws ReflectiveOperationException { + var type = meta.getType(); + + for (var field : type.getDeclaredFields()) { + try { + if (field.isSynthetic()) { + continue; + } + if (field.getDeclaringClass().equals(Object.class)) { + continue; + } + if (field.isAnnotationPresent(GraphQLIgnore.class)) { + continue; + } + // will also be on implementing class + if (Modifier.isAbstract(field.getModifiers()) || field.getDeclaringClass().isInterface()) { + continue; + } + if (Modifier.isStatic(field.getModifiers())) { + continue; + } else { + var method = type.getMethod(field.getName()); + if (method.isAnnotationPresent(GraphQLIgnore.class)) { + continue; + } + + var name = EntityUtil.getName(field.getName(), field, method); + + var f = entityProcessor.getMethodProcessor().process(null, FieldCoordinates.coordinates(typeName, name), meta, method); + graphType.field(f); + interfaceBuilder.field(f); + } + } catch (RuntimeException e) { + throw new RuntimeException("Failed to process method " + field, e); + } + } + } + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/TypeMeta.java b/src/main/java/com/fleetpin/graphql/builder/TypeMeta.java index 762b663..37cf193 100644 --- a/src/main/java/com/fleetpin/graphql/builder/TypeMeta.java +++ b/src/main/java/com/fleetpin/graphql/builder/TypeMeta.java @@ -9,9 +9,10 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder; +import com.fleetpin.graphql.builder.annotations.InnerNullable; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; @@ -22,200 +23,231 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; - +import javax.annotation.Nullable; import org.reactivestreams.Publisher; - public class TypeMeta { - enum Flag{ASYNC, ARRAY, OPTIONAL, SUBSCRIPTION} + enum Flag { + ASYNC, + ARRAY, + OPTIONAL, + SUBSCRIPTION, + DIRECT, + } private List flags; private Type genericType; private Class type; + private boolean direct; - private final EntityProcessor entityProcessor; private final TypeMeta parent; - TypeMeta(EntityProcessor entityProcessor, TypeMeta parent, Class type, Type genericType) { + private List> types; + + private AnnotatedElement element; + + TypeMeta(TypeMeta parent, Class type, Type genericType) { + this(parent, type, genericType, null); + } + + TypeMeta(TypeMeta parent, Class type, Type genericType, AnnotatedElement element) { this.parent = parent; - this.entityProcessor = entityProcessor; + this.element = element; flags = new ArrayList<>(); - process(type, genericType); + types = new ArrayList<>(); + process(type, genericType, element); Collections.reverse(flags); } - private void processGeneric(TypeMeta target, TypeVariable type) { + private void processGeneric(TypeMeta target, TypeVariable type, AnnotatedElement element) { var owningClass = target.genericType; - if(owningClass == null) { + if (owningClass == null) { owningClass = target.type; } - if(owningClass instanceof Class) { - findType(target, type, (Class) owningClass); - }else if(owningClass instanceof ParameterizedType) { + if (owningClass instanceof Class) { + findType(target, type, (Class) owningClass, element); + } else if (owningClass instanceof ParameterizedType) { var pt = (ParameterizedType) owningClass; - if(!matchType(target, type.getTypeName(), pt, true)) { + if (!matchType(target, type.getTypeName(), pt, true, element)) { throw new UnsupportedOperationException("Does not handle type " + owningClass); } - }else { + } else { throw new UnsupportedOperationException("Does not handle type " + owningClass); } - - } - private boolean matchType(TypeMeta target, String typeName, ParameterizedType type, boolean parent) { + private boolean matchType(TypeMeta target, String typeName, ParameterizedType type, boolean parent, AnnotatedElement element) { var raw = (Class) type.getRawType(); - while(raw != null) { - for(int i = 0; i < raw.getTypeParameters().length; i++) { + while (raw != null) { + for (int i = 0; i < raw.getTypeParameters().length; i++) { var arg = type.getActualTypeArguments()[i]; var param = raw.getTypeParameters()[i]; - if(param.getTypeName().equals(typeName)) { - if(arg instanceof TypeVariable) { - if(parent) { - processGeneric(target.parent, (TypeVariable) arg); - }else { - processGeneric(target, (TypeVariable) arg); + if (param.getTypeName().equals(typeName)) { + if (arg instanceof TypeVariable) { + if (parent) { + processGeneric(target.parent, (TypeVariable) arg, element); + } else { + processGeneric(target, (TypeVariable) arg, element); } return true; - }else if(arg instanceof WildcardType) { - for(var bound: param.getBounds()) { - if(bound instanceof ParameterizedType) { - process((Class) ((ParameterizedType)bound).getRawType(), bound); - }else if(bound instanceof TypeVariable) { - processGeneric(target, (TypeVariable)bound); - }else { - process((Class) bound, null); + } else if (arg instanceof WildcardType) { + for (var bound : param.getBounds()) { + if (bound instanceof ParameterizedType) { + process((Class) ((ParameterizedType) bound).getRawType(), bound, element); + } else if (bound instanceof TypeVariable) { + processGeneric(target, (TypeVariable) bound, element); + } else { + process((Class) bound, null, element); } } return true; - }else { + } else if (arg instanceof ParameterizedType pType) { + process((Class) pType.getRawType(), pType, element); + return true; + } else { var klass = (Class) arg; - process(klass, klass); + process(klass, klass, element); return true; } } - } raw = raw.getSuperclass(); } return false; } - private void findType(TypeMeta target, TypeVariable type, Class start) { + private void findType(TypeMeta target, TypeVariable type, Class start, AnnotatedElement element) { var startClass = (Class) start; var genericDeclaration = type.getGenericDeclaration(); - if(start.equals(genericDeclaration) ) { - //we don't have any implementing logic we are at this level so take the bounds - for(var bound: type.getBounds()) { - if(bound instanceof ParameterizedType) { - process((Class) ((ParameterizedType)bound).getRawType(), bound); - }else if(bound instanceof TypeVariable) { - processGeneric(target, (TypeVariable)bound); - }else { - process((Class) bound, null); + if (start.equals(genericDeclaration)) { + // we don't have any implementing logic we are at this level so take the bounds + for (var bound : type.getBounds()) { + if (bound instanceof ParameterizedType) { + process((Class) ((ParameterizedType) bound).getRawType(), bound, element); + } else if (bound instanceof TypeVariable) { + processGeneric(target, (TypeVariable) bound, element); + } else { + process((Class) bound, null, element); } } } - if(startClass.getSuperclass() != null && startClass.getSuperclass().equals(genericDeclaration)) { + if (startClass.getSuperclass() != null && startClass.getSuperclass().equals(genericDeclaration)) { var generic = (ParameterizedType) startClass.getGenericSuperclass(); - if(matchType(target, type.getTypeName(), generic, false)) { + if (matchType(target, type.getTypeName(), generic, false, element)) { return; } } - for(var inter: startClass.getGenericInterfaces()) { - if(inter instanceof ParameterizedType) { + for (var inter : startClass.getGenericInterfaces()) { + if (inter instanceof ParameterizedType) { var generic = (ParameterizedType) inter; - if(generic.getRawType().equals(genericDeclaration)) { - if(matchType(target, type.getTypeName(), generic, false)) { + if (generic.getRawType().equals(genericDeclaration)) { + if (matchType(target, type.getTypeName(), generic, false, element)) { return; } } } } - if(startClass.getSuperclass() != null) { - findType(target, type, startClass.getSuperclass()); + if (startClass.getSuperclass() != null) { + findType(target, type, startClass.getSuperclass(), element); } - for(var inter: startClass.getInterfaces()) { - findType(target, type, inter); + for (var inter : startClass.getInterfaces()) { + findType(target, type, inter, element); } } - private void process(Class type, Type genericType) { - - if(type.isArray()) { + private void process(Class type, Type genericType, AnnotatedElement element) { + if (element != null && (element.isAnnotationPresent(Nullable.class) || element.isAnnotationPresent(jakarta.annotation.Nullable.class))) { + if (!flags.contains(Flag.OPTIONAL)) { + flags.add(Flag.OPTIONAL); + } + } + + if (type.isArray()) { flags.add(Flag.ARRAY); - process(type.getComponentType(), null); + types.add(type); + process(type.getComponentType(), null, element); return; } - if(Collection.class.isAssignableFrom(type)) { + if (Collection.class.isAssignableFrom(type)) { flags.add(Flag.ARRAY); + types.add(type); genericType = ((ParameterizedType) genericType).getActualTypeArguments()[0]; - if(genericType instanceof ParameterizedType) { - process((Class) ((ParameterizedType)genericType).getRawType(), genericType); - }else if(genericType instanceof TypeVariable) { - processGeneric(parent, (TypeVariable)genericType); - }else { - process((Class) genericType, null); + if (genericType instanceof ParameterizedType) { + process((Class) ((ParameterizedType) genericType).getRawType(), genericType, element); + } else if (genericType instanceof TypeVariable) { + processGeneric(parent, (TypeVariable) genericType, element); + } else { + process((Class) genericType, null, element); } return; } - if(CompletableFuture.class.isAssignableFrom(type)) { + if (CompletableFuture.class.isAssignableFrom(type)) { flags.add(Flag.ASYNC); + types.add(type); genericType = ((ParameterizedType) genericType).getActualTypeArguments()[0]; - if(genericType instanceof ParameterizedType) { - process((Class) ((ParameterizedType)genericType).getRawType(), genericType); - }else if(genericType instanceof TypeVariable) { - processGeneric(parent, (TypeVariable)genericType); - }else { - process((Class) genericType, null); + if (genericType instanceof ParameterizedType) { + process((Class) ((ParameterizedType) genericType).getRawType(), genericType, element); + } else if (genericType instanceof TypeVariable) { + processGeneric(parent, (TypeVariable) genericType, element); + } else { + process((Class) genericType, null, element); } return; - } - if(Optional.class.isAssignableFrom(type)) { + if (Optional.class.isAssignableFrom(type)) { flags.add(Flag.OPTIONAL); + types.add(type); genericType = ((ParameterizedType) genericType).getActualTypeArguments()[0]; - if(genericType instanceof ParameterizedType) { - process((Class) ((ParameterizedType)genericType).getRawType(), genericType); - }else if(genericType instanceof TypeVariable) { - processGeneric(parent, (TypeVariable)genericType); - }else { - process((Class) genericType, null); + if (genericType instanceof ParameterizedType) { + process((Class) ((ParameterizedType) genericType).getRawType(), genericType, element); + } else if (genericType instanceof TypeVariable) { + processGeneric(parent, (TypeVariable) genericType, element); + } else { + process((Class) genericType, null, element); } return; } - - if(Publisher.class.isAssignableFrom(type)) { + if (Publisher.class.isAssignableFrom(type)) { flags.add(Flag.SUBSCRIPTION); + types.add(type); genericType = ((ParameterizedType) genericType).getActualTypeArguments()[0]; - if(genericType instanceof ParameterizedType) { - process((Class) ((ParameterizedType)genericType).getRawType(), genericType); - }else if(genericType instanceof TypeVariable) { - processGeneric(parent, (TypeVariable)genericType); - }else { - process((Class) genericType, null); + if (genericType instanceof ParameterizedType) { + process((Class) ((ParameterizedType) genericType).getRawType(), genericType, element); + } else if (genericType instanceof TypeVariable) { + processGeneric(parent, (TypeVariable) genericType, element); + } else { + process((Class) genericType, null, element); } return; } - if(genericType != null && genericType instanceof TypeVariable) { - processGeneric(parent, (TypeVariable) genericType); + + if (genericType != null && genericType instanceof TypeVariable) { + processGeneric(parent, (TypeVariable) genericType, element); return; } + + if (element != null && (element.isAnnotationPresent(InnerNullable.class))) { + if (!flags.contains(Flag.OPTIONAL)) { + flags.add(Flag.OPTIONAL); + } + } + this.type = type; this.genericType = genericType; + types.add(type); } - Class getType() { + public Class getType() { return type; } - + public Type getGenericType() { return genericType; } @@ -224,88 +256,75 @@ public List getFlags() { return flags; } - public String getName() { - return entityProcessor.process(this); - } - - public String getInputName() { - return entityProcessor.processInput(this); + public List> getTypes() { + return types; } public boolean hasUnmappedGeneric() { - if(type.getTypeParameters().length == 0) { + if (type.getTypeParameters().length == 0) { return false; } - if(genericType == null || !(genericType instanceof ParameterizedType)) { + if (genericType == null || !(genericType instanceof ParameterizedType)) { return true; } - + ParameterizedType pt = (ParameterizedType) genericType; - - for(var type: pt.getActualTypeArguments()) { - if(type instanceof TypeVariable) { - if(resolveToType((TypeVariable) type) == null) { + + for (var type : pt.getActualTypeArguments()) { + if (type instanceof TypeVariable) { + if (resolveToType((TypeVariable) type) == null) { return true; } - }else if(!(type instanceof Class)) { + } else if (!(type instanceof Class)) { return true; } } return false; - } - - - public Class resolveToType(TypeVariable variable) { var parent = this.parent; - + var parentType = type; var genericType = this.genericType; - - while(true) { - if(genericType instanceof ParameterizedType) { - var pt = parentType.getTypeParameters(); - for(int i =0; i< pt.length; i++) { + while (true) { + if (genericType instanceof ParameterizedType) { + var pt = parentType.getTypeParameters(); + for (int i = 0; i < pt.length; i++) { var p = pt[i]; - if(p.equals(variable)) { - var generic = (ParameterizedType)genericType; //safe as has to if equal vaiable + if (p.equals(variable)) { + var generic = (ParameterizedType) genericType; // safe as has to if equal vaiable var implementingType = generic.getActualTypeArguments()[i]; - if(implementingType instanceof Class) { + if (implementingType instanceof Class) { return (Class) implementingType; - }else if (implementingType instanceof TypeVariable){ + } else if (implementingType instanceof TypeVariable) { return resolveToType((TypeVariable) implementingType); - }else { + } else { throw new RuntimeException("Generics are more complex that logic currently can handle"); } - } } } - + var pt = parentType.getSuperclass().getTypeParameters(); - for(int i =0; i< pt.length; i++) { + for (int i = 0; i < pt.length; i++) { var p = pt[i]; - if(p.equals(variable)) { - - var superClass = (ParameterizedType) parentType.getGenericSuperclass(); //safe as has to if equal vaiable + if (p.equals(variable)) { + var superClass = (ParameterizedType) parentType.getGenericSuperclass(); // safe as has to if equal vaiable var implementingType = superClass.getActualTypeArguments()[i]; - - if(implementingType instanceof Class) { + + if (implementingType instanceof Class) { return (Class) implementingType; - }else if (implementingType instanceof TypeVariable){ + } else if (implementingType instanceof TypeVariable) { return resolveToType((TypeVariable) implementingType); - }else { + } else { throw new RuntimeException("Generics are more complex that logic currently can handle"); } - } - } - - if(parent == null) { + + if (parent == null) { break; } parentType = parent.getType(); @@ -315,5 +334,22 @@ public Class resolveToType(TypeVariable variable) { return null; } + public void optional() { + flags.add(Flag.OPTIONAL); + } + + public TypeMeta direct() { + var toReturn = new TypeMeta(parent, type, genericType, element); + toReturn.direct = true; + return toReturn; + } + + public boolean isDirect() { + return direct; + } + public TypeMeta notDirect() { + var toReturn = new TypeMeta(parent, type, genericType, element); + return toReturn; + } } diff --git a/src/main/java/com/fleetpin/graphql/builder/UnionType.java b/src/main/java/com/fleetpin/graphql/builder/UnionType.java new file mode 100644 index 0000000..c25ccc6 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/UnionType.java @@ -0,0 +1,83 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import com.fleetpin.graphql.builder.annotations.Union; +import com.fleetpin.graphql.builder.mapper.InputTypeBuilder; +import graphql.schema.GraphQLNamedInputType; +import graphql.schema.GraphQLNamedOutputType; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLTypeReference; +import graphql.schema.GraphQLUnionType; + +public class UnionType extends EntityHolder { + + private final EntityProcessor entityProcessor; + private final Union union; + + public UnionType(EntityProcessor entityProcessor, Union union) { + this.entityProcessor = entityProcessor; + this.union = union; + } + + @Override + protected GraphQLNamedOutputType buildType() { + String name = buildInputName(); + var builder = GraphQLUnionType.newUnionType(); + builder.name(name); + + for (var type : union.value()) { + var possible = entityProcessor.getEntity(type).getInnerType(new TypeMeta(null, type, type)); + builder.possibleType(GraphQLTypeReference.typeRef(possible.getName())); + } + + entityProcessor + .getCodeRegistry() + .typeResolver( + name, + env -> { + for (var type : union.value()) { + if (type.isInstance(env.getObject())) { + return (GraphQLObjectType) entityProcessor.getEntity(type).getInnerType(new TypeMeta(null, type, type)); + } + } + throw new RuntimeException("Union " + name + " Does not support type " + env.getObject().getClass().getSimpleName()); + } + ); + return builder.build(); + } + + @Override + protected GraphQLNamedInputType buildInput() { + return null; + } + + @Override + protected String buildInputName() { + return name(union); + } + + static String name(Union union) { + StringBuilder name = new StringBuilder("Union"); + + for (var type : union.value()) { + name.append("_"); + name.append(EntityUtil.getName(new TypeMeta(null, type, type))); + } + return name.toString(); + } + + @Override + protected InputTypeBuilder buildResolver() { + return null; + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/YearMonthCoercing.java b/src/main/java/com/fleetpin/graphql/builder/YearMonthCoercing.java deleted file mode 100644 index 1acfb72..0000000 --- a/src/main/java/com/fleetpin/graphql/builder/YearMonthCoercing.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fleetpin.graphql.builder; - -import java.time.YearMonth; - -import graphql.schema.Coercing; -import graphql.schema.CoercingParseLiteralException; -import graphql.schema.CoercingParseValueException; -import graphql.schema.CoercingSerializeException; - -public class YearMonthCoercing implements Coercing { - - @Override - public YearMonth serialize(Object dataFetcherResult) throws CoercingSerializeException { - return convertImpl(dataFetcherResult); - } - - @Override - public YearMonth parseValue(Object input) throws CoercingParseValueException { - return convertImpl(input); - } - - @Override - public YearMonth parseLiteral(Object input) throws CoercingParseLiteralException { - return convertImpl(input); - } - - - private YearMonth convertImpl(Object input) { - if (input instanceof YearMonth) { - return (YearMonth) input; - } else if (input instanceof String) { - return YearMonth.parse((String) input); - } - return null; - } -} \ No newline at end of file diff --git a/src/main/java/com/fleetpin/graphql/builder/ZoneIdCoercing.java b/src/main/java/com/fleetpin/graphql/builder/ZoneIdCoercing.java deleted file mode 100644 index c3b6c31..0000000 --- a/src/main/java/com/fleetpin/graphql/builder/ZoneIdCoercing.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed 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 com.fleetpin.graphql.builder; - -import java.time.ZoneId; - -import graphql.schema.Coercing; -import graphql.schema.CoercingParseLiteralException; -import graphql.schema.CoercingParseValueException; -import graphql.schema.CoercingSerializeException; - -public class ZoneIdCoercing implements Coercing { - - @Override - public ZoneId serialize(Object dataFetcherResult) throws CoercingSerializeException { - return convertImpl(dataFetcherResult); - } - - @Override - public ZoneId parseValue(Object input) throws CoercingParseValueException { - return convertImpl(input); - } - - @Override - public ZoneId parseLiteral(Object input) throws CoercingParseLiteralException { - return convertImpl(input); - } - - - private ZoneId convertImpl(Object input) { - if (input instanceof ZoneId) { - return (ZoneId) input; - } else if (input instanceof String) { - return ZoneId.of((String) input); - } - return null; - } - - -} diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/Context.java b/src/main/java/com/fleetpin/graphql/builder/annotations/Context.java index 06e35a9..0ebe5d7 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/Context.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/Context.java @@ -9,7 +9,6 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -19,7 +18,6 @@ import java.lang.annotation.Target; @Retention(RUNTIME) -@Target(ElementType.TYPE) +@Target({ ElementType.TYPE, ElementType.PARAMETER }) public @interface Context { - } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/DataFetcherWrapper.java b/src/main/java/com/fleetpin/graphql/builder/annotations/DataFetcherWrapper.java new file mode 100644 index 0000000..1f11f01 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/DataFetcherWrapper.java @@ -0,0 +1,14 @@ +package com.fleetpin.graphql.builder.annotations; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.fleetpin.graphql.builder.DirectiveOperation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +public @interface DataFetcherWrapper { + Class> value(); +} diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/Directive.java b/src/main/java/com/fleetpin/graphql/builder/annotations/Directive.java index 21ab8c6..7dbd689 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/Directive.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/Directive.java @@ -9,20 +9,23 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import com.fleetpin.graphql.builder.DirectiveCaller; +import com.fleetpin.graphql.builder.DirectiveOperation; +import graphql.introspection.Introspection; +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import com.fleetpin.graphql.builder.DirectiveOperation; - @Retention(RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Directive { - Class> value(); - + Introspection.DirectiveLocation[] value(); + + boolean repeatable() default false; } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/Entity.java b/src/main/java/com/fleetpin/graphql/builder/annotations/Entity.java index 9d5dac4..32184cf 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/Entity.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/Entity.java @@ -9,7 +9,6 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -22,5 +21,4 @@ @Target(ElementType.TYPE) public @interface Entity { SchemaOption value() default SchemaOption.TYPE; - } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLCreator.java b/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLCreator.java new file mode 100644 index 0000000..e70af41 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLCreator.java @@ -0,0 +1,23 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.annotations; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target({ ElementType.CONSTRUCTOR }) +public @interface GraphQLCreator { +} diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLDeprecated.java b/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLDeprecated.java index 3a6c529..49666f7 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLDeprecated.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLDeprecated.java @@ -9,7 +9,6 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -19,7 +18,7 @@ import java.lang.annotation.Target; @Retention(RUNTIME) -@Target({ElementType.METHOD}) +@Target({ ElementType.METHOD }) public @interface GraphQLDeprecated { public String value(); } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLDescription.java b/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLDescription.java index 6fa0bf0..ce6c6d7 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLDescription.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLDescription.java @@ -9,7 +9,6 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -19,7 +18,7 @@ import java.lang.annotation.Target; @Retention(RUNTIME) -@Target({ElementType.METHOD, ElementType.TYPE}) +@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER }) public @interface GraphQLDescription { public String value(); } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLIgnore.java b/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLIgnore.java index 1f174a1..e51c4ae 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLIgnore.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLIgnore.java @@ -9,7 +9,6 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -19,8 +18,6 @@ import java.lang.annotation.Target; @Retention(RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD}) +@Target({ ElementType.METHOD, ElementType.FIELD }) public @interface GraphQLIgnore { - - } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLName.java b/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLName.java new file mode 100644 index 0000000..726f7ee --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/GraphQLName.java @@ -0,0 +1,24 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.annotations; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +public @interface GraphQLName { + public String value(); +} diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/Id.java b/src/main/java/com/fleetpin/graphql/builder/annotations/Id.java index 2f9e280..c1fe376 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/Id.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/Id.java @@ -9,7 +9,6 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -19,8 +18,6 @@ import java.lang.annotation.Target; @Retention(RUNTIME) -@Target({ElementType.METHOD, ElementType.PARAMETER}) +@Target({ ElementType.METHOD, ElementType.PARAMETER }) public @interface Id { - - } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/InnerNullable.java b/src/main/java/com/fleetpin/graphql/builder/annotations/InnerNullable.java new file mode 100644 index 0000000..2802df1 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/InnerNullable.java @@ -0,0 +1,23 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.annotations; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target({ ElementType.METHOD, ElementType.PARAMETER }) +public @interface InnerNullable { +} diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/InputIgnore.java b/src/main/java/com/fleetpin/graphql/builder/annotations/InputIgnore.java index 34d83ba..23362b3 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/InputIgnore.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/InputIgnore.java @@ -9,7 +9,6 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -19,8 +18,6 @@ import java.lang.annotation.Target; @Retention(RUNTIME) -@Target({ElementType.METHOD, ElementType.PARAMETER}) +@Target({ ElementType.METHOD, ElementType.PARAMETER }) public @interface InputIgnore { - - } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/Mutation.java b/src/main/java/com/fleetpin/graphql/builder/annotations/Mutation.java index 01a48da..411e98c 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/Mutation.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/Mutation.java @@ -9,7 +9,6 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -21,5 +20,4 @@ @Retention(RUNTIME) @Target(ElementType.METHOD) public @interface Mutation { - } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/OneOf.java b/src/main/java/com/fleetpin/graphql/builder/annotations/OneOf.java new file mode 100644 index 0000000..b05736e --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/OneOf.java @@ -0,0 +1,34 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.annotations; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(ElementType.TYPE) +public @interface OneOf { + Type[] value(); + + @Retention(RUNTIME) + @Target(ElementType.TYPE) + public @interface Type { + String name(); + + Class type(); + + String description() default ""; + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/Query.java b/src/main/java/com/fleetpin/graphql/builder/annotations/Query.java index f30a5b2..bc5a73b 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/Query.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/Query.java @@ -9,7 +9,6 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -21,5 +20,4 @@ @Retention(RUNTIME) @Target(ElementType.METHOD) public @interface Query { - } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/Restrict.java b/src/main/java/com/fleetpin/graphql/builder/annotations/Restrict.java index 20245c2..b577f50 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/Restrict.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/Restrict.java @@ -9,22 +9,19 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import com.fleetpin.graphql.builder.RestrictTypeFactory; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import com.fleetpin.graphql.builder.RestrictTypeFactory; - @Retention(RUNTIME) @Target(ElementType.TYPE) @Repeatable(Restricts.class) public @interface Restrict { Class> value(); - } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/Restricts.java b/src/main/java/com/fleetpin/graphql/builder/annotations/Restricts.java index fa3c85e..dff1c64 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/Restricts.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/Restricts.java @@ -1,13 +1,24 @@ -package com.fleetpin.graphql.builder.annotations; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -@Retention(RUNTIME) -@Target(ElementType.TYPE) -public @interface Restricts { - Restrict[] value(); -} \ No newline at end of file +/* + * Licensed 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 com.fleetpin.graphql.builder.annotations; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(ElementType.TYPE) +public @interface Restricts { + Restrict[] value(); +} diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/Scalar.java b/src/main/java/com/fleetpin/graphql/builder/annotations/Scalar.java index f3cbafd..3902422 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/Scalar.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/Scalar.java @@ -9,20 +9,17 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import graphql.schema.Coercing; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import graphql.schema.Coercing; - @Retention(RUNTIME) @Target(ElementType.TYPE) public @interface Scalar { Class> value(); - } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/SchemaOption.java b/src/main/java/com/fleetpin/graphql/builder/annotations/SchemaOption.java index 95a32f6..391851b 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/SchemaOption.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/SchemaOption.java @@ -9,9 +9,10 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; public enum SchemaOption { - INPUT, TYPE, BOTH + INPUT, + TYPE, + BOTH, } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/Subscription.java b/src/main/java/com/fleetpin/graphql/builder/annotations/Subscription.java index e60a270..f661705 100644 --- a/src/main/java/com/fleetpin/graphql/builder/annotations/Subscription.java +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/Subscription.java @@ -9,7 +9,6 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -21,5 +20,4 @@ @Retention(RUNTIME) @Target(ElementType.METHOD) public @interface Subscription { - } diff --git a/src/main/java/com/fleetpin/graphql/builder/annotations/Union.java b/src/main/java/com/fleetpin/graphql/builder/annotations/Union.java new file mode 100644 index 0000000..d373c92 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/annotations/Union.java @@ -0,0 +1,24 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.annotations; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(ElementType.METHOD) +public @interface Union { + Class[] value(); +} diff --git a/src/main/java/com/fleetpin/graphql/builder/exceptions/InvalidOneOfException.java b/src/main/java/com/fleetpin/graphql/builder/exceptions/InvalidOneOfException.java new file mode 100644 index 0000000..e5cbf0e --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/exceptions/InvalidOneOfException.java @@ -0,0 +1,8 @@ +package com.fleetpin.graphql.builder.exceptions; + +public class InvalidOneOfException extends RuntimeException { + + public InvalidOneOfException(String message) { + super(message); + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/mapper/ConstructorFieldBuilder.java b/src/main/java/com/fleetpin/graphql/builder/mapper/ConstructorFieldBuilder.java new file mode 100644 index 0000000..3567063 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/mapper/ConstructorFieldBuilder.java @@ -0,0 +1,67 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.mapper; + +import graphql.GraphQLContext; +import java.util.ArrayList; +import java.util.Locale; +import java.util.Map; + +public class ConstructorFieldBuilder implements InputTypeBuilder { + + private final InputTypeBuilder map; + + public ConstructorFieldBuilder(Class type, ArrayList mappers) { + try { + var argTypes = mappers.stream().map(t -> t.type).toArray(Class[]::new); + var constructor = type.getDeclaredConstructor(argTypes); + constructor.setAccessible(true); + map = + (obj, context, locale) -> { + try { + Map map = (Map) obj; + + var args = new Object[argTypes.length]; + + for (int i = 0; i < args.length; i++) { + var mapper = mappers.get(i); + args[i] = mapper.resolver.convert(map.get(mapper.name), context, locale); + } + + return constructor.newInstance(args); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + }; + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + public Object convert(Object obj, GraphQLContext graphQLContext, Locale locale) { + return map.convert(obj, graphQLContext, locale); + } + + public static class RecordMapper { + + private final String name; + private final Class type; + private final InputTypeBuilder resolver; + + public RecordMapper(String name, Class type, InputTypeBuilder resolver) { + this.name = name; + this.type = type; + this.resolver = resolver; + } + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/mapper/InputTypeBuilder.java b/src/main/java/com/fleetpin/graphql/builder/mapper/InputTypeBuilder.java new file mode 100644 index 0000000..0554a41 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/mapper/InputTypeBuilder.java @@ -0,0 +1,19 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.mapper; + +import graphql.GraphQLContext; +import java.util.Locale; + +public interface InputTypeBuilder { + Object convert(Object obj, GraphQLContext graphQLContext, Locale locale); +} diff --git a/src/main/java/com/fleetpin/graphql/builder/mapper/ObjectFieldBuilder.java b/src/main/java/com/fleetpin/graphql/builder/mapper/ObjectFieldBuilder.java new file mode 100644 index 0000000..9fb20d8 --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/mapper/ObjectFieldBuilder.java @@ -0,0 +1,99 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.mapper; + +import com.fleetpin.graphql.builder.EntityProcessor; +import com.fleetpin.graphql.builder.TypeMeta; +import graphql.GraphQLContext; +import graphql.com.google.common.base.Preconditions; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Locale; +import java.util.Map; + +public class ObjectFieldBuilder implements InputTypeBuilder { + + private final InputTypeBuilder map; + + public ObjectFieldBuilder(Class type, ArrayList mappers) { + try { + var constructor = type.getConstructor(); + map = + (obj, context, locale) -> { + try { + var toReturn = constructor.newInstance(); + + Map map = (Map) obj; + + for (var mapper : mappers) { + var name = mapper.getName(); + if (map.containsKey(name)) { + mapper.map(toReturn, map.get(name), context, locale); + } + } + + return toReturn; + } catch (Throwable e) { + throwIfUnchecked(e); + throw new RuntimeException(e); + } + }; + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + public Object convert(Object obj, GraphQLContext graphQLContext, Locale locale) { + return map.convert(obj, graphQLContext, locale); + } + + public static class FieldMapper { + + private final Method method; + private final String name; + private final InputTypeBuilder mapper; + + private FieldMapper(String name, Method method, InputTypeBuilder objectBuilder) { + this.name = name; + this.method = method; + this.mapper = objectBuilder; + } + + public String getName() { + return name; + } + + protected void map(Object inputType, Object argument, GraphQLContext graphQLContext, Locale locale) throws Throwable { + try { + method.invoke(inputType, mapper.convert(argument, graphQLContext, locale)); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + + public static FieldMapper build(EntityProcessor entityProcessor, TypeMeta inputType, String name, Method method) { + return new FieldMapper(name, method, entityProcessor.getResolver(inputType)); + } + } + + // copied from guava + public static void throwIfUnchecked(Throwable throwable) { + Preconditions.checkNotNull(throwable); + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } else if (throwable instanceof Error) { + throw (Error) throwable; + } + } +} diff --git a/src/main/java/com/fleetpin/graphql/builder/mapper/OneOfBuilder.java b/src/main/java/com/fleetpin/graphql/builder/mapper/OneOfBuilder.java new file mode 100644 index 0000000..0484dcb --- /dev/null +++ b/src/main/java/com/fleetpin/graphql/builder/mapper/OneOfBuilder.java @@ -0,0 +1,59 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.mapper; + +import com.fleetpin.graphql.builder.EntityProcessor; +import com.fleetpin.graphql.builder.annotations.OneOf; +import com.fleetpin.graphql.builder.exceptions.InvalidOneOfException; +import graphql.GraphQLContext; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class OneOfBuilder implements InputTypeBuilder { + + private final InputTypeBuilder map; + + public OneOfBuilder(EntityProcessor entityProcessor, Class type, OneOf oneOf) { + Map builders = new HashMap<>(); + + for (var typeOf : oneOf.value()) { + if (!type.isAssignableFrom(typeOf.type())) { + throw new InvalidOneOfException("OneOf on " + type + " can not support type " + typeOf); + } + + builders.put(typeOf.name(), entityProcessor.getResolver(typeOf.type())); + } + + map = + (obj, context, locale) -> { + Map map = (Map) obj; + + if (map.size() > 1) { + var fields = String.join(", ", map.keySet()); + throw new InvalidOneOfException("OneOf must only have a single field set. Fields: " + fields); + } + + for (var entry : map.entrySet()) { + var builder = builders.get(entry.getKey()); + return builder.convert(entry.getValue(), context, locale); + } + + throw new InvalidOneOfException("OneOf must have a field set"); + }; + } + + @Override + public Object convert(Object obj, GraphQLContext graphQLContext, Locale locale) { + return map.convert(obj, graphQLContext, locale); + } +} diff --git a/src/main/java/graphql/execution/reactive/CompletionStageMappingPublisher.java b/src/main/java/graphql/execution/reactive/CompletionStageMappingPublisher.java deleted file mode 100644 index be712ce..0000000 --- a/src/main/java/graphql/execution/reactive/CompletionStageMappingPublisher.java +++ /dev/null @@ -1,114 +0,0 @@ -//REMOVE ONCE PULL REQUEST MERGED -package graphql.execution.reactive; - -import java.util.concurrent.CompletionStage; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; - -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; - -/** - * A reactive Publisher that bridges over another Publisher of `D` and maps the results - * to type `U` via a CompletionStage, handling errors in that stage - * - * @param the down stream type - * @param the up stream type to be mapped to - */ -public class CompletionStageMappingPublisher implements Publisher { - private final Publisher upstreamPublisher; - private final Function> mapper; - - /** - * You need the following : - * - * @param upstreamPublisher an upstream source of data - * @param mapper a mapper function that turns upstream data into a promise of mapped D downstream data - */ - public CompletionStageMappingPublisher(Publisher upstreamPublisher, Function> mapper) { - this.upstreamPublisher = upstreamPublisher; - this.mapper = mapper; - } - - @Override - public void subscribe(Subscriber downstreamSubscriber) { - upstreamPublisher.subscribe(new Subscriber() { - - private final AtomicInteger inFlight = new AtomicInteger(); - private volatile Runnable finish; - - Subscription delegatingSubscription; - - @Override - public void onSubscribe(Subscription subscription) { - delegatingSubscription = new DelegatingSubscription(subscription); - downstreamSubscriber.onSubscribe(delegatingSubscription); - } - - @Override - public void onNext(U u) { - CompletionStage completionStage; - try { - completionStage = mapper.apply(u); - inFlight.getAndIncrement(); - completionStage.whenComplete((d, throwable) -> { - try { - if (throwable != null) { - handleThrowable(throwable); - } else { - downstreamSubscriber.onNext(d); - } - }finally { - if(inFlight.intValue() == 1 && finish != null) { - finish.run(); - finish = null; - } - inFlight.decrementAndGet(); - } - }); - } catch (RuntimeException throwable) { - handleThrowable(throwable); - } - } - - private void handleThrowable(Throwable throwable) { - downstreamSubscriber.onError(throwable); - // - // reactive semantics say that IF an exception happens on a publisher - // then onError is called and no more messages flow. But since the exception happened - // during the mapping, the upstream publisher does not no about this. - // so we cancel to bring the semantics back together, that is as soon as an exception - // has happened, no more messages flow - // - delegatingSubscription.cancel(); - } - - @Override - public void onError(Throwable t) { - if(inFlight.intValue() > 0) { - finish = () -> downstreamSubscriber.onError(t); - if(inFlight.intValue() == 0 && finish != null) { - //happened together - downstreamSubscriber.onError(t); - } - }else { - downstreamSubscriber.onError(t); - } - } - - @Override - public void onComplete() { - if(inFlight.intValue() > 0) { - finish = () -> downstreamSubscriber.onComplete(); - if(inFlight.intValue() == 0 && finish != null) { - //happened together - downstreamSubscriber.onComplete(); - } - }else { - downstreamSubscriber.onComplete(); - } - } - }); - } -} diff --git a/src/test/java/com/fleetpin/graphql/builder/AuthorizerTest.java b/src/test/java/com/fleetpin/graphql/builder/AuthorizerTest.java new file mode 100644 index 0000000..1166304 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/AuthorizerTest.java @@ -0,0 +1,65 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class AuthorizerTest { + + @Test + public void testQueryCatAllowed() throws ReflectiveOperationException { + var result = execute("query {getCat(name: \"socks\") {age}}"); + Map> response = result.getData(); + + var cat = response.get("getCat"); + + assertEquals(3, cat.get("age")); + + assertTrue(result.getErrors().isEmpty()); + } + + @Test + public void testQueryCatNotAllowed() throws ReflectiveOperationException { + var result = execute("query {getCat(name: \"boots\") {age}}"); + + assertNull(result.getData()); + + assertEquals(1, result.getErrors().size()); + var error = result.getErrors().get(0); + + assertEquals("Exception while fetching data (/getCat) : unauthorized", error.getMessage()); + //assertEquals("", error.getErrorType()); + } + + private ExecutionResult execute(String query) { + return execute(query, null); + } + + private ExecutionResult execute(String query, Map variables) { + GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.authorizer")).build(); + var input = ExecutionInput.newExecutionInput(); + input.query(query); + if (variables != null) { + input.variables(variables); + } + ExecutionResult result = schema.execute(input); + return result; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/ContextTest.java b/src/test/java/com/fleetpin/graphql/builder/ContextTest.java new file mode 100644 index 0000000..e6e56e6 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/ContextTest.java @@ -0,0 +1,79 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fleetpin.graphql.builder.annotations.Context; +import com.fleetpin.graphql.builder.annotations.Query; +import com.fleetpin.graphql.builder.context.GraphContext; +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.introspection.IntrospectionWithDirectivesSupport; +import java.util.Map; +import java.util.function.Consumer; +import org.junit.jupiter.api.Test; + +public class ContextTest { + + @Test + public void testEntireContext() throws ReflectiveOperationException { + Map response = execute("query {entireContext} ", __ -> {}).getData(); + assertTrue(response.get("entireContext")); + } + + @Test + public void testEnv() throws ReflectiveOperationException { + Map response = execute("query {env} ", __ -> {}).getData(); + assertTrue(response.get("env")); + } + + @Test + public void testDeprecated() throws ReflectiveOperationException { + @SuppressWarnings("deprecation") + Map response = execute("query {deprecatedContext} ", b -> b.context(new GraphContext("context"))).getData(); + assertTrue(response.get("deprecatedContext")); + } + + @Test + public void testNamed() throws ReflectiveOperationException { + Map response = execute("query {namedContext} ", b -> b.graphQLContext(c -> c.of("named", new GraphContext("context")))).getData(); + assertTrue(response.get("namedContext")); + } + + @Test + public void testNamedParameter() throws ReflectiveOperationException { + Map response = execute("query {namedParemeterContext} ", b -> b.graphQLContext(c -> c.of("context", "test"))).getData(); + assertTrue(response.get("namedParemeterContext")); + } + + @Test + public void testMissing() throws ReflectiveOperationException { + var response = execute("query {missingContext} ", b -> b.graphQLContext(c -> c.of("context", "test"))); + assertEquals(1, response.getErrors().size()); + var error = response.getErrors().get(0); + assertTrue(error.getMessage().contains("Context object notPresent not found")); + } + + private ExecutionResult execute(String query, Consumer modify) { + GraphQL schema = GraphQL + .newGraphQL(new IntrospectionWithDirectivesSupport().apply(SchemaBuilder.build("com.fleetpin.graphql.builder.context"))) + .build(); + var input = ExecutionInput.newExecutionInput(); + input.query(query); + modify.accept(input); + ExecutionResult result = schema.execute(input); + return result; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/DirectiveTest.java b/src/test/java/com/fleetpin/graphql/builder/DirectiveTest.java index 96eb4f5..a374f6a 100644 --- a/src/test/java/com/fleetpin/graphql/builder/DirectiveTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/DirectiveTest.java @@ -1,35 +1,109 @@ -package com.fleetpin.graphql.builder; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import graphql.GraphQL; -import graphql.schema.FieldCoordinates; - -public class DirectiveTest { - @Test - public void testDirectiveAppliedToQuery() throws ReflectiveOperationException { - - GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type.directive")).build(); - var cat = schema.getGraphQLSchema() - .getFieldDefinition(FieldCoordinates.coordinates(schema.getGraphQLSchema().getQueryType(), "getCat")); - var capture = cat.getAppliedDirective("Capture"); - var argument = capture.getArgument("color"); - var color = argument.getValue(); - assertEquals("meow", color); - - } - - @Test - public void testPresentOnSchema() throws ReflectiveOperationException { - - GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type.directive")).build(); - var capture = schema.getGraphQLSchema().getSchemaAppliedDirective("Capture"); - var argument = capture.getArgument("color"); - var color = argument.getValue(); - assertEquals("top", color); - - } - -} +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import static org.junit.jupiter.api.Assertions.*; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.introspection.IntrospectionWithDirectivesSupport; +import graphql.schema.FieldCoordinates; +import graphql.schema.GraphQLSchema; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public class DirectiveTest { + + @Test + public void testDirectiveAppliedToQuery() throws ReflectiveOperationException { + GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type.directive")).build(); + var cat = schema.getGraphQLSchema().getFieldDefinition(FieldCoordinates.coordinates(schema.getGraphQLSchema().getQueryType(), "getCat")); + var capture = cat.getAppliedDirective("Capture"); + var argument = capture.getArgument("color"); + var color = argument.getValue(); + assertEquals("meow", color); + } + + @Test + public void testNoArgumentDirective() throws ReflectiveOperationException { + GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type.directive")).build(); + var cat = schema.getGraphQLSchema().getFieldDefinition(FieldCoordinates.coordinates(schema.getGraphQLSchema().getQueryType(), "getUpper")); + var uppercase = cat.getAppliedDirective("Uppercase"); + assertNotNull(uppercase); + assertTrue(uppercase.getArguments().isEmpty()); + } + + @Test + public void testPresentOnSchema() throws ReflectiveOperationException { + GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type.directive")).build(); + var capture = schema.getGraphQLSchema().getSchemaAppliedDirective("Capture"); + var argument = capture.getArgument("color"); + var color = argument.getValue(); + assertEquals("top", color); + } + + @Test + public void testDirectivePass() throws ReflectiveOperationException { + Map response = execute("query allowed($name: String!){allowed(name: $name)} ", Map.of("name", "tabby")).getData(); + assertEquals("tabby", response.get("allowed")); + } + + @Test + public void testDirectiveFail() throws ReflectiveOperationException { + var response = execute("query allowed($name: String!){allowed(name: $name)} ", Map.of("name", "calico")); + + assertNull(response.getData()); + + assertTrue(response.getErrors().get(0).getMessage().contains("forbidden")); + } + + @Test + public void testDirectiveArgument() { + GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type.directive")).build(); + var cat = schema.getGraphQLSchema().getFieldDefinition(FieldCoordinates.coordinates(schema.getGraphQLSchema().getQueryType(), "getNickname")); + var argument = cat.getArgument("nickName"); + var directive = argument.getAppliedDirective("Input"); + assertNotNull(directive); + var value = directive.getArgument("value").getValue(); + assertEquals("TT", value); + } + + @Test + public void testDirectiveArgumentDefinition() { + Map response = execute("query IntrospectionQuery { __schema { directives { name locations args { name } } } }", null).getData(); + List> dir = (List>) ((Map) response.get("__schema")).get("directives"); + LinkedHashMap input = dir.stream().filter(map -> map.get("name").equals("Input")).collect(Collectors.toList()).get(0); + + assertEquals(8, dir.size()); + assertEquals("ARGUMENT_DEFINITION", ((List) input.get("locations")).get(0)); + assertEquals(1, ((List) input.get("args")).size()); + //getNickname(nickName: String! @Input(value : "TT")): String! + //directive @Input(value: String!) on ARGUMENT_DEFINITION + } + + private ExecutionResult execute(String query, Map variables) { + GraphQLSchema preSchema = SchemaBuilder.builder().classpath("com.fleetpin.graphql.builder.type.directive").build().build(); + GraphQL schema = GraphQL.newGraphQL(new IntrospectionWithDirectivesSupport().apply(preSchema)).build(); + + var input = ExecutionInput.newExecutionInput(); + input.query(query); + if (variables != null) { + input.variables(variables); + } + ExecutionResult result = schema.execute(input); + return result; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/MetaTest.java b/src/test/java/com/fleetpin/graphql/builder/MetaTest.java index b0cf7d7..5e939ff 100644 --- a/src/test/java/com/fleetpin/graphql/builder/MetaTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/MetaTest.java @@ -1,22 +1,32 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Test; - import graphql.schema.GraphQLObjectType; +import org.junit.jupiter.api.Test; public class MetaTest { - + @Test public void testDeprecated() throws ReflectiveOperationException { var schema = SchemaBuilder.build("com.fleetpin.graphql.builder.type"); - + var query = schema.getQueryType().getField("deprecatedTest"); assertTrue(query.isDeprecated()); assertEquals("old", query.getDeprecationReason()); - + GraphQLObjectType type = (GraphQLObjectType) schema.getType("DeprecatedObject"); var field = type.getField("naame"); assertTrue(field.isDeprecated()); @@ -26,14 +36,13 @@ public void testDeprecated() throws ReflectiveOperationException { @Test public void testDescription() throws ReflectiveOperationException { var schema = SchemaBuilder.build("com.fleetpin.graphql.builder.type"); - + var query = schema.getQueryType().getField("descriptionTest"); assertEquals("returns something", query.getDescription()); - + GraphQLObjectType type = (GraphQLObjectType) schema.getType("DescriptionObject"); assertEquals("test description comes through", type.getDescription()); var field = type.getField("name"); assertEquals("first and last", field.getDescription()); } - } diff --git a/src/test/java/com/fleetpin/graphql/builder/ParameterParsingTest.java b/src/test/java/com/fleetpin/graphql/builder/ParameterParsingTest.java index 61f3c28..a5be89f 100644 --- a/src/test/java/com/fleetpin/graphql/builder/ParameterParsingTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/ParameterParsingTest.java @@ -9,25 +9,19 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import graphql.ExecutionResult; +import graphql.GraphQL; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; - import org.junit.jupiter.api.Test; -import com.fleetpin.graphql.builder.annotations.Id; -import com.fleetpin.graphql.builder.annotations.Query; -import com.google.common.base.Optional; - -import graphql.ExecutionResult; -import graphql.GraphQL; - public class ParameterParsingTest { //TODO:add failure cases @@ -36,89 +30,99 @@ public void testRequiredString() throws ReflectiveOperationException { Map> response = execute("query {requiredString(type: \"There\")} ").getData(); assertEquals("There", response.get("requiredString")); } - + @Test public void testOptionalStringPresent() throws ReflectiveOperationException { Map> response = execute("query {optionalString(type: \"There\")} ").getData(); assertEquals("There", response.get("optionalString")); } - + @Test public void testOptionalStringNull() throws ReflectiveOperationException { Map> response = execute("query {optionalString(type: null)} ").getData(); assertEquals(null, response.get("optionalString")); } - @Test public void testOptionalStringMissing() throws ReflectiveOperationException { Map> response = execute("query {optionalString} ").getData(); assertEquals(null, response.get("optionalString")); } - + //TODO:id checks don't confirm actual an id @Test public void testRequiredId() throws ReflectiveOperationException { Map> response = execute("query {testRequiredId(type: \"There\")} ").getData(); assertEquals("There", response.get("testRequiredId")); - } + } + @Test public void testOptionalIdPresent() throws ReflectiveOperationException { Map> response = execute("query {optionalId(type: \"There\")} ").getData(); assertEquals("There", response.get("optionalId")); } - + @Test public void testOptionalIdNull() throws ReflectiveOperationException { - Map> response = execute("query {optionalId(type: null)} ").getData(); + Map response = execute("query {optionalId(type: null)} ").getData(); assertEquals(null, response.get("optionalId")); + assertTrue(response.containsKey("optionalId")); } - - + + @Test + public void testOptionalIdNullAlways() throws ReflectiveOperationException { + Map response = execute("query {optionalIdNull{nullOptional}} ").getData(); + assertEquals(null, response.get("optionalId")); + } + @Test public void testRequiredListStringEmpty() throws ReflectiveOperationException { Map>> response = execute("query {requiredListString(type: [])} ").getData(); assertEquals(Collections.emptyList(), response.get("requiredListString")); } - + @Test public void testRequiredListString() throws ReflectiveOperationException { Map>> response = execute("query {requiredListString(type: [\"free\"])} ").getData(); assertEquals(Arrays.asList("free"), response.get("requiredListString")); } - + + @Test + public void testRequiredArrayString() throws ReflectiveOperationException { + Map>> response = execute("query {requiredArrayString(type: [\"free\"])} ").getData(); + assertEquals(Arrays.asList("free"), response.get("requiredArrayString")); + } + @Test public void testOptionalListStringEmpty() throws ReflectiveOperationException { Map>> response = execute("query {optionalListString(type: [])} ").getData(); assertEquals(Collections.emptyList(), response.get("optionalListString")); } - + @Test public void testOptionalListString() throws ReflectiveOperationException { Map>> response = execute("query {optionalListString(type: [\"free\"])} ").getData(); assertEquals(Arrays.asList("free"), response.get("optionalListString")); } - + @Test public void testOptionalListStringNull() throws ReflectiveOperationException { Map>> response = execute("query {optionalListString} ").getData(); assertEquals(null, response.get("optionalListString")); } - - + @Test public void testRequiredListOptionalString() throws ReflectiveOperationException { Map>> response = execute("query {requiredListOptionalString(type: [null, \"free\"])} ").getData(); assertEquals(Arrays.asList(null, "free"), response.get("requiredListOptionalString")); } - - + @Test public void testOptionalListOptionalString() throws ReflectiveOperationException { Map>> response = execute("query {optionalListOptionalString(type: [null, \"free\"])} ").getData(); assertEquals(Arrays.asList(null, "free"), response.get("optionalListOptionalString")); } - + @Test public void testOptionalListOptionalStringNull() throws ReflectiveOperationException { Map>> response = execute("query {optionalListOptionalString} ").getData(); @@ -130,110 +134,103 @@ public void testRequiredListIdEmpty() throws ReflectiveOperationException { Map>> response = execute("query {requiredListId(type: [])} ").getData(); assertEquals(Collections.emptyList(), response.get("requiredListId")); } - + @Test public void testRequiredListId() throws ReflectiveOperationException { Map>> response = execute("query {requiredListId(type: [\"free\"])} ").getData(); assertEquals(Arrays.asList("free"), response.get("requiredListId")); } - + @Test public void testOptionalListIdEmpty() throws ReflectiveOperationException { Map>> response = execute("query {optionalListId(type: [])} ").getData(); assertEquals(Collections.emptyList(), response.get("optionalListId")); } - + @Test public void testOptionalListId() throws ReflectiveOperationException { Map>> response = execute("query {optionalListId(type: [\"free\"])} ").getData(); assertEquals(Arrays.asList("free"), response.get("optionalListId")); } - + @Test public void testOptionalListIdNull() throws ReflectiveOperationException { Map>> response = execute("query {optionalListId} ").getData(); assertEquals(null, response.get("optionalListId")); } - - + @Test public void testRequiredListOptionalId() throws ReflectiveOperationException { Map>> response = execute("query {requiredListOptionalId(type: [null, \"free\"])} ").getData(); assertEquals(Arrays.asList(null, "free"), response.get("requiredListOptionalId")); } - - + @Test public void testOptionalListOptionalId() throws ReflectiveOperationException { Map>> response = execute("query {optionalListOptionalId(type: [null, \"free\"])} ").getData(); assertEquals(Arrays.asList(null, "free"), response.get("optionalListOptionalId")); } - + @Test public void testOptionalListOptionalIdNull() throws ReflectiveOperationException { Map>> response = execute("query {optionalListOptionalId} ").getData(); assertEquals(null, response.get("optionalListOptionalId")); } - - - + @Test public void testMultipleArguments() throws ReflectiveOperationException { Map>> response = execute("query {multipleArguments(first: \"free\", second: \"bird\")} ").getData(); assertEquals("free:bird", response.get("multipleArguments")); } - - + @Test public void testMultipleArgumentsOptional() throws ReflectiveOperationException { Map>> response = execute("query {multipleArgumentsOptional(first: \"free\", second: \"bird\")} ").getData(); assertEquals("free:bird", response.get("multipleArgumentsOptional")); } - + @Test public void testMultipleArgumentsOptionalPartial1() throws ReflectiveOperationException { Map>> response = execute("query {multipleArgumentsOptional(second: \"bird\")} ").getData(); assertEquals(":bird", response.get("multipleArgumentsOptional")); } - + @Test public void testMultipleArgumentsOptionalPartial2() throws ReflectiveOperationException { Map>> response = execute("query {multipleArgumentsOptional(second: null)} ").getData(); assertEquals(":", response.get("multipleArgumentsOptional")); } - + @Test public void testMultipleArgumentsOptionalPartial3() throws ReflectiveOperationException { Map>> response = execute("query {multipleArgumentsOptional} ").getData(); assertEquals(":", response.get("multipleArgumentsOptional")); } - + @Test public void testMultipleArgumentsMix1() throws ReflectiveOperationException { Map>> response = execute("query {multipleArgumentsMix(first: \"free\")} ").getData(); assertEquals("free:", response.get("multipleArgumentsMix")); } - + @Test public void testMultipleArgumentsMix2() throws ReflectiveOperationException { Map>> response = execute("query {multipleArgumentsMix(first: \"free\", second: null)} ").getData(); assertEquals("free:", response.get("multipleArgumentsMix")); } - + @Test public void testMultipleArgumentsMix3() throws ReflectiveOperationException { Map>> response = execute("query {multipleArgumentsMix(first: \"free\", second: \"bird\")} ").getData(); assertEquals("free:bird", response.get("multipleArgumentsMix")); } - - + private ExecutionResult execute(String query) throws ReflectiveOperationException { var schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.parameter")).build(); ExecutionResult result = schema.execute(query); - if(!result.getErrors().isEmpty()) { + if (!result.getErrors().isEmpty()) { throw new RuntimeException(result.getErrors().toString()); //TODO:cleanup } return result; } - } diff --git a/src/test/java/com/fleetpin/graphql/builder/ParameterTypeParsingTest.java b/src/test/java/com/fleetpin/graphql/builder/ParameterTypeParsingTest.java index 472692a..27e53b9 100644 --- a/src/test/java/com/fleetpin/graphql/builder/ParameterTypeParsingTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/ParameterTypeParsingTest.java @@ -9,130 +9,167 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder; import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; - +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; - +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import graphql.ExecutionInput; import graphql.ExecutionResult; import graphql.GraphQL; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; public class ParameterTypeParsingTest { + public static final ObjectMapper MAPPER = new ObjectMapper() + .registerModule(new ParameterNamesModule()) + .registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .setVisibility(PropertyAccessor.FIELD, Visibility.ANY); + // TODO:add failure cases @Test public void testRequiredType() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { - Map> response = execute( - "query test($type: InputTestInput!){requiredType(type: $type){value}} ", "{\"value\": \"There\"}") - .getData(); + Map> response = execute("query test($type: InputTestInput!){requiredType(type: $type){value}} ", "{\"value\": \"There\"}") + .getData(); assertEquals("There", response.get("requiredType").get("value")); } @Test - public void testOptionalTypePresent() - throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { - Map> response = execute( - "query test($type: InputTestInput){optionalType(type: $type){value}} ", "{\"value\": \"There\"}") - .getData(); - assertEquals("There", response.get("optionalType").get("value")); + public void testEnum() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { + Map> response = execute("query enumTest($type: AnimalType!){enumTest(type: $type)} ", "\"CAT\"").getData(); + assertEquals("CAT", response.get("enumTest")); } @Test - public void testOptionalTypeNull() - throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { + public void testDescription() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { + Map> response = execute( + "{" + + " __type(name: \"AnimalType\") {" + + " name" + + " kind" + + " description" + + " enumValues {" + + " name" + + " description" + + " }" + + " }" + + "} ", + null + ) + .getData(); + + var type = response.get("__type"); + + assertEquals("enum desc", type.get("description")); + + Map dog = new HashMap<>(); + dog.put("name", "DOG"); + dog.put("description", null); + + assertEquals(List.of(Map.of("name", "CAT", "description", "A cat"), dog), type.get("enumValues")); + } - Map> response = execute( - "query test($type: InputTestInput){optionalType(type: $type){value}} ", null).getData(); - assertEquals(null, response.get("optionalType")); + @Test + public void testOptionalTypePresent() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { + Map> response = execute("query test($type: InputTestInput){optionalType(type: $type){value}} ", "{\"value\": \"There\"}") + .getData(); + assertEquals("There", response.get("optionalType").get("value")); + } + @Test + public void testOptionalTypeNull() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { + Map> response = execute("query test($type: InputTestInput){optionalType(type: $type){value}} ", null).getData(); + assertEquals(null, response.get("optionalType")); } @Test public void testOptionalTypeMissing() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { - Map> response = execute( - "query test{optionalType{value}} ", null).getData(); + Map> response = execute("query test{optionalType{value}} ", null).getData(); assertEquals(null, response.get("optionalType")); } - @Test public void testRequiredListTypeEmpty() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { - Map>> response = execute("query {requiredListType(type: []){value}} ", null).getData(); + Map>> response = execute("query {requiredListType(type: []){value}} ", null).getData(); assertEquals(Collections.emptyList(), response.get("requiredListType")); } @Test public void testRequiredListType() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { - - Map>> response = execute( - "query test($type: [InputTestInput!]!){requiredListType(type: $type){value}} ", "[{\"value\": \"There\"}]") - .getData(); + Map>> response = execute( + "query test($type: [InputTestInput!]!){requiredListType(type: $type){value}} ", + "[{\"value\": \"There\"}]" + ) + .getData(); assertEquals("There", response.get("requiredListType").get(0).get("value")); - } @Test public void testOptionalListTypeEmpty() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { - Map>> response = execute("query {optionalListType(type: []){value}} ", null).getData(); + Map>> response = execute("query {optionalListType(type: []){value}} ", null).getData(); assertEquals(Collections.emptyList(), response.get("optionalListType")); } @Test public void testOptionalListType() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { - Map>> response = execute( - "query test($type: [InputTestInput!]){optionalListType(type: $type){value}} ", "[{\"value\": \"There\"}]") - .getData(); + Map>> response = execute( + "query test($type: [InputTestInput!]){optionalListType(type: $type){value}} ", + "[{\"value\": \"There\"}]" + ) + .getData(); assertEquals("There", response.get("optionalListType").get(0).get("value")); - } @Test public void testOptionalListTypeNull() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { - Map>> response = execute("query {optionalListType{value}} ", null).getData(); + Map>> response = execute("query {optionalListType{value}} ", null).getData(); assertEquals(null, response.get("optionalListType")); } @Test public void testRequiredListOptionalType() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { - Map>> response = execute( - "query test($type: [InputTestInput]!){requiredListOptionalType(type: $type){value}} ", "[null, {\"value\": \"There\"}]") - .getData(); + Map>> response = execute( + "query test($type: [InputTestInput]!){requiredListOptionalType(type: $type){value}} ", + "[null, {\"value\": \"There\"}]" + ) + .getData(); assertEquals("There", response.get("requiredListOptionalType").get(1).get("value")); assertEquals(null, response.get("requiredListOptionalType").get(0)); } @Test public void testOptionalListOptionalType() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { - Map>> response = execute( - "query test($type: [InputTestInput]){optionalListOptionalType(type: $type){value}} ", "[null, {\"value\": \"There\"}]") - .getData(); + Map>> response = execute( + "query test($type: [InputTestInput]){optionalListOptionalType(type: $type){value}} ", + "[null, {\"value\": \"There\"}]" + ) + .getData(); assertEquals("There", response.get("optionalListOptionalType").get(1).get("value")); assertEquals(null, response.get("optionalListOptionalType").get(0)); - } @Test public void testOptionalListOptionalTypeNull() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { - Map>> response = execute("query {optionalListOptionalType{value}} ", null).getData(); + Map>> response = execute("query {optionalListOptionalType{value}} ", null).getData(); assertEquals(null, response.get("optionalListOptionalType")); } - private ExecutionResult execute(String query, String type) - throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { + private ExecutionResult execute(String query, String type) throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { Object obj = null; if (type != null) { - obj = SchemaBuilder.MAPPER.readValue(type, Object.class); + obj = MAPPER.readValue(type, Object.class); } Map variables = new HashMap<>(); variables.put("type", obj); @@ -145,5 +182,4 @@ private ExecutionResult execute(String query, String type) } return result; } - } diff --git a/src/test/java/com/fleetpin/graphql/builder/PublishRestrictions.java b/src/test/java/com/fleetpin/graphql/builder/PublishRestrictions.java index b0e3356..9d137f9 100644 --- a/src/test/java/com/fleetpin/graphql/builder/PublishRestrictions.java +++ b/src/test/java/com/fleetpin/graphql/builder/PublishRestrictions.java @@ -9,24 +9,20 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder; import static org.junit.jupiter.api.Assertions.assertEquals; +import graphql.GraphQL; +import io.reactivex.rxjava3.core.Flowable; import java.util.Arrays; import java.util.List; import java.util.Map; - import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; -import graphql.GraphQL; -import io.reactivex.rxjava3.core.Flowable; - public class PublishRestrictions { - @Test public void testOptionalArray() throws ReflectiveOperationException { var schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.publishRestrictions")).build(); @@ -34,8 +30,4 @@ public void testOptionalArray() throws ReflectiveOperationException { Publisher response = res.getData(); assertEquals(0, Flowable.fromPublisher(response).count().blockingGet()); } - - - - } diff --git a/src/test/java/com/fleetpin/graphql/builder/RecordTest.java b/src/test/java/com/fleetpin/graphql/builder/RecordTest.java new file mode 100644 index 0000000..f077c4d --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/RecordTest.java @@ -0,0 +1,174 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.introspection.IntrospectionWithDirectivesSupport; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +//does not test all of records as needs newer version of java. But Classes that look like records +public class RecordTest { + + @Test + public void testEntireContext() { + var type = Map.of("name", "foo", "age", 4); + Map> response = execute( + "query passthrough($type: InputTypeInput!){passthrough(type: $type) {name age weight}} ", + Map.of("type", type) + ) + .getData(); + var passthrough = response.get("passthrough"); + var expected = new HashMap<>(type); + expected.put("weight", null); + assertEquals(expected, passthrough); + } + + @Test + public void testDescription() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { + Map> response = execute( + "{" + + " __type(name: \"InputTypeInput\") {" + + " name" + + " kind" + + " description" + + " inputFields {" + + " name" + + " description" + + " }" + + " }" + + "} ", + null + ) + .getData(); + + var type = response.get("__type"); + System.out.println(type); + assertEquals("record Type", type.get("description")); + + Map age = new HashMap<>(); + age.put("name", "age"); + age.put("description", null); + + Map weight = new HashMap<>(); + weight.put("name", "weight"); + weight.put("description", null); + + assertEquals(List.of(Map.of("name", "name", "description", "the name"), age, weight), type.get("inputFields")); + } + + @Test + public void testNullable() { + var response = execute("query nullableTest($type: Boolean){nullableTest(type: $type)} ", null); + var expected = new HashMap(); + expected.put("nullableTest", null); + assertEquals(expected, response.getData()); + assertTrue(response.getErrors().isEmpty()); + } + + @Test + public void testSetNullable() { + Map response = execute("query nullableTest($type: Boolean){nullableTest(type: $type)}", Map.of("type", true)).getData(); + var passthrough = response.get("nullableTest"); + assertEquals(true, passthrough); + } + + @Test + public void testNullableArray() { + List array = new ArrayList<>(); + array.add(true); + var response = execute("query nullableArrayTest($type: [Boolean!]){nullableArrayTest(type: $type)}", Map.of("type", array)); + var expected = new HashMap>(); + expected.put("nullableArrayTest", array); + assertTrue(response.getErrors().isEmpty()); + assertEquals(expected, response.getData()); + } + + @Test + public void testNullable2Array() { + var response = execute("query nullableArrayTest($type: [Boolean!]){nullableArrayTest(type: $type)}", Map.of()); + var expected = new HashMap>(); + expected.put("nullableArrayTest", null); + assertTrue(response.getErrors().isEmpty()); + assertEquals(expected, response.getData()); + } + + @Test + public void testNullableArrayFails() { + List array = new ArrayList<>(); + array.add(true); + var response = execute("query nullableArrayTest($type: [Boolean]){nullableArrayTest(type: $type)}", Map.of("type", array)); + assertFalse(response.getErrors().isEmpty()); + } + + @Test + public void testNullableInnerArray() { + List array = new ArrayList<>(); + array.add(null); + array.add(true); + var response = execute("query nullableInnerArrayTest($type: [Boolean]!){nullableInnerArrayTest(type: $type)}", Map.of("type", array)); + var expected = new HashMap>(); + expected.put("nullableInnerArrayTest", array); + assertTrue(response.getErrors().isEmpty()); + assertEquals(expected, response.getData()); + } + + @Test + public void testNullableInnerArrayFails() { + List array = new ArrayList<>(); + array.add(true); + var response = execute("query nullableInnerArrayTest($type: [Boolean]){nullableInnerArrayTest(type: $type)}", Map.of("type", array)); + assertFalse(response.getErrors().isEmpty()); + } + + @Test + public void testInnerNullableArray() { + List array = new ArrayList<>(); + array.add(null); + array.add(true); + var response = execute("query innerNullableArrayTest($type: [Boolean]!){innerNullableArrayTest(type: $type)}", Map.of("type", array)); + var expected = new HashMap>(); + expected.put("innerNullableArrayTest", array); + assertTrue(response.getErrors().isEmpty()); + assertEquals(expected, response.getData()); + } + + @Test + public void testInnerNullableArrayFails() { + List array = new ArrayList<>(); + array.add(true); + var response = execute("query innerNullableArrayTest($type: [Boolean]){innerNullableArrayTest(type: $type)}", Map.of("type", array)); + assertFalse(response.getErrors().isEmpty()); + } + + private ExecutionResult execute(String query, Map variables) { + GraphQL schema = GraphQL.newGraphQL(new IntrospectionWithDirectivesSupport().apply(SchemaBuilder.build("com.fleetpin.graphql.builder.record"))).build(); + var input = ExecutionInput.newExecutionInput(); + input.query(query); + if (variables != null) { + input.variables(variables); + } + ExecutionResult result = schema.execute(input); + return result; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/RenameTest.java b/src/test/java/com/fleetpin/graphql/builder/RenameTest.java new file mode 100644 index 0000000..5133f27 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/RenameTest.java @@ -0,0 +1,83 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.introspection.IntrospectionWithDirectivesSupport; +import java.util.Map; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +public class RenameTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Test + public void testClassRename() throws JsonProcessingException, JSONException { + var type = Map.of("nameSet", "foo"); + var response = execute(""" + query passthroughClass($type: ClassTypeInput!) { + passthroughClass(type: $type) { + nameGet + } + } + """, Map.of("type", type)) + .toSpecification(); + JSONAssert.assertEquals(""" + { + "data": { + "passthroughClass": { + "nameGet": "foo" + } + } + } + """, MAPPER.writeValueAsString(response), false); + } + + @Test + public void testRecordRename() throws JsonProcessingException, JSONException { + var type = Map.of("name", "foo"); + var response = execute(""" + query passthroughRecord($type: RecordTypeInput!) { + passthroughRecord(type: $type) { + name + } + } + """, Map.of("type", type)) + .toSpecification(); + JSONAssert.assertEquals(""" + { + "data": { + "passthroughRecord": { + "name": "foo" + } + } + } + """, MAPPER.writeValueAsString(response), false); + } + + private ExecutionResult execute(String query, Map variables) { + GraphQL schema = GraphQL.newGraphQL(new IntrospectionWithDirectivesSupport().apply(SchemaBuilder.build("com.fleetpin.graphql.builder.rename"))).build(); + var input = ExecutionInput.newExecutionInput(); + input.query(query); + if (variables != null) { + input.variables(variables); + } + ExecutionResult result = schema.execute(input); + return result; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/ScalarTest.java b/src/test/java/com/fleetpin/graphql/builder/ScalarTest.java new file mode 100644 index 0000000..76bd04e --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/ScalarTest.java @@ -0,0 +1,115 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fleetpin.graphql.builder.scalar.Fur; +import com.fleetpin.graphql.builder.scalar.Shape; +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.introspection.IntrospectionWithDirectivesSupport; +import graphql.scalars.ExtendedScalars; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ScalarTest { + + @Test + public void testCatFur() throws ReflectiveOperationException { + var scalar = getField("Fur", "SCALAR"); + assertEquals("soft", scalar.get("description")); + } + + @Test + public void testCatShape() throws ReflectiveOperationException { + var scalar = getField("Shape", "SCALAR"); + assertEquals(null, scalar.get("description")); + List> directive = (List>) scalar.get("appliedDirectives"); + assertEquals("Capture", directive.get(0).get("name")); + } + + public Map getField(String typeName, String kind) throws ReflectiveOperationException { + Map> response = execute( + "{" + + " __type(name: \"" + + typeName + + "\") {" + + " name" + + " description" + + " kind" + + " appliedDirectives {\n" + + " name\n" + + " args {\n" + + " name\n" + + " value\n" + + " }\n" + + " }" + + " }" + + "} " + ) + .getData(); + var type = response.get("__type"); + Assertions.assertEquals(typeName, type.get("name")); + Assertions.assertEquals(kind, type.get("kind")); + return type; + } + + @Test + public void testQueryCatFur() throws ReflectiveOperationException { + Map> response = execute( + "query fur($fur: Fur!, $age: Long!){getCat(fur: $fur, age: $age){ fur age}} ", + Map.of("fur", "long", "age", 2) + ) + .getData(); + var cat = response.get("getCat"); + + var fur = cat.get("fur"); + + assertEquals("long", fur.getInput()); + assertEquals(2L, cat.get("age")); + } + + @Test + public void testQueryShape() throws ReflectiveOperationException { + Map response = execute("query shape($shape: Shape!){getShape(shape: $shape)} ", Map.of("shape", "round")).getData(); + var shape = response.get("getShape"); + + assertEquals("round", shape.getInput()); + } + + private ExecutionResult execute(String query) { + return execute(query, null); + } + + private ExecutionResult execute(String query, Map variables) { + GraphQL schema = GraphQL + .newGraphQL( + new IntrospectionWithDirectivesSupport() + .apply(SchemaBuilder.builder().classpath("com.fleetpin.graphql.builder.scalar").scalar(ExtendedScalars.GraphQLLong).build().build()) + ) + .build(); + var input = ExecutionInput.newExecutionInput(); + input.query(query); + if (variables != null) { + input.variables(variables); + } + ExecutionResult result = schema.execute(input); + if (!result.getErrors().isEmpty()) { + throw new RuntimeException(result.getErrors().toString()); //TODO:cleanup + } + return result; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/TypeGenericInputParsingTest.java b/src/test/java/com/fleetpin/graphql/builder/TypeGenericInputParsingTest.java index e309b1b..b8b0ba8 100644 --- a/src/test/java/com/fleetpin/graphql/builder/TypeGenericInputParsingTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/TypeGenericInputParsingTest.java @@ -1,202 +1,205 @@ -package com.fleetpin.graphql.builder; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import graphql.ExecutionResult; -import graphql.GraphQL; - -public class TypeGenericInputParsingTest { - - @Test - public void testAnimalName() throws ReflectiveOperationException { - var name = getField("Animal", "INTERFACE", "name"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - @Test - public void testCatName() throws ReflectiveOperationException { - var name = getField("Cat", "OBJECT", "name"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - @Test - public void testCatInputName() throws ReflectiveOperationException { - var name = getInputField("CatInput", "INPUT_OBJECT", "name"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - @Test - public void testCatInputFur() throws ReflectiveOperationException { - var name = getInputField("CatInput", "INPUT_OBJECT", "fur"); - var nonNull = confirmNonNull(name); - confirmBoolean(nonNull); - } - - - @Test - public void testAnimalInputName() throws ReflectiveOperationException { - var name = getInputField("CatAnimalInput", "INPUT_OBJECT", "id"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - - @Test - public void testAnimalInputGenericName() throws ReflectiveOperationException { - var name = getInputField("AnimalInput_Cat", "INPUT_OBJECT", "id"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - - @Test - public void testAnimalInputCat() throws ReflectiveOperationException { - var name = getInputField("CatAnimalInput", "INPUT_OBJECT", "animal"); - var nonNull = confirmNonNull(name); - confirmInputObject(nonNull, "CatInput"); - - } - - @Test - public void testAnimalInputGenericCat() throws ReflectiveOperationException { - var name = getInputField("AnimalInput_Cat", "INPUT_OBJECT", "animal"); - var nonNull = confirmNonNull(name); - confirmInputObject(nonNull, "CatInput"); - - } - - - private void confirmString(Map type) { - Assertions.assertEquals("SCALAR", type.get("kind")); - Assertions.assertEquals("String", type.get("name")); - } - - - private void confirmInputObject(Map type, String name) { - Assertions.assertEquals("INPUT_OBJECT", type.get("kind")); - Assertions.assertEquals(name, type.get("name")); - } - - - private Map confirmNonNull(Map type) { - Assertions.assertEquals("NON_NULL", type.get("kind")); - var toReturn = (Map) type.get("ofType"); - Assertions.assertNotNull(toReturn); - return toReturn; - } - - private void confirmBoolean(Map type) { - Assertions.assertEquals("SCALAR", type.get("kind")); - Assertions.assertEquals("Boolean", type.get("name")); - } - - public Map getField(String typeName, String kind, String name) throws ReflectiveOperationException { - Map> response = execute("{" + - " __type(name: \"" + typeName + "\") {" + - " name" + - " kind" + - " fields {" + - " name" + - " type {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " }" + - " }" + - " }" + - " }" + - " }" + - " }" + - "} ").getData(); - var type = response.get("__type"); - Assertions.assertEquals(typeName, type.get("name")); - Assertions.assertEquals(kind, type.get("kind")); - List> fields = (List>) type.get("fields"); - var field = fields.stream().filter(map -> map.get("name").equals(name)).findAny().get(); - Assertions.assertEquals(name, field.get("name")); - return (Map) field.get("type"); - } - - public Map getInputField(String typeName, String kind, String name) throws ReflectiveOperationException { - Map> response = execute("{" + - " __type(name: \"" + typeName + "\") {" + - " name" + - " kind" + - " inputFields {" + - " name" + - " type {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " }" + - " }" + - " }" + - " }" + - " }" + - " }" + - "} ").getData(); - var type = response.get("__type"); - Assertions.assertEquals(typeName, type.get("name")); - Assertions.assertEquals(kind, type.get("kind")); - List> fields = (List>) type.get("inputFields"); - var field = fields.stream().filter(map -> map.get("name").equals(name)).findAny().get(); - Assertions.assertEquals(name, field.get("name")); - return (Map) field.get("type"); - } - - @Test - public void testQueryCatFur() throws ReflectiveOperationException { - Map response = execute("mutation {addCat(input: {id: \"1\", animal: {name: \"felix\", fur: false}})} ").getData(); - var cat = response.get("addCat"); - assertEquals("felix", cat); - } - - @Test - public void testQueryCatFurGeneric() throws ReflectiveOperationException { - Map response = execute("mutation {addCatGenerics(input: {id: \"1\", animal: {name: \"felix\", fur: true}})} ").getData(); - var cat = response.get("addCatGenerics"); - assertEquals(true, cat); - } - - - - private ExecutionResult execute(String query) { - try { - GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.inputgenerics")).build(); - ExecutionResult result = schema.execute(query); - if(!result.getErrors().isEmpty()) { - throw new RuntimeException(result.getErrors().toString()); - } - return result; - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - - } - -} +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import graphql.ExecutionResult; +import graphql.GraphQL; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TypeGenericInputParsingTest { + + @Test + public void testAnimalName() throws ReflectiveOperationException { + var name = getField("Animal", "INTERFACE", "name"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testCatName() throws ReflectiveOperationException { + var name = getField("Cat", "OBJECT", "name"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testCatInputName() throws ReflectiveOperationException { + var name = getInputField("CatInput", "INPUT_OBJECT", "name"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testCatInputFur() throws ReflectiveOperationException { + var name = getInputField("CatInput", "INPUT_OBJECT", "fur"); + var nonNull = confirmNonNull(name); + confirmBoolean(nonNull); + } + + @Test + public void testAnimalInputName() throws ReflectiveOperationException { + var name = getInputField("CatAnimalInput", "INPUT_OBJECT", "id"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testAnimalInputGenericName() throws ReflectiveOperationException { + var name = getInputField("AnimalInput_Cat", "INPUT_OBJECT", "id"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testAnimalInputCat() throws ReflectiveOperationException { + var name = getInputField("CatAnimalInput", "INPUT_OBJECT", "animal"); + var nonNull = confirmNonNull(name); + confirmInputObject(nonNull, "CatInput"); + } + + @Test + public void testAnimalInputGenericCat() throws ReflectiveOperationException { + var name = getInputField("AnimalInput_Cat", "INPUT_OBJECT", "animal"); + var nonNull = confirmNonNull(name); + confirmInputObject(nonNull, "CatInput"); + } + + private void confirmString(Map type) { + Assertions.assertEquals("SCALAR", type.get("kind")); + Assertions.assertEquals("String", type.get("name")); + } + + private void confirmInputObject(Map type, String name) { + Assertions.assertEquals("INPUT_OBJECT", type.get("kind")); + Assertions.assertEquals(name, type.get("name")); + } + + private Map confirmNonNull(Map type) { + Assertions.assertEquals("NON_NULL", type.get("kind")); + var toReturn = (Map) type.get("ofType"); + Assertions.assertNotNull(toReturn); + return toReturn; + } + + private void confirmBoolean(Map type) { + Assertions.assertEquals("SCALAR", type.get("kind")); + Assertions.assertEquals("Boolean", type.get("name")); + } + + public Map getField(String typeName, String kind, String name) throws ReflectiveOperationException { + Map> response = execute( + "{" + + " __type(name: \"" + + typeName + + "\") {" + + " name" + + " kind" + + " fields {" + + " name" + + " type {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " }" + + " }" + + " }" + + " }" + + " }" + + " }" + + "} " + ) + .getData(); + var type = response.get("__type"); + Assertions.assertEquals(typeName, type.get("name")); + Assertions.assertEquals(kind, type.get("kind")); + List> fields = (List>) type.get("fields"); + var field = fields.stream().filter(map -> map.get("name").equals(name)).findAny().get(); + Assertions.assertEquals(name, field.get("name")); + return (Map) field.get("type"); + } + + public Map getInputField(String typeName, String kind, String name) throws ReflectiveOperationException { + Map> response = execute( + "{" + + " __type(name: \"" + + typeName + + "\") {" + + " name" + + " kind" + + " inputFields {" + + " name" + + " type {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " }" + + " }" + + " }" + + " }" + + " }" + + " }" + + "} " + ) + .getData(); + var type = response.get("__type"); + Assertions.assertEquals(typeName, type.get("name")); + Assertions.assertEquals(kind, type.get("kind")); + List> fields = (List>) type.get("inputFields"); + var field = fields.stream().filter(map -> map.get("name").equals(name)).findAny().get(); + Assertions.assertEquals(name, field.get("name")); + return (Map) field.get("type"); + } + + @Test + public void testQueryCatFur() throws ReflectiveOperationException { + Map response = execute("mutation {addCat(input: {id: \"1\", animal: {name: \"felix\", fur: false}})} ").getData(); + var cat = response.get("addCat"); + assertEquals("felix", cat); + } + + @Test + public void testQueryCatFurGeneric() throws ReflectiveOperationException { + Map response = execute("mutation {addCatGenerics(input: {id: \"1\", animal: {name: \"felix\", fur: true}})} ").getData(); + var cat = response.get("addCatGenerics"); + assertEquals(true, cat); + } + + private ExecutionResult execute(String query) { + GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.inputgenerics")).build(); + ExecutionResult result = schema.execute(query); + if (!result.getErrors().isEmpty()) { + throw new RuntimeException(result.getErrors().toString()); + } + return result; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/TypeGenericInputRecords.java b/src/test/java/com/fleetpin/graphql/builder/TypeGenericInputRecords.java new file mode 100644 index 0000000..babab3b --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/TypeGenericInputRecords.java @@ -0,0 +1,81 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import graphql.ExceptionWhileDataFetching; +import graphql.ExecutionResult; +import graphql.GraphQL; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class TypeGenericInputRecords { + + @Test + public void textQuery() throws ReflectiveOperationException { + Map response = execute(""" + query {doChange(input: { + name: { + wrap: ["felix"] + }, + age: { + wrap: [234] + }, + description: { + wrap: "cat" + } + })} + """) + .getData(); + var change = response.get("doChange"); + assertEquals("felix[234]cat", change); + } + + @Test + public void textCorrectNullableQuery() throws ReflectiveOperationException { + Map response = execute(""" + query {doChange(input: { + name: { + wrap: ["felix"] + }, + age: { + }, + description: { + wrap: "cat" + } + })} + """).getData(); + var change = response.get("doChange"); + assertEquals("felixnullcat", change); + } + + @Test + public void textQueryNull() throws ReflectiveOperationException { + Map response = execute(""" + query {doChange(input: {})} + """).getData(); + var change = response.get("doChange"); + assertEquals("empty", change); + } + + private ExecutionResult execute(String query) { + GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.inputgenericsRecords")).build(); + ExecutionResult result = schema.execute(query); + if (!result.getErrors().isEmpty()) { + ExceptionWhileDataFetching d = (ExceptionWhileDataFetching) result.getErrors().get(0); + d.getException().printStackTrace(); + throw new RuntimeException(result.getErrors().toString()); + } + return result; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/TypeGenericParsingTest.java b/src/test/java/com/fleetpin/graphql/builder/TypeGenericParsingTest.java index 6218043..4d588ee 100644 --- a/src/test/java/com/fleetpin/graphql/builder/TypeGenericParsingTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/TypeGenericParsingTest.java @@ -1,311 +1,318 @@ -package com.fleetpin.graphql.builder; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import graphql.ExecutionResult; -import graphql.GraphQL; - -public class TypeGenericParsingTest { - - @Test - public void testAnimalName() throws ReflectiveOperationException { - var name = getField("Animal", "INTERFACE", "name"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - @Test - public void testAnimalFur() throws ReflectiveOperationException { - var name = getField("Animal", "INTERFACE", "fur"); - var nonNull = confirmNonNull(name); - confirmInterface(nonNull, "Fur"); - } - - @Test - public void testAnimalFurs() throws ReflectiveOperationException { - var name = getField("Animal", "INTERFACE", "furs"); - var type = confirmNonNull(name); - type = confirmArray(type); - type = confirmNonNull(type); - confirmInterface(type, "Fur"); - } - - @Test - public void testCatName() throws ReflectiveOperationException { - var name = getField("Cat", "OBJECT", "name"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - @Test - public void testCatFur() throws ReflectiveOperationException { - var name = getField("Cat", "OBJECT", "fur"); - var nonNull = confirmNonNull(name); - confirmObject(nonNull, "CatFur"); - } - - @Test - public void testCatFurs() throws ReflectiveOperationException { - var name = getField("Cat", "OBJECT", "furs"); - var type = confirmNonNull(name); - type = confirmArray(type); - type = confirmNonNull(type); - confirmObject(type, "CatFur"); - } - - @Test - public void testDogName() throws ReflectiveOperationException { - var name = getField("Dog", "OBJECT", "name"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - @Test - public void testDogFur() throws ReflectiveOperationException { - var name = getField("Dog", "OBJECT", "fur"); - var nonNull = confirmNonNull(name); - confirmObject(nonNull, "DogFur"); - } - - @Test - public void testDogFurs() throws ReflectiveOperationException { - var name = getField("Dog", "OBJECT", "furs"); - var type = confirmNonNull(name); - type = confirmArray(type); - type = confirmNonNull(type); - confirmObject(type, "DogFur"); - } - - @Test - public void testFurName() throws ReflectiveOperationException { - var name = getField("Fur", "INTERFACE", "length"); - var nonNull = confirmNonNull(name); - confirmNumber(nonNull); - } - - @Test - public void testCatFurCalico() throws ReflectiveOperationException { - var name = getField("CatFur", "OBJECT", "calico"); - var nonNull = confirmNonNull(name); - confirmBoolean(nonNull); - } - - @Test - public void testCatFurLong() throws ReflectiveOperationException { - var name = getField("CatFur", "OBJECT", "long"); - var nonNull = confirmNonNull(name); - confirmBoolean(nonNull); - } - - @Test - public void testDogFurShaggy() throws ReflectiveOperationException { - var name = getField("DogFur", "OBJECT", "shaggy"); - var nonNull = confirmNonNull(name); - confirmBoolean(nonNull); - } - - @Test - public void testCatFamilyName() throws ReflectiveOperationException { - var name = getField("CatFamily", "INTERFACE", "name"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - @Test - public void testCatFamilyFur() throws ReflectiveOperationException { - var name = getField("CatFamily", "INTERFACE", "fur"); - var nonNull = confirmNonNull(name); - confirmInterface(nonNull, "CatFamilyFur"); - } - - @Test - public void testCatFamilyFurs() throws ReflectiveOperationException { - var name = getField("CatFamily", "INTERFACE", "furs"); - var type = confirmNonNull(name); - type = confirmArray(type); - type = confirmNonNull(type); - confirmInterface(type, "CatFamilyFur"); - } - - @Test - public void testCatFamilyFurCalico() throws ReflectiveOperationException { - var name = getField("CatFamilyFur", "INTERFACE", "length"); - var nonNull = confirmNonNull(name); - confirmNumber(nonNull); - } - - @Test - public void testCatFamilyFurLong() throws ReflectiveOperationException { - var name = getField("CatFamilyFur", "INTERFACE", "long"); - var nonNull = confirmNonNull(name); - confirmBoolean(nonNull); - } - - - - - private void confirmString(Map type) { - Assertions.assertEquals("SCALAR", type.get("kind")); - Assertions.assertEquals("String", type.get("name")); - } - - private void confirmInterface(Map type, String name) { - Assertions.assertEquals("INTERFACE", type.get("kind")); - Assertions.assertEquals(name, type.get("name")); - } - - private void confirmObject(Map type, String name) { - Assertions.assertEquals("OBJECT", type.get("kind")); - Assertions.assertEquals(name, type.get("name")); - } - - - - private void confirmBoolean(Map type) { - Assertions.assertEquals("SCALAR", type.get("kind")); - Assertions.assertEquals("Boolean", type.get("name")); - } - - private void confirmNumber(Map type) { - Assertions.assertEquals("SCALAR", type.get("kind")); - Assertions.assertEquals("Int", type.get("name")); - } - - - private Map confirmNonNull(Map type) { - Assertions.assertEquals("NON_NULL", type.get("kind")); - var toReturn = (Map) type.get("ofType"); - Assertions.assertNotNull(toReturn); - return toReturn; - } - - private Map confirmArray(Map type) { - Assertions.assertEquals("LIST", type.get("kind")); - var toReturn = (Map) type.get("ofType"); - Assertions.assertNotNull(toReturn); - return toReturn; - } - - public Map getField(String typeName, String kind, String name) throws ReflectiveOperationException { - Map> response = execute("{" + - " __type(name: \"" + typeName + "\") {" + - " name" + - " kind" + - " fields {" + - " name" + - " type {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " }" + - " }" + - " }" + - " }" + - " }" + - " }" + - "} ").getData(); - var type = response.get("__type"); - Assertions.assertEquals(typeName, type.get("name")); - Assertions.assertEquals(kind, type.get("kind")); - - List> fields = (List>) type.get("fields"); - var field = fields.stream().filter(map -> map.get("name").equals(name)).findAny().get(); - Assertions.assertEquals(name, field.get("name")); - return (Map) field.get("type"); - } - - @Test - public void testQueryCatFur() throws ReflectiveOperationException { - Map>> response = execute("query {animals{" + - "name " + - "... on Cat { " + - " fur{ " + - " calico " + - " length" + - " catFur: long" + - " }" + - "} " + - "... on Dog {" + - " fur {" + - " shaggy" + - " dogFur: long" + - " length" + - " } " + - "} " + - "}} ").getData(); - - var animals = response.get("animals"); - - var cat = animals.get(0); - var dog = animals.get(1); - - var catFur = (Map )cat.get("fur"); - var dogFur = (Map) dog.get("fur"); - - assertEquals("name", cat.get("name")); - assertEquals(4, catFur.get("length")); - assertEquals(true, catFur.get("calico")); - assertEquals(true, catFur.get("catFur")); - - assertEquals(4, dogFur.get("length")); - assertEquals(true, dogFur.get("shaggy")); - assertEquals("very", dogFur.get("dogFur")); - } - - @Test - public void testMutationCatFur() throws ReflectiveOperationException { - Map>> response = execute("mutation {makeCat{" + - "item { " + - " ... on Cat { " + - " name " + - " fur{ " + - " calico " + - " length" + - " long" + - " }" + - " } " + - "}" + - "}} ").getData(); - - var makeCat = response.get("makeCat"); - - var cat = makeCat.get("item"); - - var catFur = (Map )cat.get("fur"); - - assertEquals("name", cat.get("name")); - assertEquals(4, catFur.get("length")); - assertEquals(true, catFur.get("calico")); - assertEquals(true, catFur.get("long")); - - } - - private ExecutionResult execute(String query) { - try { - GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.generics")).build(); - ExecutionResult result = schema.execute(query); - if(!result.getErrors().isEmpty()) { - throw new RuntimeException(result.getErrors().toString()); - } - return result; - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - - } - -} +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import graphql.ExecutionResult; +import graphql.GraphQL; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TypeGenericParsingTest { + + @Test + public void testAnimalName() throws ReflectiveOperationException { + var name = getField("Animal", "INTERFACE", "name"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testAnimalFur() throws ReflectiveOperationException { + var name = getField("Animal", "INTERFACE", "fur"); + var nonNull = confirmNonNull(name); + confirmInterface(nonNull, "Fur"); + } + + @Test + public void testAnimalFurs() throws ReflectiveOperationException { + var name = getField("Animal", "INTERFACE", "furs"); + var type = confirmNonNull(name); + type = confirmArray(type); + type = confirmNonNull(type); + confirmInterface(type, "Fur"); + } + + @Test + public void testCatName() throws ReflectiveOperationException { + var name = getField("Cat", "OBJECT", "name"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testCatFur() throws ReflectiveOperationException { + var name = getField("Cat", "OBJECT", "fur"); + var nonNull = confirmNonNull(name); + confirmObject(nonNull, "CatFur"); + } + + @Test + public void testCatFurs() throws ReflectiveOperationException { + var name = getField("Cat", "OBJECT", "furs"); + var type = confirmNonNull(name); + type = confirmArray(type); + type = confirmNonNull(type); + confirmObject(type, "CatFur"); + } + + @Test + public void testDogName() throws ReflectiveOperationException { + var name = getField("Dog", "OBJECT", "name"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testDogFur() throws ReflectiveOperationException { + var name = getField("Dog", "OBJECT", "fur"); + var nonNull = confirmNonNull(name); + confirmObject(nonNull, "DogFur"); + } + + @Test + public void testDogFurs() throws ReflectiveOperationException { + var name = getField("Dog", "OBJECT", "furs"); + var type = confirmNonNull(name); + type = confirmArray(type); + type = confirmNonNull(type); + confirmObject(type, "DogFur"); + } + + @Test + public void testFurName() throws ReflectiveOperationException { + var name = getField("Fur", "INTERFACE", "length"); + var nonNull = confirmNonNull(name); + confirmNumber(nonNull); + } + + @Test + public void testCatFurCalico() throws ReflectiveOperationException { + var name = getField("CatFur", "OBJECT", "calico"); + var nonNull = confirmNonNull(name); + confirmBoolean(nonNull); + } + + @Test + public void testCatFurLong() throws ReflectiveOperationException { + var name = getField("CatFur", "OBJECT", "long"); + var nonNull = confirmNonNull(name); + confirmBoolean(nonNull); + } + + @Test + public void testDogFurShaggy() throws ReflectiveOperationException { + var name = getField("DogFur", "OBJECT", "shaggy"); + var nonNull = confirmNonNull(name); + confirmBoolean(nonNull); + } + + @Test + public void testCatFamilyName() throws ReflectiveOperationException { + var name = getField("CatFamily", "INTERFACE", "name"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testCatFamilyFur() throws ReflectiveOperationException { + var name = getField("CatFamily", "INTERFACE", "fur"); + var nonNull = confirmNonNull(name); + confirmInterface(nonNull, "CatFamilyFur"); + } + + @Test + public void testCatFamilyFurs() throws ReflectiveOperationException { + var name = getField("CatFamily", "INTERFACE", "furs"); + var type = confirmNonNull(name); + type = confirmArray(type); + type = confirmNonNull(type); + confirmInterface(type, "CatFamilyFur"); + } + + @Test + public void testCatFamilyFurCalico() throws ReflectiveOperationException { + var name = getField("CatFamilyFur", "INTERFACE", "length"); + var nonNull = confirmNonNull(name); + confirmNumber(nonNull); + } + + @Test + public void testCatFamilyFurLong() throws ReflectiveOperationException { + var name = getField("CatFamilyFur", "INTERFACE", "long"); + var nonNull = confirmNonNull(name); + confirmBoolean(nonNull); + } + + private void confirmString(Map type) { + Assertions.assertEquals("SCALAR", type.get("kind")); + Assertions.assertEquals("String", type.get("name")); + } + + private void confirmInterface(Map type, String name) { + Assertions.assertEquals("INTERFACE", type.get("kind")); + Assertions.assertEquals(name, type.get("name")); + } + + private void confirmObject(Map type, String name) { + Assertions.assertEquals("OBJECT", type.get("kind")); + Assertions.assertEquals(name, type.get("name")); + } + + private void confirmBoolean(Map type) { + Assertions.assertEquals("SCALAR", type.get("kind")); + Assertions.assertEquals("Boolean", type.get("name")); + } + + private void confirmNumber(Map type) { + Assertions.assertEquals("SCALAR", type.get("kind")); + Assertions.assertEquals("Int", type.get("name")); + } + + private Map confirmNonNull(Map type) { + Assertions.assertEquals("NON_NULL", type.get("kind")); + var toReturn = (Map) type.get("ofType"); + Assertions.assertNotNull(toReturn); + return toReturn; + } + + private Map confirmArray(Map type) { + Assertions.assertEquals("LIST", type.get("kind")); + var toReturn = (Map) type.get("ofType"); + Assertions.assertNotNull(toReturn); + return toReturn; + } + + public Map getField(String typeName, String kind, String name) throws ReflectiveOperationException { + Map> response = execute( + "{" + + " __type(name: \"" + + typeName + + "\") {" + + " name" + + " kind" + + " fields {" + + " name" + + " type {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " }" + + " }" + + " }" + + " }" + + " }" + + " }" + + "} " + ) + .getData(); + var type = response.get("__type"); + Assertions.assertEquals(typeName, type.get("name")); + Assertions.assertEquals(kind, type.get("kind")); + + List> fields = (List>) type.get("fields"); + var field = fields.stream().filter(map -> map.get("name").equals(name)).findAny().get(); + Assertions.assertEquals(name, field.get("name")); + return (Map) field.get("type"); + } + + @Test + public void testQueryCatFur() throws ReflectiveOperationException { + Map>> response = execute( + "query {animals{" + + "name " + + "... on Cat { " + + " fur{ " + + " calico " + + " length" + + " catFur: long" + + " }" + + "} " + + "... on Dog {" + + " fur {" + + " shaggy" + + " dogFur: long" + + " length" + + " } " + + "} " + + "}} " + ) + .getData(); + + var animals = response.get("animals"); + + var cat = animals.get(0); + var dog = animals.get(1); + + var catFur = (Map) cat.get("fur"); + var dogFur = (Map) dog.get("fur"); + + assertEquals("name", cat.get("name")); + assertEquals(4, catFur.get("length")); + assertEquals(true, catFur.get("calico")); + assertEquals(true, catFur.get("catFur")); + + assertEquals(4, dogFur.get("length")); + assertEquals(true, dogFur.get("shaggy")); + assertEquals("very", dogFur.get("dogFur")); + } + + @Test + public void testMutationCatFur() throws ReflectiveOperationException { + Map>> response = execute( + "mutation {makeCat{" + + "item { " + + " ... on Cat { " + + " name " + + " fur{ " + + " calico " + + " length" + + " long" + + " }" + + " } " + + "}" + + "}} " + ) + .getData(); + + var makeCat = response.get("makeCat"); + + var cat = makeCat.get("item"); + + var catFur = (Map) cat.get("fur"); + + assertEquals("name", cat.get("name")); + assertEquals(4, catFur.get("length")); + assertEquals(true, catFur.get("calico")); + assertEquals(true, catFur.get("long")); + } + + private ExecutionResult execute(String query) { + GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.generics")).build(); + ExecutionResult result = schema.execute(query); + if (!result.getErrors().isEmpty()) { + throw new RuntimeException(result.getErrors().toString()); + } + return result; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/TypeInheritanceParsingTest.java b/src/test/java/com/fleetpin/graphql/builder/TypeInheritanceParsingTest.java index 972b08d..36808ac 100644 --- a/src/test/java/com/fleetpin/graphql/builder/TypeInheritanceParsingTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/TypeInheritanceParsingTest.java @@ -1,248 +1,498 @@ -package com.fleetpin.graphql.builder; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import com.fleetpin.graphql.builder.type.SimpleType; - -import graphql.ExecutionResult; -import graphql.GraphQL; -import graphql.introspection.Introspection; - -public class TypeInheritanceParsingTest { - @Test - public void findTypes() throws ReflectiveOperationException { - Map>>> response = execute("{__schema {types {name}}} ").getData(); - var types = response.get("__schema").get("types"); - var count = types.stream().filter(map -> map.get("name").equals("SimpleType")).count(); - Assertions.assertEquals(1, count); - } - - @Test - public void testAnimalName() throws ReflectiveOperationException { - var name = getField("Animal", "INTERFACE", "name"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - - @Test - public void testCatName() throws ReflectiveOperationException { - var name = getField("Cat", "OBJECT", "name"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - @Test - public void testCatAge() throws ReflectiveOperationException { - var name = getField("Cat", "OBJECT", "age"); - var nonNull = confirmNonNull(name); - confirmNumber(nonNull); - } - - @Test - public void testCatFur() throws ReflectiveOperationException { - var name = getField("Cat", "OBJECT", "fur"); - var nonNull = confirmNonNull(name); - confirmBoolean(nonNull); - } - - @Test - public void testCatCalico() throws ReflectiveOperationException { - var name = getField("Cat", "OBJECT", "calico"); - var nonNull = confirmNonNull(name); - confirmBoolean(nonNull); - } - - - @Test - public void testDogName() throws ReflectiveOperationException { - var name = getField("Dog", "OBJECT", "name"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - @Test - public void testDogFur() throws ReflectiveOperationException { - var name = getField("Dog", "OBJECT", "fur"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - @Test - public void testDogAge() throws ReflectiveOperationException { - var name = getField("Dog", "OBJECT", "age"); - var nonNull = confirmNonNull(name); - confirmNumber(nonNull); - } - - - - private void confirmString(Map type) { - Assertions.assertEquals("SCALAR", type.get("kind")); - Assertions.assertEquals("String", type.get("name")); - } - - private void confirmBoolean(Map type) { - Assertions.assertEquals("SCALAR", type.get("kind")); - Assertions.assertEquals("Boolean", type.get("name")); - } - - private void confirmNumber(Map type) { - Assertions.assertEquals("SCALAR", type.get("kind")); - Assertions.assertEquals("Int", type.get("name")); - } - - - private Map confirmNonNull(Map type) { - Assertions.assertEquals("NON_NULL", type.get("kind")); - var toReturn = (Map) type.get("ofType"); - Assertions.assertNotNull(toReturn); - return toReturn; - } - - private Map confirmArray(Map type) { - Assertions.assertEquals("LIST", type.get("kind")); - var toReturn = (Map) type.get("ofType"); - Assertions.assertNotNull(toReturn); - return toReturn; - } - - public Map getField(String typeName, String kind, String name) throws ReflectiveOperationException { - Map> response = execute("{" + - " __type(name: \"" + typeName + "\") {" + - " name" + - " kind" + - " fields {" + - " name" + - " type {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " }" + - " }" + - " }" + - " }" + - " }" + - " }" + - "} ").getData(); - var type = response.get("__type"); - Assertions.assertEquals(typeName, type.get("name")); - Assertions.assertEquals(kind, type.get("kind")); - - List> fields = (List>) type.get("fields"); - var field = fields.stream().filter(map -> map.get("name").equals(name)).findAny().get(); - Assertions.assertEquals(name, field.get("name")); - return (Map) field.get("type"); - } - - @Test - public void testQueryCatFur() throws ReflectiveOperationException { - Map>> response = execute("query {animals{" + - "name " + - "... on Cat { " + - " age " + - " fur " + - " calico " + - "} " + - "... on Dog {" + - " age " + - "} " + - "}} ").getData(); - - var animals = response.get("animals"); - - var cat = animals.get(0); - var dog = animals.get(1); - - - assertEquals("name", cat.get("name")); - assertEquals(3, cat.get("age")); - assertEquals(true, cat.get("fur")); - assertEquals(true, cat.get("calico")); - - assertEquals("name", dog.get("name")); - assertEquals(6, dog.get("age")); - } - - @Test - public void testQueryDogFur() throws ReflectiveOperationException { - Map>> response = execute("query {animals{" + - "name " + - "... on Cat { " + - " age " + - " calico " + - "} " + - "... on Dog {" + - " age " + - " fur " + - "} " + - "}} ").getData(); - - var animals = response.get("animals"); - - var cat = animals.get(0); - var dog = animals.get(1); - - - assertEquals("name", cat.get("name")); - assertEquals(3, cat.get("age")); - assertEquals(true, cat.get("calico")); - - assertEquals("name", dog.get("name")); - assertEquals(6, dog.get("age")); - assertEquals("shaggy", dog.get("fur")); - } - - @Test - public void testBothFurFails() throws ReflectiveOperationException { - Assertions.assertThrows(RuntimeException.class,() -> { - execute("query {animals{" + - "name " + - "... on Cat { " + - " age " + - " fur " + - " calico " + - "} " + - "... on Dog {" + - " age " + - " fur " + - "} " + - "}} "); - - }); - - } - - - private ExecutionResult execute(String query) { - try { - GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type")).build(); - ExecutionResult result = schema.execute(query); - if(!result.getErrors().isEmpty()) { - throw new RuntimeException(result.getErrors().toString()); //TODO:cleanup - } - return result; - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - - } - -} +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import static org.junit.jupiter.api.Assertions.*; + +import com.fleetpin.graphql.builder.exceptions.InvalidOneOfException; +import graphql.ExceptionWhileDataFetching; +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.validation.ValidationError; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TypeInheritanceParsingTest { + + @Test + public void findTypes() { + Map>>> response = execute("{__schema {types {name}}} ").getData(); + var types = response.get("__schema").get("types"); + var count = types.stream().filter(map -> map.get("name").equals("SimpleType")).count(); + Assertions.assertEquals(1, count); + } + + @Test + public void testAnimalName() { + var name = getField("Animal", "INTERFACE", "name"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testAnimalInputName() { + var name = getField("AnimalInput", "INPUT_OBJECT", "cat"); + confirmInputObject(name, "CatInput"); + } + + @Test + public void testCatName() { + var name = getField("Cat", "OBJECT", "name"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testCatAge() { + var name = getField("Cat", "OBJECT", "age"); + var nonNull = confirmNonNull(name); + confirmNumber(nonNull); + } + + @Test + public void testCatFur() { + var name = getField("Cat", "OBJECT", "fur"); + confirmBoolean(name); + } + + @Test + public void testCatCalico() { + var name = getField("Cat", "OBJECT", "calico"); + var nonNull = confirmNonNull(name); + confirmBoolean(nonNull); + } + + @Test + public void testDogName() { + var name = getField("Dog", "OBJECT", "name"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testDogFur() { + var name = getField("Dog", "OBJECT", "fur"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testDogAge() { + var name = getField("Dog", "OBJECT", "age"); + var nonNull = confirmNonNull(name); + confirmNumber(nonNull); + } + + @Test + public void testCatDescription() { + var type = getField("Cat", "OBJECT", null); + Assertions.assertEquals("cat type", type.get("description")); + } + + @Test + public void testCatFurDescription() { + var type = getField("Cat", "OBJECT", null); + + List> fields = (List>) type.get("fields"); + var field = fields.stream().filter(map -> map.get("name").equals("fur")).findAny().get(); + Assertions.assertEquals("get fur", field.get("description")); + } + + @Test + public void testCatWeightArgumentDescription() { + var type = getField("Cat", "OBJECT", null); + + List> fields = (List>) type.get("fields"); + var field = fields.stream().filter(map -> map.get("name").equals("weight")).findAny().get(); + Assertions.assertEquals(null, field.get("description")); + + List> args = (List>) field.get("args"); + var round = args.stream().filter(map -> map.get("name").equals("round")).findAny().get(); + Assertions.assertEquals("whole number", round.get("description")); + } + + @Test + public void testMutationDescription() { + var type = getField("Mutations", "OBJECT", null); + + List> fields = (List>) type.get("fields"); + var field = fields.stream().filter(map -> map.get("name").equals("getCat")).findAny().get(); + Assertions.assertEquals("cat endpoint", field.get("description")); + + List> args = (List>) field.get("args"); + var round = args.stream().filter(map -> map.get("name").equals("age")).findAny().get(); + Assertions.assertEquals("sample", round.get("description")); + } + + @Test + public void testCatFurInputDescription() { + var type = getField("CatInput", "INPUT_OBJECT", null); + + List> fields = (List>) type.get("inputFields"); + var field = fields.stream().filter(map -> map.get("name").equals("fur")).findAny().get(); + Assertions.assertEquals("set fur", field.get("description")); + } + + @Test + public void testInputCatDescription() { + var type = getField("CatInput", "INPUT_OBJECT", null); + Assertions.assertEquals("cat type", type.get("description")); + } + + @Test + public void testInputOneOfDescription() { + var type = getField("AnimalInput", "INPUT_OBJECT", null); + Assertions.assertEquals("animal desc", type.get("description")); + List> fields = (List>) type.get("inputFields"); + var field = fields.stream().filter(map -> map.get("name").equals("dog")).findAny().get(); + Assertions.assertEquals("A dog", field.get("description")); + } + + private void confirmString(Map type) { + Assertions.assertEquals("SCALAR", type.get("kind")); + Assertions.assertEquals("String", type.get("name")); + } + + private void confirmInputObject(Map type, String name) { + Assertions.assertEquals("INPUT_OBJECT", type.get("kind")); + Assertions.assertEquals(name, type.get("name")); + } + + private void confirmBoolean(Map type) { + Assertions.assertEquals("SCALAR", type.get("kind")); + Assertions.assertEquals("Boolean", type.get("name")); + } + + private void confirmNumber(Map type) { + Assertions.assertEquals("SCALAR", type.get("kind")); + Assertions.assertEquals("Int", type.get("name")); + } + + private Map confirmNonNull(Map type) { + Assertions.assertEquals("NON_NULL", type.get("kind")); + var toReturn = (Map) type.get("ofType"); + Assertions.assertNotNull(toReturn); + return toReturn; + } + + private Map confirmArray(Map type) { + Assertions.assertEquals("LIST", type.get("kind")); + var toReturn = (Map) type.get("ofType"); + Assertions.assertNotNull(toReturn); + return toReturn; + } + + public Map getField(String typeName, String kind, String name) { + Map> response = execute( + "{" + + " __type(name: \"" + + typeName + + "\") {" + + " name" + + " kind" + + " description" + + " fields {" + + " name" + + " description" + + " args {" + + " name" + + " description" + + " }" + + " type {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " }" + + " }" + + " }" + + " }" + + " }" + + " inputFields {" + + " name" + + " description" + + " type {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " }" + + " }" + + " }" + + " }" + + " }" + + " }" + + "} " + ) + .getData(); + var type = response.get("__type"); + Assertions.assertEquals(typeName, type.get("name")); + Assertions.assertEquals(kind, type.get("kind")); + + if (name == null) { + return type; + } + + List> fields = (List>) type.get("fields"); + if (fields == null) { + fields = (List>) type.get("inputFields"); + } + var field = fields.stream().filter(map -> map.get("name").equals(name)).findAny().get(); + Assertions.assertEquals(name, field.get("name")); + return (Map) field.get("type"); + } + + @Test + public void testQueryCatFur() { + Map>> response = execute( + "query {animals{" + "name " + "... on Cat { " + " age " + " fur " + " calico " + "} " + "... on Dog {" + " age " + "} " + "}} " + ) + .getData(); + + var animals = response.get("animals"); + + var cat = animals.get(0); + var dog = animals.get(1); + + assertEquals("name", cat.get("name")); + assertEquals(3, cat.get("age")); + assertEquals(true, cat.get("fur")); + assertEquals(true, cat.get("calico")); + + assertEquals("name", dog.get("name")); + assertEquals(6, dog.get("age")); + } + + @Test + public void testQueryDogFur() { + Map>> response = execute( + "query {animals{" + "name " + "... on Cat { " + " age " + " calico " + "} " + "... on Dog {" + " age " + " fur " + "} " + "}} " + ) + .getData(); + + var animals = response.get("animals"); + + var cat = animals.get(0); + var dog = animals.get(1); + + assertEquals("name", cat.get("name")); + assertEquals(3, cat.get("age")); + assertEquals(true, cat.get("calico")); + + assertEquals("name", dog.get("name")); + assertEquals(6, dog.get("age")); + assertEquals("shaggy", dog.get("fur")); + } + + @Test + public void testBothFurFails() { + var result = execute( + "query {animals{" + "name " + "... on Cat { " + " age " + " fur " + " calico " + "} " + "... on Dog {" + " age " + " fur " + "} " + "}} " + ); + + assertFalse(result.getErrors().isEmpty()); + assertTrue(result.getErrors().get(0) instanceof ValidationError); + } + + @Test + public void testOneOf() { + Map>> response = execute( + "mutation {myAnimals(animals: [" + + "{cat: {fur: true, calico: false, name: \"socks\", age: 4}}," + + "{dog: {fur: \"short\", name: \"patches\", age: 5}}" + + "]){" + + "name " + + "... on Cat { " + + " age " + + " calico " + + "} " + + "... on Dog {" + + " age " + + " fur " + + "} " + + "}} " + ) + .getData(); + + var animals = response.get("myAnimals"); + + var cat = animals.get(0); + var dog = animals.get(1); + + assertEquals("socks", cat.get("name")); + assertEquals(4, cat.get("age")); + assertEquals(false, cat.get("calico")); + + assertEquals("patches", dog.get("name")); + assertEquals(5, dog.get("age")); + assertEquals("short", dog.get("fur")); + } + + @Test + public void testOptionalFieldNotSet() { + Map>> response = execute( + "mutation {myAnimals(animals: [" + + "{cat: {calico: false, name: \"socks\", age: 4}}," + + "]){" + + "name " + + "... on Cat { " + + " age " + + " calico " + + " fur " + + "} " + + "... on Dog {" + + " age " + + "} " + + "}} " + ) + .getData(); + + var animals = response.get("myAnimals"); + + var cat = animals.get(0); + + assertEquals("socks", cat.get("name")); + assertEquals(4, cat.get("age")); + assertEquals(false, cat.get("calico")); + assertEquals(true, cat.get("fur")); + } + + @Test + public void testOptionalFieldNull() { + Map>> response = execute( + "mutation {myAnimals(animals: [" + + "{cat: {fur: null, calico: false, name: \"socks\", age: 4}}," + + "]){" + + "name " + + "... on Cat { " + + " age " + + " calico " + + " fur " + + "} " + + "... on Dog {" + + " age " + + "} " + + "}} " + ) + .getData(); + + var animals = response.get("myAnimals"); + + var cat = animals.get(0); + + assertEquals("socks", cat.get("name")); + assertEquals(4, cat.get("age")); + assertEquals(false, cat.get("calico")); + assertNull(cat.get("fur")); + } + + @Test + public void testOneOfError() { + var result = execute( + "mutation {myAnimals(animals: [" + + "{cat: {fur: true, calico: false, name: \"socks\", age: 4}," + + "dog: {fur: \"short\", name: \"patches\", age: 5}}" + + "]){" + + "name " + + "... on Cat { " + + " age " + + " calico " + + "} " + + "... on Dog {" + + " age " + + " fur " + + "} " + + "}} " + ); + + assertFalse(result.getErrors().isEmpty()); + var exception = ((ExceptionWhileDataFetching) result.getErrors().get(0)).getException(); + assertTrue(exception.getMessage().contains("OneOf must only have a single field set. Fields: cat, dog")); + } + + @Test + public void testOneOfErrorEmpty() { + var result = execute( + "mutation {myAnimals(animals: [" + + "{}" + + "]){" + + "name " + + "... on Cat { " + + " age " + + " calico " + + "} " + + "... on Dog {" + + " age " + + " fur " + + "} " + + "}} " + ); + + assertFalse(result.getErrors().isEmpty()); + var exception = ((ExceptionWhileDataFetching) result.getErrors().get(0)).getException(); + assertTrue(exception.getMessage().contains("OneOf must have a field set")); + } + + @Test + public void testOneOfErrorField() { + var result = execute( + "mutation {myAnimals(animals: [" + + "{cat: {fur: null, calico: false, name: \"socks\", age: 4, error: \"fail\"}}" + + "]){" + + "name " + + "... on Cat { " + + " age " + + " calico " + + "} " + + "... on Dog {" + + " age " + + " fur " + + "} " + + "}} " + ); + + assertFalse(result.getErrors().isEmpty()); + var exception = ((ExceptionWhileDataFetching) result.getErrors().get(0)).getException(); + assertTrue(exception.getMessage().contains("ERROR")); + } + + private ExecutionResult execute(String query) { + return execute(query, null); + } + + private ExecutionResult execute(String query, Map variables) { + GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type")).build(); + var input = ExecutionInput.newExecutionInput(); + input.query(query); + if (variables != null) { + input.variables(variables); + } + return schema.execute(input); + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/TypeParsingTest.java b/src/test/java/com/fleetpin/graphql/builder/TypeParsingTest.java index 56b6425..feaec82 100644 --- a/src/test/java/com/fleetpin/graphql/builder/TypeParsingTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/TypeParsingTest.java @@ -1,231 +1,247 @@ -package com.fleetpin.graphql.builder; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import graphql.ExecutionResult; -import graphql.GraphQL; - -public class TypeParsingTest { - @Test - public void findTypes() throws ReflectiveOperationException { - Map>>> response = execute("{__schema {types {name}}} ").getData(); - var types = response.get("__schema").get("types"); - var count = types.stream().filter(map -> map.get("name").equals("SimpleType")).count(); - Assertions.assertEquals(1, count); - } - - @Test - public void testName() throws ReflectiveOperationException { - var name = getField("name"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - - @Test - public void testDeleted() throws ReflectiveOperationException { - var name = getField("deleted"); - var nonNull = confirmNonNull(name); - confirmBoolean(nonNull); - } - - - @Test - public void testAlive() throws ReflectiveOperationException { - var name = getField("alive"); - confirmBoolean(name); - } - - @Test - public void testParts() throws ReflectiveOperationException { - var type = getField("parts"); - type = confirmNonNull(type); - type = confirmArray(type); - type = confirmNonNull(type); - confirmString(type); - } - - @Test - public void testGappyParts() throws ReflectiveOperationException { - var type = getField("gappyParts"); - type = confirmNonNull(type); - type = confirmArray(type); - confirmString(type); - } - - @Test - public void testOptioanlParts() throws ReflectiveOperationException { - var type = getField("optionalParts"); - type = confirmArray(type); - type = confirmNonNull(type); - confirmString(type); - } - - @Test - public void testOptioanlGappyParts() throws ReflectiveOperationException { - var type = getField("optionalGappyParts"); - type = confirmArray(type); - confirmString(type); - } - - - @Test - public void testNameFuture() throws ReflectiveOperationException { - var name = getField("nameFuture"); - var nonNull = confirmNonNull(name); - confirmString(nonNull); - } - @Test - public void isDeletedFuture() throws ReflectiveOperationException { - var name = getField("deletedFuture"); - var nonNull = confirmNonNull(name); - confirmBoolean(nonNull); - } - @Test - public void testAliveFuture() throws ReflectiveOperationException { - var name = getField("aliveFuture"); - confirmBoolean(name); - } - @Test - public void testPartsFuture() throws ReflectiveOperationException { - var type = getField("partsFuture"); - type = confirmNonNull(type); - type = confirmArray(type); - type = confirmNonNull(type); - confirmString(type); - } - @Test - public void testGappyPartsFuture() throws ReflectiveOperationException { - var type = getField("gappyPartsFuture"); - type = confirmNonNull(type); - type = confirmArray(type); - confirmString(type); - } - @Test - public void testOptionalPartsFuture() throws ReflectiveOperationException { - var type = getField("optionalPartsFuture"); - type = confirmArray(type); - type = confirmNonNull(type); - confirmString(type); - } - @Test - public void testOptionalGappyPartsFuture() throws ReflectiveOperationException { - var type = getField("optionalGappyPartsFuture"); - type = confirmArray(type); - confirmString(type); - } - - - - private void confirmString(Map type) { - Assertions.assertEquals("SCALAR", type.get("kind")); - Assertions.assertEquals("String", type.get("name")); - } - - private void confirmBoolean(Map type) { - Assertions.assertEquals("SCALAR", type.get("kind")); - Assertions.assertEquals("Boolean", type.get("name")); - } - - private Map confirmNonNull(Map type) { - Assertions.assertEquals("NON_NULL", type.get("kind")); - var toReturn = (Map) type.get("ofType"); - Assertions.assertNotNull(toReturn); - return toReturn; - } - - private Map confirmArray(Map type) { - Assertions.assertEquals("LIST", type.get("kind")); - var toReturn = (Map) type.get("ofType"); - Assertions.assertNotNull(toReturn); - return toReturn; - } - - public Map getField(String name) throws ReflectiveOperationException { - Map> response = execute("{" + - " __type(name: \"SimpleType\") {" + - " name" + - " fields {" + - " name" + - " type {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " ofType {" + - " name" + - " kind" + - " }" + - " }" + - " }" + - " }" + - " }" + - " }" + - "} ").getData(); - var type = response.get("__type"); - Assertions.assertEquals("SimpleType", type.get("name")); - - List> fields = (List>) type.get("fields"); - var field = fields.stream().filter(map -> map.get("name").equals(name)).findAny().get(); - Assertions.assertEquals(name, field.get("name")); - return (Map) field.get("type"); - } - - @Test - public void testQuery() throws ReflectiveOperationException { - Map> response = execute("query {simpleType{" + - "name " + - "deleted " + - "alive " + - "parts " + - "gappyParts " + - "optionalParts " + - "optionalGappyParts " + - "nameFuture " + - "deletedFuture " + - "aliveFuture " + - "partsFuture " + - "gappyPartsFuture " + - "optionalPartsFuture " + - "optionalGappyPartsFuture " + - "}} ").getData(); - - var simpleType = response.get("simpleType"); - assertEquals("green", simpleType.get("name")); - assertEquals(false, simpleType.get("deleted")); - assertEquals(null, simpleType.get("alive")); - assertEquals(Arrays.asList("green", "eggs"), simpleType.get("parts")); - assertEquals(Arrays.asList(null, "eggs"), simpleType.get("gappyParts")); - assertEquals(null, simpleType.get("optionalParts")); - assertEquals(Arrays.asList(), simpleType.get("optionalGappyParts")); - - assertEquals("green", simpleType.get("nameFuture")); - assertEquals(false, simpleType.get("deletedFuture")); - assertEquals(false, simpleType.get("aliveFuture")); - assertEquals(Arrays.asList(), simpleType.get("partsFuture")); - assertEquals(Arrays.asList(), simpleType.get("gappyPartsFuture")); - assertEquals(Arrays.asList(), simpleType.get("optionalPartsFuture")); - assertEquals(null, simpleType.get("optionalGappyPartsFuture")); - } - - - private ExecutionResult execute(String query) throws ReflectiveOperationException { - var schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type")).build(); - ExecutionResult result = schema.execute(query); - if(!result.getErrors().isEmpty()) { - throw new RuntimeException(result.getErrors().toString()); //TODO:cleanup - } - return result; - } - -} +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import graphql.ExecutionResult; +import graphql.GraphQL; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TypeParsingTest { + + @Test + public void findTypes() throws ReflectiveOperationException { + Map>>> response = execute("{__schema {types {name}}} ").getData(); + var types = response.get("__schema").get("types"); + var count = types.stream().filter(map -> map.get("name").equals("SimpleType")).count(); + Assertions.assertEquals(1, count); + } + + @Test + public void testName() throws ReflectiveOperationException { + var name = getField("name"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void testDeleted() throws ReflectiveOperationException { + var name = getField("deleted"); + var nonNull = confirmNonNull(name); + confirmBoolean(nonNull); + } + + @Test + public void testAlive() throws ReflectiveOperationException { + var name = getField("alive"); + confirmBoolean(name); + } + + @Test + public void testParts() throws ReflectiveOperationException { + var type = getField("parts"); + type = confirmNonNull(type); + type = confirmArray(type); + type = confirmNonNull(type); + confirmString(type); + } + + @Test + public void testGappyParts() throws ReflectiveOperationException { + var type = getField("gappyParts"); + type = confirmNonNull(type); + type = confirmArray(type); + confirmString(type); + } + + @Test + public void testOptioanlParts() throws ReflectiveOperationException { + var type = getField("optionalParts"); + type = confirmArray(type); + type = confirmNonNull(type); + confirmString(type); + } + + @Test + public void testOptioanlGappyParts() throws ReflectiveOperationException { + var type = getField("optionalGappyParts"); + type = confirmArray(type); + confirmString(type); + } + + @Test + public void testNameFuture() throws ReflectiveOperationException { + var name = getField("nameFuture"); + var nonNull = confirmNonNull(name); + confirmString(nonNull); + } + + @Test + public void isDeletedFuture() throws ReflectiveOperationException { + var name = getField("deletedFuture"); + var nonNull = confirmNonNull(name); + confirmBoolean(nonNull); + } + + @Test + public void testAliveFuture() throws ReflectiveOperationException { + var name = getField("aliveFuture"); + confirmBoolean(name); + } + + @Test + public void testPartsFuture() throws ReflectiveOperationException { + var type = getField("partsFuture"); + type = confirmNonNull(type); + type = confirmArray(type); + type = confirmNonNull(type); + confirmString(type); + } + + @Test + public void testGappyPartsFuture() throws ReflectiveOperationException { + var type = getField("gappyPartsFuture"); + type = confirmNonNull(type); + type = confirmArray(type); + confirmString(type); + } + + @Test + public void testOptionalPartsFuture() throws ReflectiveOperationException { + var type = getField("optionalPartsFuture"); + type = confirmArray(type); + type = confirmNonNull(type); + confirmString(type); + } + + @Test + public void testOptionalGappyPartsFuture() throws ReflectiveOperationException { + var type = getField("optionalGappyPartsFuture"); + type = confirmArray(type); + confirmString(type); + } + + private void confirmString(Map type) { + Assertions.assertEquals("SCALAR", type.get("kind")); + Assertions.assertEquals("String", type.get("name")); + } + + private void confirmBoolean(Map type) { + Assertions.assertEquals("SCALAR", type.get("kind")); + Assertions.assertEquals("Boolean", type.get("name")); + } + + private Map confirmNonNull(Map type) { + Assertions.assertEquals("NON_NULL", type.get("kind")); + var toReturn = (Map) type.get("ofType"); + Assertions.assertNotNull(toReturn); + return toReturn; + } + + private Map confirmArray(Map type) { + Assertions.assertEquals("LIST", type.get("kind")); + var toReturn = (Map) type.get("ofType"); + Assertions.assertNotNull(toReturn); + return toReturn; + } + + public Map getField(String name) throws ReflectiveOperationException { + Map> response = execute( + "{" + + " __type(name: \"SimpleType\") {" + + " name" + + " fields {" + + " name" + + " type {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " ofType {" + + " name" + + " kind" + + " }" + + " }" + + " }" + + " }" + + " }" + + " }" + + "} " + ) + .getData(); + var type = response.get("__type"); + Assertions.assertEquals("SimpleType", type.get("name")); + + List> fields = (List>) type.get("fields"); + var field = fields.stream().filter(map -> map.get("name").equals(name)).findAny().get(); + Assertions.assertEquals(name, field.get("name")); + return (Map) field.get("type"); + } + + @Test + public void testQuery() throws ReflectiveOperationException { + Map> response = execute( + "query {simpleType{" + + "name " + + "deleted " + + "alive " + + "parts " + + "gappyParts " + + "optionalParts " + + "optionalGappyParts " + + "nameFuture " + + "deletedFuture " + + "aliveFuture " + + "partsFuture " + + "gappyPartsFuture " + + "optionalPartsFuture " + + "optionalGappyPartsFuture " + + "}} " + ) + .getData(); + + var simpleType = response.get("simpleType"); + assertEquals("green", simpleType.get("name")); + assertEquals(false, simpleType.get("deleted")); + assertEquals(null, simpleType.get("alive")); + assertEquals(Arrays.asList("green", "eggs"), simpleType.get("parts")); + assertEquals(Arrays.asList(null, "eggs"), simpleType.get("gappyParts")); + assertEquals(null, simpleType.get("optionalParts")); + assertEquals(Arrays.asList(), simpleType.get("optionalGappyParts")); + + assertEquals("green", simpleType.get("nameFuture")); + assertEquals(false, simpleType.get("deletedFuture")); + assertEquals(false, simpleType.get("aliveFuture")); + assertEquals(Arrays.asList(), simpleType.get("partsFuture")); + assertEquals(Arrays.asList(), simpleType.get("gappyPartsFuture")); + assertEquals(Arrays.asList(), simpleType.get("optionalPartsFuture")); + assertEquals(null, simpleType.get("optionalGappyPartsFuture")); + } + + private ExecutionResult execute(String query) throws ReflectiveOperationException { + var schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type")).build(); + ExecutionResult result = schema.execute(query); + if (!result.getErrors().isEmpty()) { + throw new RuntimeException(result.getErrors().toString()); //TODO:cleanup + } + return result; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/UnionTest.java b/src/test/java/com/fleetpin/graphql/builder/UnionTest.java new file mode 100644 index 0000000..58cec93 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/UnionTest.java @@ -0,0 +1,85 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import graphql.ExecutionResult; +import graphql.GraphQL; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class UnionTest { + + @Test + public void testUnion() throws ReflectiveOperationException { + Map>> response = execute( + "query {union{" + + " __typename" + + " ... on SimpleType {" + + " name" + + " }" + + " ... on UnionType {" + + " type {" + + " __typename" + + " ... on SimpleType {" + + " name" + + " }" + + " }" + + " }" + + "}} " + ) + .getData(); + var union = response.get("union"); + assertEquals("green", union.get(0).get("name")); + Map simple = (Map) union.get(1).get("type"); + assertEquals("green", simple.get("name")); + } + + @Test + public void testUnionFailure() throws ReflectiveOperationException { + var error = assertThrows( + RuntimeException.class, + () -> + execute( + "query {unionFailure{" + + " __typename" + + " ... on SimpleType {" + + " name" + + " }" + + " ... on UnionType {" + + " type {" + + " __typename" + + " ... on SimpleType {" + + " name" + + " }" + + " }" + + " }" + + "}} " + ) + ); + assertEquals("Union Union_SimpleType_UnionType Does not support type Boolean", error.getMessage()); + } + + private ExecutionResult execute(String query) throws ReflectiveOperationException { + var schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type")).build(); + ExecutionResult result = schema.execute(query); + if (!result.getErrors().isEmpty()) { + throw new RuntimeException(result.getErrors().toString()); //TODO:cleanup + } + return result; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/authorizer/Cat.java b/src/test/java/com/fleetpin/graphql/builder/authorizer/Cat.java new file mode 100644 index 0000000..e228c56 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/authorizer/Cat.java @@ -0,0 +1,36 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.authorizer; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.Query; + +@Entity +public class Cat { + + public boolean isCalico() { + return true; + } + + public int getAge() { + return 3; + } + + public boolean getFur() { + return true; + } + + @Query + public static Cat getCat(String name) { + return new Cat(); + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/authorizer/CatAuthorizer.java b/src/test/java/com/fleetpin/graphql/builder/authorizer/CatAuthorizer.java new file mode 100644 index 0000000..bc0fe8f --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/authorizer/CatAuthorizer.java @@ -0,0 +1,21 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.authorizer; + +import com.fleetpin.graphql.builder.Authorizer; + +public class CatAuthorizer implements Authorizer { + + public boolean allow(String name) { + return "socks".equals(name); + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/context/GraphContext.java b/src/test/java/com/fleetpin/graphql/builder/context/GraphContext.java new file mode 100644 index 0000000..22101ac --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/context/GraphContext.java @@ -0,0 +1,29 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.context; + +import com.fleetpin.graphql.builder.annotations.Context; + +@Context +public class GraphContext { + + private final String something; + + public GraphContext(String something) { + super(); + this.something = something; + } + + public String getSomething() { + return something; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/context/Queries.java b/src/test/java/com/fleetpin/graphql/builder/context/Queries.java new file mode 100644 index 0000000..f4aefff --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/context/Queries.java @@ -0,0 +1,50 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.context; + +import com.fleetpin.graphql.builder.annotations.Context; +import com.fleetpin.graphql.builder.annotations.Query; +import graphql.GraphQLContext; +import graphql.schema.DataFetchingEnvironment; + +public class Queries { + + @Query + public static boolean entireContext(GraphQLContext context) { + return context != null; + } + + @Query + public static boolean env(DataFetchingEnvironment context) { + return context != null; + } + + @Query + public static boolean deprecatedContext(GraphContext context) { + return context != null; + } + + @Query + public static boolean namedContext(GraphContext named) { + return named != null; + } + + @Query + public static boolean namedParemeterContext(@Context String context) { + return context != null; + } + + @Query + public static boolean missingContext(GraphContext notPresent) { + return false; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/generics/Animal.java b/src/test/java/com/fleetpin/graphql/builder/generics/Animal.java index 4507700..8969d94 100644 --- a/src/test/java/com/fleetpin/graphql/builder/generics/Animal.java +++ b/src/test/java/com/fleetpin/graphql/builder/generics/Animal.java @@ -1,63 +1,73 @@ -package com.fleetpin.graphql.builder.generics; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.Mutation; -import com.fleetpin.graphql.builder.annotations.Query; - -@Entity -public abstract class Animal{ - - private final T fur; - - Animal(T fur) { - this.fur = fur; - } - - public String getName() { - return "name"; - } - - public T getFur() { - return fur; - } - - public List getFurs() { - return Arrays.asList(fur); - } - - @Query - public static List> animals() { - return Arrays.asList(new Cat(), new Dog()); - } - - @Mutation - public static MutationResponse makeCat() { - return new GenericMutationResponse<>(Optional.of(new Cat())); - } - - - @Entity - public static abstract class MutationResponse { - private Optional> item; - - - public MutationResponse(Optional> item) { - this.item = item; - } - - public Optional> getItem() { - return item; - } - - } - @Entity - public static class GenericMutationResponse extends MutationResponse{ - public GenericMutationResponse(Optional> item) { - super(item); - } - } -} \ No newline at end of file +/* + * Licensed 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 com.fleetpin.graphql.builder.generics; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.Mutation; +import com.fleetpin.graphql.builder.annotations.Query; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +@Entity +public abstract class Animal { + + private final T fur; + + Animal(T fur) { + this.fur = fur; + } + + public String getName() { + return "name"; + } + + public T getFur() { + return fur; + } + + public List getFurs() { + return Arrays.asList(fur); + } + + @Query + public static List> animals() { + return Arrays.asList(new Cat(), new Dog()); + } + + @Mutation + public static MutationResponse makeCat() { + return new GenericMutationResponse<>(Optional.of(new Cat())); + } + + @Entity + public abstract static class MutationResponse { + + private Optional> item; + + public MutationResponse(Optional> item) { + this.item = item; + } + + public Optional> getItem() { + return item; + } + } + + @Entity + public static class GenericMutationResponse extends MutationResponse { + + public GenericMutationResponse(Optional> item) { + super(item); + } + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/generics/Cat.java b/src/test/java/com/fleetpin/graphql/builder/generics/Cat.java index a683d93..1a67ebd 100644 --- a/src/test/java/com/fleetpin/graphql/builder/generics/Cat.java +++ b/src/test/java/com/fleetpin/graphql/builder/generics/Cat.java @@ -1,18 +1,28 @@ -package com.fleetpin.graphql.builder.generics; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.Mutation; - -@Entity -public class Cat extends CatFamily{ - - public Cat() { - super(new CatFur()); - } - - @Mutation - public static Cat getCat() { - return null; - } - -} +/* + * Licensed 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 com.fleetpin.graphql.builder.generics; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.Mutation; + +@Entity +public class Cat extends CatFamily { + + public Cat() { + super(new CatFur()); + } + + @Mutation + public static Cat getCat() { + return null; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/generics/CatFamily.java b/src/test/java/com/fleetpin/graphql/builder/generics/CatFamily.java index 5db2dfc..73a1d49 100644 --- a/src/test/java/com/fleetpin/graphql/builder/generics/CatFamily.java +++ b/src/test/java/com/fleetpin/graphql/builder/generics/CatFamily.java @@ -1,12 +1,22 @@ -package com.fleetpin.graphql.builder.generics; - -import com.fleetpin.graphql.builder.annotations.Entity; - -@Entity -public abstract class CatFamily extends Animal{ - - CatFamily(R fur) { - super(fur); - } - -} +/* + * Licensed 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 com.fleetpin.graphql.builder.generics; + +import com.fleetpin.graphql.builder.annotations.Entity; + +@Entity +public abstract class CatFamily extends Animal { + + CatFamily(R fur) { + super(fur); + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/generics/CatFamilyFur.java b/src/test/java/com/fleetpin/graphql/builder/generics/CatFamilyFur.java index 7b77855..07a9ea4 100644 --- a/src/test/java/com/fleetpin/graphql/builder/generics/CatFamilyFur.java +++ b/src/test/java/com/fleetpin/graphql/builder/generics/CatFamilyFur.java @@ -1,12 +1,22 @@ -package com.fleetpin.graphql.builder.generics; - -import com.fleetpin.graphql.builder.annotations.Entity; - -@Entity -public abstract class CatFamilyFur extends Fur{ - - public boolean isLong() { - return true; - } - -} +/* + * Licensed 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 com.fleetpin.graphql.builder.generics; + +import com.fleetpin.graphql.builder.annotations.Entity; + +@Entity +public abstract class CatFamilyFur extends Fur { + + public boolean isLong() { + return true; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/generics/CatFur.java b/src/test/java/com/fleetpin/graphql/builder/generics/CatFur.java index 12fcc88..7a0730c 100644 --- a/src/test/java/com/fleetpin/graphql/builder/generics/CatFur.java +++ b/src/test/java/com/fleetpin/graphql/builder/generics/CatFur.java @@ -1,11 +1,22 @@ -package com.fleetpin.graphql.builder.generics; - -import com.fleetpin.graphql.builder.annotations.Entity; - -@Entity -public class CatFur extends CatFamilyFur { - public boolean isCalico() { - return true; - } - -} +/* + * Licensed 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 com.fleetpin.graphql.builder.generics; + +import com.fleetpin.graphql.builder.annotations.Entity; + +@Entity +public class CatFur extends CatFamilyFur { + + public boolean isCalico() { + return true; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/generics/Dog.java b/src/test/java/com/fleetpin/graphql/builder/generics/Dog.java index 16228d5..3e00a5c 100644 --- a/src/test/java/com/fleetpin/graphql/builder/generics/Dog.java +++ b/src/test/java/com/fleetpin/graphql/builder/generics/Dog.java @@ -1,21 +1,32 @@ -package com.fleetpin.graphql.builder.generics; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.Mutation; - -@Entity -public class Dog extends Animal { - - public Dog() { - super(new DogFur()); - } - - public int getAge() { - return 6; - } - - @Mutation - public static Dog getDog() { - return null; - } -} +/* + * Licensed 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 com.fleetpin.graphql.builder.generics; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.Mutation; + +@Entity +public class Dog extends Animal { + + public Dog() { + super(new DogFur()); + } + + public int getAge() { + return 6; + } + + @Mutation + public static Dog getDog() { + return null; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/generics/DogFur.java b/src/test/java/com/fleetpin/graphql/builder/generics/DogFur.java index 487311b..c91fb96 100644 --- a/src/test/java/com/fleetpin/graphql/builder/generics/DogFur.java +++ b/src/test/java/com/fleetpin/graphql/builder/generics/DogFur.java @@ -1,15 +1,26 @@ -package com.fleetpin.graphql.builder.generics; - -import com.fleetpin.graphql.builder.annotations.Entity; - -@Entity -public class DogFur extends Fur { - public boolean isShaggy() { - return true; - } - - public String getLong() { - return "very"; - } - -} +/* + * Licensed 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 com.fleetpin.graphql.builder.generics; + +import com.fleetpin.graphql.builder.annotations.Entity; + +@Entity +public class DogFur extends Fur { + + public boolean isShaggy() { + return true; + } + + public String getLong() { + return "very"; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/generics/Fur.java b/src/test/java/com/fleetpin/graphql/builder/generics/Fur.java index 2a95df5..590e14d 100644 --- a/src/test/java/com/fleetpin/graphql/builder/generics/Fur.java +++ b/src/test/java/com/fleetpin/graphql/builder/generics/Fur.java @@ -1,11 +1,22 @@ -package com.fleetpin.graphql.builder.generics; - -import com.fleetpin.graphql.builder.annotations.Entity; - -@Entity -public abstract class Fur { - - public int getLength() { - return 4; - } -} +/* + * Licensed 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 com.fleetpin.graphql.builder.generics; + +import com.fleetpin.graphql.builder.annotations.Entity; + +@Entity +public abstract class Fur { + + public int getLength() { + return 4; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/inputgenerics/Animal.java b/src/test/java/com/fleetpin/graphql/builder/inputgenerics/Animal.java index ee0bfac..e0e29ee 100644 --- a/src/test/java/com/fleetpin/graphql/builder/inputgenerics/Animal.java +++ b/src/test/java/com/fleetpin/graphql/builder/inputgenerics/Animal.java @@ -1,19 +1,29 @@ -package com.fleetpin.graphql.builder.inputgenerics; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.SchemaOption; - -@Entity -public abstract class Animal { - - private String name; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - -} +/* + * Licensed 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 com.fleetpin.graphql.builder.inputgenerics; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.SchemaOption; + +@Entity +public abstract class Animal { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/inputgenerics/AnimalInput.java b/src/test/java/com/fleetpin/graphql/builder/inputgenerics/AnimalInput.java index 88621e5..6f736b9 100644 --- a/src/test/java/com/fleetpin/graphql/builder/inputgenerics/AnimalInput.java +++ b/src/test/java/com/fleetpin/graphql/builder/inputgenerics/AnimalInput.java @@ -1,27 +1,38 @@ -package com.fleetpin.graphql.builder.inputgenerics; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.SchemaOption; - -@Entity(SchemaOption.INPUT) -public class AnimalInput { - String id; - T animal; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public T getAnimal() { - return animal; - } - - public void setAnimal(T animal) { - this.animal = animal; - } - -} \ No newline at end of file +/* + * Licensed 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 com.fleetpin.graphql.builder.inputgenerics; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.SchemaOption; + +@Entity(SchemaOption.INPUT) +public class AnimalInput { + + String id; + T animal; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public T getAnimal() { + return animal; + } + + public void setAnimal(T animal) { + this.animal = animal; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/inputgenerics/AnimalOuterWrapper.java b/src/test/java/com/fleetpin/graphql/builder/inputgenerics/AnimalOuterWrapper.java index 338a49a..5dfa8aa 100644 --- a/src/test/java/com/fleetpin/graphql/builder/inputgenerics/AnimalOuterWrapper.java +++ b/src/test/java/com/fleetpin/graphql/builder/inputgenerics/AnimalOuterWrapper.java @@ -1,27 +1,38 @@ -package com.fleetpin.graphql.builder.inputgenerics; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.SchemaOption; - -@Entity(SchemaOption.BOTH) -public class AnimalOuterWrapper { - String id; - AnimalWrapper animal; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public AnimalWrapper getAnimal() { - return animal; - } - - public void setAnimal(AnimalWrapper animal) { - this.animal = animal; - } - -} \ No newline at end of file +/* + * Licensed 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 com.fleetpin.graphql.builder.inputgenerics; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.SchemaOption; + +@Entity(SchemaOption.BOTH) +public class AnimalOuterWrapper { + + String id; + AnimalWrapper animal; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public AnimalWrapper getAnimal() { + return animal; + } + + public void setAnimal(AnimalWrapper animal) { + this.animal = animal; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/inputgenerics/AnimalWrapper.java b/src/test/java/com/fleetpin/graphql/builder/inputgenerics/AnimalWrapper.java index debc015..19674dc 100644 --- a/src/test/java/com/fleetpin/graphql/builder/inputgenerics/AnimalWrapper.java +++ b/src/test/java/com/fleetpin/graphql/builder/inputgenerics/AnimalWrapper.java @@ -1,26 +1,37 @@ -package com.fleetpin.graphql.builder.inputgenerics; - -import com.fleetpin.graphql.builder.annotations.Entity; - -@Entity -public class AnimalWrapper { - String id; - T animal; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public T getAnimal() { - return animal; - } - - public void setAnimal(T animal) { - this.animal = animal; - } - -} \ No newline at end of file +/* + * Licensed 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 com.fleetpin.graphql.builder.inputgenerics; + +import com.fleetpin.graphql.builder.annotations.Entity; + +@Entity +public class AnimalWrapper { + + String id; + T animal; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public T getAnimal() { + return animal; + } + + public void setAnimal(T animal) { + this.animal = animal; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/inputgenerics/Cat.java b/src/test/java/com/fleetpin/graphql/builder/inputgenerics/Cat.java index ea2ab02..e0d45fe 100644 --- a/src/test/java/com/fleetpin/graphql/builder/inputgenerics/Cat.java +++ b/src/test/java/com/fleetpin/graphql/builder/inputgenerics/Cat.java @@ -1,47 +1,54 @@ -package com.fleetpin.graphql.builder.inputgenerics; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.Mutation; -import com.fleetpin.graphql.builder.annotations.Query; -import com.fleetpin.graphql.builder.annotations.SchemaOption; - -@Entity(SchemaOption.BOTH) -public class Cat extends Animal { - - private boolean fur; - - - public void setFur(boolean fur) { - this.fur = fur; - } - - public boolean isFur() { - return fur; - } - - @Query - public static String getCat() { - return "cat"; - } - - @Mutation - public static String addCat(CatAnimalInput input) { - return input.getAnimal().getName(); - } - - - @Mutation - public static boolean addCatGenerics(AnimalInput input) { - return input.getAnimal().isFur(); - } - - - @Mutation - public static AnimalOuterWrapper addNestedGenerics(AnimalInput input) { - var wrapper = new AnimalOuterWrapper(); - wrapper.setAnimal(new AnimalWrapper()); - wrapper.getAnimal().setAnimal(input.getAnimal()); - return wrapper; - } - -} +/* + * Licensed 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 com.fleetpin.graphql.builder.inputgenerics; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.Mutation; +import com.fleetpin.graphql.builder.annotations.Query; +import com.fleetpin.graphql.builder.annotations.SchemaOption; + +@Entity(SchemaOption.BOTH) +public class Cat extends Animal { + + private boolean fur; + + public void setFur(boolean fur) { + this.fur = fur; + } + + public boolean isFur() { + return fur; + } + + @Query + public static String getCat() { + return "cat"; + } + + @Mutation + public static String addCat(CatAnimalInput input) { + return input.getAnimal().getName(); + } + + @Mutation + public static boolean addCatGenerics(AnimalInput input) { + return input.getAnimal().isFur(); + } + + @Mutation + public static AnimalOuterWrapper addNestedGenerics(AnimalInput input) { + var wrapper = new AnimalOuterWrapper(); + wrapper.setAnimal(new AnimalWrapper()); + wrapper.getAnimal().setAnimal(input.getAnimal()); + return wrapper; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/inputgenerics/CatAnimalInput.java b/src/test/java/com/fleetpin/graphql/builder/inputgenerics/CatAnimalInput.java index d8ae618..560ef23 100644 --- a/src/test/java/com/fleetpin/graphql/builder/inputgenerics/CatAnimalInput.java +++ b/src/test/java/com/fleetpin/graphql/builder/inputgenerics/CatAnimalInput.java @@ -1,9 +1,18 @@ -package com.fleetpin.graphql.builder.inputgenerics; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.SchemaOption; - -@Entity(SchemaOption.INPUT) -public class CatAnimalInput extends AnimalInput { - -} \ No newline at end of file +/* + * Licensed 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 com.fleetpin.graphql.builder.inputgenerics; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.SchemaOption; + +@Entity(SchemaOption.INPUT) +public class CatAnimalInput extends AnimalInput {} diff --git a/src/test/java/com/fleetpin/graphql/builder/inputgenericsRecords/Change.java b/src/test/java/com/fleetpin/graphql/builder/inputgenericsRecords/Change.java new file mode 100644 index 0000000..f2f7533 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/inputgenericsRecords/Change.java @@ -0,0 +1,26 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.inputgenericsRecords; + +import com.fleetpin.graphql.builder.annotations.Query; +import jakarta.annotation.Nullable; +import java.util.List; + +public record Change(@Nullable Wrapper> name, @Nullable Wrapper> age, @Nullable Wrapper description) { + @Query + public static String doChange(Change input) { + if (input.name == null) { + return "empty"; + } + return input.name.wrap().getFirst() + input.age.wrap() + input.description.wrap(); + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/inputgenericsRecords/Wrapper.java b/src/test/java/com/fleetpin/graphql/builder/inputgenericsRecords/Wrapper.java new file mode 100644 index 0000000..74365b8 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/inputgenericsRecords/Wrapper.java @@ -0,0 +1,16 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.inputgenericsRecords; + +import jakarta.annotation.Nullable; + +public record Wrapper(@Nullable T wrap) {} diff --git a/src/test/java/com/fleetpin/graphql/builder/methodArgs/Queries.java b/src/test/java/com/fleetpin/graphql/builder/methodArgs/Queries.java new file mode 100644 index 0000000..0d3acc5 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/methodArgs/Queries.java @@ -0,0 +1,46 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.methodArgs; + +import com.fleetpin.graphql.builder.annotations.Query; + +public class Queries { + + @Query + public static InputType passthrough(InputType type) { + return type; + } + + static final class InputType { + + private final String name; + private final int age; + + private InputType(String name, int age) { + super(); + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + public int getHeight(int height) { + return height; + } + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/methodArgsTest.java b/src/test/java/com/fleetpin/graphql/builder/methodArgsTest.java new file mode 100644 index 0000000..69ba736 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/methodArgsTest.java @@ -0,0 +1,51 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.introspection.IntrospectionWithDirectivesSupport; +import java.util.Map; +import org.junit.jupiter.api.Test; + +//does not test all of records as needs newer version of java. But Classes that look like records +public class methodArgsTest { + + @Test + public void testEntireContext() { + var type = Map.of("name", "foo", "age", 4); + Map> response = execute( + "query passthrough($type: InputTypeInput!){passthrough(type: $type) {name age height(height: 12)}} ", + Map.of("type", type) + ) + .getData(); + var passthrough = response.get("passthrough"); + + assertEquals(Map.of("name", "foo", "age", 4, "height", 12), passthrough); + } + + private ExecutionResult execute(String query, Map variables) { + GraphQL schema = GraphQL + .newGraphQL(new IntrospectionWithDirectivesSupport().apply(SchemaBuilder.build("com.fleetpin.graphql.builder.methodArgs"))) + .build(); + var input = ExecutionInput.newExecutionInput(); + input.query(query); + if (variables != null) { + input.variables(variables); + } + ExecutionResult result = schema.execute(input); + return result; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/parameter/Parameter.java b/src/test/java/com/fleetpin/graphql/builder/parameter/Parameter.java index 9ff0deb..1480fad 100644 --- a/src/test/java/com/fleetpin/graphql/builder/parameter/Parameter.java +++ b/src/test/java/com/fleetpin/graphql/builder/parameter/Parameter.java @@ -9,17 +9,21 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.parameter; -import java.util.List; -import java.util.Optional; - +import com.fleetpin.graphql.builder.annotations.Entity; import com.fleetpin.graphql.builder.annotations.Id; import com.fleetpin.graphql.builder.annotations.Query; +import java.util.List; +import java.util.Optional; +@Entity public class Parameter { + public Optional getNullOptional() { + return null; + } + @Query public static String requiredString(String type) { return type; @@ -42,11 +46,21 @@ public static Optional optionalId(@Id Optional type) { return type; } + @Query + public static Parameter optionalIdNull() { + return new Parameter(); + } + @Query public static List requiredListString(List type) { return type; } + @Query + public static String[] requiredArrayString(String[] type) { + return type; + } + @Query public static Optional> optionalListString(Optional> type) { return type; @@ -85,20 +99,19 @@ public static List> requiredListOptionalId(@Id List>> optionalListOptionalId(@Id Optional>> type) { return type; } - + @Query public static String multipleArguments(String first, String second) { return first + ":" + second; } - + @Query public static String multipleArgumentsOptional(Optional first, Optional second) { return first.orElse("") + ":" + second.orElse(""); } - + @Query public static String multipleArgumentsMix(String first, Optional second) { return first + ":" + second.orElse(""); } - } diff --git a/src/test/java/com/fleetpin/graphql/builder/parameter/TypeInputParameter.java b/src/test/java/com/fleetpin/graphql/builder/parameter/TypeInputParameter.java index 70a20d2..81f6af8 100644 --- a/src/test/java/com/fleetpin/graphql/builder/parameter/TypeInputParameter.java +++ b/src/test/java/com/fleetpin/graphql/builder/parameter/TypeInputParameter.java @@ -9,20 +9,28 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.parameter; -import java.util.List; -import java.util.Optional; - import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.GraphQLDescription; import com.fleetpin.graphql.builder.annotations.Query; import com.fleetpin.graphql.builder.annotations.SchemaOption; +import java.util.List; +import java.util.Optional; public class TypeInputParameter { + @Entity + @GraphQLDescription("enum desc") + public enum AnimalType { + @GraphQLDescription("A cat") + CAT, + DOG, + } + @Entity(SchemaOption.BOTH) public static class InputTest { + private String value; public String getValue() { @@ -34,6 +42,11 @@ public void setValue(String value) { } } + @Query + public static AnimalType enumTest(AnimalType type) { + return type; + } + @Query public static InputTest requiredType(InputTest type) { type.getValue(); // makes sure is right type and not odd runtime passthrough @@ -69,10 +82,8 @@ public static List> requiredListOptionalType(List>> optionalListOptionalType( - Optional>> type) { + public static Optional>> optionalListOptionalType(Optional>> type) { type.map(tt -> tt.stream().map(t -> t.map(InputTest::getValue))); return type; } - } diff --git a/src/test/java/com/fleetpin/graphql/builder/publishRestrictions/Test.java b/src/test/java/com/fleetpin/graphql/builder/publishRestrictions/Test.java index 71c486e..ff0927a 100644 --- a/src/test/java/com/fleetpin/graphql/builder/publishRestrictions/Test.java +++ b/src/test/java/com/fleetpin/graphql/builder/publishRestrictions/Test.java @@ -1,24 +1,33 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.publishRestrictions; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; - -import org.reactivestreams.Publisher; - import com.fleetpin.graphql.builder.RestrictType; import com.fleetpin.graphql.builder.RestrictTypeFactory; import com.fleetpin.graphql.builder.annotations.Entity; import com.fleetpin.graphql.builder.annotations.Query; import com.fleetpin.graphql.builder.annotations.Restrict; import com.fleetpin.graphql.builder.annotations.Subscription; - import graphql.schema.DataFetchingEnvironment; import io.reactivex.rxjava3.core.Flowable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import org.reactivestreams.Publisher; @Entity @Restrict(Test.Restrictor.class) public class Test { + static Executor executor = CompletableFuture.delayedExecutor(100, TimeUnit.MILLISECONDS); private boolean value; @@ -29,12 +38,12 @@ public Test(boolean value) { public boolean isValue() { return value; } - + @Query public static String MustHaveAQuery() { return "String"; } - + @Subscription public static Publisher test() { return Flowable.just(new Test(false)).flatMap(f -> Flowable.fromCompletionStage(CompletableFuture.supplyAsync(() -> f, executor))); @@ -45,13 +54,11 @@ public static class Restrictor implements RestrictTypeFactory, RestrictTyp @Override public CompletableFuture> create(DataFetchingEnvironment context) { return CompletableFuture.supplyAsync(() -> this, executor); - } @Override public CompletableFuture allow(Test obj) { return CompletableFuture.supplyAsync(() -> false, executor); } - } -} \ No newline at end of file +} diff --git a/src/test/java/com/fleetpin/graphql/builder/record/Queries.java b/src/test/java/com/fleetpin/graphql/builder/record/Queries.java new file mode 100644 index 0000000..8c09be2 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/record/Queries.java @@ -0,0 +1,53 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.record; + +import com.fleetpin.graphql.builder.annotations.GraphQLDescription; +import com.fleetpin.graphql.builder.annotations.InnerNullable; +import com.fleetpin.graphql.builder.annotations.Query; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nullable; + +public class Queries { + + @Query + public static InputType passthrough(InputType type) { + return type; + } + + @Query + @Nullable + public static Boolean nullableTest(@Nullable Boolean type) { + return type; + } + + @Query + @Nullable + public static List nullableArrayTest(@Nullable List type) { + return type; + } + + @Query + @InnerNullable + public static List innerNullableArrayTest(@InnerNullable List type) { + return type; + } + + @Query + public static List> nullableInnerArrayTest(List> type) { + return type; + } + + @GraphQLDescription("record Type") + static final record InputType(@GraphQLDescription("the name") String name, int age, Optional weight) {} +} diff --git a/src/test/java/com/fleetpin/graphql/builder/rename/Queries.java b/src/test/java/com/fleetpin/graphql/builder/rename/Queries.java new file mode 100644 index 0000000..84be3fd --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/rename/Queries.java @@ -0,0 +1,47 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.rename; + +import com.fleetpin.graphql.builder.annotations.GraphQLName; +import com.fleetpin.graphql.builder.annotations.Query; + +public class Queries { + + @Query + @GraphQLName("passthroughClass") + public static ClassType passthroughClassWrong(@GraphQLName("type") ClassType typeWrong) { + return typeWrong; + } + + @Query + @GraphQLName("passthroughRecord") + public static RecordType passthroughRecordWrong(@GraphQLName("type") RecordType typeWrong) { + return typeWrong; + } + + public static record RecordType(@GraphQLName("name") String nameWrong) {} + + public static class ClassType { + + private String nameWrong; + + @GraphQLName("nameGet") + public String getNameWrong() { + return nameWrong; + } + + @GraphQLName("nameSet") + public void setNameWrong(String nameWrong) { + this.nameWrong = nameWrong; + } + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/restrictions/EntityRestrictions.java b/src/test/java/com/fleetpin/graphql/builder/restrictions/EntityRestrictions.java index 5292b49..09a95a1 100644 --- a/src/test/java/com/fleetpin/graphql/builder/restrictions/EntityRestrictions.java +++ b/src/test/java/com/fleetpin/graphql/builder/restrictions/EntityRestrictions.java @@ -1,12 +1,21 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.restrictions; -import java.util.concurrent.CompletableFuture; - import com.fleetpin.graphql.builder.RestrictType; import com.fleetpin.graphql.builder.RestrictTypeFactory; import com.fleetpin.graphql.builder.restrictions.parameter.RestrictedEntity; - import graphql.schema.DataFetchingEnvironment; +import java.util.concurrent.CompletableFuture; public class EntityRestrictions implements RestrictTypeFactory { @@ -14,7 +23,7 @@ public class EntityRestrictions implements RestrictTypeFactory public CompletableFuture> create(DataFetchingEnvironment context) { return CompletableFuture.completedFuture(new DatabaseRestrict()); } - + public static class DatabaseRestrict implements RestrictType { @Override @@ -22,4 +31,4 @@ public CompletableFuture allow(RestrictedEntity obj) { return CompletableFuture.completedFuture(obj.isAllowed()); } } -} \ No newline at end of file +} diff --git a/src/test/java/com/fleetpin/graphql/builder/restrictions/RestrictionTypesTest.java b/src/test/java/com/fleetpin/graphql/builder/restrictions/RestrictionTypesTest.java index a093fc0..62c766b 100644 --- a/src/test/java/com/fleetpin/graphql/builder/restrictions/RestrictionTypesTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/restrictions/RestrictionTypesTest.java @@ -1,106 +1,135 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.restrictions; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fleetpin.graphql.builder.SchemaBuilder; +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fleetpin.graphql.builder.SchemaBuilder; - -import graphql.ExecutionInput; -import graphql.ExecutionResult; -import graphql.GraphQL; - public class RestrictionTypesTest { + /* * Really basic tests, just to ensure restrictions work on different types. */ - + private static GraphQL schema; + @BeforeAll public static void init() throws ReflectiveOperationException { - schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.restrictions.parameter")).build(); + schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.restrictions.parameter")).build(); } - + private static String singleQueryGql = "query entityQuery( $allowed: Boolean! ) { single(allowed: $allowed) { __typename } }"; private static String singleOptionalQueryGql = "query entityQuery( $allowed: Boolean ) { singleOptional(allowed: $allowed) { __typename } }"; private static String listQueryGql = "query entityQuery( $allowed: [Boolean!]! ) { list(allowed: $allowed) { __typename } }"; private static String listOptionalQueryGql = "query entityQuery( $allowed: [Boolean!] ) { listOptional(allowed: $allowed) { __typename } }"; - - + private static final String LIST_QUERY_INHERITANCE_GPL = """ + query entityQuery( $allowed: [Boolean!]! ) { + listInheritance(allowed: $allowed) { + __typename + } + } + """; + @Test public void singleEntityQuery() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { Map variables = new HashMap<>(); variables.put("allowed", true); - Map> response = execute(singleQueryGql, variables).getData(); + Map> response = execute(singleQueryGql, variables).getData(); // No fancy checks. Just want to make ensure it executes without issue. Assertions.assertTrue(response.get("single").containsKey("__typename")); } - + @Test public void singleOptionalEntityQuery() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { Map variables = new HashMap<>(); - + // Allowed variables.put("allowed", true); - Map> responseAllowed = execute(singleOptionalQueryGql, variables).getData(); + Map> responseAllowed = execute(singleOptionalQueryGql, variables).getData(); Assertions.assertTrue(responseAllowed.get("singleOptional").containsKey("__typename")); - + // Not allowed - variables.put("allowed", false); + variables.put("allowed", false); Map responseDenied = execute(singleOptionalQueryGql, variables).getData(); Assertions.assertNull(responseDenied.get("singleOptional")); } - - + @Test public void listEntityQuery() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { Map variables = new HashMap<>(); - - variables.put("allowed", Arrays.asList(true,true,true)); + + variables.put("allowed", Arrays.asList(true, true, true)); Map> responseAllAllowed = execute(listQueryGql, variables).getData(); Assertions.assertEquals(3, responseAllAllowed.get("list").size()); - - variables.put("allowed", Arrays.asList(true,false,true)); + + variables.put("allowed", Arrays.asList(true, false, true)); Map> responseSomeAllowed = execute(listQueryGql, variables).getData(); Assertions.assertEquals(2, responseSomeAllowed.get("list").size()); - - variables.put("allowed", Arrays.asList(false,false,false)); + + variables.put("allowed", Arrays.asList(false, false, false)); Map> responseNoneAllowed = execute(listQueryGql, variables).getData(); Assertions.assertEquals(0, responseNoneAllowed.get("list").size()); } - - + + @Test + public void listEntityInheritanceQuery() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { + Map variables = new HashMap<>(); + + variables.put("allowed", Arrays.asList(true, true, true)); + Map> responseAllAllowed = execute(LIST_QUERY_INHERITANCE_GPL, variables).getData(); + Assertions.assertEquals(3, responseAllAllowed.get("listInheritance").size()); + + variables.put("allowed", Arrays.asList(true, false, true)); + Map> responseSomeAllowed = execute(LIST_QUERY_INHERITANCE_GPL, variables).getData(); + Assertions.assertEquals(2, responseSomeAllowed.get("listInheritance").size()); + + variables.put("allowed", Arrays.asList(false, false, false)); + Map> responseNoneAllowed = execute(LIST_QUERY_INHERITANCE_GPL, variables).getData(); + Assertions.assertEquals(0, responseNoneAllowed.get("listInheritance").size()); + } + @Test public void optionalListEntityQuery() throws ReflectiveOperationException, JsonMappingException, JsonProcessingException { Map variables = new HashMap<>(); - + // No list passed through Map> responseNoVariables = execute(listOptionalQueryGql, variables).getData(); Assertions.assertNull(responseNoVariables.get("listOptional")); - - variables.put("allowed", Arrays.asList(true,true,true)); + + variables.put("allowed", Arrays.asList(true, true, true)); Map> responseAllAllowed = execute(listOptionalQueryGql, variables).getData(); Assertions.assertEquals(3, responseAllAllowed.get("listOptional").size()); - - variables.put("allowed", Arrays.asList(true,false,true)); + + variables.put("allowed", Arrays.asList(true, false, true)); Map> responseSomeAllowed = execute(listOptionalQueryGql, variables).getData(); Assertions.assertEquals(2, responseSomeAllowed.get("listOptional").size()); - - variables.put("allowed", Arrays.asList(false,false,false)); + + variables.put("allowed", Arrays.asList(false, false, false)); Map> responseNoneAllowed = execute(listOptionalQueryGql, variables).getData(); Assertions.assertEquals(0, responseNoneAllowed.get("listOptional").size()); } - - private static ExecutionResult execute(String query, Map variables) - throws JsonMappingException, JsonProcessingException { - + + private static ExecutionResult execute(String query, Map variables) throws JsonMappingException, JsonProcessingException { var input = ExecutionInput.newExecutionInput().query(query).variables(variables).build(); ExecutionResult result = schema.execute(input); if (!result.getErrors().isEmpty()) { @@ -108,5 +137,4 @@ private static ExecutionResult execute(String query, Map variabl } return result; } - } diff --git a/src/test/java/com/fleetpin/graphql/builder/restrictions/parameter/RestrictedEntity.java b/src/test/java/com/fleetpin/graphql/builder/restrictions/parameter/RestrictedEntity.java index 1f1ee7d..3d14875 100644 --- a/src/test/java/com/fleetpin/graphql/builder/restrictions/parameter/RestrictedEntity.java +++ b/src/test/java/com/fleetpin/graphql/builder/restrictions/parameter/RestrictedEntity.java @@ -9,17 +9,15 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package com.fleetpin.graphql.builder.restrictions.parameter; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - import com.fleetpin.graphql.builder.annotations.Entity; import com.fleetpin.graphql.builder.annotations.Query; import com.fleetpin.graphql.builder.annotations.Restrict; import com.fleetpin.graphql.builder.restrictions.EntityRestrictions; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; @Entity @Restrict(EntityRestrictions.class) @@ -35,14 +33,13 @@ public void setAllowed(boolean allowed) { this.allowed = allowed; } - @Query public static RestrictedEntity single(Boolean allowed) { RestrictedEntity entity = new RestrictedEntity(); entity.setAllowed(allowed); return entity; } - + @Query public static Optional singleOptional(Optional allowed) { if (allowed.isEmpty()) return Optional.empty(); @@ -50,25 +47,33 @@ public static Optional singleOptional(Optional allowe entity.setAllowed(allowed.get()); return Optional.of(entity); } - + @Query public static List list(List allowed) { - return allowed.stream().map(isAllowed -> { - RestrictedEntity entity = new RestrictedEntity(); - entity.setAllowed(isAllowed); - return entity; - }).collect(Collectors.toList()); + return allowed + .stream() + .map(isAllowed -> { + RestrictedEntity entity = new RestrictedEntity(); + entity.setAllowed(isAllowed); + return entity; + }) + .collect(Collectors.toList()); } - + @Query public static Optional> listOptional(Optional> allowed) { if (allowed.isEmpty()) return Optional.empty(); - - return Optional.of(allowed.get().stream().map(isAllowed -> { - RestrictedEntity entity = new RestrictedEntity(); - entity.setAllowed(isAllowed); - return entity; - }).collect(Collectors.toList())); + + return Optional.of( + allowed + .get() + .stream() + .map(isAllowed -> { + RestrictedEntity entity = new RestrictedEntity(); + entity.setAllowed(isAllowed); + return entity; + }) + .collect(Collectors.toList()) + ); } - } diff --git a/src/test/java/com/fleetpin/graphql/builder/restrictions/parameter/RestrictedEntityInheritance.java b/src/test/java/com/fleetpin/graphql/builder/restrictions/parameter/RestrictedEntityInheritance.java new file mode 100644 index 0000000..fa5b218 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/restrictions/parameter/RestrictedEntityInheritance.java @@ -0,0 +1,31 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.restrictions.parameter; + +import com.fleetpin.graphql.builder.annotations.Query; +import java.util.List; +import java.util.stream.Collectors; + +public class RestrictedEntityInheritance extends RestrictedEntityParent { + + @Query + public static List listInheritance(List allowed) { + return allowed + .stream() + .map(isAllowed -> { + RestrictedEntityInheritance entity = new RestrictedEntityInheritance(); + entity.setAllowed(isAllowed); + return entity; + }) + .collect(Collectors.toList()); + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/restrictions/parameter/RestrictedEntityParent.java b/src/test/java/com/fleetpin/graphql/builder/restrictions/parameter/RestrictedEntityParent.java new file mode 100644 index 0000000..e738927 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/restrictions/parameter/RestrictedEntityParent.java @@ -0,0 +1,48 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.restrictions.parameter; + +import com.fleetpin.graphql.builder.RestrictType; +import com.fleetpin.graphql.builder.RestrictTypeFactory; +import com.fleetpin.graphql.builder.annotations.Restrict; +import graphql.schema.DataFetchingEnvironment; +import java.util.concurrent.CompletableFuture; + +@Restrict(RestrictedEntityParent.EntityRestrictions.class) +public abstract class RestrictedEntityParent { + + private boolean allowed; + + public boolean isAllowed() { + return allowed; + } + + public void setAllowed(boolean allowed) { + this.allowed = allowed; + } + + public static class EntityRestrictions implements RestrictTypeFactory { + + @Override + public CompletableFuture> create(DataFetchingEnvironment context) { + return CompletableFuture.completedFuture(new DatabaseRestrict()); + } + + public static class DatabaseRestrict implements RestrictType { + + @Override + public CompletableFuture allow(RestrictedEntityParent obj) { + return CompletableFuture.completedFuture(obj.isAllowed()); + } + } + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/scalar/Capture.java b/src/test/java/com/fleetpin/graphql/builder/scalar/Capture.java new file mode 100644 index 0000000..94a1f68 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/scalar/Capture.java @@ -0,0 +1,26 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.scalar; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.fleetpin.graphql.builder.annotations.Directive; +import graphql.introspection.Introspection.DirectiveLocation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Directive(DirectiveLocation.OBJECT) +@Retention(RUNTIME) +@Target({ ElementType.TYPE }) +public @interface Capture { +} diff --git a/src/test/java/com/fleetpin/graphql/builder/scalar/CaptureType.java b/src/test/java/com/fleetpin/graphql/builder/scalar/CaptureType.java new file mode 100644 index 0000000..413d7b4 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/scalar/CaptureType.java @@ -0,0 +1,18 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.scalar; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.SchemaOption; + +@Entity(SchemaOption.INPUT) +public class CaptureType {} diff --git a/src/test/java/com/fleetpin/graphql/builder/scalar/Cat.java b/src/test/java/com/fleetpin/graphql/builder/scalar/Cat.java new file mode 100644 index 0000000..fcbdc9d --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/scalar/Cat.java @@ -0,0 +1,46 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.scalar; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.Query; + +@Entity +public class Cat { + + private final Fur fur; + + private long age; + + private Cat(Fur fur, long age) { + this.fur = fur; + this.age = age; + } + + public Fur getFur() { + return fur; + } + + public long getAge() { + return age; + } + + @Query + public static Cat getCat(Fur fur, Long age) { + return new Cat(fur, age); + } + + @Query + public static Shape getShape(Shape shape) { + return shape; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/scalar/Fur.java b/src/test/java/com/fleetpin/graphql/builder/scalar/Fur.java new file mode 100644 index 0000000..0c2f2a6 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/scalar/Fur.java @@ -0,0 +1,61 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.scalar; + +import com.fleetpin.graphql.builder.annotations.GraphQLDescription; +import com.fleetpin.graphql.builder.annotations.Scalar; +import graphql.schema.Coercing; +import graphql.schema.CoercingParseLiteralException; +import graphql.schema.CoercingParseValueException; +import graphql.schema.CoercingSerializeException; + +@Scalar(Fur.FurCoercing.class) +@GraphQLDescription("soft") +public class Fur { + + private String input; + + public Fur(String input) { + this.input = input; + } + + public String getInput() { + return input; + } + + public static class FurCoercing implements Coercing { + + @Override + public Fur serialize(Object dataFetcherResult) throws CoercingSerializeException { + return convertImpl(dataFetcherResult); + } + + @Override + public Fur parseValue(Object input) throws CoercingParseValueException { + return convertImpl(input); + } + + @Override + public Fur parseLiteral(Object input) throws CoercingParseLiteralException { + return convertImpl(input); + } + + private Fur convertImpl(Object input) { + if (input instanceof Fur) { + return (Fur) input; + } else if (input instanceof String) { + return new Fur((String) input); + } + throw new CoercingParseLiteralException(); + } + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/scalar/Shape.java b/src/test/java/com/fleetpin/graphql/builder/scalar/Shape.java new file mode 100644 index 0000000..fb73e84 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/scalar/Shape.java @@ -0,0 +1,60 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.scalar; + +import com.fleetpin.graphql.builder.annotations.Scalar; +import graphql.schema.Coercing; +import graphql.schema.CoercingParseLiteralException; +import graphql.schema.CoercingParseValueException; +import graphql.schema.CoercingSerializeException; + +@Scalar(Shape.ShapeCoercing.class) +@Capture +public class Shape { + + private String input; + + public Shape(String input) { + this.input = input; + } + + public String getInput() { + return input; + } + + public static class ShapeCoercing implements Coercing { + + @Override + public Shape serialize(Object dataFetcherResult) throws CoercingSerializeException { + return convertImpl(dataFetcherResult); + } + + @Override + public Shape parseValue(Object input) throws CoercingParseValueException { + return convertImpl(input); + } + + @Override + public Shape parseLiteral(Object input) throws CoercingParseLiteralException { + return convertImpl(input); + } + + private Shape convertImpl(Object input) { + if (input instanceof Shape) { + return (Shape) input; + } else if (input instanceof String) { + return new Shape((String) input); + } + throw new CoercingParseLiteralException(); + } + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/type/Circular.java b/src/test/java/com/fleetpin/graphql/builder/type/Circular.java index 6621914..1f0bd46 100644 --- a/src/test/java/com/fleetpin/graphql/builder/type/Circular.java +++ b/src/test/java/com/fleetpin/graphql/builder/type/Circular.java @@ -1,19 +1,28 @@ - -package com.fleetpin.graphql.builder.type; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.Mutation; - -@Entity -public class Circular { - - public Circular getCircular() { - return null; - } - - - @Mutation - public static Circular circularTest() { - return new Circular(); - } -} +/* + * Licensed 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 com.fleetpin.graphql.builder.type; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.Mutation; + +@Entity +public class Circular { + + public Circular getCircular() { + return null; + } + + @Mutation + public static Circular circularTest() { + return new Circular(); + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/type/DeprecatedObject.java b/src/test/java/com/fleetpin/graphql/builder/type/DeprecatedObject.java index cb53174..c8e3800 100644 --- a/src/test/java/com/fleetpin/graphql/builder/type/DeprecatedObject.java +++ b/src/test/java/com/fleetpin/graphql/builder/type/DeprecatedObject.java @@ -1,4 +1,14 @@ - +/* + * Licensed 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 com.fleetpin.graphql.builder.type; import com.fleetpin.graphql.builder.annotations.Entity; @@ -9,10 +19,10 @@ public class DeprecatedObject { @GraphQLDeprecated("spelling") - public DeprecatedObject getNaame() { + public DeprecatedObject getNaame() { return null; } - + @Query @GraphQLDeprecated("old") public static DeprecatedObject deprecatedTest() { diff --git a/src/test/java/com/fleetpin/graphql/builder/type/DescriptionObject.java b/src/test/java/com/fleetpin/graphql/builder/type/DescriptionObject.java index 5db8659..d3d58ba 100644 --- a/src/test/java/com/fleetpin/graphql/builder/type/DescriptionObject.java +++ b/src/test/java/com/fleetpin/graphql/builder/type/DescriptionObject.java @@ -1,4 +1,14 @@ - +/* + * Licensed 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 com.fleetpin.graphql.builder.type; import com.fleetpin.graphql.builder.annotations.Entity; @@ -10,10 +20,10 @@ public class DescriptionObject { @GraphQLDescription("first and last") - public DescriptionObject getName() { + public DescriptionObject getName() { return null; } - + @Query @GraphQLDescription("returns something") public static DescriptionObject descriptionTest() { diff --git a/src/test/java/com/fleetpin/graphql/builder/type/SimpleType.java b/src/test/java/com/fleetpin/graphql/builder/type/SimpleType.java index f7c5b51..9b21a5f 100644 --- a/src/test/java/com/fleetpin/graphql/builder/type/SimpleType.java +++ b/src/test/java/com/fleetpin/graphql/builder/type/SimpleType.java @@ -1,74 +1,84 @@ -package com.fleetpin.graphql.builder.type; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.Query; - -@Entity -public class SimpleType { - - public String getName() { - return "green"; - } - - public boolean isDeleted() { - return false; - } - - public Optional getAlive() { - return Optional.empty(); - } - - public List getParts() { - return Arrays.asList("green", "eggs"); - } - - public List> getGappyParts() { - return Arrays.asList(Optional.empty(), Optional.of("eggs")); - } - - public Optional> getOptionalParts() { - return Optional.empty(); - } - - public Optional>> getOptionalGappyParts() { - return Optional.of(Arrays.asList()); - } - - public CompletableFuture getNameFuture() { - return CompletableFuture.completedFuture("green"); - } - - public CompletableFuture isDeletedFuture() { - return CompletableFuture.completedFuture(false); - } - - public CompletableFuture> getAliveFuture() { - return CompletableFuture.completedFuture(Optional.of(false)); - } - - public CompletableFuture> getPartsFuture() { - return CompletableFuture.completedFuture(Arrays.asList()); - } - - public CompletableFuture>> getGappyPartsFuture() { - return CompletableFuture.completedFuture(Arrays.asList()); - } - - public CompletableFuture>> getOptionalPartsFuture() { - return CompletableFuture.completedFuture(Optional.of(Arrays.asList())); - } - - public CompletableFuture>>> getOptionalGappyPartsFuture() { - return CompletableFuture.completedFuture(Optional.empty()); - } - - @Query - public static SimpleType simpleType() { - return new SimpleType(); - } -} +/* + * Licensed 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 com.fleetpin.graphql.builder.type; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.Query; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +@Entity +public class SimpleType { + + public String getName() { + return "green"; + } + + public boolean isDeleted() { + return false; + } + + public Optional getAlive() { + return Optional.empty(); + } + + public List getParts() { + return Arrays.asList("green", "eggs"); + } + + public List> getGappyParts() { + return Arrays.asList(Optional.empty(), Optional.of("eggs")); + } + + public Optional> getOptionalParts() { + return Optional.empty(); + } + + public Optional>> getOptionalGappyParts() { + return Optional.of(Arrays.asList()); + } + + public CompletableFuture getNameFuture() { + return CompletableFuture.completedFuture("green"); + } + + public CompletableFuture isDeletedFuture() { + return CompletableFuture.completedFuture(false); + } + + public CompletableFuture> getAliveFuture() { + return CompletableFuture.completedFuture(Optional.of(false)); + } + + public CompletableFuture> getPartsFuture() { + return CompletableFuture.completedFuture(Arrays.asList()); + } + + public CompletableFuture>> getGappyPartsFuture() { + return CompletableFuture.completedFuture(Arrays.asList()); + } + + public CompletableFuture>> getOptionalPartsFuture() { + return CompletableFuture.completedFuture(Optional.of(Arrays.asList())); + } + + public CompletableFuture>>> getOptionalGappyPartsFuture() { + return CompletableFuture.completedFuture(Optional.empty()); + } + + @Query + public static SimpleType simpleType() { + return new SimpleType(); + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/type/UnionType.java b/src/test/java/com/fleetpin/graphql/builder/type/UnionType.java new file mode 100644 index 0000000..f754ba1 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/type/UnionType.java @@ -0,0 +1,44 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.type; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.Query; +import com.fleetpin.graphql.builder.annotations.Union; +import java.util.List; + +@Entity +public class UnionType { + + private final Object type; + + public UnionType(Object type) { + this.type = type; + } + + @Union({ SimpleType.class, UnionType.class }) + public Object getType() { + return type; + } + + @Query + @Union({ SimpleType.class, UnionType.class }) + public static List union() { + return List.of(new SimpleType(), new UnionType(new SimpleType())); + } + + @Query + @Union({ SimpleType.class, UnionType.class }) + public static List unionFailure() { + return List.of(new UnionType(new UnionType(4d)), false); + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/type/directive/Admin.java b/src/test/java/com/fleetpin/graphql/builder/type/directive/Admin.java new file mode 100644 index 0000000..a1e1cfa --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/type/directive/Admin.java @@ -0,0 +1,42 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.type.directive; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.fleetpin.graphql.builder.DirectiveCaller; +import com.fleetpin.graphql.builder.annotations.DataFetcherWrapper; +import com.fleetpin.graphql.builder.annotations.Directive; +import graphql.introspection.Introspection; +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@DataFetcherWrapper(Admin.Processor.class) +@Retention(RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface Admin { + String value(); + + static class Processor implements DirectiveCaller { + + @Override + public Object process(Admin annotation, DataFetchingEnvironment env, DataFetcher fetcher) throws Exception { + if (env.getArgument("name").equals(annotation.value())) { + return fetcher.get(env); + } + throw new RuntimeException("forbidden"); + } + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/type/directive/Capture.java b/src/test/java/com/fleetpin/graphql/builder/type/directive/Capture.java index 2f7c7ab..daa0d34 100644 --- a/src/test/java/com/fleetpin/graphql/builder/type/directive/Capture.java +++ b/src/test/java/com/fleetpin/graphql/builder/type/directive/Capture.java @@ -1,38 +1,28 @@ -package com.fleetpin.graphql.builder.type.directive; - - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import java.util.List; - -import com.fleetpin.graphql.builder.SDLDirective; -import com.fleetpin.graphql.builder.annotations.Directive; - -import graphql.introspection.Introspection.DirectiveLocation; - -@Directive(Capture.Processor.class) -@Retention(RUNTIME) -@Target({ElementType.METHOD, ElementType.TYPE}) -public @interface Capture { - - String value(); - - static class Processor implements SDLDirective { - - - @Override - public List validLocations() { - return List.of(DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.SCHEMA); - } - - @Override - public CaptureType build(Capture annotation, Class location) { - return new CaptureType().setColor(annotation.value()); - } - - } - -} +/* + * Licensed 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 com.fleetpin.graphql.builder.type.directive; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.fleetpin.graphql.builder.annotations.Directive; +import graphql.introspection.Introspection; +import graphql.introspection.Introspection.DirectiveLocation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Directive({ Introspection.DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.SCHEMA }) +@Retention(RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface Capture { + String color(); +} diff --git a/src/test/java/com/fleetpin/graphql/builder/type/directive/CaptureType.java b/src/test/java/com/fleetpin/graphql/builder/type/directive/CaptureType.java index 22a8f90..3d7c923 100644 --- a/src/test/java/com/fleetpin/graphql/builder/type/directive/CaptureType.java +++ b/src/test/java/com/fleetpin/graphql/builder/type/directive/CaptureType.java @@ -1,18 +1,30 @@ -package com.fleetpin.graphql.builder.type.directive; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.SchemaOption; - -@Entity(SchemaOption.INPUT) -public class CaptureType { - private String color; - - public CaptureType setColor(String color) { - this.color = color; - return this; - } - - public String getColor() { - return color; - } -} \ No newline at end of file +/* + * Licensed 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 com.fleetpin.graphql.builder.type.directive; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.SchemaOption; + +@Entity(SchemaOption.INPUT) +public class CaptureType { + + private String color; + + public CaptureType setColor(String color) { + this.color = color; + return this; + } + + public String getColor() { + return color; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/type/directive/Cat.java b/src/test/java/com/fleetpin/graphql/builder/type/directive/Cat.java index 01ad286..9935400 100644 --- a/src/test/java/com/fleetpin/graphql/builder/type/directive/Cat.java +++ b/src/test/java/com/fleetpin/graphql/builder/type/directive/Cat.java @@ -1,29 +1,54 @@ -package com.fleetpin.graphql.builder.type.directive; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.Query; - -@Entity -public class Cat { - - - - public boolean isCalico() { - return true; - } - - public int getAge() { - return 3; - } - - public boolean getFur() { - return true; - } - - @Query - @Capture("meow") - public static Cat getCat() { - return new Cat(); - } -} - +/* + * Licensed 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 com.fleetpin.graphql.builder.type.directive; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.Query; + +@Entity +public class Cat { + + public boolean isCalico() { + return true; + } + + public int getAge() { + return 3; + } + + public boolean getFur() { + return true; + } + + @Query + @Capture(color = "meow") + public static Cat getCat() { + return new Cat(); + } + + @Query + @Uppercase + public static Cat getUpper() { + return new Cat(); + } + + @Query + @Admin("tabby") + public static String allowed(String name) { + return name; + } + + @Query + public static String getNickname(@Input("TT") String nickName) { + return nickName; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/type/directive/CatSchema.java b/src/test/java/com/fleetpin/graphql/builder/type/directive/CatSchema.java index 4b2cff6..bc463f0 100644 --- a/src/test/java/com/fleetpin/graphql/builder/type/directive/CatSchema.java +++ b/src/test/java/com/fleetpin/graphql/builder/type/directive/CatSchema.java @@ -1,8 +1,17 @@ -package com.fleetpin.graphql.builder.type.directive; - -import com.fleetpin.graphql.builder.SchemaConfiguration; - -@Capture("top") -public class CatSchema implements SchemaConfiguration { - -} +/* + * Licensed 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 com.fleetpin.graphql.builder.type.directive; + +import com.fleetpin.graphql.builder.SchemaConfiguration; + +@Capture(color = "top") +public class CatSchema implements SchemaConfiguration {} diff --git a/src/test/java/com/fleetpin/graphql/builder/type/directive/Input.java b/src/test/java/com/fleetpin/graphql/builder/type/directive/Input.java new file mode 100644 index 0000000..c83a360 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/type/directive/Input.java @@ -0,0 +1,16 @@ +package com.fleetpin.graphql.builder.type.directive; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.fleetpin.graphql.builder.annotations.Directive; +import graphql.introspection.Introspection; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Directive(Introspection.DirectiveLocation.ARGUMENT_DEFINITION) +@Retention(RUNTIME) +@Target({ ElementType.PARAMETER }) +public @interface Input { + String value(); +} diff --git a/src/test/java/com/fleetpin/graphql/builder/type/directive/Uppercase.java b/src/test/java/com/fleetpin/graphql/builder/type/directive/Uppercase.java new file mode 100644 index 0000000..9886ba3 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/type/directive/Uppercase.java @@ -0,0 +1,26 @@ +/* + * Licensed 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 com.fleetpin.graphql.builder.type.directive; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.fleetpin.graphql.builder.annotations.Directive; +import graphql.introspection.Introspection; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Directive(Introspection.DirectiveLocation.FIELD_DEFINITION) +@Retention(RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface Uppercase { +} diff --git a/src/test/java/com/fleetpin/graphql/builder/type/inheritance/Animal.java b/src/test/java/com/fleetpin/graphql/builder/type/inheritance/Animal.java index 3ddb593..99b24ea 100644 --- a/src/test/java/com/fleetpin/graphql/builder/type/inheritance/Animal.java +++ b/src/test/java/com/fleetpin/graphql/builder/type/inheritance/Animal.java @@ -1,20 +1,48 @@ -package com.fleetpin.graphql.builder.type.inheritance; - -import java.util.Arrays; -import java.util.List; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.Query; - -@Entity -public abstract class Animal{ - - public String getName() { - return "name"; - } - - @Query - public static List animals() { - return Arrays.asList(new Cat(), new Dog()); - } -} \ No newline at end of file +/* + * Licensed 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 com.fleetpin.graphql.builder.type.inheritance; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.GraphQLDescription; +import com.fleetpin.graphql.builder.annotations.Mutation; +import com.fleetpin.graphql.builder.annotations.OneOf; +import com.fleetpin.graphql.builder.annotations.Query; +import com.fleetpin.graphql.builder.annotations.SchemaOption; +import java.util.Arrays; +import java.util.List; + +@Entity(SchemaOption.BOTH) +@GraphQLDescription("animal desc") +@OneOf(value = { @OneOf.Type(name = "cat", type = Cat.class), @OneOf.Type(name = "dog", type = Dog.class, description = "A dog") }) +public abstract class Animal { + + private String name = "name"; + + @GraphQLDescription("the name") + public String getName() { + return name; + } + + @Query + public static List animals() { + return Arrays.asList(new Cat(), new Dog()); + } + + public void setName(String name) { + this.name = name; + } + + @Mutation + public static List myAnimals(List animals) { + return animals; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/type/inheritance/Cat.java b/src/test/java/com/fleetpin/graphql/builder/type/inheritance/Cat.java index d235f23..e121187 100644 --- a/src/test/java/com/fleetpin/graphql/builder/type/inheritance/Cat.java +++ b/src/test/java/com/fleetpin/graphql/builder/type/inheritance/Cat.java @@ -1,26 +1,80 @@ -package com.fleetpin.graphql.builder.type.inheritance; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.Mutation; - -@Entity -public class Cat extends Animal{ - - public boolean isCalico() { - return true; - } - - public int getAge() { - return 3; - } - - public boolean getFur() { - return true; - } - - @Mutation - public static Cat getCat() { - return null; - } -} - +/* + * Licensed 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 com.fleetpin.graphql.builder.type.inheritance; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.GraphQLDescription; +import com.fleetpin.graphql.builder.annotations.Mutation; +import com.fleetpin.graphql.builder.annotations.SchemaOption; +import java.util.Optional; + +@Entity(SchemaOption.BOTH) +@GraphQLDescription("cat type") +public class Cat extends Animal { + + private boolean calico; + private int age; + private Optional fur; + + public Cat() { + calico = true; + age = 3; + fur = Optional.of(true); + } + + public Cat(boolean calico, int age, Optional fur) { + super(); + this.calico = calico; + this.age = age; + this.fur = fur; + } + + public boolean isCalico() { + return calico; + } + + public void setCalico(boolean calico) { + this.calico = calico; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @GraphQLDescription("get fur") + public Optional getFur() { + return fur; + } + + public Optional getWeight(@GraphQLDescription("whole number") Boolean round) { + return fur; + } + + @GraphQLDescription("set fur") + public void setFur(Optional fur) { + this.fur = fur; + } + + public void setError(Optional ignore) { + throw new RuntimeException("ERROR"); + } + + @Mutation + @GraphQLDescription("cat endpoint") + public static Cat getCat(@GraphQLDescription("sample") Optional age) { + return null; + } +} diff --git a/src/test/java/com/fleetpin/graphql/builder/type/inheritance/Dog.java b/src/test/java/com/fleetpin/graphql/builder/type/inheritance/Dog.java index 4302816..6c7bbf4 100644 --- a/src/test/java/com/fleetpin/graphql/builder/type/inheritance/Dog.java +++ b/src/test/java/com/fleetpin/graphql/builder/type/inheritance/Dog.java @@ -1,20 +1,44 @@ -package com.fleetpin.graphql.builder.type.inheritance; - -import com.fleetpin.graphql.builder.annotations.Entity; -import com.fleetpin.graphql.builder.annotations.Mutation; - -@Entity -public class Dog extends Animal{ - - public int getAge() { - return 6; - } - public String getFur() { - return "shaggy"; - } - - @Mutation - public static Dog getDog() { - return null; - } -} +/* + * Licensed 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 com.fleetpin.graphql.builder.type.inheritance; + +import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.Mutation; +import com.fleetpin.graphql.builder.annotations.SchemaOption; + +@Entity(SchemaOption.BOTH) +public class Dog extends Animal { + + private int age = 6; + private String fur = "shaggy"; + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public String getFur() { + return fur; + } + + public void setFur(String fur) { + this.fur = fur; + } + + @Mutation + public static Dog getDog() { + return null; + } +}