Skip to content

Commit

Permalink
Merge pull request #167 from danieltaub96/master
Browse files Browse the repository at this point in the history
DataFetcherConstructor args constructor as the annotation + tests
  • Loading branch information
yarinvak authored May 13, 2018
2 parents 9c7a3a9 + ae93fe0 commit f3393ed
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 10 deletions.
50 changes: 47 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ dependencies {
}
```

(Maven syntax)

```groovy
<dependency>
<groupId>io.github.graphql-java</groupId>
<artifactId>graphql-java-annotations</artifactId>
<version>5.2</version>
</dependency>
```


## Defining Objects

Expand Down Expand Up @@ -58,7 +68,7 @@ class, it will be used instead of the default constructor.

To have a union, you must annotate an interface with `@GraphQLUnion`. In the annotation, you must declare all the
possible types of the union, and a type resolver.
If no type resolver is specified, `UnionTypeResovler` is used. It follows this algorithm:
If no type resolver is specified, `UnionTypeResolver` is used. It follows this algorithm:
The resolver assumes the the DB entity's name is the same as the API entity's name.
If so, it takes the result from the dataFetcher and decides to which
API entity it should be mapped (according to the name).
Expand Down Expand Up @@ -166,9 +176,43 @@ You can specify a custom data fetcher for a field with `@GraphQLDataFetcher`. Th
which will be used as data fetcher.

An instance of the data fetcher will be created. The `args` attribute on the annotation can be used to specify a list of
String arguments to pass to the construcor, allowing to reuse the same class on different fields, with different parameter.
String arguments to pass to the constructor, allowing to reuse the same class on different fields, with different parameter.
The `firstArgIsTargetName` attribute can also be set on `@GraphQLDataFetcher` to pass the field name as a single parameter of the constructor.

Assuming you are using `@GraphQLDataFetcher` this way:

```java
@GraphQLField
@GraphQLDataFetcher(value = HelloWorldDataFetcher.class, args = { "arg1", "arg2" })
public String getHelloWorld(){
return null;
}
```

Then the class that extends from `DataFetcher.class` will get this args to two supported constructors <br>
Or to a constructor that expecting String array that's way (`String[] args` or `String... args`) or for a constructor that expecting the same number of args that you send with in the annotation.<br>
You get to choose which implementation you want.
```java
public class HelloWorldDataFetcher implements DataFetcher<String> {

public HelloWorldDataFetcher(String[] args){
// Do something with your args
}

// Note that you need to expect the same number of args as you send with in the annotation args
public HelloWorldDataFetcher(String arg1, String arg2){
// Do something with your args
}

@Override
public String get(DataFetchingEnvironment environment) {
return "something";
}
}
```



If no argument is needed and a `getInstance` method is present, this method will be called instead of the constructor.

## Type extensions
Expand Down Expand Up @@ -218,7 +262,7 @@ public class HumanExtension {
Classes marked as "extensions" will actually not define a new type, but rather set new fields on the class it extends when it will be created.
All GraphQL annotations can be used on extension classes.

Extensions are registered in GraqhQLAnnotationProcessor by using `registerTypeExtension`. Note that extensions must be registered before the type itself is requested with `getObject()` :
Extensions are registered in GraphQLAnnotationProcessor by using `registerTypeExtension`. Note that extensions must be registered before the type itself is requested with `getObject()` :

```
GraphQLAnnotationsProcessor processor = GraphQLAnnotations.getInstance();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,31 @@ public DataFetcher constructDataFetcher(String fieldName, GraphQLDataFetcher ann
if (args.length == 0) {
return newInstance(annotatedDataFetcher.value());
} else {
try {
final Constructor<? extends DataFetcher> ctr = annotatedDataFetcher.value().getDeclaredConstructor(
stream(args).map(v -> String.class).toArray(Class[]::new));
return constructNewInstance(ctr, (Object[]) args);
} catch (final NoSuchMethodException e) {
throw new GraphQLAnnotationsException("Unable to instantiate DataFetcher via constructor for: " + fieldName, e);
}
return Stream.of(annotatedDataFetcher.value().getConstructors())
// filter only constructor that have the same args as the annotation args
// or that get String[], String... args
.filter(x -> (x.getParameterCount() == args.length) ||
(x.getParameterTypes().length == 1 && x.getParameterTypes()[0] == String[].class))
.map(x -> {
try {
Constructor<? extends DataFetcher> constructor;
if (x.getParameterTypes().length == 1 && x.getParameterTypes()[0] == String[].class) {
constructor = annotatedDataFetcher.value().getDeclaredConstructor(String[].class);
return constructNewInstance(constructor, new Object[]{args});
}

constructor = annotatedDataFetcher.value().getDeclaredConstructor(
stream(args).map(v -> String.class).toArray(Class[]::new));
return constructNewInstance(constructor, (Object[]) args);
} catch (NoSuchMethodException e) {
throw new GraphQLAnnotationsException("Unable to instantiate DataFetcher via constructor for: " + fieldName, e);
}
})
.findFirst()
.orElseThrow(NoArgsConstructorException::new);
}
}

