Skip to content

Commit acab5c1

Browse files
authored
Merge pull request #186 from yarinvak/add-directive-creation
Add directive creation
2 parents b677fcc + acefa4d commit acab5c1

File tree

11 files changed

+515
-3
lines changed

11 files changed

+515
-3
lines changed

README.md

+61
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,67 @@ GraphQLAnnotations.register(new UUIDTypeFunction())
322322

323323
You can also specify custom type function for any field with `@GraphQLType` annotation.
324324

325+
## Directives
326+
You can wire your fields using directives with annotations.
327+
We allow both defining directives using annotations, and wiring fields.
328+
329+
### Creating/Defining a ``GraphQLDirective``
330+
In order to create a directive, you first have to create a class that the directive will be created from.
331+
For example:
332+
333+
```java
334+
@GraphQLName("upper")
335+
@GraphQLDescription("upper")
336+
@DirectiveLocations({Introspection.DirectiveLocation.FIELD_DEFINITION, Introspection.DirectiveLocation.INTERFACE})
337+
public static class UpperDirective {
338+
private boolean isActive = true;
339+
}
340+
```
341+
342+
The name of the directive will be taken from the ``@GraphQLName`` annotation (if not specified, the name will be the class's name).
343+
The description of the directive will be taken from the ``@GraphQLDescription`` annotation's value.
344+
The valid locations of the directive (locations which the directive can be applied on) will be taken from ``@DirectiveLocations``.
345+
The arguments of the directive will be taken from the fields defined in the class - notice that you can only use primitive types as arguments of a directive.
346+
For example, we defined an ``isActive`` field - which is boolean, and its default value is true. That's how the argument of the directive will be defined.
347+
You can also use ``@GraphQLName`` and ``@GraphQLDescription`` annotations on the field.
348+
349+
After you created the class, you will be able to create the ``GraphQLDirective`` object using the following code:
350+
```java
351+
GraphQLAnnotations.directive(UpperDirective.class);
352+
```
353+
354+
### Wiring with directives
355+
Using directives you will be able to wire fields and more, for example, changing the data fetchers of the fields.
356+
357+
In order to define a wiring functionality, you have to create a Wiring class matching one of you directives. For example:
358+
```java
359+
public class UpperWiring implements AnnotationsDirectiveWiring {
360+
@Override
361+
public GraphQLFieldDefinition onField(AnnotationsWiringEnvironment environment) {
362+
GraphQLFieldDefinition field = (GraphQLFieldDefinition) environment.getElement();
363+
boolean isActive = (boolean) environment.getDirective().getArgument("isActive").getValue();
364+
DataFetcher dataFetcher = DataFetcherFactories.wrapDataFetcher(field.getDataFetcher(), (((dataFetchingEnvironment, value) -> {
365+
if (value instanceof String && isActive) {
366+
return ((String) value).toUpperCase();
367+
}
368+
return value;
369+
})));
370+
return field.transform(builder -> builder.dataFetcher(dataFetcher));
371+
}
372+
}
373+
```
374+
This class turns your string field to upper case if the directive argument "isActive" is set to true.
375+
Now, you have to wire the field itself:
376+
```java
377+
@GraphQLField
378+
@GraphQLDirectives(@Directive(name = "upperCase", wiringClass = UpperWiring.class, argumentsValues = {"true"}))
379+
public static String name() {
380+
return "yarin";
381+
}
382+
```
383+
We now wired the field "name" - so it will turn upper case when calling the field.
384+
The ``Directive`` annotations requires the name of the directive, the wiring class (the ``UpperWiring`` class defined earlier), and the values of the arguments. If an argument has a default value, you don't have to supply a value in the arguments values.
385+
325386
## Relay support
326387

327388
### Mutations

build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ gradle.projectsEvaluated {
6969

7070
dependencies {
7171
compile 'javax.validation:validation-api:1.1.0.Final'
72-
compile 'com.graphql-java:graphql-java:9.1'
72+
compile 'com.graphql-java:graphql-java:9.2'
7373

7474
// OSGi
7575
compileOnly 'org.osgi:org.osgi.core:6.0.0'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright 2016 Yurii Rashkovskii
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
*/
15+
package graphql.annotations.directives.creation;
16+
17+
import graphql.introspection.Introspection;
18+
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
@Target({ElementType.TYPE})
25+
@Retention(RetentionPolicy.RUNTIME)
26+
public @interface DirectiveLocations {
27+
Introspection.DirectiveLocation[] value();
28+
}

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

+20
Original file line numberDiff line numberDiff line change
@@ -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
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Copyright 2016 Yurii Rashkovskii
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
*/
15+
package graphql.annotations.processor.directives;
16+
17+
import graphql.annotations.annotationTypes.GraphQLDescription;
18+
import graphql.annotations.annotationTypes.GraphQLName;
19+
20+
import java.lang.reflect.AnnotatedElement;
21+
import java.lang.reflect.Field;
22+
23+
public class CommonPropertiesCreator {
24+
public String getDescription(AnnotatedElement annotatedElement) {
25+
GraphQLDescription graphQLDescriptionAnnotation = annotatedElement.getAnnotation(GraphQLDescription.class);
26+
if (graphQLDescriptionAnnotation != null) {
27+
return graphQLDescriptionAnnotation.value();
28+
}
29+
return null;
30+
}
31+
32+
public String getName(AnnotatedElement annotatedElement) {
33+
GraphQLName graphQLNameAnnotation = annotatedElement.getAnnotation(GraphQLName.class);
34+
if (graphQLNameAnnotation != null) {
35+
return graphQLNameAnnotation.value();
36+
} else if (annotatedElement instanceof Class<?>) {
37+
return ((Class<?>) annotatedElement).getSimpleName();
38+
} else if (annotatedElement instanceof Field) {
39+
return ((Field) annotatedElement).getName();
40+
}
41+
return null;
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Copyright 2016 Yurii Rashkovskii
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
*/
15+
package graphql.annotations.processor.directives;
16+
17+
import graphql.annotations.processor.ProcessingElementsContainer;
18+
import graphql.annotations.processor.exceptions.GraphQLAnnotationsException;
19+
import graphql.annotations.processor.typeFunctions.TypeFunction;
20+
import graphql.schema.GraphQLArgument;
21+
import graphql.schema.GraphQLInputType;
22+
23+
import java.lang.reflect.Field;
24+
25+
import static graphql.schema.GraphQLArgument.newArgument;
26+
27+
public class DirectiveArgumentCreator {
28+
private CommonPropertiesCreator commonPropertiesCreator;
29+
private TypeFunction typeFunction;
30+
private ProcessingElementsContainer container;
31+
32+
public DirectiveArgumentCreator(CommonPropertiesCreator commonPropertiesCreator, TypeFunction typeFunction, ProcessingElementsContainer container) {
33+
this.commonPropertiesCreator = commonPropertiesCreator;
34+
this.typeFunction = typeFunction;
35+
this.container = container;
36+
}
37+
38+
39+
public GraphQLArgument getArgument(Field field, Class<?> containingClass) {
40+
GraphQLArgument.Builder builder = newArgument()
41+
.name(commonPropertiesCreator.getName(field))
42+
.description(commonPropertiesCreator.getDescription(field))
43+
.type(getType(field));
44+
try {
45+
builder.defaultValue(getDefaultValue(field, containingClass));
46+
} catch (IllegalAccessException | InstantiationException e) {
47+
throw new GraphQLAnnotationsException(e);
48+
}
49+
50+
return builder.build();
51+
}
52+
53+
private Object getDefaultValue(Field field, Class<?> containingClass) throws IllegalAccessException, InstantiationException {
54+
field.setAccessible(true);
55+
Object object = containingClass.newInstance();
56+
return field.get(object);
57+
}
58+
59+
private GraphQLInputType getType(Field field) {
60+
return (GraphQLInputType) typeFunction.buildType(true, field.getType(),
61+
field.getAnnotatedType(), container);
62+
}
63+
64+
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Copyright 2016 Yurii Rashkovskii
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
*/
15+
package graphql.annotations.processor.directives;
16+
17+
import graphql.annotations.directives.creation.DirectiveLocations;
18+
import graphql.annotations.processor.exceptions.GraphQLAnnotationsException;
19+
import graphql.introspection.Introspection;
20+
import graphql.schema.GraphQLDirective;
21+
22+
import java.util.Arrays;
23+
24+
import static graphql.schema.GraphQLDirective.newDirective;
25+
26+
public class DirectiveCreator {
27+
28+
private DirectiveArgumentCreator directiveArgumentCreator;
29+
private CommonPropertiesCreator commonPropertiesCreator;
30+
31+
32+
public DirectiveCreator(DirectiveArgumentCreator directiveArgumentCreator, CommonPropertiesCreator commonPropertiesCreator) {
33+
this.directiveArgumentCreator = directiveArgumentCreator;
34+
this.commonPropertiesCreator = commonPropertiesCreator;
35+
}
36+
37+
public GraphQLDirective getDirective(Class<?> annotatedClass) {
38+
GraphQLDirective.Builder builder = newDirective()
39+
.name(commonPropertiesCreator.getName(annotatedClass))
40+
.description(commonPropertiesCreator.getDescription(annotatedClass));
41+
Introspection.DirectiveLocation[] validLocations = getValidLocations(annotatedClass);
42+
if (validLocations == null || validLocations.length == 0) {
43+
throw new GraphQLAnnotationsException("No valid locations defined on directive", null);
44+
}
45+
builder.validLocations(validLocations);
46+
buildArguments(builder, annotatedClass);
47+
48+
return builder.build();
49+
}
50+
51+
private void buildArguments(GraphQLDirective.Builder builder, Class<?> annotatedClass) {
52+
Arrays.stream(annotatedClass.getDeclaredFields()).forEach(x ->
53+
builder.argument(directiveArgumentCreator.getArgument(x, annotatedClass)));
54+
}
55+
56+
57+
private Introspection.DirectiveLocation[] getValidLocations(Class<?> annotatedClass) {
58+
DirectiveLocations directiveLocationsAnnotation = annotatedClass.getAnnotation(DirectiveLocations.class);
59+
if (directiveLocationsAnnotation != null) {
60+
return directiveLocationsAnnotation.value();
61+
}
62+
return null;
63+
}
64+
65+
}

0 commit comments

Comments
 (0)