Skip to content

Commit 0a3b4fe

Browse files
committed
add directive creation code
1 parent 859b58d commit 0a3b4fe

File tree

8 files changed

+309
-5
lines changed

8 files changed

+309
-5
lines changed

src/main/java/graphql/annotations/directives/Directive.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
2323
@Retention(RetentionPolicy.RUNTIME)
2424
public @interface Directive {
25-
// Class<? extends BasicDirectiveInfo> info();
2625
String name();
2726

2827
Class<? extends AnnotationsDirectiveWiring> wiringClass();
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package graphql.annotations.directives.creation;
2+
3+
import graphql.introspection.Introspection;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
@Target({ElementType.TYPE})
11+
@Retention(RetentionPolicy.RUNTIME)
12+
public @interface DirectiveLocations {
13+
Introspection.DirectiveLocation[] value();
14+
}

src/main/java/graphql/annotations/processor/GraphQLAnnotations.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/**
22
* Copyright 2016 Yurii Rashkovskii
3-
*
3+
* <p>
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
1212
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,6 +15,9 @@
1515
package graphql.annotations.processor;
1616

1717
import graphql.annotations.annotationTypes.GraphQLName;
18+
import graphql.annotations.processor.directives.CommonPropertiesCreator;
19+
import graphql.annotations.processor.directives.DirectiveArgumentCreator;
20+
import graphql.annotations.processor.directives.DirectiveCreator;
1821
import graphql.annotations.processor.exceptions.GraphQLAnnotationsException;
1922
import graphql.annotations.processor.graphQLProcessors.GraphQLAnnotationsProcessor;
2023
import graphql.annotations.processor.graphQLProcessors.GraphQLInputProcessor;
@@ -42,6 +45,7 @@ public class GraphQLAnnotations implements GraphQLAnnotationsProcessor {
4245

4346
private GraphQLObjectHandler graphQLObjectHandler;
4447
private GraphQLExtensionsHandler graphQLExtensionsHandler;
48+
private DirectiveCreator directiveCreator;
4549

4650
private ProcessingElementsContainer container;
4751

@@ -78,6 +82,10 @@ public GraphQLAnnotations() {
7882
this.graphQLObjectHandler = objectHandler;
7983
this.graphQLExtensionsHandler = extensionsHandler;
8084
this.container = new ProcessingElementsContainer(defaultTypeFunction);
85+
86+
DirectiveArgumentCreator directiveArgumentCreator = new DirectiveArgumentCreator(new CommonPropertiesCreator(),
87+
container.getDefaultTypeFunction(), container);
88+
this.directiveCreator = new DirectiveCreator(directiveArgumentCreator, new CommonPropertiesCreator());
8189
}
8290

8391
public GraphQLAnnotations(TypeFunction defaultTypeFunction, GraphQLObjectHandler graphQLObjectHandler, GraphQLExtensionsHandler graphQLExtensionsHandler) {
@@ -125,6 +133,18 @@ public static GraphQLObjectType object(Class<?> object, GraphQLDirective... dire
125133
}
126134
}
127135

136+
public static GraphQLDirective directive(Class<?> object) throws GraphQLAnnotationsException {
137+
GraphQLAnnotations instance = getInstance();
138+
139+
try {
140+
return instance.directiveCreator.getDirective(object);
141+
} catch (GraphQLAnnotationsException e) {
142+
instance.getContainer().getProcessing().clear();
143+
instance.getTypeRegistry().clear();
144+
throw e;
145+
}
146+
}
147+
128148
public void registerTypeExtension(Class<?> objectClass) {
129149
graphQLExtensionsHandler.registerTypeExtension(objectClass, container);
130150
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package graphql.annotations.processor.directives;
2+
3+
import graphql.annotations.annotationTypes.GraphQLDescription;
4+
import graphql.annotations.annotationTypes.GraphQLName;
5+
6+
import java.lang.reflect.AnnotatedElement;
7+
import java.lang.reflect.Field;
8+
9+
public class CommonPropertiesCreator {
10+
public String getDescription(AnnotatedElement annotatedElement) {
11+
GraphQLDescription graphQLDescriptionAnnotation = annotatedElement.getAnnotation(GraphQLDescription.class);
12+
if (graphQLDescriptionAnnotation != null) {
13+
return graphQLDescriptionAnnotation.value();
14+
}
15+
return null;
16+
}
17+
18+
public String getName(AnnotatedElement annotatedElement) {
19+
GraphQLName graphQLNameAnnotation = annotatedElement.getAnnotation(GraphQLName.class);
20+
if (graphQLNameAnnotation != null) {
21+
return graphQLNameAnnotation.value();
22+
}
23+
if (annotatedElement instanceof Class<?>) {
24+
return ((Class<?>) annotatedElement).getSimpleName();
25+
}
26+
else if (annotatedElement instanceof Field){
27+
return ((Field) annotatedElement).getName();
28+
}
29+
return null;
30+
}
31+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package graphql.annotations.processor.directives;
2+
3+
import graphql.annotations.processor.ProcessingElementsContainer;
4+
import graphql.annotations.processor.exceptions.GraphQLAnnotationsException;
5+
import graphql.annotations.processor.typeFunctions.TypeFunction;
6+
import graphql.schema.GraphQLArgument;
7+
import graphql.schema.GraphQLInputType;
8+
9+
import java.lang.reflect.Field;
10+
11+
import static graphql.schema.GraphQLArgument.newArgument;
12+
13+
public class DirectiveArgumentCreator {
14+
private CommonPropertiesCreator commonPropertiesCreator;
15+
private TypeFunction typeFunction;
16+
private ProcessingElementsContainer container;
17+
18+
public DirectiveArgumentCreator(CommonPropertiesCreator commonPropertiesCreator, TypeFunction typeFunction, ProcessingElementsContainer container) {
19+
this.commonPropertiesCreator = commonPropertiesCreator;
20+
this.typeFunction = typeFunction;
21+
this.container = container;
22+
}
23+
24+
25+
public GraphQLArgument getArgument(Field field, Class<?> containingClass) {
26+
GraphQLArgument.Builder builder = newArgument();
27+
builder.name(commonPropertiesCreator.getName(field));
28+
builder.description(commonPropertiesCreator.getDescription(field));
29+
builder.type(getType(field));
30+
try {
31+
builder.defaultValue(getDefaultValue(field, containingClass));
32+
} catch (IllegalAccessException | InstantiationException e) {
33+
throw new GraphQLAnnotationsException(e);
34+
}
35+
36+
return builder.build();
37+
}
38+
39+
private Object getDefaultValue(Field field, Class<?> containingClass) throws IllegalAccessException, InstantiationException {
40+
field.setAccessible(true);
41+
Object object = containingClass.newInstance();
42+
return field.get(object);
43+
}
44+
45+
private GraphQLInputType getType(Field field) {
46+
//todo check if primitive type, if not - throw an exception
47+
return (GraphQLInputType) typeFunction.buildType(true, field.getType(),
48+
field.getAnnotatedType(), container);
49+
}
50+
51+
52+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package graphql.annotations.processor.directives;
2+
3+
import graphql.annotations.directives.creation.DirectiveLocations;
4+
import graphql.annotations.processor.exceptions.GraphQLAnnotationsException;
5+
import graphql.introspection.Introspection;
6+
import graphql.schema.GraphQLArgument;
7+
import graphql.schema.GraphQLDirective;
8+
9+
import java.lang.reflect.Field;
10+
import java.util.Arrays;
11+
12+
import static graphql.schema.GraphQLDirective.newDirective;
13+
14+
public class DirectiveCreator {
15+
16+
private DirectiveArgumentCreator directiveArgumentCreator;
17+
private CommonPropertiesCreator commonPropertiesCreator;
18+
19+
20+
public DirectiveCreator(DirectiveArgumentCreator directiveArgumentCreator, CommonPropertiesCreator commonPropertiesCreator) {
21+
this.directiveArgumentCreator = directiveArgumentCreator;
22+
this.commonPropertiesCreator = commonPropertiesCreator;
23+
}
24+
25+
public GraphQLDirective getDirective(Class<?> annotatedClass) {
26+
GraphQLDirective.Builder builder = newDirective();
27+
builder.name(commonPropertiesCreator.getName(annotatedClass));
28+
builder.description(commonPropertiesCreator.getDescription(annotatedClass));
29+
Introspection.DirectiveLocation[] validLocations = getValidLocations(annotatedClass);
30+
if (validLocations == null || validLocations.length == 0) {
31+
throw new GraphQLAnnotationsException("No valid locations defined on directive", null);
32+
}
33+
builder.validLocations(validLocations);
34+
buildArguments(builder, annotatedClass);
35+
36+
return builder.build();
37+
}
38+
39+
private void buildArguments(GraphQLDirective.Builder builder, Class<?> annotatedClass) {
40+
Arrays.stream(annotatedClass.getDeclaredFields()).forEach(x ->
41+
builder.argument(directiveArgumentCreator.getArgument(x, annotatedClass)));
42+
}
43+
44+
45+
private Introspection.DirectiveLocation[] getValidLocations(Class<?> annotatedClass) {
46+
DirectiveLocations directiveLocationsAnnotation = annotatedClass.getAnnotation(DirectiveLocations.class);
47+
if (directiveLocationsAnnotation != null) {
48+
return directiveLocationsAnnotation.value();
49+
}
50+
return null;
51+
}
52+
53+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package graphql.annotations;
2+
3+
import graphql.annotations.annotationTypes.GraphQLDescription;
4+
import graphql.annotations.annotationTypes.GraphQLName;
5+
import graphql.annotations.directives.creation.DirectiveLocations;
6+
import graphql.annotations.processor.GraphQLAnnotations;
7+
import graphql.introspection.Introspection;
8+
import graphql.schema.GraphQLArgument;
9+
import graphql.schema.GraphQLDirective;
10+
import org.testng.annotations.Test;
11+
12+
import static graphql.Scalars.GraphQLBoolean;
13+
import static graphql.Scalars.GraphQLString;
14+
import static org.testng.AssertJUnit.assertEquals;
15+
import static org.testng.AssertJUnit.assertNotNull;
16+
import static org.testng.AssertJUnit.assertNull;
17+
import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
18+
19+
public class GraphQLDirectiveCreationTest {
20+
21+
@GraphQLName("upper")
22+
@GraphQLDescription("makes string upper case")
23+
@DirectiveLocations({Introspection.DirectiveLocation.FIELD_DEFINITION, Introspection.DirectiveLocation.INTERFACE})
24+
public static class UpperDirective {
25+
private boolean isActive = true;
26+
@GraphQLName("suffixToAdd")
27+
@GraphQLDescription("adds suffix to the string")
28+
private String suffix = "";
29+
30+
private String noDefaultValue;
31+
}
32+
33+
@Test
34+
public void test_directive_creation() {
35+
// Act
36+
GraphQLDirective directive = GraphQLAnnotations.directive(UpperDirective.class);
37+
38+
// Assert
39+
assertEquals(directive.getName(), "upper");
40+
assertEquals(directive.getDescription(), "makes string upper case");
41+
assertArrayEquals(directive.validLocations().toArray(), new Introspection.DirectiveLocation[]{Introspection.DirectiveLocation.FIELD_DEFINITION,
42+
Introspection.DirectiveLocation.INTERFACE});
43+
GraphQLArgument isActive = directive.getArgument("isActive");
44+
assertNotNull(isActive);
45+
assertEquals(isActive.getName(), "isActive");
46+
assertEquals(isActive.getType(), GraphQLBoolean);
47+
assertEquals(isActive.getDefaultValue(), true);
48+
49+
GraphQLArgument suffixToAdd = directive.getArgument("suffixToAdd");
50+
assertNotNull(suffixToAdd);
51+
assertEquals(suffixToAdd.getType(), GraphQLString);
52+
assertEquals(suffixToAdd.getDescription(), "adds suffix to the string");
53+
assertEquals(suffixToAdd.getDefaultValue(), "");
54+
55+
GraphQLArgument noDefaultValue = directive.getArgument("noDefaultValue");
56+
assertNotNull(noDefaultValue);
57+
assertNull(noDefaultValue.getDefaultValue());
58+
}
59+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package graphql.annotations.directives.creation;
2+
3+
import graphql.annotations.annotationTypes.GraphQLDescription;
4+
import graphql.annotations.annotationTypes.GraphQLName;
5+
import graphql.annotations.processor.directives.CommonPropertiesCreator;
6+
import graphql.annotations.processor.directives.DirectiveArgumentCreator;
7+
import graphql.annotations.processor.directives.DirectiveCreator;
8+
import graphql.annotations.processor.exceptions.GraphQLAnnotationsException;
9+
import graphql.introspection.Introspection;
10+
import graphql.schema.GraphQLArgument;
11+
import graphql.schema.GraphQLDirective;
12+
import org.mockito.Mockito;
13+
import org.testng.annotations.BeforeMethod;
14+
import org.testng.annotations.Test;
15+
16+
import static graphql.Scalars.GraphQLBoolean;
17+
import static graphql.Scalars.GraphQLString;
18+
import static org.mockito.ArgumentMatchers.any;
19+
import static org.mockito.Mockito.when;
20+
import static org.testng.AssertJUnit.assertEquals;
21+
import static org.testng.AssertJUnit.assertNotNull;
22+
import static org.testng.AssertJUnit.assertNull;
23+
import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
24+
25+
public class DirectiveCreatorTest {
26+
private DirectiveCreator directiveCreator;
27+
private DirectiveArgumentCreator directiveArgumentCreator;
28+
private CommonPropertiesCreator commonPropertiesCreator;
29+
30+
@GraphQLName("upper")
31+
@GraphQLDescription("upper")
32+
@DirectiveLocations({Introspection.DirectiveLocation.FIELD_DEFINITION, Introspection.DirectiveLocation.INTERFACE})
33+
public static class UpperDirective {
34+
private boolean isActive = true;
35+
}
36+
37+
38+
@GraphQLName("upper")
39+
@GraphQLDescription("upper")
40+
public static class UpperDirectiveNoValidLocations {
41+
private boolean isActive = true;
42+
}
43+
@BeforeMethod
44+
public void setUp() throws NoSuchFieldException {
45+
directiveArgumentCreator = Mockito.mock(DirectiveArgumentCreator.class);
46+
commonPropertiesCreator = Mockito.mock(CommonPropertiesCreator.class);
47+
when(directiveArgumentCreator.getArgument(UpperDirective.class.getDeclaredField("isActive"), UpperDirective.class))
48+
.thenReturn(GraphQLArgument.newArgument().name("isActive").type(GraphQLBoolean).defaultValue(true).build());
49+
when(commonPropertiesCreator.getDescription(any())).thenCallRealMethod();
50+
when(commonPropertiesCreator.getName(any())).thenCallRealMethod();
51+
directiveCreator = new DirectiveCreator(directiveArgumentCreator, commonPropertiesCreator);
52+
}
53+
54+
@Test(expectedExceptions = GraphQLAnnotationsException.class)
55+
public void getDirective_noValidLocations_exceptionIsThrown(){
56+
// Act
57+
GraphQLDirective directive = directiveCreator.getDirective(UpperDirectiveNoValidLocations.class);
58+
}
59+
60+
@Test
61+
public void getDirective_goodDirectiveClass_directiveIsCorrect() {
62+
// Act
63+
GraphQLDirective directive = directiveCreator.getDirective(UpperDirective.class);
64+
65+
// Assert
66+
assertEquals(directive.getName(), "upper");
67+
assertEquals(directive.getDescription(), "upper");
68+
assertArrayEquals(directive.validLocations().toArray(), new Introspection.DirectiveLocation[]{Introspection.DirectiveLocation.FIELD_DEFINITION,
69+
Introspection.DirectiveLocation.INTERFACE});
70+
GraphQLArgument isActive = directive.getArgument("isActive");
71+
assertNotNull(isActive);
72+
assertEquals(isActive.getName(), "isActive");
73+
assertEquals(isActive.getType(), GraphQLBoolean);
74+
assertEquals(isActive.getDefaultValue(), true);
75+
}
76+
}

0 commit comments

Comments
 (0)