diff --git a/src/main/java/graphql/annotations/BatchedTypeFunction.java b/src/main/java/graphql/annotations/BatchedTypeFunction.java index 96d85f14..705e133c 100644 --- a/src/main/java/graphql/annotations/BatchedTypeFunction.java +++ b/src/main/java/graphql/annotations/BatchedTypeFunction.java @@ -37,7 +37,7 @@ public boolean canBuildType(final Class aClass, final AnnotatedType type) { } @Override - public graphql.schema.GraphQLType buildType(String typeName, final Class aClass, final AnnotatedType annotatedType) { + public graphql.schema.GraphQLType buildType(final boolean inputType, final Class aClass, final AnnotatedType annotatedType) { if (!aClass.isAssignableFrom(List.class)) { throw new IllegalArgumentException("Batched method should return a List"); } @@ -52,7 +52,6 @@ public graphql.schema.GraphQLType buildType(String typeName, final Class aCla } else { klass = (Class) arg.getType(); } - typeName = klass.getSimpleName(); - return defaultTypeFunction.buildType(typeName, klass, arg); + return defaultTypeFunction.buildType(inputType, klass, arg); } } diff --git a/src/main/java/graphql/annotations/DefaultTypeFunction.java b/src/main/java/graphql/annotations/DefaultTypeFunction.java index 2385cdda..cb444d61 100644 --- a/src/main/java/graphql/annotations/DefaultTypeFunction.java +++ b/src/main/java/graphql/annotations/DefaultTypeFunction.java @@ -15,27 +15,20 @@ package graphql.annotations; import graphql.Scalars; -import graphql.schema.GraphQLEnumType; import graphql.schema.GraphQLList; import graphql.schema.GraphQLType; -import graphql.schema.GraphQLTypeReference; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.*; import java.lang.reflect.AnnotatedParameterizedType; import java.lang.reflect.AnnotatedType; -import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Stream; import static graphql.annotations.util.NamingKit.toGraphqlName; -import static graphql.schema.GraphQLEnumType.newEnum; @Component(property = "type=default") public class DefaultTypeFunction implements TypeFunction { @@ -69,7 +62,7 @@ public boolean canBuildType(Class aClass, AnnotatedType annotatedType) { } @Override - public GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType) { + public GraphQLType buildType(boolean inputType, Class aClass, AnnotatedType annotatedType) { return Scalars.GraphQLID; } } @@ -87,7 +80,7 @@ public boolean canBuildType(Class aClass, AnnotatedType annotatedType) { } @Override - public GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType) { + public GraphQLType buildType(boolean inputType, Class aClass, AnnotatedType annotatedType) { return Scalars.GraphQLString; } } @@ -105,7 +98,7 @@ public boolean canBuildType(Class aClass, AnnotatedType annotatedType) { } @Override - public GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType) { + public GraphQLType buildType(boolean inputType, Class aClass, AnnotatedType annotatedType) { return Scalars.GraphQLBoolean; } } @@ -123,7 +116,7 @@ public boolean canBuildType(Class aClass, AnnotatedType annotatedType) { } @Override - public GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType) { + public GraphQLType buildType(boolean inputType, Class aClass, AnnotatedType annotatedType) { return Scalars.GraphQLFloat; } } @@ -141,7 +134,7 @@ public boolean canBuildType(Class aClass, AnnotatedType annotatedType) { } @Override - public GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType) { + public GraphQLType buildType(boolean inputType, Class aClass, AnnotatedType annotatedType) { return Scalars.GraphQLInt; } } @@ -159,7 +152,7 @@ public boolean canBuildType(Class aClass, AnnotatedType annotatedType) { } @Override - public GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType) { + public GraphQLType buildType(boolean inputType, Class aClass, AnnotatedType annotatedType) { return Scalars.GraphQLLong; } } @@ -175,7 +168,7 @@ public boolean canBuildType(Class aClass, AnnotatedType annotatedType) { } @Override - public GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType) { + public GraphQLType buildType(boolean inputType, Class aClass, AnnotatedType annotatedType) { if (!(annotatedType instanceof AnnotatedParameterizedType)) { throw new IllegalArgumentException("List type parameter should be specified"); } @@ -187,7 +180,7 @@ public GraphQLType buildType(String typeName, Class aClass, AnnotatedType ann } else { klass = (Class) arg.getType(); } - return new GraphQLList(DefaultTypeFunction.this.buildType(klass, arg)); + return new GraphQLList(DefaultTypeFunction.this.buildType(inputType, klass, arg)); } } @@ -199,7 +192,7 @@ public boolean canBuildType(Class aClass, AnnotatedType annotatedType) { } @Override - public GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType) { + public GraphQLType buildType(boolean inputType, Class aClass, AnnotatedType annotatedType) { if (!(annotatedType instanceof AnnotatedParameterizedType)) { throw new IllegalArgumentException("Stream type parameter should be specified"); } @@ -211,7 +204,7 @@ public GraphQLType buildType(String typeName, Class aClass, AnnotatedType ann } else { klass = (Class) arg.getType(); } - return new GraphQLList(DefaultTypeFunction.this.buildType(klass, arg)); + return new GraphQLList(DefaultTypeFunction.this.buildType(inputType, klass, arg)); } } @@ -229,9 +222,9 @@ public String getTypeName(Class aClass, AnnotatedType annotatedType) { } @Override - public GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType) { + public GraphQLType buildType(boolean inputType, Class aClass, AnnotatedType annotatedType) { AnnotatedType arg = getAnnotatedType(annotatedType); - return DefaultTypeFunction.this.buildType(typeName, getClass(annotatedType), arg); + return DefaultTypeFunction.this.buildType(inputType, getClass(annotatedType), arg); } private AnnotatedType getAnnotatedType(AnnotatedType annotatedType) { @@ -253,69 +246,7 @@ private Class getClass(AnnotatedType annotatedType) { } } - private class EnumFunction implements TypeFunction { - private final Map processing = new ConcurrentHashMap<>(); - private final Map types = new ConcurrentHashMap<>(); - - @Override - public String getTypeName(Class aClass, AnnotatedType annotatedType) { - GraphQLName name = aClass.getAnnotation(GraphQLName.class); - return toGraphqlName(name == null ? aClass.getSimpleName() : name.value()); - } - - @Override - public boolean canBuildType(Class aClass, AnnotatedType annotatedType) { - return Enum.class.isAssignableFrom(aClass); - } - - @Override - public GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType) { - if (types.containsKey(typeName)) { - return types.get(typeName); - } else if (processing.containsKey(typeName)) { - return processing.getOrDefault(typeName, new GraphQLTypeReference(typeName)); - } else { - - processing.put(typeName, new GraphQLTypeReference(typeName)); - - //noinspection unchecked - Class enumClass = (Class) aClass; - GraphQLEnumType.Builder builder = newEnum(); - builder.name(typeName); - - GraphQLDescription description = aClass.getAnnotation(GraphQLDescription.class); - if (description != null) { - builder.description(description.value()); - } - - List constants = Arrays.asList(enumClass.getEnumConstants()); - - Arrays.stream(enumClass.getEnumConstants()).map(Enum::name).forEachOrdered(n -> { - try { - Field field = aClass.getField(n); - GraphQLName fieldName = field.getAnnotation(GraphQLName.class); - GraphQLDescription fieldDescription = field.getAnnotation(GraphQLDescription.class); - Enum constant = constants.stream().filter(c -> c.name().contentEquals(n)).findFirst().get(); - String name_ = fieldName == null ? n : fieldName.value(); - builder.value(name_, constant, fieldDescription == null ? name_ : fieldDescription.value()); - } catch (NoSuchFieldException ignore) { - } - }); - - final GraphQLEnumType type = builder.build(); - types.put(typeName, type); - //noinspection SuspiciousMethodCalls - processing.remove(type); - return type; - } - } - } - private class ObjectFunction implements TypeFunction { - - private final Map processing = new ConcurrentHashMap<>(); - private final Map types = new ConcurrentHashMap<>(); - @Override public String getTypeName(Class aClass, AnnotatedType annotatedType) { GraphQLName name = aClass.getAnnotation(GraphQLName.class); @@ -328,22 +259,11 @@ public boolean canBuildType(Class aClass, AnnotatedType annotatedType) { } @Override - public GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType) { - if (types.containsKey(typeName)) { - return types.get(typeName); - } else if (processing.containsKey(typeName)) { - return processing.getOrDefault(typeName, new GraphQLTypeReference(typeName)); + public GraphQLType buildType(boolean inputType, Class aClass, AnnotatedType annotatedType) { + if (inputType) { + return annotationsProcessor.getInputObject(aClass); } else { - processing.put(typeName, new GraphQLTypeReference(typeName)); - GraphQLType type; - if (aClass.isInterface()) { - type = annotationsProcessor.getInterface(aClass); - } else { - type = annotationsProcessor.getObjectOrRef(aClass); - } - types.put(typeName, type); - processing.remove(typeName); - return type; + return annotationsProcessor.getOutputTypeOrRef(aClass); } } } @@ -362,8 +282,6 @@ public DefaultTypeFunction() { typeFunctions.add(new IterableFunction()); typeFunctions.add(new StreamFunction()); - typeFunctions.add(new EnumFunction()); - typeFunctions.add(new OptionalFunction()); typeFunctions.add(new ObjectFunction()); @@ -403,13 +321,13 @@ public String getTypeName(Class aClass, AnnotatedType annotatedType) { } @Override - public GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType) { + public GraphQLType buildType(boolean inputType, Class aClass, AnnotatedType annotatedType) { TypeFunction typeFunction = getTypeFunction(aClass, annotatedType); if (typeFunction == null) { throw new IllegalArgumentException("unsupported type"); } - GraphQLType result = typeFunction.buildType(typeName, aClass, annotatedType); + GraphQLType result = typeFunction.buildType(inputType, aClass, annotatedType); if (aClass.getAnnotation(GraphQLNonNull.class) != null || (annotatedType != null && annotatedType.getAnnotation(GraphQLNonNull.class) != null)) { result = new graphql.schema.GraphQLNonNull(result); diff --git a/src/main/java/graphql/annotations/GraphQLAnnotations.java b/src/main/java/graphql/annotations/GraphQLAnnotations.java index a72067b1..813addb5 100644 --- a/src/main/java/graphql/annotations/GraphQLAnnotations.java +++ b/src/main/java/graphql/annotations/GraphQLAnnotations.java @@ -33,6 +33,7 @@ import static graphql.annotations.ReflectionKit.newInstance; import static graphql.annotations.util.NamingKit.toGraphqlName; import static graphql.schema.GraphQLArgument.newArgument; +import static graphql.schema.GraphQLEnumType.newEnum; import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; import static graphql.schema.GraphQLInputObjectField.newInputObjectField; import static graphql.schema.GraphQLInterfaceType.newInterface; @@ -50,6 +51,8 @@ public class GraphQLAnnotations implements GraphQLAnnotationsProcessor { private static final List TYPES_FOR_CONNECTION = Arrays.asList(GraphQLObjectType.class, GraphQLInterfaceType.class, GraphQLUnionType.class, GraphQLTypeReference.class); + private static final String DEFAULT_INPUT_PREFIX = "Input"; + private Map typeRegistry = new HashMap<>(); private Map, Set>> extensionsTypeRegistry = new HashMap<>(); private final Stack processing = new Stack<>(); @@ -75,21 +78,8 @@ public void setRelay(Relay relay) { } @Override - public graphql.schema.GraphQLType getInterface(Class iface) throws GraphQLAnnotationsException { - String typeName = getTypeName(iface); - graphql.schema.GraphQLType type = typeRegistry.get(typeName); - if (type != null) { // type already exists, do not build a new new one - return type; - } - if (iface.getAnnotation(GraphQLUnion.class) != null) { - type = getUnionBuilder(iface).build(); - } else if (!iface.isAnnotationPresent(GraphQLTypeResolver.class)) { - type = getObject(iface); - } else { - type = getIfaceBuilder(iface).build(); - } - typeRegistry.put(typeName, type); - return type; + public graphql.schema.GraphQLOutputType getInterface(Class iface) throws GraphQLAnnotationsException { + return getOutputType(iface); } public static graphql.schema.GraphQLType iface(Class iface) throws GraphQLAnnotationsException { @@ -252,26 +242,92 @@ private boolean parentalSearch(Field field) { @Override public GraphQLObjectType getObject(Class object) throws GraphQLAnnotationsException { + GraphQLOutputType type = getOutputType(object); + if (type instanceof GraphQLObjectType) { + return (GraphQLObjectType) type; + } else { + throw new IllegalArgumentException("Object resolve to a "+type.getClass().getSimpleName()); + } + } + + @Override + public GraphQLOutputType getOutputType(Class object) throws GraphQLAnnotationsException { // because the TypeFunction can call back to this processor and // Java classes can be circular, we need to protect against // building the same type twice because graphql-java 3.x requires // all type instances to be unique singletons String typeName = getTypeName(object); - processing.push(typeName); - GraphQLObjectType.Builder builder = getObjectBuilder(object); + GraphQLOutputType type = (GraphQLOutputType) typeRegistry.get(typeName); + if (type != null) { // type already exists, do not build a new new one + return type; + } + processing.push(typeName); + if (object.getAnnotation(GraphQLUnion.class) != null) { + type = getUnionBuilder(object).build(); + } else if (object.isAnnotationPresent(GraphQLTypeResolver.class)) { + type = getIfaceBuilder(object).build(); + } else if (Enum.class.isAssignableFrom(object)) { + type = getEnumBuilder(object).build(); + } else { + type = new GraphQLObjectTypeWrapper(object, getObjectBuilder(object).build()); + } + + typeRegistry.put(typeName, type); processing.pop(); - return new GraphQLObjectTypeWrapper(object, builder.build()); + + return type; + } + + public static GraphQLOutputType outputType(Class object) { + return getInstance().getOutputType(object); + } + + public GraphQLEnumType.Builder getEnumBuilder(Class aClass) { + String typeName = getTypeName(aClass); + //noinspection unchecked + Class enumClass = (Class) aClass; + GraphQLEnumType.Builder builder = newEnum(); + builder.name(typeName); + + GraphQLDescription description = aClass.getAnnotation(GraphQLDescription.class); + if (description != null) { + builder.description(description.value()); + } + + List constants = Arrays.asList(enumClass.getEnumConstants()); + + Arrays.stream(enumClass.getEnumConstants()).map(Enum::name).forEachOrdered(n -> { + try { + Field field = aClass.getField(n); + GraphQLName fieldName = field.getAnnotation(GraphQLName.class); + GraphQLDescription fieldDescription = field.getAnnotation(GraphQLDescription.class); + Enum constant = constants.stream().filter(c -> c.name().contentEquals(n)).findFirst().get(); + String name_ = fieldName == null ? n : fieldName.value(); + builder.value(name_, constant, fieldDescription == null ? name_ : fieldDescription.value()); + } catch (NoSuchFieldException ignore) { + } + }); + return builder; + } + + public static GraphQLEnumType.Builder enumBuilder(Class object) throws GraphQLAnnotationsException { + return getInstance().getEnumBuilder(object); } - @Override public GraphQLOutputType getObjectOrRef(Class object) throws GraphQLAnnotationsException { + return getOutputTypeOrRef(object); + } + + @Override + public GraphQLOutputType getOutputTypeOrRef(Class object) throws GraphQLAnnotationsException { String typeName = getTypeName(object); if (processing.contains(typeName)) { return new GraphQLTypeReference(typeName); } - return getObject(object); + + return getOutputType(object); } public static GraphQLObjectType object(Class object) throws GraphQLAnnotationsException { @@ -325,7 +381,12 @@ public GraphQLObjectType.Builder getObjectBuilder(Class object) throws GraphQ for (Class iface : object.getInterfaces()) { if (iface.getAnnotation(GraphQLTypeResolver.class) != null) { - builder.withInterface((GraphQLInterfaceType) getInterface(iface)); + String ifaceName = getTypeName(iface); + if (processing.contains(ifaceName)) { + builder.withInterface(new GraphQLTypeReference(ifaceName)); + } else { + builder.withInterface((GraphQLInterfaceType) getInterface(iface)); + } builder.fields(getExtensionFields(iface, fieldsDefined)); } } @@ -571,11 +632,7 @@ protected GraphQLFieldDefinition getField(Method method) throws GraphQLAnnotatio filter(p -> !DataFetchingEnvironment.class.isAssignableFrom(p.getType())). map(parameter -> { Class t = parameter.getType(); - graphql.schema.GraphQLType graphQLType = finalTypeFunction.buildType(t, parameter.getAnnotatedType()); - if (graphQLType instanceof GraphQLObjectType) { - GraphQLInputObjectType inputObject = getInputObject((GraphQLObjectType) graphQLType, "input"); - graphQLType = inputObject; - } + graphql.schema.GraphQLType graphQLType = getInputObject(finalTypeFunction.buildType(t, parameter.getAnnotatedType()), DEFAULT_INPUT_PREFIX); return getArgument(parameter, graphQLType); }).collect(Collectors.toList()); @@ -641,27 +698,49 @@ protected static GraphQLFieldDefinition field(Method method) throws Instantiatio } + public GraphQLInputObjectType getInputObject(Class object) { + String typeName = DEFAULT_INPUT_PREFIX + getTypeName(object); + if (typeRegistry.containsKey(typeName)) { + return (GraphQLInputObjectType) typeRegistry.get(typeName); + } else { + graphql.schema.GraphQLType graphQLType = getObject(object); + GraphQLInputObjectType inputObject = (GraphQLInputObjectType) getInputObject(graphQLType, DEFAULT_INPUT_PREFIX); + typeRegistry.put(inputObject.getName(), inputObject); + return inputObject; + } + } + @Override - public GraphQLInputObjectType getInputObject(GraphQLObjectType graphQLType, String newNamePrefix) { - GraphQLObjectType object = graphQLType; - return new GraphQLInputObjectType("" + newNamePrefix + object.getName(), object.getDescription(), - object.getFieldDefinitions().stream(). - map(field -> { - GraphQLOutputType type = field.getType(); - GraphQLInputType inputType; - if (type instanceof GraphQLObjectType) { - inputType = getInputObject((GraphQLObjectType) type, newNamePrefix); - } else { - inputType = (GraphQLInputType) type; - } - - return new GraphQLInputObjectField(field.getName(), field.getDescription(), inputType, null); - }). - collect(Collectors.toList())); + public GraphQLInputType getInputObject(graphql.schema.GraphQLType graphQLType, String newNamePrefix) { + if (graphQLType instanceof GraphQLObjectType) { + GraphQLObjectType object = (GraphQLObjectType) graphQLType; + if (typeRegistry.containsKey(newNamePrefix + object.getName()) && typeRegistry.get(newNamePrefix + object.getName()) instanceof GraphQLInputType) { + return (GraphQLInputType) typeRegistry.get(newNamePrefix + object.getName()); + } + GraphQLInputObjectType inputObjectType = new GraphQLInputObjectType(newNamePrefix + object.getName(), object.getDescription(), + object.getFieldDefinitions().stream(). + map(field -> { + GraphQLOutputType type = field.getType(); + GraphQLInputType inputType = getInputObject(type, newNamePrefix); + return new GraphQLInputObjectField(field.getName(), field.getDescription(), inputType, null); + }). + collect(Collectors.toList())); + typeRegistry.put(inputObjectType.getName(), inputObjectType); + return inputObjectType; + } else if (graphQLType instanceof GraphQLList) { + return new GraphQLList(getInputObject(((GraphQLList)graphQLType).getWrappedType(), newNamePrefix)); + } else if (graphQLType instanceof GraphQLNonNull) { + return new GraphQLNonNull(getInputObject(((GraphQLNonNull)graphQLType).getWrappedType(), newNamePrefix)); + } else if (graphQLType instanceof GraphQLTypeReference) { + return new GraphQLTypeReference(newNamePrefix + ((GraphQLTypeReference)graphQLType).getName()); + } else if (graphQLType instanceof GraphQLInputType){ + return (GraphQLInputType) graphQLType; + } + throw new IllegalArgumentException("Cannot convert type to input : "+graphQLType); } public static GraphQLInputObjectType inputObject(GraphQLObjectType graphQLType, String newNamePrefix) { - return getInstance().getInputObject(graphQLType, newNamePrefix); + return (GraphQLInputObjectType) getInstance().getInputObject(graphQLType, newNamePrefix); } protected GraphQLArgument getArgument(Parameter parameter, graphql.schema.GraphQLType t) throws diff --git a/src/main/java/graphql/annotations/GraphQLAnnotationsProcessor.java b/src/main/java/graphql/annotations/GraphQLAnnotationsProcessor.java index 824a06c9..79ceab25 100644 --- a/src/main/java/graphql/annotations/GraphQLAnnotationsProcessor.java +++ b/src/main/java/graphql/annotations/GraphQLAnnotationsProcessor.java @@ -14,24 +14,12 @@ */ package graphql.annotations; -import graphql.schema.GraphQLInputObjectType; -import graphql.schema.GraphQLInterfaceType; -import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLOutputType; -import graphql.schema.GraphQLUnionType; +import graphql.schema.*; +import graphql.schema.GraphQLType; public interface GraphQLAnnotationsProcessor { /** - * This will examine the class and if its annotated with {@link GraphQLUnion} it will return - * a {@link GraphQLUnionType.Builder}, if its annotated with {@link GraphQLTypeResolver} it will return - * a {@link GraphQLObjectType} otherwise it will return a {@link GraphQLInterfaceType.Builder}. - * - * @param iface interface to examine - * - * @return a GraphQLType that represents that interface - * - * @throws GraphQLAnnotationsException if the interface cannot be examined - * @throws IllegalArgumentException if iface is not an interface + * @deprecated See {@link #getOutputType(Class)} */ graphql.schema.GraphQLType getInterface(Class iface) throws GraphQLAnnotationsException; @@ -60,19 +48,27 @@ public interface GraphQLAnnotationsProcessor { GraphQLInterfaceType.Builder getIfaceBuilder(Class iface) throws GraphQLAnnotationsException, IllegalArgumentException; /** - * This will examine the object class and return a {@link GraphQLObjectType} representation + * This will examine the object class and return a {@link GraphQLEnumType.Builder} ready for further definition * * @param object the object class to examine * - * @return a {@link GraphQLObjectType} that represents that object class + * @return a {@link GraphQLEnumType.Builder} that represents that object class * * @throws GraphQLAnnotationsException if the object class cannot be examined */ + GraphQLEnumType.Builder getEnumBuilder(Class object) throws GraphQLAnnotationsException; + + /** + * @deprecated See {@link #getOutputType(Class)} + */ GraphQLObjectType getObject(Class object) throws GraphQLAnnotationsException; /** - * This will examine the object class and return a {@link GraphQLOutputType} representation - * which may be a {@link GraphQLObjectType} or a {@link graphql.schema.GraphQLTypeReference} + * This will examine the object and will return a {@link GraphQLOutputType} based on the class type and annotations. + * - If its annotated with {@link GraphQLUnion} it will return a {@link GraphQLUnionType} + * - If its annotated with {@link GraphQLTypeResolver} it will return a {@link GraphQLInterfaceType} + * - It it's an Enum it will return a {@link GraphQLEnumType}, + * otherwise it will return a {@link GraphQLObjectType}. * * @param object the object class to examine * @@ -80,8 +76,25 @@ public interface GraphQLAnnotationsProcessor { * * @throws GraphQLAnnotationsException if the object class cannot be examined */ + GraphQLOutputType getOutputType(Class object) throws GraphQLAnnotationsException; + + /** + * @deprecated See {@link #getOutputTypeOrRef(Class)} + */ GraphQLOutputType getObjectOrRef(Class object) throws GraphQLAnnotationsException; + /** + * This will examine the object class and return a {@link GraphQLOutputType} representation + * which may be a {@link GraphQLOutputType} or a {@link graphql.schema.GraphQLTypeReference} + * + * @param object the object class to examine + * + * @return a {@link GraphQLOutputType} that represents that object class + * + * @throws GraphQLAnnotationsException if the object class cannot be examined + */ + GraphQLOutputType getOutputTypeOrRef(Class object) throws GraphQLAnnotationsException; + /** * This will examine the object class and return a {@link GraphQLObjectType.Builder} ready for further definition * @@ -93,6 +106,17 @@ public interface GraphQLAnnotationsProcessor { */ GraphQLObjectType.Builder getObjectBuilder(Class object) throws GraphQLAnnotationsException; + /** + * This will examine the object class and return a {@link GraphQLInputType} representation + * + * @param object the object class to examine + * + * @return a {@link GraphQLInputType} that represents that object class + * + * @throws GraphQLAnnotationsException if the object class cannot be examined + */ + GraphQLInputObjectType getInputObject(Class object) throws GraphQLAnnotationsException; + /** * This will turn a {@link GraphQLObjectType} into a corresponding {@link GraphQLInputObjectType} * @@ -101,7 +125,7 @@ public interface GraphQLAnnotationsProcessor { * * @return a {@link GraphQLInputObjectType} */ - GraphQLInputObjectType getInputObject(GraphQLObjectType graphQLType, String newNamePrefix); + GraphQLInputType getInputObject(GraphQLType graphQLType, String newNamePrefix); /** * Register a new type extension class. This extension will be used when the extended object will be created. diff --git a/src/main/java/graphql/annotations/MethodDataFetcher.java b/src/main/java/graphql/annotations/MethodDataFetcher.java index b91946e6..47876511 100644 --- a/src/main/java/graphql/annotations/MethodDataFetcher.java +++ b/src/main/java/graphql/annotations/MethodDataFetcher.java @@ -14,23 +14,16 @@ */ package graphql.annotations; -import graphql.schema.DataFetcher; -import graphql.schema.DataFetchingEnvironment; -import graphql.schema.GraphQLObjectType; +import graphql.schema.*; +import graphql.schema.GraphQLType; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.Parameter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; +import java.lang.reflect.*; +import java.util.*; import static graphql.annotations.ReflectionKit.constructNewInstance; import static graphql.annotations.ReflectionKit.constructor; import static graphql.annotations.ReflectionKit.newInstance; +import static graphql.annotations.util.NamingKit.toGraphqlName; class MethodDataFetcher implements DataFetcher { private final Method method; @@ -77,15 +70,50 @@ private Object[] invocationArgs(DataFetchingEnvironment environment) { result.add(environment); continue; } - graphql.schema.GraphQLType graphQLType = typeFunction.buildType(paramType, p.getAnnotatedType()); - if (graphQLType instanceof GraphQLObjectType) { - Constructor constructor = constructor(paramType, HashMap.class); - result.add(constructNewInstance(constructor, envArgs.next())); - } else { - result.add(envArgs.next()); - } + graphql.schema.GraphQLType graphQLType = typeFunction.buildType(true, paramType, p.getAnnotatedType()); + Object arg = envArgs.next(); + result.add(buildArg(p.getParameterizedType(), graphQLType, arg)); } return result.toArray(); } + + private Object buildArg(Type p, GraphQLType graphQLType, Object arg) { + if (arg == null) { + return null; + } + if (graphQLType instanceof graphql.schema.GraphQLNonNull) { + graphQLType = ((graphql.schema.GraphQLNonNull) graphQLType).getWrappedType(); + } + if (p instanceof Class && graphQLType instanceof GraphQLInputObjectType) { + Constructor constructors[] = ((Class) p).getConstructors(); + for (Constructor constructor : constructors) { + Parameter[] parameters = constructor.getParameters(); + if (parameters.length == 1 && parameters[0].getType().isAssignableFrom(arg.getClass())) { + return constructNewInstance(constructor, arg); + } else { + List objects = new ArrayList<>(); + Map map = (Map) arg; + for (Parameter parameter : parameters) { + String name = toGraphqlName(parameter.getAnnotation(GraphQLName.class) != null ? parameter.getAnnotation(GraphQLName.class).value() : parameter.getName()); + objects.add(buildArg(parameter.getParameterizedType(), ((GraphQLInputObjectType)graphQLType).getField(name).getType(),map.get(name))); + } + return constructNewInstance(constructor, objects.toArray(new Object[objects.size()])); + } + } + return null; + } else if (p instanceof ParameterizedType && graphQLType instanceof GraphQLList) { + List list = new ArrayList<>(); + Type subType = ((ParameterizedType)p).getActualTypeArguments()[0]; + GraphQLType wrappedType = ((GraphQLList) graphQLType).getWrappedType(); + + for (Object item : ((List) arg)) { + list.add(buildArg(subType, wrappedType, item)); + } + + return list; + } else { + return arg; + } + } } diff --git a/src/main/java/graphql/annotations/TypeFunction.java b/src/main/java/graphql/annotations/TypeFunction.java index fc854b18..5e003f2c 100644 --- a/src/main/java/graphql/annotations/TypeFunction.java +++ b/src/main/java/graphql/annotations/TypeFunction.java @@ -43,21 +43,21 @@ default String getTypeName(Class aClass, AnnotatedType annotatedType) { /** * Build a {@link GraphQLType} object from a java type. - * This is a convenience method for calling {@link #buildType(String, Class, AnnotatedType)} without a type name. + * This is a convenience method for calling {@link #buildType(boolean, Class, AnnotatedType)} without a type name. * @param aClass The java type to build the type name for * @param annotatedType The {@link AnnotatedType} of the java type, which may be a {link AnnotatedParameterizedType} * @return The built {@link GraphQLType} */ default GraphQLType buildType(Class aClass, AnnotatedType annotatedType) { - return buildType(getTypeName(aClass, annotatedType), aClass, annotatedType); + return buildType(false, aClass, annotatedType); } /** * Build a {@link GraphQLType} object from a java type. - * @param typeName The name to give the graphql type + * @param input is InputType * @param aClass The java type to build the type name for * @param annotatedType The {@link AnnotatedType} of the java type, which may be a {link AnnotatedParameterizedType} * @return The built {@link GraphQLType} */ - GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType); + GraphQLType buildType(boolean input, Class aClass, AnnotatedType annotatedType); } diff --git a/src/test/java/graphql/annotations/DefaultTypeFunctionTest.java b/src/test/java/graphql/annotations/DefaultTypeFunctionTest.java index 9244e9fa..81fa2b44 100644 --- a/src/test/java/graphql/annotations/DefaultTypeFunctionTest.java +++ b/src/test/java/graphql/annotations/DefaultTypeFunctionTest.java @@ -16,6 +16,8 @@ import graphql.schema.*; import graphql.schema.GraphQLType; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import java.lang.reflect.Field; @@ -30,6 +32,11 @@ public class DefaultTypeFunctionTest { + @BeforeMethod + public void init() { + GraphQLAnnotations.getInstance().getTypeRegistry().clear(); + } + private enum A { @GraphQLName("someA") @GraphQLDescription("a") A, B } diff --git a/src/test/java/graphql/annotations/GraphQLBatchedTest.java b/src/test/java/graphql/annotations/GraphQLBatchedTest.java index e21c938b..6f4ce6e5 100644 --- a/src/test/java/graphql/annotations/GraphQLBatchedTest.java +++ b/src/test/java/graphql/annotations/GraphQLBatchedTest.java @@ -20,6 +20,8 @@ import graphql.execution.batched.BatchedExecutionStrategy; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import java.util.Arrays; @@ -33,6 +35,12 @@ @SuppressWarnings("unchecked") public class GraphQLBatchedTest { + + @BeforeMethod + public void init() { + GraphQLAnnotations.getInstance().getTypeRegistry().clear(); + } + private static class SimpleBatchedField { @GraphQLField @GraphQLBatched diff --git a/src/test/java/graphql/annotations/GraphQLConnectionTest.java b/src/test/java/graphql/annotations/GraphQLConnectionTest.java index 25fa4c7a..c4a24cc8 100644 --- a/src/test/java/graphql/annotations/GraphQLConnectionTest.java +++ b/src/test/java/graphql/annotations/GraphQLConnectionTest.java @@ -22,6 +22,7 @@ import graphql.schema.GraphQLList; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.Arrays; @@ -37,6 +38,11 @@ @SuppressWarnings("unchecked") public class GraphQLConnectionTest { + @BeforeMethod + public void init() { + GraphQLAnnotations.getInstance().getTypeRegistry().clear(); + } + public static class Obj { @GraphQLField public String id; diff --git a/src/test/java/graphql/annotations/GraphQLDataFetcherTest.java b/src/test/java/graphql/annotations/GraphQLDataFetcherTest.java index 15273a7e..189405c7 100644 --- a/src/test/java/graphql/annotations/GraphQLDataFetcherTest.java +++ b/src/test/java/graphql/annotations/GraphQLDataFetcherTest.java @@ -21,6 +21,7 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; import graphql.schema.PropertyDataFetcher; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.HashMap; @@ -32,6 +33,11 @@ public class GraphQLDataFetcherTest { + @BeforeMethod + public void init() { + GraphQLAnnotations.getInstance().getTypeRegistry().clear(); + } + @Test public void shouldUsePreferredConstructor() { // Given diff --git a/src/test/java/graphql/annotations/GraphQLEnumTest.java b/src/test/java/graphql/annotations/GraphQLEnumTest.java index 675e5ba4..881532de 100644 --- a/src/test/java/graphql/annotations/GraphQLEnumTest.java +++ b/src/test/java/graphql/annotations/GraphQLEnumTest.java @@ -17,6 +17,8 @@ import graphql.ExecutionResult; import graphql.GraphQL; import graphql.schema.*; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import java.lang.reflect.AnnotatedType; @@ -30,6 +32,10 @@ public class GraphQLEnumTest { + @BeforeMethod + public void init() { + GraphQLAnnotations.getInstance().getTypeRegistry().clear(); + } public enum Foo { ONE, diff --git a/src/test/java/graphql/annotations/GraphQLExtensionsTest.java b/src/test/java/graphql/annotations/GraphQLExtensionsTest.java index 6dc85fbc..7ff508bb 100644 --- a/src/test/java/graphql/annotations/GraphQLExtensionsTest.java +++ b/src/test/java/graphql/annotations/GraphQLExtensionsTest.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -35,8 +35,7 @@ public class GraphQLExtensionsTest { @GraphQLName("TestObject") public static class TestObject { @GraphQLField - public - String field() { + public String field() { return "test"; } @@ -88,15 +87,16 @@ public String getField() { public static class TestDataFetcher implements DataFetcher { @Override public Object get(DataFetchingEnvironment environment) { - return ((TestObject)environment.getSource()).field() + " test3"; + return ((TestObject) environment.getSource()).field() + " test3"; } } @Test public void fields() { - GraphQLAnnotations.getInstance().registerTypeExtension(TestObjectExtension.class); - GraphQLObjectType object = GraphQLAnnotations.object(GraphQLExtensionsTest.TestObject.class); - GraphQLAnnotations.getInstance().unregisterTypeExtension(TestObjectExtension.class); + GraphQLAnnotations instance = new GraphQLAnnotations(); + instance.registerTypeExtension(TestObjectExtension.class); + GraphQLObjectType object = instance.getObject(GraphQLExtensionsTest.TestObject.class); + instance.unregisterTypeExtension(TestObjectExtension.class); List fields = object.getFieldDefinitions(); assertEquals(fields.size(), 5); @@ -112,9 +112,10 @@ public void fields() { @Test public void values() { - GraphQLAnnotations.getInstance().registerTypeExtension(TestObjectExtension.class); - GraphQLObjectType object = GraphQLAnnotations.object(GraphQLExtensionsTest.TestObject.class); - GraphQLAnnotations.getInstance().unregisterTypeExtension(TestObjectExtension.class); + GraphQLAnnotations instance = new GraphQLAnnotations(); + instance.registerTypeExtension(TestObjectExtension.class); + GraphQLObjectType object = instance.getObject(GraphQLExtensionsTest.TestObject.class); + instance.unregisterTypeExtension(TestObjectExtension.class); GraphQLSchema schema = newSchema().query(object).build(); GraphQLSchema schemaInherited = newSchema().query(object).build(); @@ -130,9 +131,10 @@ public void values() { @Test public void testDuplicateField() { - GraphQLAnnotations.getInstance().registerTypeExtension(TestObjectExtensionInvalid.class); - GraphQLAnnotationsException e = expectThrows(GraphQLAnnotationsException.class, () -> GraphQLAnnotations.object(TestObject.class)); + GraphQLAnnotations instance = new GraphQLAnnotations(); + instance.registerTypeExtension(TestObjectExtensionInvalid.class); + GraphQLAnnotationsException e = expectThrows(GraphQLAnnotationsException.class, () -> instance.getObject(TestObject.class)); assertTrue(e.getMessage().startsWith("Duplicate field")); - GraphQLAnnotations.getInstance().unregisterTypeExtension(TestObjectExtensionInvalid.class); + instance.unregisterTypeExtension(TestObjectExtensionInvalid.class); } } diff --git a/src/test/java/graphql/annotations/GraphQLFragmentTest.java b/src/test/java/graphql/annotations/GraphQLFragmentTest.java index 3787c31a..f993f9f4 100644 --- a/src/test/java/graphql/annotations/GraphQLFragmentTest.java +++ b/src/test/java/graphql/annotations/GraphQLFragmentTest.java @@ -21,6 +21,7 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; import graphql.schema.TypeResolver; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.Arrays; @@ -35,6 +36,11 @@ public class GraphQLFragmentTest { + @BeforeMethod + public void init() { + GraphQLAnnotations.getInstance().getTypeRegistry().clear(); + } + static Map registry; /** diff --git a/src/test/java/graphql/annotations/GraphQLInputTest.java b/src/test/java/graphql/annotations/GraphQLInputTest.java new file mode 100644 index 00000000..992cc96a --- /dev/null +++ b/src/test/java/graphql/annotations/GraphQLInputTest.java @@ -0,0 +1,218 @@ +/** + * Copyright 2016 Yurii Rashkovskii + * + * 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 + */ +package graphql.annotations; + +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.TypeResolutionEnvironment; +import graphql.schema.*; +import org.testng.annotations.Test; + +import java.util.*; + +import static graphql.schema.GraphQLSchema.newSchema; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +@SuppressWarnings("unchecked") +public class GraphQLInputTest { + + public static class Resolver implements TypeResolver { + + @Override + public GraphQLObjectType getType(TypeResolutionEnvironment env) { + try { + return GraphQLAnnotations.object(TestObject.class); + } catch (GraphQLAnnotationsException e) { + return null; + } + } + } + + static class SubInputObject { + public SubInputObject(String subKey) { + this.subKey = subKey; + } + + @GraphQLField + private String subKey; + } + + static class InputObject { + public InputObject(String key, List complex) { + this.key = key; + this.complex = complex; + } + + @GraphQLField + private String key; + + @GraphQLField + private List complex; + } + + static class RecursiveInputObject { + public RecursiveInputObject(HashMap map) { + key = (String) map.get("key"); + if (map.containsKey("rec")) { + rec = new RecursiveInputObject((HashMap) map.get("rec")); + } + } + + @GraphQLField + private String key; + + @GraphQLField + private RecursiveInputObject rec; + } + + @GraphQLTypeResolver(Resolver.class) + interface TestIface { + @GraphQLField + String value(InputObject input); + } + + static class TestObject implements TestIface { + + @Override + public String value(InputObject input) { + return input.key + "a"; + } + } + + static class TestObjectList { + @GraphQLField + public String value(List>> input) { + InputObject inputObject = input.get(0).get(0).get(0); + return inputObject.key + "-" + inputObject.complex.get(0).subKey; + } + } + + static class TestObjectRec { + @GraphQLField + public String value(RecursiveInputObject input) { + return (input.rec != null ? ("rec"+input.rec.key) : input.key) + "a"; + } + } + + static class Code { + public Code(HashMap map) { + this.firstField = (String) map.get("firstField"); + this.secondField = (String) map.get("secondField"); + } + + @GraphQLField + public String firstField; + @GraphQLField + public String secondField; + } + + static class QueryMultipleDefinitions { + @GraphQLField + public String something(Code code) { + return code.firstField + code.secondField; + } + + @GraphQLField + public String somethingElse(Code code) { + return code.firstField + code.secondField; + } + } + + static class Query { + @GraphQLField + public TestIface object() { + return new TestObject(); + }; + } + + static class QueryRecursion { + @GraphQLField + public TestObjectRec object() { + return new TestObjectRec(); + }; + } + + static class QueryList { + @GraphQLField + public TestObjectList object() { + return new TestObjectList(); + }; + } + + static class QueryIface { + @GraphQLField + public TestObject iface() { + return new TestObject(); + } + } + + + @Test + public void query() { + GraphQLSchema schema = newSchema().query(GraphQLAnnotations.object(Query.class)).build(); + + GraphQL graphQL = GraphQL.newGraphQL(schema).build(); + ExecutionResult result = graphQL.execute("{ object { value(input:{key:\"test\"}) } }", new Query()); + assertTrue(result.getErrors().isEmpty()); + assertEquals(((Map>) result.getData()).get("object").get("value"), "testa"); + } + + @Test + public void queryMultipleDefinitions() { + GraphQLSchema schema = newSchema().query(GraphQLAnnotations.object(QueryMultipleDefinitions.class)).build(); + + GraphQL graphQL = GraphQL.newGraphQL(schema).build(); + ExecutionResult result = graphQL.execute("{ something(code: {firstField:\"a\",secondField:\"b\"}) somethingElse(code: {firstField:\"c\",secondField:\"d\"}) }", new QueryMultipleDefinitions()); + assertTrue(result.getErrors().isEmpty()); + assertEquals(((Map) result.getData()).get("something"), "ab"); + assertEquals(((Map) result.getData()).get("somethingElse"), "cd"); + } + + @Test + public void queryWithRecursion() { + GraphQLSchema schema = newSchema().query(GraphQLAnnotations.object(QueryRecursion.class)).build(); + + GraphQL graphQL = GraphQL.newGraphQL(schema).build(); + ExecutionResult result = graphQL.execute("{ object { value(input:{key:\"test\"}) } }", new QueryRecursion()); + assertTrue(result.getErrors().isEmpty()); + assertEquals(((Map>) result.getData()).get("object").get("value"), "testa"); + + result = graphQL.execute("{ object { value(input:{rec:{key:\"test\"}}) } }", new QueryRecursion()); + assertTrue(result.getErrors().isEmpty()); + assertEquals(((Map>) result.getData()).get("object").get("value"), "rectesta"); + } + + @Test + public void queryWithList() { + GraphQLSchema schema = newSchema().query(GraphQLAnnotations.object(QueryList.class)).build(); + + GraphQL graphQL = GraphQL.newGraphQL(schema).build(); + ExecutionResult result = graphQL.execute("{ object { value(input:[[[{key:\"test\", complex:[{subKey:\"subtest\"},{subKey:\"subtest2\"}]}]]]) } }", new QueryList()); + assertEquals(((Map>) result.getData()).get("object").get("value"), "test-subtest"); + } + + @Test + public void queryWithInterface() { + GraphQLSchema schema = newSchema().query(GraphQLAnnotations.object(QueryIface.class)).build(Collections.singleton(GraphQLAnnotations.object(TestObject.class))); + + GraphQL graphQL = GraphQL.newGraphQL(schema).build(); + ExecutionResult result = graphQL.execute("{ iface { value(input:{key:\"test\"}) } }", new QueryIface()); + assertTrue(result.getErrors().isEmpty()); + assertEquals(((Map>) result.getData()).get("iface").get("value"), "testa"); + } + + +} diff --git a/src/test/java/graphql/annotations/GraphQLInterfaceTest.java b/src/test/java/graphql/annotations/GraphQLInterfaceTest.java index d23928e4..6ff391c2 100644 --- a/src/test/java/graphql/annotations/GraphQLInterfaceTest.java +++ b/src/test/java/graphql/annotations/GraphQLInterfaceTest.java @@ -26,6 +26,7 @@ import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLUnionType; import graphql.schema.TypeResolver; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.List; @@ -34,10 +35,16 @@ import static graphql.schema.GraphQLSchema.newSchema; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; @SuppressWarnings("unchecked") public class GraphQLInterfaceTest { + @BeforeMethod + public void init() { + GraphQLAnnotations.getInstance().getTypeRegistry().clear(); + } + interface NoResolverIface { @GraphQLField String value(); diff --git a/src/test/java/graphql/annotations/GraphQLObjectTest.java b/src/test/java/graphql/annotations/GraphQLObjectTest.java index f21f5a63..41dffeaa 100644 --- a/src/test/java/graphql/annotations/GraphQLObjectTest.java +++ b/src/test/java/graphql/annotations/GraphQLObjectTest.java @@ -28,15 +28,12 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLType; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import javax.validation.constraints.NotNull; import java.lang.reflect.AnnotatedType; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.function.Supplier; import static graphql.Scalars.GraphQLString; @@ -50,6 +47,11 @@ @SuppressWarnings("unchecked") public class GraphQLObjectTest { + @BeforeMethod + public void init() { + GraphQLAnnotations.getInstance().getTypeRegistry().clear(); + } + public static class DefaultAValue implements Supplier { @Override @@ -540,19 +542,42 @@ private static class TestInputArgument { @GraphQLField public int b; - public TestInputArgument(HashMap args) { - a = (String) args.get("a"); - b = (int) args.get("b"); + public TestInputArgument(String a, int b) { + this.a = a; + this.b = b; + } + } + + private static class TestComplexInputArgument { + + public Collection inputs; + + public TestComplexInputArgument(Collection inputs) { + this.inputs = inputs; } + + @GraphQLField + public Collection getInputs() { + return inputs; + } + } + + private static class TestObjectInput { @GraphQLField public String test(int other, TestInputArgument arg) { return arg.a; } + + @GraphQLField + public String test2(int other, TestComplexInputArgument arg) { + return arg.inputs.iterator().next().a; + } } + @Test public void inputObjectArgument() { GraphQLObjectType object = GraphQLAnnotations.object(TestObjectInput.class); @@ -567,6 +592,20 @@ public void inputObjectArgument() { assertEquals(v.get("test"), "ok"); } + @Test + public void complexInputObjectArgument() { + GraphQLObjectType object = GraphQLAnnotations.object(TestObjectInput.class); + GraphQLArgument argument = object.getFieldDefinition("test2").getArgument("arg"); + assertTrue(argument.getType() instanceof GraphQLInputObjectType); + assertEquals(argument.getName(), "arg"); + + GraphQLSchema schema = newSchema().query(object).build(); + ExecutionResult result = GraphQL.newGraphQL(schema).build().execute("{ test2(arg: {inputs:[{ a:\"ok\", b:2 }]}, other:0) }", new TestObjectInput()); + assertTrue(result.getErrors().isEmpty()); + Map v = (Map) result.getData(); + assertEquals(v.get("test2"), "ok"); + } + @Test public void inputObject() { GraphQLObjectType object = GraphQLAnnotations.object(TestObjectInput.class); @@ -586,7 +625,7 @@ public String getTypeName(Class aClass, AnnotatedType annotatedType) { } @Override - public GraphQLType buildType(String typeName, Class aClass, AnnotatedType annotatedType) { + public GraphQLType buildType(boolean inputType, Class aClass, AnnotatedType annotatedType) { return GraphQLString; } } diff --git a/src/test/java/graphql/annotations/GraphQLSimpleSchemaTest.java b/src/test/java/graphql/annotations/GraphQLSimpleSchemaTest.java index 2ee013ba..f61e82d3 100644 --- a/src/test/java/graphql/annotations/GraphQLSimpleSchemaTest.java +++ b/src/test/java/graphql/annotations/GraphQLSimpleSchemaTest.java @@ -17,6 +17,7 @@ import graphql.ExecutionResult; import graphql.GraphQL; import graphql.schema.GraphQLObjectType; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import static graphql.schema.GraphQLSchema.newSchema; @@ -24,6 +25,12 @@ public class GraphQLSimpleSchemaTest { + @BeforeMethod + public void init() { + GraphQLAnnotations.getInstance().getTypeRegistry().clear(); + } + + public static class User { private String name; diff --git a/src/test/java/graphql/annotations/MethodDataFetcherTest.java b/src/test/java/graphql/annotations/MethodDataFetcherTest.java index cfb46e74..d8758ee9 100644 --- a/src/test/java/graphql/annotations/MethodDataFetcherTest.java +++ b/src/test/java/graphql/annotations/MethodDataFetcherTest.java @@ -15,6 +15,7 @@ package graphql.annotations; import graphql.schema.DataFetchingEnvironmentImpl; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.ArrayList; @@ -22,6 +23,12 @@ public class MethodDataFetcherTest { + @BeforeMethod + public void init() { + GraphQLAnnotations.getInstance().getTypeRegistry().clear(); + } + + public class TestException extends Exception { } diff --git a/src/test/java/graphql/annotations/RelayTest.java b/src/test/java/graphql/annotations/RelayTest.java index ca8d13ad..dcfefa9b 100644 --- a/src/test/java/graphql/annotations/RelayTest.java +++ b/src/test/java/graphql/annotations/RelayTest.java @@ -20,6 +20,7 @@ import graphql.schema.*; import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLType; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.HashMap; @@ -30,6 +31,11 @@ @SuppressWarnings("unchecked") public class RelayTest { + @BeforeMethod + public void init() { + GraphQLAnnotations.getInstance().getTypeRegistry().clear(); + } + public static class ResultTypeResolver implements TypeResolver { @Override