Skip to content

Commit

Permalink
add graphql client class
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulBredl committed May 4, 2024
1 parent f797ebc commit 97697ef
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ dependencies {
implementation 'io.dapr:dapr-sdk:1.+' // Dapr's core SDK with all features, except Actors.
implementation 'io.dapr:dapr-sdk-springboot:1.+' // Dapr's SDK integration with SpringBoot
implementation 'org.modelmapper:modelmapper:3.+'
implementation "io.github.kobylynskyi:graphql-java-codegen:5.+"
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'org.postgresql:postgresql'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package de.unistuttgart.iste.meitrex.common.graphqlclient;

import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLOperationRequest;
import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLRequest;
import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLResponseProjection;
import graphql.ErrorType;
import graphql.GraphqlErrorException;
import lombok.RequiredArgsConstructor;
import org.springframework.graphql.ResponseError;
import org.springframework.graphql.client.ClientGraphQlResponse;
import org.springframework.graphql.client.WebGraphQlClient;
import reactor.core.publisher.Mono;

import java.util.Optional;
import java.util.function.Supplier;

/**
* Executes GraphQL requests, using generated client classes from the GraphQL codegen plugin.
* <p>
* An example how to build requests and projections:
*
* <pre>
* GraphQlRequestExecutor executor = new GraphQlRequestExecutor(graphQlClient, authTokenSupplier);
* ProductByIdQueryRequest productByIdQueryRequest = new ProductByIdQueryRequest();
* productByIdQueryRequest.setId(productId);
* ProductResponseProjection responseProjection = new ProductResponseProjection()
* .id()
* .title()
* .price();
*
* executor.request(productByIdQueryRequest)
* .projectTo(Product.class, responseProjection)
* .retrieve()
* .subscribe(product -> // do something with the product);
* </pre>
*/
public class GraphQlRequestExecutor {

private final Supplier<WebGraphQlClient> graphQlClientSupplier;

/**
* Create a new request executor.
*
* @param graphQlClientSupplier The supplier for the client to use for the requests
* This is a supplier to allow adding headers to the client for each request.
*/
public GraphQlRequestExecutor(Supplier<WebGraphQlClient> graphQlClientSupplier) {
this.graphQlClientSupplier = graphQlClientSupplier;
}

/**
* Start building a new request.
*
* @param request The request object to build
* @return The builder for the request
*/
public OperationSpecification request(GraphQLOperationRequest request) {
return new OperationSpecification(request);
}

/**
* Internal class to build a request and projection.
*/
@RequiredArgsConstructor
public class OperationSpecification {
private final GraphQLOperationRequest request;

/**
* Specify the result type and projection for the request.
*
* @param responseType The type of the response
* @param projection The projection, specifying which fields to retrieve
* @param <T> The type of the response
* @return An object that can be used to retrieve the response
*/
public <T> ProjectionSpecification<T> projectTo(Class<T> responseType, GraphQLResponseProjection projection) {
return new ProjectionSpecification<>(request, projection, responseType);
}
}

/**
* Internal class used to retrieve the response.
*
* @param <T> The type of the response
*/
@RequiredArgsConstructor
public class ProjectionSpecification<T> {
private final GraphQLOperationRequest request;
private final GraphQLResponseProjection projection;
private final Class<T> responseType;

/**
* Retrieve the response from the server.
*
* @return A Mono that will emit the response
*/
public Mono<T> retrieve() {
GraphQLRequest graphQlRequest = new GraphQLRequest(request, projection);

return graphQlClientSupplier.get()
.document(graphQlRequest.toQueryString())
.execute()
.handle((response, sink) -> {
if (response.getErrors().isEmpty()) {
// map to the response type
extractResponseData(response).ifPresent(sink::next);
} else {
sink.error(toGraphQlException(response));
}
});
}

private Optional<T> extractResponseData(ClientGraphQlResponse response) {
String retrievalName = request.getAlias() != null ? request.getAlias() : request.getOperationName();
T result = response.field(retrievalName).toEntity(responseType);

return Optional.ofNullable(result);
}

private static GraphqlErrorException toGraphQlException(ClientGraphQlResponse response) {
// only handle the first error as only one exception can be thrown
ResponseError error = response.getErrors().getFirst();
return GraphqlErrorException.newErrorException()
.message(error.getMessage())
.path(error.getParsedPath())
.sourceLocations(error.getLocations())
.errorClassification(ErrorType.DataFetchingException)
.extensions(error.getExtensions())
.build();
}

}

}

0 comments on commit 97697ef

Please sign in to comment.