All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.graphql.client.GraphQlClient Maven / Gradle / Ivy

There is a newer version: 1.3.4
Show newest version
/*
 * Copyright 2002-2024 the original author or authors.
 *
 * 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
 *
 *      https://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
 * limitations under the License.
 */

package org.springframework.graphql.client;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.graphql.GraphQlResponse;
import org.springframework.graphql.ResponseField;
import org.springframework.graphql.support.DocumentSource;
import org.springframework.graphql.support.ResourceDocumentSource;
import org.springframework.lang.Nullable;

/**
 * Define a workflow to execute GraphQL requests that is independent of the
 * underlying transport.
 *
 * 

For most cases, use a transport specific extension: *

    *
  • {@link HttpGraphQlClient} *
  • {@link WebSocketGraphQlClient} *
* *

Alternatively, create an instance with any other transport via * {@link #builder(GraphQlTransport)}. Or create a transport specific extension * similar to HTTP and WebSocket. * * @author Rossen Stoyanchev * @since 1.0.0 */ public interface GraphQlClient { /** * Start defining a GraphQL request with the given document, which is the * textual representation of an operation (or operations) to perform, * including selection sets and fragments. * @param document the document for the request * @return spec to further define or execute the request */ RequestSpec document(String document); /** * Variant of {@link #document(String)} that uses the given key to resolve * the GraphQL document from a file with the help of the configured * {@link Builder#documentSource(DocumentSource) DocumentSource}. * @param name the document name * @throws IllegalArgumentException if the content could not be loaded */ RequestSpec documentName(String name); /** * Return a builder initialized from the configuration of "this" client * to use to build a new, independently configured client instance. */ BaseBuilder mutate(); /** * Create a builder with the given custom {@code GraphQlTransport}. *

For most cases, use a transport specific extension such as * {@link HttpGraphQlClient} or {@link WebSocketGraphQlClient}. This method * is for use with a custom {@code GraphQlTransport}. * @param transport the transport to execute requests with * @return the builder for further initialization */ static Builder builder(GraphQlTransport transport) { return new DefaultTransportGraphQlClientBuilder(transport); } /** * Base builder for creating and initializing a {@link GraphQlClient}. * @param the type of builder */ interface BaseBuilder> { /** * Configure a {@link DocumentSource} strategy to resolve a document by * name. For use within {@link #documentName(String)}. *

By default, this is set to {@link ResourceDocumentSource} with * classpath location {@code "graphql-documents/"} and * {@link ResourceDocumentSource#FILE_EXTENSIONS} as extensions. * @param contentLoader the strategy for resolving documents by their names */ B documentSource(DocumentSource contentLoader); /** * Configure a timeout to use for blocking execution. *

By default this is not set, in which case the behavior depends on * connection and request timeout settings of the underlying transport. * We recommend configuring timeout values directly if possible on the * underlying transport library such an HTTP client library as that can * provide more control over such settings. * @param blockingTimeout the timeout to use */ B blockingTimeout(@Nullable Duration blockingTimeout); /** * Build the {@code GraphQlClient} instance. */ GraphQlClient build(); } /** * Builder to create a {@link GraphQlClient} instance with a * synchronous execution chain and transport. * @param the type of builder * @see SyncGraphQlTransport */ interface SyncBuilder> extends BaseBuilder { /** * Configure interceptors to be invoked before delegating to the * {@link SyncGraphQlTransport} to perform the request. * @param interceptors the interceptors to add * @return this builder */ B interceptor(SyncGraphQlClientInterceptor... interceptors); /** * Customize the list of interceptors. The provided list is "live", so * the consumer can inspect and insert interceptors accordingly. * @param interceptorsConsumer consumer to customize the interceptors with * @return this builder */ B interceptors(Consumer> interceptorsConsumer); /** * The scheduler to use for non-blocking execution with * {@link RequestSpec#execute()} and {@link RequestSpec#retrieve(String)}. *

By default this is set to {@link Schedulers#boundedElastic()}. * @param scheduler the scheduler */ B scheduler(Scheduler scheduler); } /** * Builder to create a {@link GraphQlClient} with a non-blocking execution * chain and transport. * @param the type of builder */ interface Builder> extends BaseBuilder { /** * Configure interceptors to be invoked before delegating to the * {@link GraphQlTransport} to perform the request. * @param interceptors the interceptors to add * @return this builder */ B interceptor(GraphQlClientInterceptor... interceptors); /** * Customize the list of interceptors. The provided list is "live", so * the consumer can inspect and insert interceptors accordingly. * @param interceptorsConsumer consumer to customize the interceptors with * @return this builder */ B interceptors(Consumer> interceptorsConsumer); } /** * Declare options to gather input for a GraphQL request and execute it. */ interface RequestSpec { /** * Set the name of the operation in the {@link #document(String) document} * to execute, if the document contains multiple operations. * @param operationName the operation name * @return this request spec */ RequestSpec operationName(@Nullable String operationName); /** * Add a value for a variable defined by the operation. * @param name the variable name * @param value the variable value * @return this request spec */ RequestSpec variable(String name, @Nullable Object value); /** * Add all given values for variables defined by the operation. * @param variables the variable values * @return this request spec */ RequestSpec variables(Map variables); /** * Add a value for a protocol extension. * @param name the protocol extension name * @param value the extension value * @return this request spec */ RequestSpec extension(String name, @Nullable Object value); /** * Add all given protocol extensions. * @param extensions the protocol extensions * @return this request spec */ RequestSpec extensions(Map extensions); /** * Set a client request attribute. *

This is purely for client side request processing, i.e. available * throughout the {@link GraphQlClientInterceptor} chain but not sent. * @param name the name of the attribute * @param value the attribute value * @return this builder */ RequestSpec attribute(String name, Object value); /** * Manipulate the client request attributes. The map provided to the consumer * is "live", so the consumer can inspect and modify attributes accordingly. * @param attributesConsumer consumer to customize attributes with * @return this builder */ RequestSpec attributes(Consumer> attributesConsumer); /** * Shortcut for {@link #executeSync()} with a field path to decode from. *

If you want to decode the full data instead, use: *

		 * client.document("..").executeSync()
		 * 
* @param path the field path * @return a spec with decoding options * @throws FieldAccessException if the field has any field errors, * including errors at, above or below the field path. * @since 1.3.0 */ RetrieveSyncSpec retrieveSync(String path); /** * Shortcut for {@link #execute()} with a field path to decode from. *

If you want to decode the full data instead, use: *

		 * client.document("..").execute().map(response -> ...)
		 * 
* @param path the field path * @return a spec with decoding options * @throws FieldAccessException if the field has any field errors, * including errors at, above or below the field path. */ RetrieveSpec retrieve(String path); /** * Shortcut for {@link #executeSubscription()} with a field path to * decode from for each result. *

If you want to decode the full data, use: *

		 * client.document("..").executeSubscription().map(response -> ...)
		 * 
* @param path the field path * @return a spec with decoding options */ RetrieveSubscriptionSpec retrieveSubscription(String path); /** * Execute request with a single response, e.g. "query" or "mutation", and * return a response for further options. * @return a {@code ClientGraphQlResponse} for further decoding of the response. * @throws GraphQlTransportException in case of errors due to transport or * other issues related to encoding and decoding the request and response. * @since 1.3.0 */ ClientGraphQlResponse executeSync(); /** * Execute request with a single response, e.g. "query" or "mutation", and * return a response for further options. * @return a {@code Mono} with a {@code ClientGraphQlResponse} for further * decoding of the response. The {@code Mono} may end with an error due * to transport level issues. */ Mono execute(); /** * Execute a "subscription" request and return a stream of responses. * @return a {@code Flux} with responses that provide further options for * decoding of each response. The {@code Flux} may terminate as follows: *
    *
  • Completes if the subscription completes before the connection is closed. *
  • {@link SubscriptionErrorException} if the subscription ends with an error. *
  • {@link WebSocketDisconnectedException} if the connection is closed or * lost before the stream terminates. *
  • Exception for connection and GraphQL session initialization issues. *
*

The {@code Flux} may be cancelled to notify the server to end the * subscription stream. */ Flux executeSubscription(); } /** * Declares options to decode a field in a single response. */ interface RetrieveSyncSpec { /** * Decode the field to an entity of the given type. * @param the type to convert to * @param entityType the type to convert to * @return the entity or null if the field is {@code null} and has no errors. * @throws FieldAccessException in case of {@link ResponseField field * errors} or an {@link GraphQlResponse#isValid() invalid} response; * @see ResponseField#getErrors() */ @Nullable D toEntity(Class entityType); /** * Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}. * @param the type to convert to * @param entityType the type to convert to */ @Nullable D toEntity(ParameterizedTypeReference entityType); /** * Variant of {@link #toEntity(Class)} to decode to a List of entities. * @param the type to convert to * @param elementType the type of elements in the list */ List toEntityList(Class elementType); /** * Variant of {@link #toEntityList(Class)} with a {@link ParameterizedTypeReference}. * @param the type to convert to * @param elementType the type of elements in the list */ List toEntityList(ParameterizedTypeReference elementType); } /** * Declares options to decode a field in a single response. */ interface RetrieveSpec { /** * Decode the field to an entity of the given type. * @param the type to convert to * @param entityType the type to convert to * @return {@code Mono} with the decoded entity; completes with * {@link FieldAccessException} in case of {@link ResponseField field * errors} or an {@link GraphQlResponse#isValid() invalid} response; * completes empty if the field is {@code null} but has no errors. * @see ResponseField#getErrors() */ Mono toEntity(Class entityType); /** * Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}. * @param the entity type * @param entityType the type to convert to */ Mono toEntity(ParameterizedTypeReference entityType); /** * Variant of {@link #toEntity(Class)} to decode to a List of entities. * @param the type to convert to * @param elementType the type of elements in the list */ Mono> toEntityList(Class elementType); /** * Variant of {@link #toEntityList(Class)} with a {@link ParameterizedTypeReference}. * @param the type to convert to * @param elementType the type of elements in the list */ Mono> toEntityList(ParameterizedTypeReference elementType); } /** * Declares options to decode a field in each response of a subscription. */ interface RetrieveSubscriptionSpec { /** * Decode the field to an entity of the given type. * @param the type to convert to * @param entityType the type to convert to * @return {@code Mono} with the decoded entity; completes with * {@link FieldAccessException} in case of {@link ResponseField field * errors} or an {@link GraphQlResponse#isValid() invalid} response; * completes empty if the field is {@code null} but has no errors. * @see ResponseField#getErrors() */ Flux toEntity(Class entityType); /** * Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}. * @param the type to convert to * @param entityType the type to convert to */ Flux toEntity(ParameterizedTypeReference entityType); /** * Variant of {@link #toEntity(Class)} to decode each response to a List of entities. * @param the type to convert to * @param elementType the type of elements in the list */ Flux> toEntityList(Class elementType); /** * Variant of {@link #toEntity(Class)} to decode each response to a List of entities. * @param the type to convert to * @param elementType the type of elements in the list */ Flux> toEntityList(ParameterizedTypeReference elementType); } }