public static class NoArgsConstructorException extends RuntimeException {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* 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.processor.util;

import graphql.annotations.annotationTypes.GraphQLDataFetcher;
import graphql.schema.DataFetcher;
import org.testng.annotations.Test;

import java.lang.annotation.Annotation;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;

/**
* @author danieltaub on 07/05/2018.
*/
public class DataFetcherConstructorTest {
private DataFetcherConstructor constructor = new DataFetcherConstructor();

@Test
public void graphQLDataFetcherWithArrayCtorTest() {
GraphQLDataFetcher graphQLDataFetcher = getGraphQLDataFetcher(DataFetcherMock.class, false, "Arg1", "Arg2");
DataFetcherMock dataFetcher = (DataFetcherMock) constructor.constructDataFetcher(null, graphQLDataFetcher);

assertEquals(dataFetcher.getArgs().length, 2);
assertEquals(dataFetcher.getArgs()[0], "Arg1");
assertEquals(dataFetcher.getArgs()[1], "Arg2");
}

@Test
public void graphQLDataFetcherWithParamsCtorTest() {
GraphQLDataFetcher graphQLDataFetcher = getGraphQLDataFetcher(DataFetcherMock.class, false, "Arg1", "Arg2", "Arg3");
DataFetcherMock dataFetcher = (DataFetcherMock) constructor.constructDataFetcher(null, graphQLDataFetcher);

assertEquals(dataFetcher.getArgs().length, 3);
assertEquals(dataFetcher.getArgs()[0], "Arg1");
assertEquals(dataFetcher.getArgs()[1], "Arg2");
assertEquals(dataFetcher.getArgs()[2], "Arg3");
}

@Test
public void graphQLDataFetcherDefaultCtorTest() {
GraphQLDataFetcher graphQLDataFetcher = getGraphQLDataFetcher(DataFetcherMock.class, false);
DataFetcherMock dataFetcher = (DataFetcherMock) constructor.constructDataFetcher(null, graphQLDataFetcher);

assertNull(dataFetcher.getArgs());
}

private GraphQLDataFetcher getGraphQLDataFetcher(final Class<? extends DataFetcher> value,
boolean argsInTarget, String... args) {
GraphQLDataFetcher annotation = new GraphQLDataFetcher() {
@Override
public Class<? extends Annotation> annotationType() {
return GraphQLDataFetcher.class;
}

@Override
public Class<? extends DataFetcher> value() {
return value;
}

@Override
public String[] args() {
return args;
}

@Override
public boolean firstArgIsTargetName() {
return argsInTarget;
}
};

return annotation;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* 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.processor.util;

import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

/**
* @author danieltaub on 07/05/2018.
*/
public class DataFetcherMock implements DataFetcher {
private String[] args;

public DataFetcherMock(String... args) {
this.args = args;
}

public DataFetcherMock(String arg1, String arg2, String arg3) {
this.args = new String[]{
arg1, arg2, arg3
};
}

public DataFetcherMock() {
}

@Override
public Object get(DataFetchingEnvironment environment) {
return null;
}

public String[] getArgs() {
return args;
}
}

0 comments on commit f3393ed

Please sign in to comment.