From 4f35a65e60619f87e6474a07054cd77409d44b62 Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 3 Mar 2025 13:50:29 +1300 Subject: [PATCH 01/20] Refactoring --- .../graphql/builder/DirectiveProcessor.java | 9 +- .../graphql/builder/DirectivesSchema.java | 23 +---- .../graphql/builder/MethodProcessor.java | 59 ++---------- .../graphql/builder/SchemaBuilder.java | 91 +++++++++++-------- 4 files changed, 70 insertions(+), 112 deletions(-) diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java b/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java index 18d429d8..c9401ba5 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java @@ -2,11 +2,16 @@ import com.fleetpin.graphql.builder.annotations.Directive; import graphql.introspection.Introspection; -import graphql.schema.*; +import graphql.schema.GraphQLAppliedDirective; +import graphql.schema.GraphQLAppliedDirectiveArgument; +import graphql.schema.GraphQLArgument; +import graphql.schema.GraphQLDirective; + import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.*; +import java.util.HashMap; +import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java index ce6e59e7..f8351aba 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java @@ -17,19 +17,17 @@ import graphql.schema.DataFetchingEnvironment; import graphql.schema.GraphQLAppliedDirective; import graphql.schema.GraphQLDirective; +import org.reactivestreams.Publisher; + 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.*; 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; class DirectivesSchema { @@ -179,23 +177,6 @@ private CompletableFuture applyRestrict(RestrictType restrict, Objec } } - 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()) - ); - } - public void addSchemaDirective(AnnotatedElement element, Class location, Consumer builder) { for (Annotation annotation : element.getAnnotations()) { var processor = this.directiveProcessors.get(annotation.annotationType()); diff --git a/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java b/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java index 8bef984f..6030a814 100644 --- a/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java +++ b/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java @@ -1,35 +1,23 @@ 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 com.fleetpin.graphql.builder.annotations.*; 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.*; 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; +import static com.fleetpin.graphql.builder.EntityUtil.isContext; + class MethodProcessor { private final DataFetcherRunner dataFetcherRunner; private final EntityProcessor entityProcessor; - private final DirectivesSchema diretives; + private final DirectivesSchema directives; private final GraphQLCodeRegistry.Builder codeRegistry; @@ -40,7 +28,7 @@ class MethodProcessor { public MethodProcessor(DataFetcherRunner dataFetcherRunner, EntityProcessor entityProcessor, DirectivesSchema diretives) { this.dataFetcherRunner = dataFetcherRunner; this.entityProcessor = entityProcessor; - this.diretives = diretives; + this.directives = diretives; this.codeRegistry = GraphQLCodeRegistry.newCodeRegistry(); this.graphQuery = GraphQLObjectType.newObject(); @@ -109,47 +97,18 @@ Builder process(AuthorizerSchema authorizer, FieldCoordinates coordinates, TypeM 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); - } + entityProcessor.addSchemaDirective(parameter, method.getDeclaringClass(), argument::withAppliedDirective); argument.name(EntityUtil.getName(parameter.getName(), parameter)); // TODO: argument.defaultValue(defaultValue) field.argument(argument); } - DataFetcher fetcher = buildFetcher(diretives, authorizer, method, meta); + DataFetcher fetcher = buildFetcher(directives, 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); diff --git a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java index 24bf2fcb..72472255 100644 --- a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java +++ b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java @@ -14,13 +14,15 @@ import com.fleetpin.graphql.builder.annotations.*; import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLSchema; +import org.reflections.Reflections; +import org.reflections.scanners.Scanners; + +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import org.reflections.Reflections; -import org.reflections.scanners.Scanners; public class SchemaBuilder { @@ -75,7 +77,7 @@ private graphql.schema.GraphQLSchema.Builder build(Set builder.additionalDirective(directive)); + directives.getSchemaDirective().forEach(builder::additionalDirective); for (var schema : schemaConfiguration) { this.directives.addSchemaDirective(schema, schema, builder::withSchemaAppliedDirective); @@ -131,41 +133,7 @@ public GraphQLSchema.Builder build() { 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); - } - - 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 (!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 directivesSchema = DirectivesSchema.build(globalRestricts, directivesTypes); // Entry point for directives + DirectivesSchema directivesSchema = getDirectivesSchema(reflections); Set> types = reflections.getTypesAnnotatedWith(Entity.class); @@ -178,7 +146,7 @@ public GraphQLSchema.Builder build() { endPoints.addAll(queries); types.removeIf(t -> t.getDeclaredAnnotation(Entity.class) == null); - types.removeIf(t -> t.isAnonymousClass()); + types.removeIf(Class::isAnonymousClass); return new SchemaBuilder(dataFetcherRunner, scalars, directivesSchema, authorizer) .processTypes(types) @@ -188,5 +156,50 @@ public GraphQLSchema.Builder build() { throw new RuntimeException(e); } } + + private static DirectivesSchema getDirectivesSchema(Reflections reflections) throws ReflectiveOperationException { + Set> directivesTypes = reflections.getTypesAnnotatedWith(Directive.class); + directivesTypes.addAll(reflections.getTypesAnnotatedWith(DataFetcherWrapper.class)); + + List> globalRestricts = getGlobalRestricts(reflections); + + return DirectivesSchema.build(globalRestricts, directivesTypes); + } + + private static List> getGlobalRestricts(Reflections reflections) + throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + 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); + } + + 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 (!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); + } + } + + return globalRestricts; + } } } From 603b6ddb095d59504440fbc6920e4085aab63cf5 Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 3 Mar 2025 14:19:56 +1300 Subject: [PATCH 02/20] tests --- pom.xml | 495 +++++++++--------- .../JakartaValidationDirectiveTest.java | 55 ++ .../graphql/builder/type/directive/Cat.java | 7 + 3 files changed, 315 insertions(+), 242 deletions(-) create mode 100644 src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java diff --git a/pom.xml b/pom.xml index e572a4cd..99ae47fd 100644 --- a/pom.xml +++ b/pom.xml @@ -11,259 +11,270 @@ the License. --> - - 4.0.0 - com.fleetpin - graphql-builder - 3.1.0-SNAPSHOT + + 4.0.0 + com.fleetpin + graphql-builder + 3.1.0-SNAPSHOT - GraphQL Builder - Builds a graphql schema from a model using reflection - https://github.com/ashley-taylor/graphql-builder + GraphQL Builder + Builds a graphql schema from a model using reflection + https://github.com/ashley-taylor/graphql-builder - - 5.11.2 - UTF-8 - 2.18.0 - 1.17.0 - 1.2.1 - 22.3 - + + 5.11.2 + UTF-8 + 2.18.0 + 1.17.0 + 1.2.1 + 22.3 + - - - sonatype - central snapshot - https://oss.sonatype.org/content/repositories/snapshots - - - sonatype - central release - https://oss.sonatype.org/service/local/staging/deploy/maven2 - - + + + sonatype + central snapshot + https://oss.sonatype.org/content/repositories/snapshots + + + sonatype + central release + https://oss.sonatype.org/service/local/staging/deploy/maven2 + + - - 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 - + + 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 + - - - Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + - - - Ashley Taylor - ashley.taylor@fleetpin.co.nz - Fleetpin - http://www.fleetpin.co.nz - - + + + Ashley Taylor + ashley.taylor@fleetpin.co.nz + Fleetpin + http://www.fleetpin.co.nz + + - - - - org.apache.maven.plugins - maven-compiler-plugin - - 21 - 21 - -parameters - - - - org.apache.maven.plugins - maven-release-plugin - 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
-
-
-
-
+ + + + org.apache.maven.plugins + maven-compiler-plugin + + 21 + 21 + -parameters + + + + org.apache.maven.plugins + maven-release-plugin + 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 - ${graphql.version} - - - com.graphql-java - graphql-java-extended-scalars - 21.0 - test - - - org.reflections - reflections - 0.10.2 - - - jakarta.annotation - jakarta.annotation-api - 3.0.0 - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - test - - - com.fasterxml.jackson.module - jackson-module-parameter-names - ${jackson.version} - test - - - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - ${jackson.version} - test - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - ${jackson.version} - test - - - io.reactivex.rxjava3 - rxjava - 3.1.9 - test - + + + com.graphql-java + graphql-java + ${graphql.version} + + + com.graphql-java + graphql-java-extended-scalars + 21.0 + test + + + com.graphql-java + graphql-java-extended-validation + 21.0 + + + org.reflections + reflections + 0.10.2 + + + jakarta.annotation + jakarta.annotation-api + 3.0.0 + + + jakarta.validation + jakarta.validation-api + 3.1.0 + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + test + + + com.fasterxml.jackson.module + jackson-module-parameter-names + ${jackson.version} + test + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + ${jackson.version} + test + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + test + + + io.reactivex.rxjava3 + rxjava + 3.1.9 + test + - - org.junit.jupiter - junit-jupiter - ${junit.jupiter.version} - test - - - - org.skyscreamer - jsonassert - 1.5.3 - test - + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + - + + org.skyscreamer + jsonassert + 1.5.3 + test + + - - - sonatype - - - - org.apache.maven.plugins - maven-gpg-plugin - 3.2.7 - - - sign-artifacts - verify - - sign - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.7.0 - true - - sonatype - https://oss.sonatype.org/ - true - - - - - - + + + + sonatype + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.7 + + + sign-artifacts + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.7.0 + true + + sonatype + https://oss.sonatype.org/ + true + + + + + +
diff --git a/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java b/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java new file mode 100644 index 00000000..6e7102c6 --- /dev/null +++ b/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java @@ -0,0 +1,55 @@ +package com.fleetpin.graphql.builder; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.introspection.IntrospectionWithDirectivesSupport; +import graphql.schema.FieldCoordinates; +import graphql.schema.GraphQLSchema; +import org.junit.jupiter.api.Test; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class JakartaValidationDirectiveTest { + @Test + public void testJakartaArgumentAnnotationChangedToConstraint() { + GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type.directive")).build(); + var name = schema.getGraphQLSchema().getFieldDefinition(FieldCoordinates.coordinates(schema.getGraphQLSchema().getMutationType(), "setName")); + var constraint = name.getArgument("name").getAppliedDirective("Constraint"); + var argument = constraint.getArgument("min"); + var min = argument.getValue(); + assertEquals(3, min); + } + + @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 constraint = dir.stream().filter(map -> map.get("name").equals("Constraint")).collect(Collectors.toList()).get(0); + + assertEquals(9, dir.size()); + assertEquals("ARGUMENT_DEFINITION", ((List) constraint.get("locations")).get(0)); + assertEquals(1, ((List) constraint.get("args")).size()); + assertEquals("{name=validatedBy}", ((List) constraint.get("args")).getFirst().toString()); + //setName(name: String! @Size(min : 3)): Int! + //directive @Constraint(name: 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/type/directive/Cat.java b/src/test/java/com/fleetpin/graphql/builder/type/directive/Cat.java index 99354009..235e1886 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 @@ -12,7 +12,9 @@ package com.fleetpin.graphql.builder.type.directive; import com.fleetpin.graphql.builder.annotations.Entity; +import com.fleetpin.graphql.builder.annotations.Mutation; import com.fleetpin.graphql.builder.annotations.Query; +import jakarta.validation.constraints.Size; @Entity public class Cat { @@ -41,6 +43,11 @@ public static Cat getUpper() { return new Cat(); } + @Mutation + public static String setName(@Size(min = 3) String name) { + return name; + } + @Query @Admin("tabby") public static String allowed(String name) { From dc120e464c5064529449791729f742cb9f1ab248 Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Wed, 12 Mar 2025 16:48:50 +1300 Subject: [PATCH 03/20] WIP --- .../graphql/builder/DirectiveProcessor.java | 22 +++++++++++++++---- .../graphql/builder/DirectivesSchema.java | 4 +++- .../graphql/builder/MethodProcessor.java | 16 ++++++++++++-- .../graphql/builder/SchemaBuilder.java | 9 +++++++- .../JakartaValidationDirectiveTest.java | 4 ++-- 5 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java b/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java index c9401ba5..49adc674 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java @@ -27,15 +27,29 @@ public DirectiveProcessor(GraphQLDirective directive, Map directive) { var builder = GraphQLDirective.newDirective().name(directive.getSimpleName()); - var validLocations = directive.getAnnotation(Directive.class).value(); + + Introspection.DirectiveLocation[] validLocations; + if ( + !directive.isAnnotationPresent(Directive.class) && + directive + .getName() + .startsWith("jakarta.validation.constraints") // Jakarta constraint annotations don't have the Directive annotation, so we need to manually add in the locations + ) { + validLocations = new Introspection.DirectiveLocation[] { + Introspection.DirectiveLocation.ARGUMENT_DEFINITION, + Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION }; + } else { + validLocations = directive.getAnnotation(Directive.class).value(); + + // Check for repeatable tag in annotation and add it + builder.repeatable(directive.getAnnotation(Directive.class).repeatable()); + } + // 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<>(); diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java index f8351aba..0ac01aeb 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java @@ -61,7 +61,7 @@ public static DirectivesSchema build(List> globalDirectiv } continue; } - if (!directiveType.isAnnotationPresent(Directive.class)) { + if (!directiveType.isAnnotationPresent(Directive.class) && !directiveType.getName().startsWith("jakarta.validation.constraints")) { continue; } if (!directiveType.isAnnotation()) { @@ -69,6 +69,8 @@ public static DirectivesSchema build(List> globalDirectiv } allDirectives.add((Class) directiveType); } + // allDirectives.addAll(extra); + // we could pass the constraint annotations around as an extra list, separate to the other directive. return new DirectivesSchema(globalDirectives, targets, allDirectives); } diff --git a/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java b/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java index 6030a814..ec2defb9 100644 --- a/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java +++ b/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java @@ -2,13 +2,17 @@ import com.fleetpin.graphql.builder.annotations.*; import graphql.GraphQLContext; +import graphql.GraphQLError; +import graphql.execution.DataFetcherResult; import graphql.schema.*; import graphql.schema.GraphQLFieldDefinition.Builder; +import graphql.validation.rules.ValidationRules; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.List; import java.util.function.Function; import static com.fleetpin.graphql.builder.EntityUtil.isContext; @@ -61,8 +65,7 @@ void process(AuthorizerSchema authorizer, Method method) throws ReflectiveOperat object.field(process(authorizer, coordinates, null, method)); } - Builder process(AuthorizerSchema authorizer, FieldCoordinates coordinates, TypeMeta parentMeta, Method method) - throws InvocationTargetException, IllegalAccessException { + Builder process(AuthorizerSchema authorizer, FieldCoordinates coordinates, TypeMeta parentMeta, Method method) { GraphQLFieldDefinition.Builder field = GraphQLFieldDefinition.newFieldDefinition(); entityProcessor.addSchemaDirective(method, method.getDeclaringClass(), field::withAppliedDirective); @@ -133,8 +136,17 @@ private DataFetcher buildDataFetcher(TypeMeta meta, Method method) { resolvers[i] = buildResolver(name, argMeta, parameter.getAnnotations()); } + ValidationRules validationRules = ValidationRules + .newValidationRules() + .build(); + DataFetcher fetcher = env -> { try { + List errors = validationRules.runValidationRules(env); + if (!errors.isEmpty()) { + return DataFetcherResult.newResult().errors(errors).data(null).build(); + } + Object[] args = new Object[resolvers.length]; for (int i = 0; i < resolvers.length; i++) { args[i] = resolvers[i].apply(env); diff --git a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java index 72472255..bc09a52e 100644 --- a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java +++ b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java @@ -14,6 +14,7 @@ import com.fleetpin.graphql.builder.annotations.*; import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLSchema; +import jakarta.validation.constraints.Size; import org.reflections.Reflections; import org.reflections.scanners.Scanners; @@ -161,11 +162,17 @@ private static DirectivesSchema getDirectivesSchema(Reflections reflections) thr Set> directivesTypes = reflections.getTypesAnnotatedWith(Directive.class); directivesTypes.addAll(reflections.getTypesAnnotatedWith(DataFetcherWrapper.class)); + addAllJakartaAnnotations(directivesTypes); + List> globalRestricts = getGlobalRestricts(reflections); return DirectivesSchema.build(globalRestricts, directivesTypes); } + private static void addAllJakartaAnnotations(Set> directivesTypes) { + directivesTypes.add(Size.class); // use reflection? + } + private static List> getGlobalRestricts(Reflections reflections) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { Set> restrict = reflections.getTypesAnnotatedWith(Restrict.class); @@ -198,7 +205,7 @@ private static List> getGlobalRestricts(Reflections refle globalRestricts.add(factory); } } - + return globalRestricts; } } diff --git a/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java b/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java index 6e7102c6..4d26393c 100644 --- a/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java @@ -20,7 +20,7 @@ public class JakartaValidationDirectiveTest { public void testJakartaArgumentAnnotationChangedToConstraint() { GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type.directive")).build(); var name = schema.getGraphQLSchema().getFieldDefinition(FieldCoordinates.coordinates(schema.getGraphQLSchema().getMutationType(), "setName")); - var constraint = name.getArgument("name").getAppliedDirective("Constraint"); + var constraint = name.getArgument("name").getAppliedDirective("Size"); var argument = constraint.getArgument("min"); var min = argument.getValue(); assertEquals(3, min); @@ -30,7 +30,7 @@ public void testJakartaArgumentAnnotationChangedToConstraint() { 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 constraint = dir.stream().filter(map -> map.get("name").equals("Constraint")).collect(Collectors.toList()).get(0); + LinkedHashMap constraint = dir.stream().filter(map -> map.get("name").equals("Size")).collect(Collectors.toList()).get(0); assertEquals(9, dir.size()); assertEquals("ARGUMENT_DEFINITION", ((List) constraint.get("locations")).get(0)); From b36f3a1f53d4f72684f56d84a3dde06461b3e80c Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 17 Mar 2025 10:06:21 +1300 Subject: [PATCH 04/20] Revert "WIP" This reverts commit dc120e464c5064529449791729f742cb9f1ab248. --- .../graphql/builder/DirectiveProcessor.java | 22 ++++--------------- .../graphql/builder/DirectivesSchema.java | 4 +--- .../graphql/builder/MethodProcessor.java | 16 ++------------ .../graphql/builder/SchemaBuilder.java | 9 +------- .../JakartaValidationDirectiveTest.java | 4 ++-- 5 files changed, 10 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java b/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java index 49adc674..c9401ba5 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java @@ -27,29 +27,15 @@ public DirectiveProcessor(GraphQLDirective directive, Map directive) { var builder = GraphQLDirective.newDirective().name(directive.getSimpleName()); - - Introspection.DirectiveLocation[] validLocations; - if ( - !directive.isAnnotationPresent(Directive.class) && - directive - .getName() - .startsWith("jakarta.validation.constraints") // Jakarta constraint annotations don't have the Directive annotation, so we need to manually add in the locations - ) { - validLocations = new Introspection.DirectiveLocation[] { - Introspection.DirectiveLocation.ARGUMENT_DEFINITION, - Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION }; - } else { - validLocations = directive.getAnnotation(Directive.class).value(); - - // Check for repeatable tag in annotation and add it - builder.repeatable(directive.getAnnotation(Directive.class).repeatable()); - } - + 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<>(); diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java index 0ac01aeb..f8351aba 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java @@ -61,7 +61,7 @@ public static DirectivesSchema build(List> globalDirectiv } continue; } - if (!directiveType.isAnnotationPresent(Directive.class) && !directiveType.getName().startsWith("jakarta.validation.constraints")) { + if (!directiveType.isAnnotationPresent(Directive.class)) { continue; } if (!directiveType.isAnnotation()) { @@ -69,8 +69,6 @@ public static DirectivesSchema build(List> globalDirectiv } allDirectives.add((Class) directiveType); } - // allDirectives.addAll(extra); - // we could pass the constraint annotations around as an extra list, separate to the other directive. return new DirectivesSchema(globalDirectives, targets, allDirectives); } diff --git a/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java b/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java index ec2defb9..6030a814 100644 --- a/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java +++ b/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java @@ -2,17 +2,13 @@ import com.fleetpin.graphql.builder.annotations.*; import graphql.GraphQLContext; -import graphql.GraphQLError; -import graphql.execution.DataFetcherResult; import graphql.schema.*; import graphql.schema.GraphQLFieldDefinition.Builder; -import graphql.validation.rules.ValidationRules; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.List; import java.util.function.Function; import static com.fleetpin.graphql.builder.EntityUtil.isContext; @@ -65,7 +61,8 @@ void process(AuthorizerSchema authorizer, Method method) throws ReflectiveOperat object.field(process(authorizer, coordinates, null, method)); } - Builder process(AuthorizerSchema authorizer, FieldCoordinates coordinates, TypeMeta parentMeta, Method 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); @@ -136,17 +133,8 @@ private DataFetcher buildDataFetcher(TypeMeta meta, Method method) { resolvers[i] = buildResolver(name, argMeta, parameter.getAnnotations()); } - ValidationRules validationRules = ValidationRules - .newValidationRules() - .build(); - DataFetcher fetcher = env -> { try { - List errors = validationRules.runValidationRules(env); - if (!errors.isEmpty()) { - return DataFetcherResult.newResult().errors(errors).data(null).build(); - } - Object[] args = new Object[resolvers.length]; for (int i = 0; i < resolvers.length; i++) { args[i] = resolvers[i].apply(env); diff --git a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java index bc09a52e..72472255 100644 --- a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java +++ b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java @@ -14,7 +14,6 @@ import com.fleetpin.graphql.builder.annotations.*; import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLSchema; -import jakarta.validation.constraints.Size; import org.reflections.Reflections; import org.reflections.scanners.Scanners; @@ -162,17 +161,11 @@ private static DirectivesSchema getDirectivesSchema(Reflections reflections) thr Set> directivesTypes = reflections.getTypesAnnotatedWith(Directive.class); directivesTypes.addAll(reflections.getTypesAnnotatedWith(DataFetcherWrapper.class)); - addAllJakartaAnnotations(directivesTypes); - List> globalRestricts = getGlobalRestricts(reflections); return DirectivesSchema.build(globalRestricts, directivesTypes); } - private static void addAllJakartaAnnotations(Set> directivesTypes) { - directivesTypes.add(Size.class); // use reflection? - } - private static List> getGlobalRestricts(Reflections reflections) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { Set> restrict = reflections.getTypesAnnotatedWith(Restrict.class); @@ -205,7 +198,7 @@ private static List> getGlobalRestricts(Reflections refle globalRestricts.add(factory); } } - + return globalRestricts; } } diff --git a/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java b/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java index 4d26393c..6e7102c6 100644 --- a/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java @@ -20,7 +20,7 @@ public class JakartaValidationDirectiveTest { public void testJakartaArgumentAnnotationChangedToConstraint() { GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type.directive")).build(); var name = schema.getGraphQLSchema().getFieldDefinition(FieldCoordinates.coordinates(schema.getGraphQLSchema().getMutationType(), "setName")); - var constraint = name.getArgument("name").getAppliedDirective("Size"); + var constraint = name.getArgument("name").getAppliedDirective("Constraint"); var argument = constraint.getArgument("min"); var min = argument.getValue(); assertEquals(3, min); @@ -30,7 +30,7 @@ public void testJakartaArgumentAnnotationChangedToConstraint() { 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 constraint = dir.stream().filter(map -> map.get("name").equals("Size")).collect(Collectors.toList()).get(0); + LinkedHashMap constraint = dir.stream().filter(map -> map.get("name").equals("Constraint")).collect(Collectors.toList()).get(0); assertEquals(9, dir.size()); assertEquals("ARGUMENT_DEFINITION", ((List) constraint.get("locations")).get(0)); From bbd3c688561f5a355ded9828c2097242c5b9790c Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 17 Mar 2025 10:30:15 +1300 Subject: [PATCH 05/20] Can add Size annotation --- .../graphql/builder/DirectiveProcessor.java | 22 +++++++++++++++---- .../graphql/builder/DirectivesSchema.java | 2 +- .../graphql/builder/MethodProcessor.java | 16 ++++++++++++-- .../graphql/builder/SchemaBuilder.java | 9 +++++++- .../JakartaValidationDirectiveTest.java | 4 ++-- 5 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java b/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java index c9401ba5..49adc674 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java @@ -27,15 +27,29 @@ public DirectiveProcessor(GraphQLDirective directive, Map directive) { var builder = GraphQLDirective.newDirective().name(directive.getSimpleName()); - var validLocations = directive.getAnnotation(Directive.class).value(); + + Introspection.DirectiveLocation[] validLocations; + if ( + !directive.isAnnotationPresent(Directive.class) && + directive + .getName() + .startsWith("jakarta.validation.constraints") // Jakarta constraint annotations don't have the Directive annotation, so we need to manually add in the locations + ) { + validLocations = new Introspection.DirectiveLocation[] { + Introspection.DirectiveLocation.ARGUMENT_DEFINITION, + Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION }; + } else { + validLocations = directive.getAnnotation(Directive.class).value(); + + // Check for repeatable tag in annotation and add it + builder.repeatable(directive.getAnnotation(Directive.class).repeatable()); + } + // 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<>(); diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java index f8351aba..49239b4e 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java @@ -61,7 +61,7 @@ public static DirectivesSchema build(List> globalDirectiv } continue; } - if (!directiveType.isAnnotationPresent(Directive.class)) { + if (!directiveType.isAnnotationPresent(Directive.class) && !directiveType.getName().startsWith("jakarta.validation.constraints")) { continue; } if (!directiveType.isAnnotation()) { diff --git a/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java b/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java index 6030a814..ec2defb9 100644 --- a/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java +++ b/src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java @@ -2,13 +2,17 @@ import com.fleetpin.graphql.builder.annotations.*; import graphql.GraphQLContext; +import graphql.GraphQLError; +import graphql.execution.DataFetcherResult; import graphql.schema.*; import graphql.schema.GraphQLFieldDefinition.Builder; +import graphql.validation.rules.ValidationRules; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.List; import java.util.function.Function; import static com.fleetpin.graphql.builder.EntityUtil.isContext; @@ -61,8 +65,7 @@ void process(AuthorizerSchema authorizer, Method method) throws ReflectiveOperat object.field(process(authorizer, coordinates, null, method)); } - Builder process(AuthorizerSchema authorizer, FieldCoordinates coordinates, TypeMeta parentMeta, Method method) - throws InvocationTargetException, IllegalAccessException { + Builder process(AuthorizerSchema authorizer, FieldCoordinates coordinates, TypeMeta parentMeta, Method method) { GraphQLFieldDefinition.Builder field = GraphQLFieldDefinition.newFieldDefinition(); entityProcessor.addSchemaDirective(method, method.getDeclaringClass(), field::withAppliedDirective); @@ -133,8 +136,17 @@ private DataFetcher buildDataFetcher(TypeMeta meta, Method method) { resolvers[i] = buildResolver(name, argMeta, parameter.getAnnotations()); } + ValidationRules validationRules = ValidationRules + .newValidationRules() + .build(); + DataFetcher fetcher = env -> { try { + List errors = validationRules.runValidationRules(env); + if (!errors.isEmpty()) { + return DataFetcherResult.newResult().errors(errors).data(null).build(); + } + Object[] args = new Object[resolvers.length]; for (int i = 0; i < resolvers.length; i++) { args[i] = resolvers[i].apply(env); diff --git a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java index 72472255..bc09a52e 100644 --- a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java +++ b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java @@ -14,6 +14,7 @@ import com.fleetpin.graphql.builder.annotations.*; import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLSchema; +import jakarta.validation.constraints.Size; import org.reflections.Reflections; import org.reflections.scanners.Scanners; @@ -161,11 +162,17 @@ private static DirectivesSchema getDirectivesSchema(Reflections reflections) thr Set> directivesTypes = reflections.getTypesAnnotatedWith(Directive.class); directivesTypes.addAll(reflections.getTypesAnnotatedWith(DataFetcherWrapper.class)); + addAllJakartaAnnotations(directivesTypes); + List> globalRestricts = getGlobalRestricts(reflections); return DirectivesSchema.build(globalRestricts, directivesTypes); } + private static void addAllJakartaAnnotations(Set> directivesTypes) { + directivesTypes.add(Size.class); // use reflection? + } + private static List> getGlobalRestricts(Reflections reflections) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { Set> restrict = reflections.getTypesAnnotatedWith(Restrict.class); @@ -198,7 +205,7 @@ private static List> getGlobalRestricts(Reflections refle globalRestricts.add(factory); } } - + return globalRestricts; } } diff --git a/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java b/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java index 6e7102c6..4d26393c 100644 --- a/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java @@ -20,7 +20,7 @@ public class JakartaValidationDirectiveTest { public void testJakartaArgumentAnnotationChangedToConstraint() { GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type.directive")).build(); var name = schema.getGraphQLSchema().getFieldDefinition(FieldCoordinates.coordinates(schema.getGraphQLSchema().getMutationType(), "setName")); - var constraint = name.getArgument("name").getAppliedDirective("Constraint"); + var constraint = name.getArgument("name").getAppliedDirective("Size"); var argument = constraint.getArgument("min"); var min = argument.getValue(); assertEquals(3, min); @@ -30,7 +30,7 @@ public void testJakartaArgumentAnnotationChangedToConstraint() { 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 constraint = dir.stream().filter(map -> map.get("name").equals("Constraint")).collect(Collectors.toList()).get(0); + LinkedHashMap constraint = dir.stream().filter(map -> map.get("name").equals("Size")).collect(Collectors.toList()).get(0); assertEquals(9, dir.size()); assertEquals("ARGUMENT_DEFINITION", ((List) constraint.get("locations")).get(0)); From 7c63ec65c83de8a05d636fb651b1f0405ef93389 Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 17 Mar 2025 12:14:10 +1300 Subject: [PATCH 06/20] Tidy --- .../graphql/builder/DirectivesSchema.java | 6 +++-- .../graphql/builder/SchemaBuilder.java | 11 ++++---- .../JakartaValidationDirectiveTest.java | 27 +++++++++---------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java index 49239b4e..09ab5b8a 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java @@ -47,7 +47,7 @@ private DirectivesSchema( } //TODO:mess of exceptions - public static DirectivesSchema build(List> globalDirectives, Set> directiveTypes) throws ReflectiveOperationException { + public static DirectivesSchema build(List> globalDirectives, Set> directiveTypes, Set> jakartaDirectiveTypes) throws ReflectiveOperationException { Map, DirectiveCaller> targets = new HashMap<>(); Collection> allDirectives = new ArrayList<>(); @@ -61,7 +61,7 @@ public static DirectivesSchema build(List> globalDirectiv } continue; } - if (!directiveType.isAnnotationPresent(Directive.class) && !directiveType.getName().startsWith("jakarta.validation.constraints")) { + if (!directiveType.isAnnotationPresent(Directive.class)) { continue; } if (!directiveType.isAnnotation()) { @@ -70,6 +70,8 @@ public static DirectivesSchema build(List> globalDirectiv allDirectives.add((Class) directiveType); } + allDirectives.addAll(jakartaDirectiveTypes); + return new DirectivesSchema(globalDirectives, targets, allDirectives); } diff --git a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java index bc09a52e..1063f5b0 100644 --- a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java +++ b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java @@ -18,6 +18,7 @@ import org.reflections.Reflections; import org.reflections.scanners.Scanners; +import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -162,15 +163,15 @@ private static DirectivesSchema getDirectivesSchema(Reflections reflections) thr Set> directivesTypes = reflections.getTypesAnnotatedWith(Directive.class); directivesTypes.addAll(reflections.getTypesAnnotatedWith(DataFetcherWrapper.class)); - addAllJakartaAnnotations(directivesTypes); - List> globalRestricts = getGlobalRestricts(reflections); - return DirectivesSchema.build(globalRestricts, directivesTypes); + return DirectivesSchema.build(globalRestricts, directivesTypes, getJakartaAnnotations()); } - private static void addAllJakartaAnnotations(Set> directivesTypes) { - directivesTypes.add(Size.class); // use reflection? + private static Set> getJakartaAnnotations() { + Set> list = new HashSet<>(); + list.add(Size.class); + return list; } private static List> getGlobalRestricts(Reflections reflections) diff --git a/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java b/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java index 4d26393c..07406222 100644 --- a/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java @@ -15,9 +15,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class JakartaValidationDirectiveTest { +class JakartaValidationDirectiveTest { @Test - public void testJakartaArgumentAnnotationChangedToConstraint() { + void testJakartaArgumentAnnotationChangedToConstraint() { GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type.directive")).build(); var name = schema.getGraphQLSchema().getFieldDefinition(FieldCoordinates.coordinates(schema.getGraphQLSchema().getMutationType(), "setName")); var constraint = name.getArgument("name").getAppliedDirective("Size"); @@ -27,29 +27,28 @@ public void testJakartaArgumentAnnotationChangedToConstraint() { } @Test - public void testDirectiveArgumentDefinition() { - Map response = execute("query IntrospectionQuery { __schema { directives { name locations args { name } } } }", null).getData(); + void testDirectiveArgumentDefinition() { + Map response = execute("query IntrospectionQuery { __schema { directives { name locations args { name } } } }").getData(); List> dir = (List>) ((Map) response.get("__schema")).get("directives"); LinkedHashMap constraint = dir.stream().filter(map -> map.get("name").equals("Size")).collect(Collectors.toList()).get(0); assertEquals(9, dir.size()); assertEquals("ARGUMENT_DEFINITION", ((List) constraint.get("locations")).get(0)); - assertEquals(1, ((List) constraint.get("args")).size()); - assertEquals("{name=validatedBy}", ((List) constraint.get("args")).getFirst().toString()); - //setName(name: String! @Size(min : 3)): Int! - //directive @Constraint(name: String!) on ARGUMENT_DEFINITION + assertEquals("INPUT_FIELD_DEFINITION", ((List) constraint.get("locations")).get(1)); + assertEquals(5, ((List) constraint.get("args")).size()); + assertEquals("{name=payload}", ((List) constraint.get("args")).getFirst().toString()); + assertEquals("{name=min}", ((List) constraint.get("args")).get(1).toString()); + assertEquals("{name=max}", ((List) constraint.get("args")).get(2).toString()); + assertEquals("{name=message}", ((List) constraint.get("args")).get(3).toString()); + assertEquals("{name=groups}", ((List) constraint.get("args")).get(4).toString()); } - private ExecutionResult execute(String query, Map variables) { + private ExecutionResult execute(String query) { 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; + return schema.execute(input); } } From c340d5ad93dcafe5870a78a974662d079dad84a9 Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 17 Mar 2025 15:54:54 +1300 Subject: [PATCH 07/20] Add all Jakarta annotations --- pom.xml | 1 - .../graphql/builder/DirectiveProcessor.java | 9 +--- .../graphql/builder/DirectivesSchema.java | 44 ++++++++++++------- .../graphql/builder/SchemaBuilder.java | 29 ++++++------ .../graphql/builder/DirectiveTest.java | 4 +- .../JakartaValidationDirectiveTest.java | 10 ++--- 6 files changed, 54 insertions(+), 43 deletions(-) diff --git a/pom.xml b/pom.xml index 99ae47fd..11101f2b 100644 --- a/pom.xml +++ b/pom.xml @@ -173,7 +173,6 @@ com.graphql-java graphql-java-extended-scalars 21.0 - test com.graphql-java diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java b/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java index 49adc674..a978e861 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java @@ -25,16 +25,11 @@ public DirectiveProcessor(GraphQLDirective directive, Map directive) { + public static DirectiveProcessor build(EntityProcessor entityProcessor, Class directive, boolean isJakarta) { var builder = GraphQLDirective.newDirective().name(directive.getSimpleName()); Introspection.DirectiveLocation[] validLocations; - if ( - !directive.isAnnotationPresent(Directive.class) && - directive - .getName() - .startsWith("jakarta.validation.constraints") // Jakarta constraint annotations don't have the Directive annotation, so we need to manually add in the locations - ) { + if (isJakarta) { validLocations = new Introspection.DirectiveLocation[] { Introspection.DirectiveLocation.ARGUMENT_DEFINITION, Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION }; diff --git a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java index 09ab5b8a..dea2484b 100644 --- a/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java +++ b/src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java @@ -34,20 +34,27 @@ class DirectivesSchema { private final Collection> global; private final Map, DirectiveCaller> targets; private final Collection> directives; + private final Collection> jakartaDirectives; private Map, DirectiveProcessor> directiveProcessors; private DirectivesSchema( Collection> global, Map, DirectiveCaller> targets, - Collection> directives + Collection> directives, + Collection> jakartaDirectives ) { this.global = global; this.targets = targets; this.directives = directives; + this.jakartaDirectives = jakartaDirectives; } //TODO:mess of exceptions - public static DirectivesSchema build(List> globalDirectives, Set> directiveTypes, Set> jakartaDirectiveTypes) throws ReflectiveOperationException { + public static DirectivesSchema build( + List> globalDirectives, + Set> directiveTypes, + Set> jakartaDirectiveTypes + ) throws ReflectiveOperationException { Map, DirectiveCaller> targets = new HashMap<>(); Collection> allDirectives = new ArrayList<>(); @@ -70,9 +77,15 @@ public static DirectivesSchema build(List> globalDirectiv allDirectives.add((Class) directiveType); } - allDirectives.addAll(jakartaDirectiveTypes); + Collection> jakartaDirectives = new ArrayList<>(); + for (Class jakartaDirectiveType : jakartaDirectiveTypes) { + if (!jakartaDirectiveType.isAnnotation()) { + continue; + } + jakartaDirectives.add((Class) jakartaDirectiveType); + } - return new DirectivesSchema(globalDirectives, targets, allDirectives); + return new DirectivesSchema(globalDirectives, targets, allDirectives, jakartaDirectives); } private DirectiveCaller get(Annotation annotation) { @@ -80,9 +93,7 @@ private DirectiveCaller get(Annotation annotation) { } private DataFetcher wrap(DirectiveCaller directive, T annotation, DataFetcher fetcher) { - return env -> { - return directive.process(annotation, env, fetcher); - }; + return env -> directive.process(annotation, env, fetcher); } public Stream getSchemaDirective() { @@ -104,8 +115,8 @@ private DataFetcher wrap(RestrictTypeFactory directive, DataFetcher fet } return applyRestrict(restrict, response); } catch (Exception e) { - if (e instanceof RuntimeException) { - throw (RuntimeException) e; + if (e instanceof RuntimeException runtimeException) { + throw runtimeException; } throw new RuntimeException(e); } @@ -113,9 +124,9 @@ private DataFetcher wrap(RestrictTypeFactory directive, DataFetcher fet } public boolean target(Method method, TypeMeta meta) { - for (var global : this.global) { + for (var globalRestricts : this.global) { //TODO: extract class - if (global.extractType().isAssignableFrom(meta.getType())) { + if (globalRestricts.extractType().isAssignableFrom(meta.getType())) { return true; } } @@ -134,7 +145,7 @@ public DataFetcher wrap(Method method, TypeMeta meta, DataFetcher fetcher) } } for (Annotation annotation : method.getAnnotations()) { - DirectiveCaller directive = (DirectiveCaller) get(annotation); + DirectiveCaller directive = get(annotation); if (directive != null) { fetcher = wrap(directive, annotation, fetcher); } @@ -142,7 +153,7 @@ public DataFetcher wrap(Method method, TypeMeta meta, DataFetcher fetcher) return fetcher; } - private CompletableFuture applyRestrict(RestrictType restrict, Object response) { + private CompletableFuture applyRestrict(RestrictType restrict, Object response) { if (response instanceof List) { return restrict.filter((List) response); } else if (response instanceof Publisher) { @@ -193,9 +204,10 @@ public void addSchemaDirective(AnnotatedElement element, Class location, Cons } public void processDirectives(EntityProcessor ep) { // Replacement of processSDL - Map, DirectiveProcessor> directiveProcessors = new HashMap<>(); + Map, DirectiveProcessor> directiveProcessorsList = new HashMap<>(); - this.directives.forEach(dir -> directiveProcessors.put(dir, DirectiveProcessor.build(ep, dir))); - this.directiveProcessors = directiveProcessors; + this.directives.forEach(dir -> directiveProcessorsList.put(dir, DirectiveProcessor.build(ep, dir, false))); + this.jakartaDirectives.forEach(dir -> directiveProcessorsList.put(dir, DirectiveProcessor.build(ep, dir, true))); + this.directiveProcessors = directiveProcessorsList; } } diff --git a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java index 1063f5b0..b4c367b2 100644 --- a/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java +++ b/src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java @@ -14,17 +14,17 @@ import com.fleetpin.graphql.builder.annotations.*; import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLSchema; -import jakarta.validation.constraints.Size; +import graphql.scalars.ExtendedScalars; +import jakarta.validation.Constraint; import org.reflections.Reflections; import org.reflections.scanners.Scanners; -import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; + +import static org.reflections.scanners.Scanners.SubTypes; public class SchemaBuilder { @@ -106,8 +106,8 @@ public static Builder builder() { public static class Builder { private DataFetcherRunner dataFetcherRunner = (method, fetcher) -> fetcher; - private List classpaths = new ArrayList<>(); - private List scalars = new ArrayList<>(); + private final List classpaths = new ArrayList<>(); + private final List scalars = new ArrayList<>(); private Builder() {} @@ -128,7 +128,9 @@ public Builder scalar(GraphQLScalarType scalar) { public GraphQLSchema.Builder build() { try { - Reflections reflections = new Reflections(classpaths, Scanners.SubTypes, Scanners.MethodsAnnotated, Scanners.TypesAnnotated); + this.scalar(ExtendedScalars.GraphQLLong); + + Reflections reflections = new Reflections(classpaths, 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); @@ -168,10 +170,11 @@ private static DirectivesSchema getDirectivesSchema(Reflections reflections) thr return DirectivesSchema.build(globalRestricts, directivesTypes, getJakartaAnnotations()); } - private static Set> getJakartaAnnotations() { - Set> list = new HashSet<>(); - list.add(Size.class); - return list; + private static Set> getJakartaAnnotations() { + Reflections reflections = new Reflections("jakarta.validation.constraints", SubTypes.filterResultsBy(c -> true)); + return reflections.getSubTypesOf(Object.class).stream() + .filter(a -> a.isAnnotationPresent(Constraint.class)) + .collect(Collectors.toSet()); } private static List> getGlobalRestricts(Reflections reflections) diff --git a/src/test/java/com/fleetpin/graphql/builder/DirectiveTest.java b/src/test/java/com/fleetpin/graphql/builder/DirectiveTest.java index a374f6a2..5b53a527 100644 --- a/src/test/java/com/fleetpin/graphql/builder/DirectiveTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/DirectiveTest.java @@ -19,10 +19,12 @@ 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 { @@ -87,7 +89,7 @@ public void testDirectiveArgumentDefinition() { 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(30, 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! diff --git a/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java b/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java index 07406222..2573a2e3 100644 --- a/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java +++ b/src/test/java/com/fleetpin/graphql/builder/JakartaValidationDirectiveTest.java @@ -17,22 +17,22 @@ class JakartaValidationDirectiveTest { @Test - void testJakartaArgumentAnnotationChangedToConstraint() { + void testJakartaSizeAnnotationAddedAsDirective() { GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type.directive")).build(); var name = schema.getGraphQLSchema().getFieldDefinition(FieldCoordinates.coordinates(schema.getGraphQLSchema().getMutationType(), "setName")); - var constraint = name.getArgument("name").getAppliedDirective("Size"); - var argument = constraint.getArgument("min"); + var directive = name.getArgument("name").getAppliedDirective("Size"); + var argument = directive.getArgument("min"); var min = argument.getValue(); assertEquals(3, min); } @Test - void testDirectiveArgumentDefinition() { + void testJakartaSizeDirectiveArgumentDefinition() { Map response = execute("query IntrospectionQuery { __schema { directives { name locations args { name } } } }").getData(); List> dir = (List>) ((Map) response.get("__schema")).get("directives"); LinkedHashMap constraint = dir.stream().filter(map -> map.get("name").equals("Size")).collect(Collectors.toList()).get(0); - assertEquals(9, dir.size()); + assertEquals(30, dir.size()); assertEquals("ARGUMENT_DEFINITION", ((List) constraint.get("locations")).get(0)); assertEquals("INPUT_FIELD_DEFINITION", ((List) constraint.get("locations")).get(1)); assertEquals(5, ((List) constraint.get("args")).size()); From 0b984e8d3e9a0ea9442c0efda32b1afefd9d153f Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 17 Mar 2025 16:44:48 +1300 Subject: [PATCH 08/20] Remove unused function --- .../graphql/builder/DirectivesSchema.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/DirectivesSchema.java b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/DirectivesSchema.java index c1c59f74..a8cbc645 100644 --- a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/DirectivesSchema.java +++ b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/DirectivesSchema.java @@ -192,23 +192,6 @@ private CompletableFuture applyRestrict(RestrictType restrict, Object re } } - 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()) - ); - } - public void addSchemaDirective(AnnotatedElement element, Class location, Consumer builder) { for (Annotation annotation : element.getAnnotations()) { var processor = this.directiveProcessors.get(annotation.annotationType()); From 4d625b6b90a53ae4ff49503d5fbc2f5dad34c877 Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 28 Apr 2025 13:21:26 +1200 Subject: [PATCH 09/20] Fix the tests --- .../graphql/builder/MethodProcessor.java | 6 +++--- .../JakartaValidationDirectiveTest.java | 20 +++++++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/MethodProcessor.java b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/MethodProcessor.java index 305b4eba..2aa419eb 100644 --- a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/MethodProcessor.java +++ b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/MethodProcessor.java @@ -44,10 +44,10 @@ class MethodProcessor { private final GraphQLObjectType.Builder graphMutations; private final GraphQLObjectType.Builder graphSubscriptions; - public MethodProcessor(DataFetcherRunner dataFetcherRunner, EntityProcessor entityProcessor, DirectivesSchema diretives) { + public MethodProcessor(DataFetcherRunner dataFetcherRunner, EntityProcessor entityProcessor, DirectivesSchema directives) { this.dataFetcherRunner = dataFetcherRunner; this.entityProcessor = entityProcessor; - this.directives = diretives; + this.directives = directives; this.codeRegistry = GraphQLCodeRegistry.newCodeRegistry(); this.graphQuery = GraphQLObjectType.newObject(); @@ -58,7 +58,7 @@ public MethodProcessor(DataFetcherRunner dataFetcherRunner, EntityProcessor enti graphSubscriptions.name("Subscriptions"); } - void process(AuthorizerSchema authorizer, Method method) throws ReflectiveOperationException { + void process(AuthorizerSchema authorizer, Method method) { if (!Modifier.isStatic(method.getModifiers())) { throw new RuntimeException("End point must be a static method"); } diff --git a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java index a1597fd0..f048c488 100644 --- a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java +++ b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java @@ -18,7 +18,7 @@ class JakartaValidationDirectiveTest { @Test void testJakartaSizeAnnotationAddedAsDirective() { - GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.fleetpin.graphql.builder.type.directive")).build(); + GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.phocassoftware.graphql.builder.type.directive")).build(); var name = schema.getGraphQLSchema().getFieldDefinition(FieldCoordinates.coordinates(schema.getGraphQLSchema().getMutationType(), "setName")); var directive = name.getArgument("name").getAppliedDirective("Size"); var argument = directive.getArgument("min"); @@ -28,7 +28,7 @@ void testJakartaSizeAnnotationAddedAsDirective() { @Test void testJakartaSizeDirectiveArgumentDefinition() { - Map response = execute("query IntrospectionQuery { __schema { directives { name locations args { name } } } }").getData(); + Map response = execute("query IntrospectionQuery { __schema { directives { name locations args { name } } } }", null).getData(); List> dir = (List>) ((Map) response.get("__schema")).get("directives"); LinkedHashMap constraint = dir.stream().filter(map -> map.get("name").equals("Size")).collect(Collectors.toList()).get(0); @@ -43,12 +43,24 @@ void testJakartaSizeDirectiveArgumentDefinition() { assertEquals("{name=groups}", ((List) constraint.get("args")).get(4).toString()); } - private ExecutionResult execute(String query) { - GraphQLSchema preSchema = SchemaBuilder.builder().classpath("com.fleetpin.graphql.builder.type.directive").build().build(); + @Test + void testJakartaValidationIsApplied() { + var name = "Roger"; + Map response = execute("mutation setName($name: String!){setName(name: $name)} ", Map.of("name", name)).getData(); + var result = response.get("setName"); + + assertEquals(name, result); + } + + private ExecutionResult execute(String query, Map variables) { + GraphQLSchema preSchema = SchemaBuilder.builder().classpath("com.phocassoftware.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); + } return schema.execute(input); } } From 62d84502d38265fb589d753cec60bbc3574952e6 Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 28 Apr 2025 15:43:30 +1200 Subject: [PATCH 10/20] Add test for min and max on an integer --- .../JakartaValidationDirectiveTest.java | 30 ++++++++++++++++++- .../graphql/builder/type/directive/Cat.java | 7 +++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java index f048c488..90dada85 100644 --- a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java +++ b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java @@ -14,6 +14,7 @@ import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; class JakartaValidationDirectiveTest { @Test @@ -44,12 +45,39 @@ void testJakartaSizeDirectiveArgumentDefinition() { } @Test - void testJakartaValidationIsApplied() { + void testJakartaSizeValidationIsApplied() { var name = "Roger"; Map response = execute("mutation setName($name: String!){setName(name: $name)} ", Map.of("name", name)).getData(); var result = response.get("setName"); assertEquals(name, result); + + name = "Po"; + response = execute("mutation setName($name: String!){setName(name: $name)} ", Map.of("name", name)).getData(); + + // TODO: response is currently null, I would expect it to return an error. + assertNull(response); + } + + @Test + void testJakartaMinAndMaxValidationIsApplied() { + var age = 4; + Map response = execute("mutation setAge($age: Int!){setAge(age: $age)} ", Map.of("age", age)).getData(); + var result = response.get("setAge"); + + assertEquals(age, result); + + age = 2; + response = execute("mutation setAge($age: int!){setAge(age: $age)} ", Map.of("age", age)).getData(); + + // TODO: response is currently null, I would expect it to return an error. + assertNull(response); + + age = 100; + response = execute("mutation setAge($age: int!){setAge(age: $age)} ", Map.of("age", age)).getData(); + + // TODO: response is currently null, I would expect it to return an error. + assertNull(response); } private ExecutionResult execute(String query, Map variables) { diff --git a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/type/directive/Cat.java b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/type/directive/Cat.java index e24822dd..ed0b7295 100644 --- a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/type/directive/Cat.java +++ b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/type/directive/Cat.java @@ -14,6 +14,8 @@ import com.phocassoftware.graphql.builder.annotations.Entity; import com.phocassoftware.graphql.builder.annotations.Mutation; import com.phocassoftware.graphql.builder.annotations.Query; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Size; @Entity @@ -48,6 +50,11 @@ public static String setName(@Size(min = 3) String name) { return name; } + @Mutation + public static int setAge(@Min(value = 3) @Max(value = 99) int age) { + return age; + } + @Query @Admin("tabby") public static String allowed(String name) { From 9f35bf3d0095fb1966c37f4479b9032f4a05a02a Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 28 Apr 2025 15:53:12 +1200 Subject: [PATCH 11/20] Add test for a record --- .../JakartaValidationDirectiveTest.java | 10 +++++ .../type/directive/record/CatRecord.java | 37 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 graphql-builder/src/test/java/com/phocassoftware/graphql/builder/type/directive/record/CatRecord.java diff --git a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java index 90dada85..19fb09b7 100644 --- a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java +++ b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java @@ -27,6 +27,16 @@ void testJakartaSizeAnnotationAddedAsDirective() { assertEquals(3, min); } + @Test + void testJakartaSizeAnnotationAddedAsDirectiveOnARecord() { + GraphQL schema = GraphQL.newGraphQL(SchemaBuilder.build("com.phocassoftware.graphql.builder.type.directive.record")).build(); + var name = schema.getGraphQLSchema().getFieldDefinition(FieldCoordinates.coordinates(schema.getGraphQLSchema().getMutationType(), "setName")); + var directive = name.getArgument("name").getAppliedDirective("Size"); + var argument = directive.getArgument("min"); + var min = argument.getValue(); + assertEquals(3, min); + } + @Test void testJakartaSizeDirectiveArgumentDefinition() { Map response = execute("query IntrospectionQuery { __schema { directives { name locations args { name } } } }", null).getData(); diff --git a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/type/directive/record/CatRecord.java b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/type/directive/record/CatRecord.java new file mode 100644 index 00000000..1983409d --- /dev/null +++ b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/type/directive/record/CatRecord.java @@ -0,0 +1,37 @@ +/* + * 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.phocassoftware.graphql.builder.type.directive.record; + +import com.phocassoftware.graphql.builder.annotations.Entity; +import com.phocassoftware.graphql.builder.annotations.Mutation; +import com.phocassoftware.graphql.builder.annotations.Query; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Size; + +@Entity +public record CatRecord(int age, String name) { + @Mutation + public static String setName(@Size(min = 3) String name) { + return name; + } + + @Mutation + public static int setAge(@Min(value = 3) @Max(value = 99) int age) { + return age; + } + + @Query + public static String getName(String name) { + return name; + } +} From 5ea1d0e3ee298224e2fb82d49487226f5f0593d5 Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 26 May 2025 11:09:32 +1200 Subject: [PATCH 12/20] Tests pass --- .../graphql/builder/DirectivesSchema.java | 6 ++---- .../graphql/builder/MethodProcessor.java | 14 ++++++++++---- .../graphql/builder/SchemaBuilder.java | 14 +++++++++----- .../builder/JakartaValidationDirectiveTest.java | 16 ++++++---------- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/DirectivesSchema.java b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/DirectivesSchema.java index a8cbc645..fb0fe63c 100644 --- a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/DirectivesSchema.java +++ b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/DirectivesSchema.java @@ -51,11 +51,9 @@ private DirectivesSchema( this.jakartaDirectives = jakartaDirectives; } - //TODO:mess of exceptions + // TODO:mess of exceptions public static DirectivesSchema build( - List> globalDirectives, - Set> directiveTypes, - Set> jakartaDirectiveTypes + List> globalDirectives, Set> directiveTypes, Set> jakartaDirectiveTypes ) throws ReflectiveOperationException { Map, DirectiveCaller> targets = new HashMap<>(); diff --git a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/MethodProcessor.java b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/MethodProcessor.java index 2aa419eb..8c188ad9 100644 --- a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/MethodProcessor.java +++ b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/MethodProcessor.java @@ -21,8 +21,16 @@ import graphql.GraphQLContext; import graphql.GraphQLError; import graphql.execution.DataFetcherResult; -import graphql.schema.*; +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 graphql.validation.rules.ValidationRules; import java.lang.annotation.Annotation; @@ -151,9 +159,7 @@ private DataFetcher buildDataFetcher(TypeMeta meta, Method method) { resolvers[i] = buildResolver(name, argMeta, parameter.getAnnotations()); } - ValidationRules validationRules = ValidationRules - .newValidationRules() - .build(); + ValidationRules validationRules = ValidationRules.newValidationRules().build(); DataFetcher fetcher = env -> { try { diff --git a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/SchemaBuilder.java b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/SchemaBuilder.java index d67c3957..d4666ac1 100644 --- a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/SchemaBuilder.java +++ b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/SchemaBuilder.java @@ -12,18 +12,22 @@ package com.phocassoftware.graphql.builder; import com.phocassoftware.graphql.builder.annotations.*; +import graphql.scalars.ExtendedScalars; import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLSchema; -import graphql.scalars.ExtendedScalars; -import jakarta.validation.Constraint; -import org.reflections.Reflections; -import org.reflections.scanners.Scanners; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; +import jakarta.validation.Constraint; +import org.reflections.Reflections; +import org.reflections.scanners.Scanners; + import static org.reflections.scanners.Scanners.SubTypes; public class SchemaBuilder { diff --git a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java index 19fb09b7..356927cf 100644 --- a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java +++ b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java @@ -14,7 +14,6 @@ import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; class JakartaValidationDirectiveTest { @Test @@ -63,10 +62,9 @@ void testJakartaSizeValidationIsApplied() { assertEquals(name, result); name = "Po"; - response = execute("mutation setName($name: String!){setName(name: $name)} ", Map.of("name", name)).getData(); + var error = execute("mutation setName($name: String!){setName(name: $name)} ", Map.of("name", name)).getErrors().getFirst(); - // TODO: response is currently null, I would expect it to return an error. - assertNull(response); + assertEquals("size must be between 3 and 2147483647", error.getMessage()); } @Test @@ -78,16 +76,14 @@ void testJakartaMinAndMaxValidationIsApplied() { assertEquals(age, result); age = 2; - response = execute("mutation setAge($age: int!){setAge(age: $age)} ", Map.of("age", age)).getData(); + var error = execute("mutation setAge($age: Int!){setAge(age: $age)} ", Map.of("age", age)).getErrors().getFirst(); - // TODO: response is currently null, I would expect it to return an error. - assertNull(response); + assertEquals("must be greater than or equal to 3", error.getMessage()); age = 100; - response = execute("mutation setAge($age: int!){setAge(age: $age)} ", Map.of("age", age)).getData(); + error = execute("mutation setAge($age: Int!){setAge(age: $age)} ", Map.of("age", age)).getErrors().getFirst(); - // TODO: response is currently null, I would expect it to return an error. - assertNull(response); + assertEquals("must be less than or equal to 99", error.getMessage()); } private ExecutionResult execute(String query, Map variables) { From 0debef35c9220fc9edd1517298e7706fafdc4734 Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 26 May 2025 13:40:59 +1200 Subject: [PATCH 13/20] Put validate behinda flag on the SchemaBuilder --- .../graphql/builder/EntityProcessor.java | 7 ++-- .../graphql/builder/MethodProcessor.java | 32 +++++++++++++------ .../graphql/builder/SchemaBuilder.java | 12 +++++-- .../JakartaValidationDirectiveTest.java | 27 +++++++++++----- 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/EntityProcessor.java b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/EntityProcessor.java index 519942c9..1d4041fb 100644 --- a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/EntityProcessor.java +++ b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/EntityProcessor.java @@ -21,6 +21,7 @@ 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; @@ -38,13 +39,13 @@ public class EntityProcessor { private final Map entities; private final MethodProcessor methodProcessor; - EntityProcessor(DataFetcherRunner dataFetcherRunner, List scalars, DirectivesSchema diretives) { - this.methodProcessor = new MethodProcessor(dataFetcherRunner, this, diretives); + EntityProcessor(DataFetcherRunner dataFetcherRunner, List scalars, DirectivesSchema directives) { + this.methodProcessor = new MethodProcessor(dataFetcherRunner, this, directives); this.entities = new HashMap<>(); addDefaults(); addScalars(scalars); - this.directives = diretives; + this.directives = directives; } private void addDefaults() { diff --git a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/MethodProcessor.java b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/MethodProcessor.java index 8c188ad9..c635619f 100644 --- a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/MethodProcessor.java +++ b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/MethodProcessor.java @@ -66,7 +66,7 @@ public MethodProcessor(DataFetcherRunner dataFetcherRunner, EntityProcessor enti graphSubscriptions.name("Subscriptions"); } - void process(AuthorizerSchema authorizer, Method method) { + void process(AuthorizerSchema authorizer, Method method, boolean shouldValidate) { if (!Modifier.isStatic(method.getModifiers())) { throw new RuntimeException("End point must be a static method"); } @@ -85,10 +85,14 @@ void process(AuthorizerSchema authorizer, Method method) { return; } - object.field(process(authorizer, coordinates, null, method)); + object.field(process(authorizer, coordinates, null, method, shouldValidate)); } Builder process(AuthorizerSchema authorizer, FieldCoordinates coordinates, TypeMeta parentMeta, Method method) { + return process(authorizer, coordinates, parentMeta, method, false); + } + + Builder process(AuthorizerSchema authorizer, FieldCoordinates coordinates, TypeMeta parentMeta, Method method, boolean shouldValidate) { GraphQLFieldDefinition.Builder field = GraphQLFieldDefinition.newFieldDefinition(); entityProcessor.addSchemaDirective(method, method.getDeclaringClass(), field::withAppliedDirective); @@ -130,14 +134,20 @@ Builder process(AuthorizerSchema authorizer, FieldCoordinates coordinates, TypeM field.argument(argument); } - DataFetcher fetcher = buildFetcher(directives, authorizer, method, meta); + DataFetcher fetcher = buildFetcher(directives, authorizer, method, meta, shouldValidate); codeRegistry.dataFetcher(coordinates, fetcher); return field; } - private DataFetcher buildFetcher(DirectivesSchema diretives, AuthorizerSchema authorizer, Method method, TypeMeta meta) { - DataFetcher fetcher = buildDataFetcher(meta, method); - fetcher = diretives.wrap(method, meta, fetcher); + private DataFetcher buildFetcher( + DirectivesSchema directives, + AuthorizerSchema authorizer, + Method method, + TypeMeta meta, + boolean shouldValidate + ) { + DataFetcher fetcher = buildDataFetcher(meta, method, shouldValidate); + fetcher = directives.wrap(method, meta, fetcher); if (authorizer != null) { fetcher = authorizer.wrap(fetcher, method); @@ -145,7 +155,7 @@ private DataFetcher buildFetcher(DirectivesSchema dire return fetcher; } - private DataFetcher buildDataFetcher(TypeMeta meta, Method method) { + private DataFetcher buildDataFetcher(TypeMeta meta, Method method, boolean shouldValidate) { Function[] resolvers = new Function[method.getParameterCount()]; method.setAccessible(true); @@ -163,9 +173,11 @@ private DataFetcher buildDataFetcher(TypeMeta meta, Method method) { DataFetcher fetcher = env -> { try { - List errors = validationRules.runValidationRules(env); - if (!errors.isEmpty()) { - return DataFetcherResult.newResult().errors(errors).data(null).build(); + if (shouldValidate) { + List errors = validationRules.runValidationRules(env); + if (!errors.isEmpty()) { + return DataFetcherResult.newResult().errors(errors).data(null).build(); + } } Object[] args = new Object[resolvers.length]; diff --git a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/SchemaBuilder.java b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/SchemaBuilder.java index d4666ac1..bdc6b416 100644 --- a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/SchemaBuilder.java +++ b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/SchemaBuilder.java @@ -57,10 +57,10 @@ private SchemaBuilder processTypes(Set> types) { return this; } - private SchemaBuilder process(HashSet endPoints) throws ReflectiveOperationException { + private SchemaBuilder process(HashSet endPoints, boolean shouldValidate) throws ReflectiveOperationException { var methodProcessor = this.entityProcessor.getMethodProcessor(); for (var method : endPoints) { - methodProcessor.process(authorizer, method); + methodProcessor.process(authorizer, method, shouldValidate); } return this; @@ -112,6 +112,7 @@ public static class Builder { private DataFetcherRunner dataFetcherRunner = (method, fetcher) -> fetcher; private final List classpaths = new ArrayList<>(); private final List scalars = new ArrayList<>(); + private boolean shouldValidate = false; private Builder() {} @@ -130,6 +131,11 @@ public Builder scalar(GraphQLScalarType scalar) { return this; } + public Builder validate() { + this.shouldValidate = true; + return this; + } + public GraphQLSchema.Builder build() { try { this.scalar(ExtendedScalars.GraphQLLong); @@ -158,7 +164,7 @@ public GraphQLSchema.Builder build() { return new SchemaBuilder(dataFetcherRunner, scalars, directivesSchema, authorizer) .processTypes(types) - .process(endPoints) + .process(endPoints, shouldValidate) .build(schemaConfiguration); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); diff --git a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java index 356927cf..69283165 100644 --- a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java +++ b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java @@ -38,7 +38,7 @@ void testJakartaSizeAnnotationAddedAsDirectiveOnARecord() { @Test void testJakartaSizeDirectiveArgumentDefinition() { - Map response = execute("query IntrospectionQuery { __schema { directives { name locations args { name } } } }", null).getData(); + Map response = execute("query IntrospectionQuery { __schema { directives { name locations args { name } } } }", null, true).getData(); List> dir = (List>) ((Map) response.get("__schema")).get("directives"); LinkedHashMap constraint = dir.stream().filter(map -> map.get("name").equals("Size")).collect(Collectors.toList()).get(0); @@ -56,38 +56,49 @@ void testJakartaSizeDirectiveArgumentDefinition() { @Test void testJakartaSizeValidationIsApplied() { var name = "Roger"; - Map response = execute("mutation setName($name: String!){setName(name: $name)} ", Map.of("name", name)).getData(); + Map response = execute("mutation setName($name: String!){setName(name: $name)} ", Map.of("name", name), true).getData(); var result = response.get("setName"); assertEquals(name, result); name = "Po"; - var error = execute("mutation setName($name: String!){setName(name: $name)} ", Map.of("name", name)).getErrors().getFirst(); + var error = execute("mutation setName($name: String!){setName(name: $name)} ", Map.of("name", name), true).getErrors().getFirst(); assertEquals("size must be between 3 and 2147483647", error.getMessage()); } + @Test + void testJakartaSizeValidationIsNotAppliedWhenFlagIsFalse() { + var name = "Po"; + Map response = execute("mutation setName($name: String!){setName(name: $name)} ", Map.of("name", name), false).getData(); + var result = response.get("setName"); + + assertEquals(name, result); + } + @Test void testJakartaMinAndMaxValidationIsApplied() { var age = 4; - Map response = execute("mutation setAge($age: Int!){setAge(age: $age)} ", Map.of("age", age)).getData(); + Map response = execute("mutation setAge($age: Int!){setAge(age: $age)} ", Map.of("age", age), true).getData(); var result = response.get("setAge"); assertEquals(age, result); age = 2; - var error = execute("mutation setAge($age: Int!){setAge(age: $age)} ", Map.of("age", age)).getErrors().getFirst(); + var error = execute("mutation setAge($age: Int!){setAge(age: $age)} ", Map.of("age", age), true).getErrors().getFirst(); assertEquals("must be greater than or equal to 3", error.getMessage()); age = 100; - error = execute("mutation setAge($age: Int!){setAge(age: $age)} ", Map.of("age", age)).getErrors().getFirst(); + error = execute("mutation setAge($age: Int!){setAge(age: $age)} ", Map.of("age", age), true).getErrors().getFirst(); assertEquals("must be less than or equal to 99", error.getMessage()); } - private ExecutionResult execute(String query, Map variables) { - GraphQLSchema preSchema = SchemaBuilder.builder().classpath("com.phocassoftware.graphql.builder.type.directive").build().build(); + private ExecutionResult execute(String query, Map variables, boolean validate) { + var builder = SchemaBuilder.builder().classpath("com.phocassoftware.graphql.builder.type.directive"); + if (validate) builder = builder.validate(); + GraphQLSchema preSchema = builder.build().build(); GraphQL schema = GraphQL.newGraphQL(new IntrospectionWithDirectivesSupport().apply(preSchema)).build(); var input = ExecutionInput.newExecutionInput(); From 0f0b741ddebfaf6fbf150340ac56163fa50f0de5 Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 26 May 2025 14:53:21 +1200 Subject: [PATCH 14/20] Downgrade graphql-java to 22 --- graphql-builder/pom.xml | 26 +++++++++---------- .../graphql/builder/DirectiveTest.java | 2 +- .../JakartaValidationDirectiveTest.java | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/graphql-builder/pom.xml b/graphql-builder/pom.xml index d867e367..33bc232e 100644 --- a/graphql-builder/pom.xml +++ b/graphql-builder/pom.xml @@ -17,20 +17,20 @@ graphql-builder Builds a graphql schema from a model using reflection - - com.phocassoftware - graphql-builder-parent - 1.1.0-SNAPSHOT - + + com.phocassoftware + graphql-builder-parent + 1.1.0-SNAPSHOT + graphql-builder - - 5.12.2 - UTF-8 - 2.19.0 - 24.0 - + + 5.12.2 + UTF-8 + 2.19.0 + 22.0 + @@ -49,12 +49,12 @@ com.graphql-java graphql-java-extended-scalars - 21.0 + ${graphql.version} com.graphql-java graphql-java-extended-validation - 21.0 + ${graphql.version} org.reflections diff --git a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/DirectiveTest.java b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/DirectiveTest.java index a23d8c90..ef3964ba 100644 --- a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/DirectiveTest.java +++ b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/DirectiveTest.java @@ -89,7 +89,7 @@ public void testDirectiveArgumentDefinition() { 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(32, dir.size()); + assertEquals(30, 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! diff --git a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java index 7677f7e2..69283165 100644 --- a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java +++ b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java @@ -42,7 +42,7 @@ void testJakartaSizeDirectiveArgumentDefinition() { List> dir = (List>) ((Map) response.get("__schema")).get("directives"); LinkedHashMap constraint = dir.stream().filter(map -> map.get("name").equals("Size")).collect(Collectors.toList()).get(0); - assertEquals(32, dir.size()); + assertEquals(30, dir.size()); assertEquals("ARGUMENT_DEFINITION", ((List) constraint.get("locations")).get(0)); assertEquals("INPUT_FIELD_DEFINITION", ((List) constraint.get("locations")).get(1)); assertEquals(5, ((List) constraint.get("args")).size()); From aa7ce9e1d76d1b4332c6c6f1388a18528e448472 Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 26 May 2025 15:00:42 +1200 Subject: [PATCH 15/20] formatter --- .../graphql/builder/DirectivesSchema.java | 37 ++++++++++--------- .../graphql/builder/SchemaBuilder.java | 4 +- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/DirectivesSchema.java b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/DirectivesSchema.java index fb0fe63c..7da6dbd8 100644 --- a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/DirectivesSchema.java +++ b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/DirectivesSchema.java @@ -53,7 +53,9 @@ private DirectivesSchema( // TODO:mess of exceptions public static DirectivesSchema build( - List> globalDirectives, Set> directiveTypes, Set> jakartaDirectiveTypes + List> globalDirectives, + Set> directiveTypes, + Set> jakartaDirectiveTypes ) throws ReflectiveOperationException { Map, DirectiveCaller> targets = new HashMap<>(); @@ -104,28 +106,27 @@ private DataFetcher wrap(RestrictTypeFactory directive, DataFetcher fet // 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 -> - 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 runtimeException) { - throw runtimeException; - } - throw new 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 runtimeException) { + throw runtimeException; + } + throw new RuntimeException(e); + } + }); } public boolean target(Method method, TypeMeta meta) { for (var globalRestricts : this.global) { - //TODO: extract class + // TODO: extract class if (globalRestricts.extractType().isAssignableFrom(meta.getType())) { return true; } diff --git a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/SchemaBuilder.java b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/SchemaBuilder.java index bdc6b416..df939412 100644 --- a/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/SchemaBuilder.java +++ b/graphql-builder/src/main/java/com/phocassoftware/graphql/builder/SchemaBuilder.java @@ -182,7 +182,9 @@ private static DirectivesSchema getDirectivesSchema(Reflections reflections) thr private static Set> getJakartaAnnotations() { Reflections reflections = new Reflections("jakarta.validation.constraints", SubTypes.filterResultsBy(c -> true)); - return reflections.getSubTypesOf(Object.class).stream() + return reflections + .getSubTypesOf(Object.class) + .stream() .filter(a -> a.isAnnotationPresent(Constraint.class)) .collect(Collectors.toSet()); } From 23ad41b27491cb1ae09cf5cf8b3f5f00b075f2f1 Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 26 May 2025 15:02:46 +1200 Subject: [PATCH 16/20] Licenses --- .../builder/JakartaValidationDirectiveTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java index 69283165..677f5815 100644 --- a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java +++ b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java @@ -1,3 +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.phocassoftware.graphql.builder; import graphql.ExecutionInput; From aa6b1cb7a16e9a6a4fe2f537198863008c5041bd Mon Sep 17 00:00:00 2001 From: Josie Smith Date: Mon, 26 May 2025 15:09:16 +1200 Subject: [PATCH 17/20] Downgrade graphql-java to 22 --- .../graphql/database/manager/Database.java | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/graphql-database-manager-core/src/main/java/com/phocassoftware/graphql/database/manager/Database.java b/graphql-database-manager-core/src/main/java/com/phocassoftware/graphql/database/manager/Database.java index e8766fb9..f38d0c5f 100644 --- a/graphql-database-manager-core/src/main/java/com/phocassoftware/graphql/database/manager/Database.java +++ b/graphql-database-manager-core/src/main/java/com/phocassoftware/graphql/database/manager/Database.java @@ -17,6 +17,7 @@ import com.phocassoftware.graphql.database.manager.util.BackupItem; import com.phocassoftware.graphql.database.manager.util.HistoryBackupItem; import com.phocassoftware.graphql.database.manager.util.TableCoreUtil; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -32,8 +33,8 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; + import org.dataloader.DataLoader; -import org.dataloader.DataLoaderFactory; import org.dataloader.DataLoaderOptions; @SuppressWarnings("unchecked") @@ -60,29 +61,32 @@ public class Database { this.submitted = new AtomicInteger(); items = new TableDataLoader<>( - DataLoaderFactory - .newDataLoader( - driver::get, - DataLoaderOptions.newOptions().setMaxBatchSize(driver.maxBatchSize()).build() - ), + new DataLoader, Table>( + keys -> { + return driver.get(keys); + }, + DataLoaderOptions.newOptions().setMaxBatchSize(driver.maxBatchSize()) + ), this::handleFuture ); // will auto call global queries = new TableDataLoader<>( - DataLoaderFactory - .newDataLoader( - keys -> merge(keys.stream().map(driver::query)), - DataLoaderOptions.newOptions().setBatchingEnabled(false).build() - ), + new DataLoader, List>( + keys -> { + return merge(keys.stream().map(driver::query)); + }, + DataLoaderOptions.newOptions().setBatchingEnabled(false) + ), this::handleFuture ); // will auto call global queryHistories = new TableDataLoader<>( - DataLoaderFactory - .newDataLoader( - keys -> merge(keys.stream().map(driver::queryHistory)), - DataLoaderOptions.newOptions().setBatchingEnabled(false).build() - ), + new DataLoader, List
>( + keys -> { + return merge(keys.stream().map(driver::queryHistory)); + }, + DataLoaderOptions.newOptions().setBatchingEnabled(false) + ), this::handleFuture ); // will auto call global @@ -254,8 +258,7 @@ public CompletableFuture destroyOrganisation(final String organisationI * * @param database entity type to update * @param entity revision must match database or request will fail - * @return updated entity with the revision incremented by one - * CompletableFuture will fail with a RevisionMismatchException + * @return updated entity with the revision incremented by one CompletableFuture will fail with a RevisionMismatchException */ public CompletableFuture put(T entity) { return put(entity, true); @@ -265,8 +268,7 @@ public CompletableFuture put(T entity) { * @param database entity type to update * @param entity revision must match database or request will fail * @param check Will only pass if the entity revision matches what is currently in the database - * @return updated entity with the revision incremented by one - * CompletableFuture will fail with a RevisionMismatchException + * @return updated entity with the revision incremented by one CompletableFuture will fail with a RevisionMismatchException */ public CompletableFuture put(T entity, boolean check) { return putAllow From 8208742260247a7f8b6f632d41f9c9c0f29c6705 Mon Sep 17 00:00:00 2001 From: Josie Atkins Date: Mon, 9 Jun 2025 09:51:36 +1200 Subject: [PATCH 18/20] Downgrade to graphql 22 --- graphql-builder/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/graphql-builder/pom.xml b/graphql-builder/pom.xml index 332ba082..e0293ead 100644 --- a/graphql-builder/pom.xml +++ b/graphql-builder/pom.xml @@ -25,12 +25,12 @@ graphql-builder - - 5.13.0 - UTF-8 - 2.19.0 - 24.1 - + + 5.13.0 + UTF-8 + 2.19.0 + 22.0 + From ee548daecfe154b81fb65465c141a007c6f115d7 Mon Sep 17 00:00:00 2001 From: Josie Atkins Date: Mon, 7 Jul 2025 09:27:30 +1200 Subject: [PATCH 19/20] Revert "Downgrade to graphql 22" This reverts commit 8208742260247a7f8b6f632d41f9c9c0f29c6705. # Conflicts: # graphql-builder/pom.xml --- graphql-builder/pom.xml | 6 ++-- .../graphql/builder/DirectiveTest.java | 2 +- .../JakartaValidationDirectiveTest.java | 2 +- .../graphql/database/manager/Database.java | 34 +++++++++---------- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/graphql-builder/pom.xml b/graphql-builder/pom.xml index e0293ead..2a53ff89 100644 --- a/graphql-builder/pom.xml +++ b/graphql-builder/pom.xml @@ -29,7 +29,7 @@ 5.13.0 UTF-8 2.19.0 - 22.0 + 24.1 @@ -49,12 +49,12 @@ com.graphql-java graphql-java-extended-scalars - ${graphql.version} + 22.0 com.graphql-java graphql-java-extended-validation - ${graphql.version} + 22.0 org.reflections diff --git a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/DirectiveTest.java b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/DirectiveTest.java index ef3964ba..a23d8c90 100644 --- a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/DirectiveTest.java +++ b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/DirectiveTest.java @@ -89,7 +89,7 @@ public void testDirectiveArgumentDefinition() { 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(30, dir.size()); + assertEquals(32, 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! diff --git a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java index 677f5815..8406e685 100644 --- a/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java +++ b/graphql-builder/src/test/java/com/phocassoftware/graphql/builder/JakartaValidationDirectiveTest.java @@ -53,7 +53,7 @@ void testJakartaSizeDirectiveArgumentDefinition() { List> dir = (List>) ((Map) response.get("__schema")).get("directives"); LinkedHashMap constraint = dir.stream().filter(map -> map.get("name").equals("Size")).collect(Collectors.toList()).get(0); - assertEquals(30, dir.size()); + assertEquals(32, dir.size()); assertEquals("ARGUMENT_DEFINITION", ((List) constraint.get("locations")).get(0)); assertEquals("INPUT_FIELD_DEFINITION", ((List) constraint.get("locations")).get(1)); assertEquals(5, ((List) constraint.get("args")).size()); diff --git a/graphql-database-manager-core/src/main/java/com/phocassoftware/graphql/database/manager/Database.java b/graphql-database-manager-core/src/main/java/com/phocassoftware/graphql/database/manager/Database.java index f38d0c5f..fe3e9fcc 100644 --- a/graphql-database-manager-core/src/main/java/com/phocassoftware/graphql/database/manager/Database.java +++ b/graphql-database-manager-core/src/main/java/com/phocassoftware/graphql/database/manager/Database.java @@ -35,6 +35,7 @@ import java.util.stream.Stream; import org.dataloader.DataLoader; +import org.dataloader.DataLoaderFactory; import org.dataloader.DataLoaderOptions; @SuppressWarnings("unchecked") @@ -61,32 +62,29 @@ public class Database { this.submitted = new AtomicInteger(); items = new TableDataLoader<>( - new DataLoader, Table>( - keys -> { - return driver.get(keys); - }, - DataLoaderOptions.newOptions().setMaxBatchSize(driver.maxBatchSize()) - ), + DataLoaderFactory + .newDataLoader( + driver::get, + DataLoaderOptions.newOptions().setMaxBatchSize(driver.maxBatchSize()).build() + ), this::handleFuture ); // will auto call global queries = new TableDataLoader<>( - new DataLoader, List
>( - keys -> { - return merge(keys.stream().map(driver::query)); - }, - DataLoaderOptions.newOptions().setBatchingEnabled(false) - ), + DataLoaderFactory + .newDataLoader( + keys -> merge(keys.stream().map(driver::query)), + DataLoaderOptions.newOptions().setBatchingEnabled(false).build() + ), this::handleFuture ); // will auto call global queryHistories = new TableDataLoader<>( - new DataLoader, List
>( - keys -> { - return merge(keys.stream().map(driver::queryHistory)); - }, - DataLoaderOptions.newOptions().setBatchingEnabled(false) - ), + DataLoaderFactory + .newDataLoader( + keys -> merge(keys.stream().map(driver::queryHistory)), + DataLoaderOptions.newOptions().setBatchingEnabled(false).build() + ), this::handleFuture ); // will auto call global From 50e0b05198958722b64913c66fec46cf7fd6074b Mon Sep 17 00:00:00 2001 From: Josie Atkins Date: Mon, 21 Jul 2025 09:03:29 +1200 Subject: [PATCH 20/20] Upgrade libraries to graphql 24 --- graphql-builder/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphql-builder/pom.xml b/graphql-builder/pom.xml index 2a53ff89..6f5e95ec 100644 --- a/graphql-builder/pom.xml +++ b/graphql-builder/pom.xml @@ -49,12 +49,12 @@ com.graphql-java graphql-java-extended-scalars - 22.0 + 24.0 com.graphql-java graphql-java-extended-validation - 22.0 + 24.0 org.reflections