Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Added GraphQLOperation annotation support. #17

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 68 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,30 @@ GraphQLResponseEntity<SampleModel> responseEntity = graphQLTemplate.query(reques

### Annotations to configure fields

#### `@GraphQLOperation(name="name", method=GraphQLTemplate.GraphQLMethod, property=@GraphQLProperty, varibales={@GraphQLVariable})`
> Used above classes for more complex queries with variables.<br><b>name (required)</b>: GraphQL operation name <br><b>method (optional)</b>: GraphQL operation method (Query, Mutation) defaults to Query.<br><b>property (optional)</b>: Define property for the operation. Overrides @GraphQLProperty. Defaults to the class name.<br><b>variables (optional)</b>: Variables to use in the operation.

*example:*
```java
...
@GraphQLOperation(name="MyUserQuery",
property = @GraphQLProperty(name = "User", arguments = {
@GraphQLArgument(name = "name", variable="name")
}),
variables={@GraphQLVariable(name = "name", scalar = "String")})
class User {
...
}
```

*result:*
```
query MyUserQuery($name:String){
UserQuery(name: $name) {
...
}
```

#### `@GraphQLArgument(name="name", value="defaultVal", type="String")`
> Used above property fields<br><b>name (required)</b>: GraphQL argument name<br><b>value (optional)</b>: default value to set the argument to<br><b>type (optional)</b>: how to parse the optional value. Accepts an enum of "String", "Boolean", "Integer", or "Float" - defaults to be parsed as a string.<br>You can specify fields arguments directly inline using this. This is good for static field arguments such as result display settings, for instance the format of a date or the locale of a message.

Expand Down Expand Up @@ -150,8 +174,7 @@ query {
```

#### `@GraphQLVariable(name="name", scalar="Float!")`
> Used above property fields<br><b>name (required)</b>: GraphQL variable name<br><b>type (required)</b>: GraphQL scalar type. Including optional and required parsing (!)<br>This is good for sharing the same variables across multiple input parameters in a query.

> (deprecated) Used above property fields<br><b>name (required)</b>: GraphQL variable name<br><b>type (required)</b>: GraphQL scalar type. Including optional and required parsing (!)<br>This is good for sharing the same variables across multiple input parameters in a query.
*example:*
```java
...
Expand All @@ -168,7 +191,49 @@ query($isPublic: Boolean) {
...
```

#### `@GraphQLVariables({@GraphQLVariable})`
> (Preferred) Use `@GraphQLArgument` in models to define the placeholder, then Use w/ `@GraphQLOperation` when specifying variables used in the operation. Finally link the argument w/ the variable when building the request.
```java
@GraphQLArguments({
@GraphQLArgument(name="isPublic", value="true", type="Boolean"),
@GraphQLArgument(name="name")
})
class User {
...
};

@GraphQLOperation(name = "FindUserByName", variables = {
@GraphQLVariable(name = "nameFilter", scalar = "String")
})
class UserByName extends User {
}

class UserRepo {
public User findUserByName(String name) {
GraphQLTemplate graphQLTemplate = new GraphQLTemplate();

GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
.url("http://graphql.example.com/graphql");
.variables(new Variable("nameFilter", name))
.arguments(new Arguments("UserByName",
new Argument("name", null, "nameFilter")))
.request(UserByName.class)
.build();
GraphQLResponseEntity<UserByName> responseEntity = graphQLTemplate.query(requestEntity, UserByName.class);
}
}
```

*result:*
```
query FindUserByName($nameFilter: String) {
UserByName(name: $nameFilter) {
...
}
}
```


#### `@GraphQLVariables({@GraphQLVariable})` (deprecated)
> Used above property fields<br>Annotation for allowing mutliple variables for a given field.

*example:*
Expand Down
9 changes: 9 additions & 0 deletions nodes/src/main/java/io/aexp/nodes/graphql/Argument.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,19 @@
package io.aexp.nodes.graphql;

public class Argument<T> extends Parameter {
private String variable;
public Argument(String key, T value) {
super(key, value);
}

public String getVariable() {
return variable;
}

public void setVariable(String variable) {
this.variable = variable;
}

@Override
public String toString() {
return super.toString(this.getClass().getSimpleName());
Expand Down
152 changes: 103 additions & 49 deletions nodes/src/main/java/io/aexp/nodes/graphql/GraphQLRequestEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@
import java.util.List;
import java.util.Map;

import io.aexp.nodes.graphql.annotations.GraphQLArgument;
import io.aexp.nodes.graphql.annotations.GraphQLArguments;
import io.aexp.nodes.graphql.annotations.GraphQLIgnore;
import io.aexp.nodes.graphql.annotations.GraphQLProperty;
import io.aexp.nodes.graphql.annotations.GraphQLVariable;
import io.aexp.nodes.graphql.annotations.GraphQLVariables;
import io.aexp.nodes.graphql.annotations.*;
import io.aexp.nodes.graphql.exceptions.GraphQLException;

public final class GraphQLRequestEntity {
Expand All @@ -42,9 +37,8 @@ public static RequestBuilder Builder() {
private final URL url;
private final Map<String, String> headers;
private final Map<String, Object> variables;
private final Property property = new Property();
private final List<Class> scalars;
private GraphQLTemplate.GraphQLMethod requestMethod = GraphQLTemplate.GraphQLMethod.QUERY;
private final Operation operation = new Operation();
private String request;

GraphQLRequestEntity(RequestBuilder builder) {
Expand All @@ -55,7 +49,7 @@ public static RequestBuilder Builder() {
if (builder.request != null) {
this.request = builder.request;
} else {
setPropertiesFromClass(builder.clazz);
setOperationFromClass(builder.clazz);
}
if (builder.arguments != null) {
for (Arguments arguments : builder.arguments) {
Expand All @@ -76,7 +70,7 @@ public String getRequest() {
if (this.request != null) {
return this.request;
}
return property.getMessage(null);
return operation.getMessage();
}

public Map<String, Object> getVariables() {
Expand All @@ -88,8 +82,7 @@ public List<Class> getScalars() {
}

void setRequestMethod(GraphQLTemplate.GraphQLMethod requestMethod) {
this.requestMethod = requestMethod;
property.setMethod(requestMethod);
operation.setMethod(requestMethod);
}

@Override
Expand All @@ -102,6 +95,53 @@ public String toString() {

// Utility

/**
* Builds the request tree from the provided class.
*
* @param clazz the class used to construct the request
*/
private void setOperationFromClass(Class clazz) {
Map<String, Object> propertyVariables = new HashMap<String, Object>();
Map<String, Property> children = this.getChildren(clazz, propertyVariables);

GraphQLProperty graphQLProperty = (GraphQLProperty) clazz.getAnnotation(GraphQLProperty.class);
GraphQLOperation graphQLOperation = (GraphQLOperation) clazz.getAnnotation(GraphQLOperation.class);
GraphQLProperty operationGraphQLProperty = null;

if(graphQLOperation != null) {
operation.setName(graphQLOperation.name());
operation.setMethod(graphQLOperation.method());

for(GraphQLVariable graphQLVariable : graphQLOperation.variables()) {
propertyVariables.put("$" + graphQLVariable.name(), graphQLVariable.scalar());
}

operationGraphQLProperty = graphQLOperation.property();
if(operationGraphQLProperty != null){
Property classProperty = annotationToProperty(operationGraphQLProperty);
// fix for RequestBuilderTest tests that expectation top level property has no resource name (alias)
// TODO - remove when RequestBuilderTests are fixed
classProperty.setResourceName(null);
classProperty.setChildren(children);
operation.getChildren().put(operationGraphQLProperty.name(), classProperty);
}
}

if(operationGraphQLProperty == null && graphQLProperty != null) {
Property classProperty = annotationToProperty(graphQLProperty);
// fix for RequestBuilderTest tests that expectation top level property has no resource name (alias)
// TODO - remove when RequestBuilderTests are fixed
classProperty.setResourceName(null);
classProperty.setChildren(children);
operation.getChildren().put(graphQLProperty.name(), classProperty);
} else if(operationGraphQLProperty == null){
operation.setChildren(children);
}

operation.setVariables(propertyVariables);
operation.setChildren(Collections.unmodifiableMap(operation.getChildren()));
}

/**
* Set arguments on a field in the request.
*
Expand All @@ -113,11 +153,28 @@ public String toString() {
* @throws GraphQLException is thrown at runtime if the dotPath does not correlate to a field accepting arguments
*/
private void setArguments(String dotPath, List<Argument> arguments) throws GraphQLException {
Property argProp = property;
String[] path = dotPath.split("\\.");
Map<String, Property> children = operation.getChildren();
Property argProp = null;

if(path.length == 0) {
if(children.size() > 1) {
throw new GraphQLException("Operation contains more than one property, dot path must specify a top level property");
}

Map.Entry<String,Property> entry = children.entrySet().iterator().next();
argProp = entry.getValue();
}

int childrenIndex = 0;
for (String key: path) {
argProp = argProp.getChildren().get(key);
if(childrenIndex++ == 0) {
argProp = children.get(key);
} else if(argProp != null) {
argProp = argProp.getChildren().get(key);
}
}

if (argProp == null) throw new GraphQLException("'" + dotPath + "' is an invalid property path");
List<Argument> args = argProp.getArguments();
if (args == null) {
Expand All @@ -129,6 +186,7 @@ private void setArguments(String dotPath, List<Argument> arguments) throws Graph
if (index == -1) throw new GraphQLException("Argument '" + argument + "' doesn't exist on path '" + dotPath + "'");
Argument propArg = args.get(index);
propArg.setValue(argument.getValue());
propArg.setVariable(argument.getVariable());
}
}

Expand All @@ -143,36 +201,6 @@ private Integer indexOfArg(List<Argument> arguments, String key) {
return -1;
}

/**
* Builds the request tree from the provided class.
*
* @param clazz the class used to construct the request
*/
private void setPropertiesFromClass(Class clazz) {
Map<String, Object> propertyVariables = new HashMap<String, Object>();
Map<String, Property> children = this.getChildren(clazz, propertyVariables);
GraphQLProperty graphQLProperty = (GraphQLProperty) clazz.getAnnotation(GraphQLProperty.class);
if (graphQLProperty != null) {
Property resourceProperty = new Property();
List<Argument> arguments = null;
String resourceName = graphQLProperty.name();
if (graphQLProperty.arguments().length > 0) {
arguments = new ArrayList<Argument>();
for (GraphQLArgument graphQLArgument : graphQLProperty.arguments()) {
arguments = setArgument(arguments, graphQLArgument);
}
}
resourceProperty.setArguments(arguments);
resourceProperty.setChildren(children);
Map<String, Property> resourceChild = new HashMap<String, Property>();
resourceChild.put(resourceName, resourceProperty);
children = resourceChild;
}
property.setChildren(Collections.unmodifiableMap(children));
property.setMethod(requestMethod);
property.setVariables(Collections.unmodifiableMap(propertyVariables));
}

private Map<String, Object> variableListToMap(List<Variable> variables) {
Map<String, Object> variableMap = new HashMap<String, Object>();
for (Variable variable : variables) {
Expand All @@ -181,6 +209,27 @@ private Map<String, Object> variableListToMap(List<Variable> variables) {
return variableMap;
}

private Property annotationToProperty(GraphQLProperty annotation) {
Property property = new Property();
property.setChildren(new HashMap<String, Property>());
property.setArguments(new ArrayList<Argument>());

if( annotation == null) {
return property;
}

List<Argument> arguments = new ArrayList<Argument>();
String name = annotation.name();
property.setResourceName(name);

for (GraphQLArgument graphQLArgument : annotation.arguments()) {
arguments = setArgument(arguments, graphQLArgument);
}

property.setArguments(arguments);
return property;
}

private boolean isList(Field field) {
String simpleName = field.getType().getSimpleName();
return simpleName.equalsIgnoreCase("ArrayList") ||
Expand All @@ -202,23 +251,28 @@ private boolean isProperty(Class clazz) {
}

/**
* Adds the arguments into the argument list from the GraphQLArgument annotation to provide the correct type
* Adds the arguments into the argument list from the GraphQLArgument annotation to provide the correct method
*
* @param arguments list of arguments to add the annotated argument to
* @param graphQLArgument annotated argument to add to the request construction
* @return List\<Argument>
*/
private List<Argument> setArgument(List<Argument> arguments, GraphQLArgument graphQLArgument) {
String type = graphQLArgument.type();
Argument argument;
if ("Boolean".equalsIgnoreCase(type)) {
arguments.add(new Argument<Boolean>(graphQLArgument.name(), Boolean.valueOf(graphQLArgument.value())));
argument = new Argument<Boolean>(graphQLArgument.name(), Boolean.valueOf(graphQLArgument.value()));
} else if ("Integer".equalsIgnoreCase(type)) {
arguments.add(new Argument<Integer>(graphQLArgument.name(), Integer.valueOf(graphQLArgument.value())));
argument = new Argument<Integer>(graphQLArgument.name(), Integer.valueOf(graphQLArgument.value()));
} else if ("Float".equalsIgnoreCase(type)) {
arguments.add(new Argument<Float>(graphQLArgument.name(), Float.valueOf(graphQLArgument.value())));
argument = new Argument<Float>(graphQLArgument.name(), Float.valueOf(graphQLArgument.value()));
} else {
arguments.add(new Argument<String>(graphQLArgument.name(), graphQLArgument.value()));
argument = new Argument<String>(graphQLArgument.name(), graphQLArgument.value());
}

argument.setVariable(graphQLArgument.variable());

arguments.add(argument);
return arguments;
}

Expand Down
Loading