Skip to content

Commit

Permalink
Replace using @name to @namespace on GraphQLApi and GraphQLClientApi
Browse files Browse the repository at this point in the history
  • Loading branch information
Roman Lovakov authored and jmartisk committed Sep 26, 2024
1 parent e5f605f commit bd3497e
Show file tree
Hide file tree
Showing 31 changed files with 1,029 additions and 544 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
import java.util.List;
import java.util.Map;

import org.eclipse.microprofile.graphql.Name;
import org.jboss.logging.Logger;

import io.smallrye.graphql.api.Namespace;
import io.smallrye.graphql.client.impl.ErrorMessageProvider;
import io.smallrye.graphql.client.impl.GraphQLClientConfiguration;
import io.smallrye.graphql.client.impl.GraphQLClientsConfiguration;
Expand Down Expand Up @@ -145,6 +147,14 @@ public VertxTypesafeGraphQLClientBuilder websocketInitializationTimeout(Integer

@Override
public <T> T build(Class<T> apiClass) {
Name nameAnnotation = apiClass.getAnnotation(Name.class);
Namespace namespaceAnnotation = apiClass.getAnnotation(Namespace.class);

if (nameAnnotation != null && namespaceAnnotation != null) {
throw new RuntimeException("You can only use one of the annotations - @Name or @Namespace " +
"over the GraphQLClientApi interface. Please, fix the following interface: " + apiClass.getName());
}

if (this.options == null) {
this.options = new WebClientOptions();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ private JsonObject request(MethodInvocation method) {
}
request.add("query", query);
request.add("variables", variables(method));
request.add("operationName", method.getName());
request.add("operationName", method.getOperationName());
JsonObject result = request.build();
log.tracef("full graphql request: %s", result.toString());
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,11 @@ public QueryBuilder(MethodInvocation method) {
public String build() {
StringBuilder request = new StringBuilder(method.getOperationTypeAsString());
request.append(" ");
request.append(method.getName());
request.append(method.getOperationName());
if (method.hasValueParameters())
request.append(method.valueParameters().map(this::declare).collect(joining(", ", "(", ")")));

String groupName = method.getGroupName();
if (groupName != null) {
request.append(" { ");
request.append(groupName);
}
method.getNamespaces().forEach(namespace -> request.append(" { ").append(namespace));

if (method.isSingle()) {
request.append(" { ");
Expand All @@ -45,11 +41,11 @@ public String build() {

request.append(fields(method.getReturnType()));

if (method.isSingle())
if (method.isSingle()) {
request.append(" }");
}

if (groupName != null)
request.append(" } ");
request.append(" }".repeat(method.getNamespaces().size()));

return request.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,12 @@ public Object read() {
private JsonObject readData() {
if (!response.containsKey("data") || response.isNull("data"))
return null;
String groupName = method.getGroupName();
JsonObject data = groupName != null
? response.getJsonObject("data").getJsonObject(groupName)
: response.getJsonObject("data");

JsonObject data = response.getJsonObject("data");
for (String namespace : method.getNamespaces()) {
data = data.getJsonObject(namespace);
}

if (method.isSingle() && !data.containsKey(method.getName()))
throw new InvalidResponseException("No data for '" + method.getName() + "'");
return data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

Expand All @@ -23,6 +24,7 @@
import org.eclipse.microprofile.graphql.Name;
import org.eclipse.microprofile.graphql.Query;

import io.smallrye.graphql.api.Namespace;
import io.smallrye.graphql.api.Subscription;
import io.smallrye.graphql.client.core.OperationType;
import io.smallrye.graphql.client.model.MethodKey;
Expand All @@ -36,14 +38,16 @@ public static MethodInvocation of(Method method, Object... args) {
private final TypeInfo type;
private final Method method;
private final Object[] parameterValues;
private final String groupName;
private final List<String> namespaces;
private final String operationName;
private List<ParameterInfo> parameters;

private MethodInvocation(TypeInfo type, Method method, Object[] parameterValues) {
this.type = type;
this.method = method;
this.parameterValues = parameterValues;
this.groupName = readGroupName(method);
this.namespaces = readNamespaces(method);
this.operationName = readOperationName(this.namespaces);
}

@Override
Expand Down Expand Up @@ -262,18 +266,41 @@ public String getOperationTypeAsString() {
}
}

public String getGroupName() {
return groupName;
public List<String> getNamespaces() {
return namespaces;
}

private String readGroupName(Method method) {
Name annotation = method.getDeclaringClass().getAnnotation(Name.class);
if (annotation != null) {
String groupName = annotation.value().trim();
if (!groupName.isEmpty()) {
return groupName;
public String getOperationName() {
return operationName;
}

private List<String> readNamespaces(Method method) {
if (method.getDeclaringClass().isAnnotationPresent(Namespace.class)) {
String[] names = method.getDeclaringClass().getAnnotation(Namespace.class).value();
if (names.length > 0) {
return List.of(names);
}
} else if (method.getDeclaringClass().isAnnotationPresent(Name.class)) {
String name = method.getDeclaringClass().getAnnotation(Name.class).value();
if (!name.isBlank()) {
return List.of(name);
}
}
return List.of();
}

private String readOperationName(List<String> names) {
if (names.isEmpty()) {
return getName();
} else {
String namespace = names.stream()
.map(this::makeFirstLetterUppercase)
.collect(Collectors.joining());
return namespace + makeFirstLetterUppercase(getName());
}
return null;
}

private String makeFirstLetterUppercase(String value) {
return value.substring(0, 1).toUpperCase() + value.substring(1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ private static Map<DotName, AnnotationInstance> getAnnotationsWithFilter(Type ty
public static final DotName NON_BLOCKING = DotName.createSimple("io.smallrye.common.annotation.NonBlocking");

// SmallRye GraphQL Annotations (Experimental)
public static final DotName NAMESPACE = DotName.createSimple("io.smallrye.graphql.api.Namespace");
public static final DotName TO_SCALAR = DotName.createSimple("io.smallrye.graphql.api.ToScalar"); // TODO: Remove
public static final DotName ADAPT_TO_SCALAR = DotName.createSimple("io.smallrye.graphql.api.AdaptToScalar");
public static final DotName ADAPT_WITH = DotName.createSimple("io.smallrye.graphql.api.AdaptWith");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package io.smallrye.graphql.client.model;

import static io.smallrye.graphql.client.model.Annotations.GRAPHQL_CLIENT_API;
import static io.smallrye.graphql.client.model.Annotations.NAME;
import static io.smallrye.graphql.client.model.Annotations.NAMESPACE;
import static io.smallrye.graphql.client.model.ScanningContext.getIndex;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
Expand Down Expand Up @@ -56,6 +59,8 @@ private ClientModels generateClientModels() {
Collection<AnnotationInstance> graphQLApiAnnotations = getIndex()
.getAnnotations(GRAPHQL_CLIENT_API);

validateNamespaceAnnotations(graphQLApiAnnotations);

graphQLApiAnnotations.forEach(graphQLApiAnnotation -> {
ClientModel operationMap = new ClientModel();
ClassInfo apiClass = graphQLApiAnnotation.target().asClass();
Expand All @@ -75,6 +80,19 @@ private ClientModels generateClientModels() {
return clientModels;
}

private void validateNamespaceAnnotations(Collection<AnnotationInstance> graphQLApiAnnotations) {
List<String> errorInterfaces = graphQLApiAnnotations.stream()
.map(annotation -> annotation.target().asClass())
.filter(classInfo -> classInfo.hasDeclaredAnnotation(NAMESPACE) && classInfo.hasDeclaredAnnotation(NAME))
.map(classInfo -> classInfo.name().toString())
.collect(Collectors.toList());
if (!errorInterfaces.isEmpty()) {
throw new RuntimeException("You can only use one of the annotations - @Name or @Namespace " +
"over the GraphQLClientApi interface. Please, fix the following interfaces: " +
String.join(", ", errorInterfaces));
}
}

/**
* Retrieves all methods, including those from superclasses (interfaces).
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,12 @@ public QueryBuilder(MethodInfo method) {
public String build() {
StringBuilder request = new StringBuilder(method.getOperationTypeAsString());
request.append(" ");
request.append(method.getName()); // operationName
request.append(method.getOperationName());
if (method.hasValueParameters()) {
request.append(method.valueParameters().stream().map(method::declare).collect(joining(", ", "(", ")")));
}

String groupName = method.getGroupName();
if (groupName != null) {
request.append(" { ");
request.append(groupName);
}
method.getNamespaces().forEach(namespace -> request.append(" { ").append(namespace));

if (method.isSingle()) {
request.append(" { ");
Expand All @@ -61,11 +57,11 @@ public String build() {

request.append(method.fields(method.getReturnType()));

if (method.isSingle())
if (method.isSingle()) {
request.append(" }");
}

if (groupName != null)
request.append(" } ");
request.append(" }".repeat(method.getNamespaces().size()));

return request.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static io.smallrye.graphql.client.model.Annotations.MULTIPLE;
import static io.smallrye.graphql.client.model.Annotations.MUTATION;
import static io.smallrye.graphql.client.model.Annotations.NAME;
import static io.smallrye.graphql.client.model.Annotations.NAMESPACE;
import static io.smallrye.graphql.client.model.Annotations.QUERY;
import static io.smallrye.graphql.client.model.Annotations.SUBCRIPTION;
import static io.smallrye.graphql.client.model.ScanningContext.getIndex;
Expand Down Expand Up @@ -37,7 +38,8 @@ public class OperationModel implements NamedElement {
private final Stack<String> expressionStack = new Stack<>();
private Stack<TypeModel> rawParametrizedTypes = new Stack<>();
private final List<DirectiveInstance> directives;
private final String groupName;
private final String operationName;
private final List<String> namespaces;

/**
* Creates a new {@code OperationModel} instance based on the provided Jandex {@link MethodInfo}.
Expand All @@ -51,7 +53,8 @@ public class OperationModel implements NamedElement {
getDirectiveLocation(), AnnotationTarget.Kind.METHOD)
.map(DirectiveInstance::of)
.collect(toList());
this.groupName = readGroupName(method);
this.namespaces = readNamespaces(method);
this.operationName = readOperationName(this.namespaces);
}

/**
Expand Down Expand Up @@ -399,23 +402,42 @@ private boolean isRawParametrizedType(TypeModel type) {
return type.isCustomParametrizedType() && !type.getFirstRawType().isTypeVariable();
}

public String getGroupName() {
return groupName;
public List<String> getNamespaces() {
return namespaces;
}

private String readGroupName(MethodInfo method) {
List<AnnotationInstance> annotationInstances = method.declaringClass().annotations(NAME);
for (AnnotationInstance annotationInstance : annotationInstances) {
if (annotationInstance.target().kind() == AnnotationTarget.Kind.CLASS) {
if (annotationInstance.target().asClass().name().equals(method.declaringClass().name())) {
String groupName = annotationInstance.value().asString().trim();
if (!groupName.isEmpty()) {
return groupName;
}
}
public String getOperationName() {
return operationName;
}

private List<String> readNamespaces(MethodInfo method) {
if (method.declaringClass().hasDeclaredAnnotation(NAMESPACE)) {
String[] names = method.declaringClass().declaredAnnotation(NAMESPACE).value().asStringArray();
if (names.length > 0) {
return List.of(names);
}
} else if (method.declaringClass().hasDeclaredAnnotation(NAME)) {
String value = method.declaringClass().declaredAnnotation(NAME).value().asString();
if (!value.isEmpty()) {
return List.of(value);
}
}
return List.of();
}

private String readOperationName(List<String> names) {
if (names.isEmpty()) {
return getName();
} else {
String namespace = names.stream()
.map(this::makeFirstLetterUppercase)
.collect(joining());
return namespace + makeFirstLetterUppercase(getName());
}
return null;
}

private String makeFirstLetterUppercase(String value) {
return value.substring(0, 1).toUpperCase() + value.substring(1);
}

private String fieldsFragment(TypeModel type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.jboss.jandex.Index;
import org.junit.jupiter.api.Test;

import io.smallrye.graphql.api.Namespace;
import io.smallrye.graphql.api.Subscription;
import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi;

Expand Down Expand Up @@ -235,13 +236,40 @@ void namedClientModelTest() throws IOException {
assertEquals(3, clientModel.getOperationMap().size());
assertOperation(clientModel,
new MethodKey("findAllStringsQuery", new Class[0]),
"query findAll { named { findAll } } ");
"query NamedFindAll { named { findAll } }");
assertOperation(clientModel,
new MethodKey("findAllStringsName", new Class[0]),
"query findAll { named { findAll } } ");
"query NamedFindAll { named { findAll } }");
assertOperation(clientModel,
new MethodKey("update", new Class[] { String.class }),
"mutation update($s: String) { named { update(s: $s) } } ");
"mutation NamedUpdate($s: String) { named { update(s: $s) } }");
}

@Namespace({ "first", "second" })
@GraphQLClientApi(configKey = "namespaced-string-api")
interface NamespacedClientApi {
@Query("findAll")
List<String> findAllStringsQuery();

@Mutation("update")
@Name("update")
String update(String s);
}

@Test
void namespacedClientModelTest() throws IOException {
String configKey = "namespaced-string-api";
ClientModels clientModels = ClientModelBuilder
.build(Index.of(NamespacedClientApi.class));
assertNotNull(clientModels.getClientModelByConfigKey(configKey));
ClientModel clientModel = clientModels.getClientModelByConfigKey(configKey);
assertEquals(2, clientModel.getOperationMap().size());
assertOperation(clientModel,
new MethodKey("findAllStringsQuery", new Class[0]),
"query FirstSecondFindAll { first { second { findAll } } }");
assertOperation(clientModel,
new MethodKey("update", new Class[] { String.class }),
"mutation FirstSecondUpdate($s: String) { first { second { update(s: $s) } } }");
}

private void assertOperation(ClientModel clientModel, MethodKey methodKey, String expectedQuery) {
Expand Down
Loading

0 comments on commit bd3497e

Please sign in to comment.