diff --git a/spring-graphql-docs/build.gradle b/spring-graphql-docs/build.gradle index f5e32712..35b9580d 100644 --- a/spring-graphql-docs/build.gradle +++ b/spring-graphql-docs/build.gradle @@ -19,14 +19,18 @@ dependencies { dependencyManagement(enforcedPlatform(dependencies.project(path: ":platform"))) api project(':spring-graphql') api project(':spring-graphql-test') - api 'org.springframework:spring-webflux' - api 'org.springframework:spring-webmvc' - api 'org.springframework:spring-websocket' - api 'org.springframework:spring-messaging' - api 'org.springframework.data:spring-data-commons' - api 'com.querydsl:querydsl-core' - api "org.springframework.boot:spring-boot-starter-graphql:${springBootVersion}" - api "org.springframework.boot:spring-boot-starter-web:${springBootVersion}" + implementation 'org.springframework:spring-webflux' + implementation 'org.springframework:spring-webmvc' + implementation 'org.springframework:spring-websocket' + implementation 'org.springframework:spring-messaging' + implementation 'org.springframework.data:spring-data-commons' + implementation 'com.querydsl:querydsl-core' + implementation "org.springframework.boot:spring-boot-starter-graphql:${springBootVersion}" + implementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}" + implementation 'io.rsocket:rsocket-core' + implementation 'io.rsocket:rsocket-transport-netty' + implementation 'io.projectreactor:reactor-test' + implementation 'org.assertj:assertj-core' } jar { diff --git a/spring-graphql-docs/modules/ROOT/pages/client.adoc b/spring-graphql-docs/modules/ROOT/pages/client.adoc index 2270702c..21aee2b5 100644 --- a/spring-graphql-docs/modules/ROOT/pages/client.adoc +++ b/spring-graphql-docs/modules/ROOT/pages/client.adoc @@ -40,35 +40,14 @@ above `GraphQlClient` extensions. to execute GraphQL requests over HTTP through a blocking transport contract and chain of interceptors. -[source,java,indent=0,subs="verbatim,quotes"] ----- -RestClient restClient = ... ; -HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient); ----- +include-code::SyncClientUsage[tag=create,indent=0] Once `HttpSyncGraphQlClient` is created, you can begin to xref:client.adoc#client.requests[execute requests] using the same API, independent of the underlying transport. If you need to change any transport specific details, use `mutate()` on an existing `HttpSyncGraphQlClient` to create a new instance with customized settings: -[source,java,indent=0,subs="verbatim,quotes"] ----- - RestClient restClient = ... ; - - HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.builder(restClient) - .headers(headers -> headers.setBasicAuth("joe", "...")) - .build(); - - // Perform requests with graphQlClient... - - HttpSyncGraphQlClient anotherGraphQlClient = graphQlClient.mutate() - .headers(headers -> headers.setBasicAuth("peter", "...")) - .build(); - - // Perform requests with anotherGraphQlClient... - ----- - +include-code::SyncClientUsage[tag=mutate,indent=0] [[client.httpgraphqlclient]] @@ -79,35 +58,14 @@ existing `HttpSyncGraphQlClient` to create a new instance with customized settin GraphQL requests over HTTP through a non-blocking transport contract and chain of interceptors. -[source,java,indent=0,subs="verbatim,quotes"] ----- -WebClient webClient = ... ; -HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient); ----- +include-code::ClientUsage[tag=create,indent=0] Once `HttpGraphQlClient` is created, you can begin to xref:client.adoc#client.requests[execute requests] using the same API, independent of the underlying transport. If you need to change any transport specific details, use `mutate()` on an existing `HttpGraphQlClient` to create a new instance with customized settings: -[source,java,indent=0,subs="verbatim,quotes"] ----- - WebClient webClient = ... ; - - HttpGraphQlClient graphQlClient = HttpGraphQlClient.builder(webClient) - .headers(headers -> headers.setBasicAuth("joe", "...")) - .build(); - - // Perform requests with graphQlClient... - - HttpGraphQlClient anotherGraphQlClient = graphQlClient.mutate() - .headers(headers -> headers.setBasicAuth("peter", "...")) - .build(); - - // Perform requests with anotherGraphQlClient... - ----- - +include-code::ClientUsage[tag=mutate,indent=0] [[client.websocketgraphqlclient]] @@ -118,13 +76,7 @@ It is built using the {spring-framework-ref-docs}/web/webflux-websocket.html#webflux-websocket-client[WebSocketClient] from Spring WebFlux and you can create it as follows: -[source,java,indent=0,subs="verbatim,quotes"] ----- - String url = "wss://localhost:8080/graphql"; - WebSocketClient client = new ReactorNettyWebSocketClient(); - - WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client).build(); ----- +include-code::WebSocketClientUsage[tag=create,indent=0] In contrast to `HttpGraphQlClient`, the `WebSocketGraphQlClient` is connection oriented, which means it needs to establish a connection before making any requests. As you begin @@ -146,37 +98,12 @@ xref:client.adoc#client.requests[execute requests] using the same API, independe transport. If you need to change any transport specific details, use `mutate()` on an existing `WebSocketGraphQlClient` to create a new instance with customized settings: -[source,java,indent=0,subs="verbatim,quotes"] ----- - URI url = ... ; - WebSocketClient client = ... ; - - WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client) - .headers(headers -> headers.setBasicAuth("joe", "...")) - .build(); - - // Use graphQlClient... - - WebSocketGraphQlClient anotherGraphQlClient = graphQlClient.mutate() - .headers(headers -> headers.setBasicAuth("peter", "...")) - .build(); - - // Use anotherGraphQlClient... - ----- +include-code::WebSocketClientUsage[tag=mutate,indent=0] `WebSocketGraphQlClient` supports sending periodic ping messages to keep the connection active when no other messages are sent or received. You can enable that as follows: -[source,java,indent=0,subs="verbatim,quotes"] ----- - URI url = ... ; - WebSocketClient client = ... ; - - WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client) - .keepAlive(Duration.ofSeconds(30)) - .build(); ----- +include-code::WebSocketClientUsage[tag=keepAlive,indent=0] [[client.websocketgraphqlclient.interceptor]] @@ -220,15 +147,7 @@ can be at most one interceptor of type `WebSocketGraphQlClientInterceptor`. {spring-framework-ref-docs}/rsocket.html#rsocket-requester[RSocketRequester] to execute GraphQL requests over RSocket requests. -[source,java,indent=0,subs="verbatim,quotes"] ----- - URI uri = URI.create("wss://localhost:8080/rsocket"); - WebsocketClientTransport transport = WebsocketClientTransport.create(url); - - RSocketGraphQlClient client = RSocketGraphQlClient.builder() - .clientTransport(transport) - .build(); ----- +include-code::RSocketClientUsage[tag=create,indent=0] In contrast to `HttpGraphQlClient`, the `RSocketGraphQlClient` is connection oriented, which means it needs to establish a session before making any requests. As you begin @@ -266,7 +185,6 @@ builders of all extensions. Currently, it has lets you configure: - [[client.requests]] == Requests @@ -287,36 +205,14 @@ Sync:: + [source,java,indent=0,subs="verbatim,quotes",role="primary"] ---- - String document = "{" + - " project(slug:\"spring-framework\") {" + - " name" + - " releases {" + - " version" + - " }"+ - " }" + - "}"; - - Project project = graphQlClient.document(document) <1> - .retrieveSync("project") <2> - .toEntity(Project.class); <3> +include::{include-java}/client/requests/retrieve/Retrieve.java[tag=retrieveSync,indent=0] ---- Non-Blocking:: + [source,java,indent=0,subs="verbatim,quotes",role="secondary"] ---- - String document = "{" + - " project(slug:\"spring-framework\") {" + - " name" + - " releases {" + - " version" + - " }"+ - " }" + - "}"; - - Mono projectMono = graphQlClient.document(document) <1> - .retrieve("project") <2> - .toEntity(Project.class); <3> +include::{include-java}/client/requests/retrieve/Retrieve.java[tag=retrieve,indent=0] ---- ====== @@ -342,32 +238,14 @@ Sync:: + [source,java,indent=0,subs="verbatim,quotes",role="primary"] ---- - try { - Project project = graphQlClient.document(document) - .retrieveSync("project") - .toEntity(Project.class); - } - catch (FieldAccessException ex) { - ClientGraphQlResponse response = ex.getResponse(); - // ... - ClientResponseField field = ex.getField(); - // ... - } +include::{include-java}/client/requests/retrieve/Retrieve.java[tag=fieldErrorSync,indent=0] ---- Non-Blocking:: + [source,java,indent=0,subs="verbatim,quotes",role="secondary"] ---- - Mono projectMono = graphQlClient.document(document) - .retrieve("project") - .toEntity(Project.class) - .onErrorResume(FieldAccessException.class, ex -> { - ClientGraphQlResponse response = ex.getResponse(); - // ... - ClientResponseField field = ex.getField(); - // ... - }); +include::{include-java}/client/requests/retrieve/Retrieve.java[tag=fieldError,indent=0] ---- ====== @@ -387,54 +265,20 @@ Sync:: + [source,java,indent=0,subs="verbatim,quotes",role="primary"] ---- - ClientGraphQlResponse response = graphQlClient.document(document).executeSync(); - - if (!response.isValid()) { - // Request failure... <1> - } - - ClientResponseField field = response.field("project"); - if (!field.hasValue()) { - if (field.getError() != null) { - // Field failure... <2> - } - else { - // Optional field set to null... <3> - } - } - - Project project = field.toEntity(Project.class); <4> +include::{include-java}/client/requests/execute/Execute.java[tag=executeSync,indent=0] ---- Non-Blocking:: + [source,java,indent=0,subs="verbatim,quotes",role="secondary"] ---- - Mono projectMono = graphQlClient.document(document) - .execute() - .map(response -> { - if (!response.isValid()) { - // Request failure... <1> - } - - ClientResponseField field = response.field("project"); - if (!field.hasValue()) { - if (field.getError() != null) { - // Field failure... <2> - } - else { - // Optional field set to null... <3> - } - } - - return field.toEntity(Project.class); <4> - }); +include::{include-java}/client/requests/execute/Execute.java[tag=execute,indent=0] ---- ====== <1> The response does not have data, only errors -<2> Field that is `null` and has an associated error -<3> Field that was set to `null` by its `DataFetcher` +<2> Field that was set to `null` by its `DataFetcher` +<3> Field that is `null` and has an associated error <4> Decode the data at the given path @@ -468,63 +312,12 @@ You can then: [source,java,indent=0,subs="verbatim,quotes"] ---- - Project project = graphQlClient.documentName("projectReleases") <1> - .variable("slug", "spring-framework") <2> - .retrieveSync() - .toEntity(Project.class); +include::{include-java}/client/requests/documentsource/DocumentSource.java[tag=documentSource,indent=0] ---- <1> Load the document from "projectReleases.graphql" <2> Provide variable values. -This approach also works for loading fragments for your queries. -Fragments are reusable field selection sets that avoid repetition in a request document. -For example, we can use a `...releases` fragment in multiple queries: - -[source,graphql,indent=0,subs="verbatim,quotes"] -.src/main/resources/graphql-documents/projectReleases.graphql ----- - query frameworkReleases { - project(slug: "spring-framework") { - name - ...releases - } - } - query graphqlReleases { - project(slug: "spring-graphql") { - name - ...releases - } - } ----- - -This fragment can be defined in a separate file for reuse: - -[source,graphql,indent=0,subs="verbatim,quotes"] -.src/main/resources/graphql-documents/releases.graphql ----- - fragment releases on Project { - releases { - version - } - } ----- - - -You can then send this fragment along the query document: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - Project project = graphQlClient.documentName("projectReleases") <1> - .fragmentName("releases") <2> - .retrieveSync() - .toEntity(Project.class); ----- -<1> Load the document from "projectReleases.graphql" -<2> Load the fragment from "releases.graphql" and append it to the document - - - The "JS GraphQL" plugin for IntelliJ supports GraphQL query files with code completion. You can use the `GraphQlClient` xref:client.adoc#client.graphqlclient.builder[Builder] to customize the @@ -552,18 +345,13 @@ To start a subscription stream, use `retrieveSubscription` which is similar to xref:client.adoc#client.requests.retrieve[retrieve] for a single response but returning a stream of responses, each decoded to some data: -[source,java,indent=0,subs="verbatim,quotes"] ----- - Flux greetingFlux = client.document("subscription { greetings }") - .retrieveSubscription("greeting") - .toEntity(String.class); ----- +include-code::RetrieveSubscription[tag=subscriptionRetrieve,indent=0] The `Flux` may terminate with `SubscriptionErrorException` if the subscription ends from the server side with an "error" message. The exception provides access to GraphQL errors decoded from the "error" message. -The `Flux` may termiate with `GraphQlTransportException` such as +The `Flux` may terminate with `GraphQlTransportException` such as `WebSocketDisconnectedException` if the underlying connection is closed or lost. In that case you can use the `retry` operator to restart the subscription. @@ -582,29 +370,7 @@ xref:client.adoc#client.subscriptions.retrieve[Retrieve] is only a shortcut to d response map. For more control, use the `executeSubscription` method and handle each response directly: -[source,java,indent=0,subs="verbatim,quotes"] ----- - Flux greetingFlux = client.document("subscription { greetings }") - .executeSubscription() - .map(response -> { - if (!response.isValid()) { - // Request failure... - } - - ClientResponseField field = response.field("project"); - if (!field.hasValue()) { - if (field.getError() != null) { - // Field failure... - } - else { - // Optional field set to null... <3> - } - } - - return field.toEntity(String.class) - }); ----- - +include-code::ExecuteSubscription[tag=subscriptionExecute,indent=0] @@ -614,52 +380,16 @@ response directly: For blocking transports created with the `GraphQlClient.SyncBuilder`, you create a `SyncGraphQlClientInterceptor` to intercept all requests through the client: -[source,java,indent=0,subs="verbatim,quotes"] ----- -static class MyInterceptor implements SyncGraphQlClientInterceptor { - - @Override - public ClientGraphQlResponse intercept(ClientGraphQlRequest request, Chain chain) { - // ... - return chain.next(request); - } -} ----- +include-code::SyncInterceptor[] For non-blocking transports created with `GraphQlClient.Builder`, you create a `GraphQlClientInterceptor` to intercept all requests through the client: -[source,java,indent=0,subs="verbatim,quotes"] ----- -static class MyInterceptor implements GraphQlClientInterceptor { - - @Override - public Mono intercept(ClientGraphQlRequest request, Chain chain) { - // ... - return chain.next(request); - } - - @Override - public Flux interceptSubscription(ClientGraphQlRequest request, SubscriptionChain chain) { - // ... - return chain.next(request); - } - -} ----- +include-code::MyInterceptor[] Once the interceptor is created, register it through the client builder. For example: -[source,java,indent=0,subs="verbatim,quotes"] ----- - URI url = ... ; - WebSocketClient client = ... ; - - WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client) - .interceptor(new MyInterceptor()) - .build(); ----- - +include-code::UseInterceptor[tag=register,indent=0] @@ -697,7 +427,7 @@ You can perform a request as follows: List books = dgsClient.request(new BooksGraphQLQuery()) // <2> .projection(new BooksProjectionRoot<>().id().name()) // <3> - .retrieveSync() + .retrieveSync("books") .toEntityList(Book.class); ---- diff --git a/spring-graphql-docs/modules/ROOT/pages/testing.adoc b/spring-graphql-docs/modules/ROOT/pages/testing.adoc index 1793d926..34f77cb0 100644 --- a/spring-graphql-docs/modules/ROOT/pages/testing.adoc +++ b/spring-graphql-docs/modules/ROOT/pages/testing.adoc @@ -74,66 +74,22 @@ GraphQL requests over HTTP, with or without a live server, depending on how To test in Spring WebFlux, without a live server, point to your Spring configuration that declares the GraphQL HTTP endpoint: -[source,java,indent=0,subs="verbatim,quotes"] ----- - ApplicationContext context = ... ; - - WebTestClient client = - WebTestClient.bindToApplicationContext(context) - .configureClient() - .baseUrl("/graphql") - .build(); - - HttpGraphQlTester tester = HttpGraphQlTester.create(client); ----- +include-code::HttpSetup[tag=webTestClient,indent=0] To test in Spring MVC, without a live server, do the same using `MockMvcWebTestClient`: -[source,java,indent=0,subs="verbatim,quotes"] ----- - ApplicationContext context = ... ; - - WebTestClient client = - MockMvcWebTestClient.bindToApplicationContext(context) - .configureClient() - .baseUrl("/graphql") - .build(); - - HttpGraphQlTester tester = HttpGraphQlTester.create(client); ----- +include-code::HttpSetup[tag=mockMvc,indent=0] Or to test against a live server running on a port: -[source,java,indent=0,subs="verbatim,quotes"] ----- - WebTestClient client = - WebTestClient.bindToServer() - .baseUrl("http://localhost:8080/graphql") - .build(); - - HttpGraphQlTester tester = HttpGraphQlTester.create(client); ----- +include-code::HttpSetup[tag=liveServer,indent=0] Once `HttpGraphQlTester` is created, you can begin to xref:testing.adoc#testing.requests[execute requests] using the same API, independent of the underlying transport. If you need to change any transport specific details, use `mutate()` on an existing `HttpSocketGraphQlTester` to create a new instance with customized settings: -[source,java,indent=0,subs="verbatim,quotes"] ----- - HttpGraphQlTester tester = HttpGraphQlTester.builder(clientBuilder) - .headers(headers -> headers.setBasicAuth("joe", "...")) - .build(); - - // Use tester... - - HttpGraphQlTester anotherTester = tester.mutate() - .headers(headers -> headers.setBasicAuth("peter", "...")) - .build(); - - // Use anotherTester... - ----- +include-code::HttpSetup[tag=executeRequests,indent=0] @@ -145,13 +101,7 @@ It is built using the {spring-framework-ref-docs}/web/webflux-websocket.html#webflux-websocket-client[WebSocketClient] from Spring WebFlux and you can create it as follows: -[source,java,indent=0,subs="verbatim,quotes"] ----- - String url = "http://localhost:8080/graphql"; - WebSocketClient client = new ReactorNettyWebSocketClient(); - - WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client).build(); ----- +include-code::WsSetup[tag=setup,indent=0] `WebSocketGraphQlTester` is connection oriented and multiplexed. Each instance establishes its own single, shared connection for all requests. Typically, you'll want to use a single @@ -162,24 +112,7 @@ xref:testing.adoc#testing.requests[execute requests] using the same API, indepen transport. If you need to change any transport specific details, use `mutate()` on an existing `WebSocketGraphQlTester` to create a new instance with customized settings: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - URI url = ... ; - WebSocketClient client = ... ; - - WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client) - .headers(headers -> headers.setBasicAuth("joe", "...")) - .build(); - - // Use tester... - - WebSocketGraphQlTester anotherTester = tester.mutate() - .headers(headers -> headers.setBasicAuth("peter", "...")) - .build(); - - // Use anotherTester... ----- +include-code::WsSetup[tag=customSetup,indent=0] `WebSocketGraphQlTester` provides a `stop()` method that you can use to have the WebSocket connection closed, e.g. after a test runs. @@ -192,15 +125,7 @@ connection closed, e.g. after a test runs. `RSocketGraphQlTester` uses `RSocketRequester` from spring-messaging to execute GraphQL requests over RSocket: -[source,java,indent=0,subs="verbatim,quotes"] ----- - URI uri = URI.create("wss://localhost:8080/rsocket"); - WebsocketClientTransport transport = WebsocketClientTransport.create(url); - - RSocketGraphQlTester client = RSocketGraphQlTester.builder() - .clientTransport(transport) - .build(); ----- +include-code::RSocketSetup[tag=rsocketSetup,indent=0] `RSocketGraphQlTester` is connection oriented and multiplexed. Each instance establishes its own single, shared session for all requests. Typically, you'll want to use a single @@ -219,11 +144,7 @@ Many times it's enough to test GraphQL requests on the server side, without the client to send requests over a transport protocol. To test directly against a `ExecutionGraphQlService`, use the `ExecutionGraphQlServiceTester` extension: -[source,java,indent=0,subs="verbatim,quotes"] ----- - ExecutionGraphQlService service = ... ; - ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.create(service); ----- +include-code::ServiceSetup[tag=serviceSetup,indent=0] Once `ExecutionGraphQlServiceTester` is created, you can begin to xref:testing.adoc#testing.requests[execute requests] using the same API, independent of the underlying @@ -231,13 +152,7 @@ transport. `ExecutionGraphQlServiceTester.Builder` provides an option to customize `ExecutionInput` details: -[source,java,indent=0,subs="verbatim,quotes"] ----- - ExecutionGraphQlService service = ... ; - ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.builder(service) - .configureExecutionInput((executionInput, builder) -> builder.executionId(id).build()) - .build(); ----- +include-code::ServiceSetup[tag=customServiceSetup,indent=0] @@ -252,22 +167,11 @@ The `WebGraphQlTester` extension lets you processes request through the `WebGraphQlInterceptor` chain before handing off to `ExecutionGraphQlService` for request execution: -[source,java,indent=0,subs="verbatim,quotes"] ----- - WebGraphQlHandler handler = ... ; - WebGraphQlTester tester = WebGraphQlTester.create(handler); ----- +include-code::WebSetup[tag=webSetup,indent=0] The builder for this extension allows you to define HTTP request details: -[source,java,indent=0,subs="verbatim,quotes"] ----- - WebGraphQlHandler handler = ... ; - - WebGraphQlTester tester = WebGraphQlTester.builder(handler) - .headers(headers -> headers.setBasicAuth("joe", "...")) - .build(); ----- +include-code::WebSetup[tag=customWebSetup,indent=0] Once `WebGraphQlTester` is created, you can begin to xref:testing.adoc#testing.requests[execute requests] using the same API, independent of the underlying transport. @@ -297,22 +201,7 @@ Once you have a `GraphQlTester`, you can begin to test requests. The below execu query for a project and uses https://github.com/json-path/JsonPath[JsonPath] to extract project release versions from the response: -[source,java,indent=0,subs="verbatim,quotes"] ----- - String document = "{" + - " project(slug:\"spring-framework\") {" + - " releases {" + - " version" + - " }"+ - " }" + - "}"; - - graphQlTester.document(document) - .execute() - .path("project.releases[*].version") - .entityList(String.class) - .hasSizeGreaterThan(1); ----- +include-code::TesterRequests[tag=inlineDocument,indent=0] The JsonPath is relative to the "data" section of the response. @@ -335,18 +224,52 @@ For example, given a file called `projectReleases.graphql` in You can then use: -[source,java,indent=0,subs="verbatim,quotes"] ----- - graphQlTester.documentName("projectReleases") <1> - .variable("slug", "spring-framework") <2> - .execute() - .path("project.releases[*].version") - .entityList(String.class) - .hasSizeGreaterThan(1); ----- +include-code::TesterRequests[tag=documentName,indent=0] <1> Refer to the document in the file named "project". <2> Set the `slug` variable. + +This approach also works for loading fragments for your queries. +Fragments are reusable field selection sets that avoid repetition in a request document. +For example, we can use a `...releases` fragment in multiple queries: + +[source,graphql,indent=0,subs="verbatim,quotes"] +.src/main/resources/graphql-documents/projectReleases.graphql +---- + query frameworkReleases { + project(slug: "spring-framework") { + name + ...releases + } + } + query graphqlReleases { + project(slug: "spring-graphql") { + name + ...releases + } + } +---- + +This fragment can be defined in a separate file for reuse: + +[source,graphql,indent=0,subs="verbatim,quotes"] +.src/main/resources/graphql-documents/releases.graphql +---- + fragment releases on Project { + releases { + version + } + } +---- + + +You can then send this fragment along the query document: + +include-code::TesterRequests[tag=fragment,indent=0] +<1> Load the document from "projectReleases.graphql" +<2> Load the fragment from "releases.graphql" and append it to the document + + [TIP] ==== The "JS GraphQL" plugin for IntelliJ supports GraphQL query files with code completion. @@ -364,45 +287,24 @@ See xref:testing.adoc#testing.errors[Errors] for more details on error handling. -[[testing.requests.nestedPaths]] +[[testing.requests.nestedpaths]] === Nested Paths By default, paths are relative to the "data" section of the GraphQL response. You can also nest down to a path, and inspect multiple paths relative to it as follows: -[source,java,indent=0,subs="verbatim,quotes"] ----- - graphQlTester.document(document) - .execute() - .path("project", project -> project // <1> - .path("name").entity(String.class).isEqualTo("spring-framework") - .path("releases[*].version").entityList(String.class).hasSizeGreaterThan(1)); ----- - +include-code::NestedPaths[tag=nestedPaths,indent=0] <1> Use a callback to inspect paths relative to "project". - - [[testing.subscriptions]] == Subscriptions To test subscriptions, call `executeSubscription` instead of `execute` to obtain a stream of responses and then use `StepVerifier` from Project Reactor to inspect the stream: -[source,java,indent=0,subs="verbatim,quotes"] ----- - Flux greetingFlux = tester.document("subscription { greetings }") - .executeSubscription() - .toFlux("greetings", String.class); // decode at JSONPath - - StepVerifier.create(greetingFlux) - .expectNext("Hi") - .expectNext("Bonjour") - .expectNext("Hola") - .verifyComplete(); ----- +include-code::TestSubscriptions[tag=testSubscriptions,indent=0] Subscriptions are supported only with xref:testing.adoc#testing.websocketgraphqltester[WebSocketGraphQlTester] , or with the server side @@ -417,51 +319,19 @@ When you use `verify()`, any errors under the "errors" key in the response will an assertion failure. To suppress a specific error, use the error filter before `verify()`: -[source,java,indent=0,subs="verbatim,quotes"] ----- - graphQlTester.query(query) - .execute() - .errors() - .filter(error -> ...) - .verify() - .path("project.releases[*].version") - .entityList(String.class) - .hasSizeGreaterThan(1); ----- + +include-code::TestErrors[tag=verifyErrors,indent=0] You can register an error filter at the builder level, to apply to all tests: -[source,java,indent=0,subs="verbatim,quotes"] ----- - WebGraphQlTester graphQlTester = WebGraphQlTester.builder(client) - .errorFilter(error -> ...) - .build(); ----- +include-code::TestErrors[tag=setupErrorFilter,indent=0] If you want to verify that an error does exist, and in contrast to `filter`, throw an assertion error if it doesn't, then use `expect` instead: -[source,java,indent=0,subs="verbatim,quotes"] ----- - graphQlTester.query(query) - .execute() - .errors() - .expect(error -> ...) - .verify() - .path("project.releases[*].version") - .entityList(String.class) - .hasSizeGreaterThan(1); ----- +include-code::TestErrors[tag=expectedErrors,indent=0] You can also inspect all errors through a `Consumer`, and doing so also marks them as filtered, so you can then also inspect the data in the response: -[source,java,indent=0,subs="verbatim,quotes"] ----- - graphQlTester.query(query) - .execute() - .errors() - .satisfy(errors -> { - // ... - }); ----- +include-code::TestErrors[tag=satisfyErrors,indent=0] diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpgraphqlclient/ClientUsage.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpgraphqlclient/ClientUsage.java new file mode 100644 index 00000000..73502e07 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpgraphqlclient/ClientUsage.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020-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.docs.client.httpgraphqlclient; + +import org.springframework.graphql.client.HttpGraphQlClient; +import org.springframework.web.reactive.function.client.WebClient; + +public class ClientUsage { + + void create() { + // tag::create[] + WebClient webClient = WebClient.create("https://spring.io/graphql"); + HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient); + // end::create[] + } + + void mutateClient() { + // tag::mutate[] + WebClient webClient = WebClient.create("https://spring.io/graphql"); + + HttpGraphQlClient graphQlClient = HttpGraphQlClient.builder(webClient) + .headers((headers) -> headers.setBasicAuth("joe", "...")) + .build(); + + // Perform requests with graphQlClient... + + HttpGraphQlClient anotherGraphQlClient = graphQlClient.mutate() + .headers((headers) -> headers.setBasicAuth("peter", "...")) + .build(); + + // Perform requests with anotherGraphQlClient... + // end::mutate[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpsyncgraphqlclient/SyncClientUsage.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpsyncgraphqlclient/SyncClientUsage.java new file mode 100644 index 00000000..4a5f5e6b --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpsyncgraphqlclient/SyncClientUsage.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020-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.docs.client.httpsyncgraphqlclient; + +import org.springframework.graphql.client.HttpSyncGraphQlClient; +import org.springframework.web.client.RestClient; + +public class SyncClientUsage { + + void createClient() { + // tag::create[] + RestClient restClient = RestClient.create("https://spring.io/graphql"); + HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient); + // end::create[] + } + + void mutateClient() { + // tag::mutate[] + RestClient restClient = RestClient.create("https://spring.io/graphql"); + HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.builder(restClient) + .headers((headers) -> headers.setBasicAuth("joe", "...")) + .build(); + + // Perform requests with graphQlClient... + + HttpSyncGraphQlClient anotherGraphQlClient = graphQlClient.mutate() + .headers((headers) -> headers.setBasicAuth("peter", "...")) + .build(); + + // Perform requests with anotherGraphQlClient... + + // end::mutate[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/MyInterceptor.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/MyInterceptor.java new file mode 100644 index 00000000..3c509c4d --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/MyInterceptor.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020-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.docs.client.interception; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.graphql.client.ClientGraphQlRequest; +import org.springframework.graphql.client.ClientGraphQlResponse; +import org.springframework.graphql.client.GraphQlClientInterceptor; + +public class MyInterceptor implements GraphQlClientInterceptor { + + @Override + public Mono intercept(ClientGraphQlRequest request, Chain chain) { + // ... + return chain.next(request); + } + + @Override + public Flux interceptSubscription(ClientGraphQlRequest request, SubscriptionChain chain) { + // ... + return chain.next(request); + } + +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/SyncInterceptor.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/SyncInterceptor.java new file mode 100644 index 00000000..f26a72ba --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/SyncInterceptor.java @@ -0,0 +1,30 @@ +/* + * Copyright 2020-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.docs.client.interception; + +import org.springframework.graphql.client.ClientGraphQlRequest; +import org.springframework.graphql.client.ClientGraphQlResponse; +import org.springframework.graphql.client.SyncGraphQlClientInterceptor; + +public class SyncInterceptor implements SyncGraphQlClientInterceptor { + + @Override + public ClientGraphQlResponse intercept(ClientGraphQlRequest request, Chain chain) { + // ... + return chain.next(request); + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/UseInterceptor.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/UseInterceptor.java new file mode 100644 index 00000000..f7c592fc --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/UseInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020-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.docs.client.interception; + +import java.net.URI; + +import org.springframework.graphql.client.WebSocketGraphQlClient; +import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; +import org.springframework.web.reactive.socket.client.WebSocketClient; + +public class UseInterceptor { + + void registerInterceptor() { + // tag::register[] + URI url = URI.create("wss://localhost:8080/graphql"); + WebSocketClient client = new ReactorNettyWebSocketClient(); + + WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client) + .interceptor(new MyInterceptor()) + .build(); + // end::register[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/documentsource/DocumentSource.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/documentsource/DocumentSource.java new file mode 100644 index 00000000..aa15ef66 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/documentsource/DocumentSource.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020-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.docs.client.requests.documentsource; + +import org.springframework.graphql.client.GraphQlClient; + +public class DocumentSource { + + void documentSource() { + GraphQlClient graphQlClient = null; + // tag::documentSource[] + Project project = graphQlClient.documentName("projectReleases") // <1> + .variable("slug", "spring-framework") // <2> + .retrieveSync("projectReleases.project") + .toEntity(Project.class); + // end::documentSource[] + } + + record Project() { + + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/execute/Execute.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/execute/Execute.java new file mode 100644 index 00000000..beb5f821 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/execute/Execute.java @@ -0,0 +1,80 @@ +/* + * Copyright 2020-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.docs.client.requests.execute; + +import reactor.core.publisher.Mono; + +import org.springframework.graphql.client.ClientGraphQlResponse; +import org.springframework.graphql.client.ClientResponseField; +import org.springframework.graphql.client.GraphQlClient; + +public class Execute { + + void executeSync() { + GraphQlClient graphQlClient = null; + String document = ""; + // tag::executeSync[] + ClientGraphQlResponse response = graphQlClient.document(document).executeSync(); + + if (!response.isValid()) { + // Request failure... <1> + } + + ClientResponseField field = response.field("project"); + if (field.getValue() == null) { + if (field.getErrors().isEmpty()) { + // Optional field set to null... <2> + } + else { + // Field failure... <3> + } + } + + Project project = field.toEntity(Project.class); // <4> + // end::executeSync[] + } + + void execute() { + GraphQlClient graphQlClient = null; + String document = ""; + // tag::execute[] + Mono projectMono = graphQlClient.document(document) + .execute() + .map((response) -> { + if (!response.isValid()) { + // Request failure... <1> + } + + ClientResponseField field = response.field("project"); + if (field.getValue() == null) { + if (field.getErrors().isEmpty()) { + // Optional field set to null... <2> + } + else { + // Field failure... <3> + } + } + + return field.toEntity(Project.class); // <4> + }); + // end::execute[] + } + + record Project() { + + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/retrieve/Retrieve.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/retrieve/Retrieve.java new file mode 100644 index 00000000..0551a167 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/retrieve/Retrieve.java @@ -0,0 +1,114 @@ +/* + * Copyright 2020-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.docs.client.requests.retrieve; + +import reactor.core.publisher.Mono; + +import org.springframework.graphql.client.ClientGraphQlResponse; +import org.springframework.graphql.client.ClientResponseField; +import org.springframework.graphql.client.FieldAccessException; +import org.springframework.graphql.client.GraphQlClient; + +public class Retrieve { + + void retrieveSync() { + GraphQlClient graphQlClient = null; + + // tag::retrieveSync[] + String document = + """ + { + project(slug:"spring-framework") { + name + releases { + version + } + } + } + """; + + Project project = graphQlClient.document(document) // <1> + .retrieveSync("project") // <2> + .toEntity(Project.class); // <3> + // end::retrieveSync[] + } + + void retrieve() { + GraphQlClient graphQlClient = null; + + // tag::retrieve[] + String document = + """ + { + project(slug:"spring-framework") { + name + releases { + version + } + } + } + """; + + Mono project = graphQlClient.document(document) // <1> + .retrieve("project") // <2> + .toEntity(Project.class); // <3> + // end::retrieve[] + } + + Project fieldErrorSync() { + GraphQlClient graphQlClient = null; + String document = ""; + + // tag::fieldErrorSync[] + try { + Project project = graphQlClient.document(document) + .retrieveSync("project") + .toEntity(Project.class); + return project; + } + catch (FieldAccessException ex) { + ClientGraphQlResponse response = ex.getResponse(); + // ... + ClientResponseField field = ex.getField(); + // return fallback value + return new Project(); + } + // end::fieldErrorSync[] + } + + void fieldError() { + GraphQlClient graphQlClient = null; + String document = ""; + + // tag::fieldError[] + Mono projectMono = graphQlClient.document(document) + .retrieve("project") + .toEntity(Project.class) + .onErrorResume(FieldAccessException.class, (ex) -> { + ClientGraphQlResponse response = ex.getResponse(); + // ... + ClientResponseField field = ex.getField(); + // return fallback value + return Mono.just(new Project()); + }); + // end::fieldError[] + } + + record Project() { + + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/rsocketgraphqlclient/RSocketClientUsage.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/rsocketgraphqlclient/RSocketClientUsage.java new file mode 100644 index 00000000..a8542d82 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/rsocketgraphqlclient/RSocketClientUsage.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020-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.docs.client.rsocketgraphqlclient; + +import java.net.URI; + +import io.rsocket.transport.netty.client.WebsocketClientTransport; + +import org.springframework.graphql.client.RSocketGraphQlClient; + +public class RSocketClientUsage { + + void create() { + // tag::create[] + URI uri = URI.create("wss://localhost:8080/rsocket"); + WebsocketClientTransport transport = WebsocketClientTransport.create(uri); + + RSocketGraphQlClient client = RSocketGraphQlClient.builder() + .clientTransport(transport) + .build(); + // end::create[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/execute/ExecuteSubscription.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/execute/ExecuteSubscription.java new file mode 100644 index 00000000..ffa22978 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/execute/ExecuteSubscription.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020-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.docs.client.subscriptions.execute; + +import reactor.core.publisher.Flux; + +import org.springframework.graphql.client.ClientResponseField; +import org.springframework.graphql.client.GraphQlClient; + +public class ExecuteSubscription { + + void subscriptionExecute() { + GraphQlClient client = null; + // tag::subscriptionExecute[] + Flux greetingFlux = client.document("subscription { greetings }") + .executeSubscription() + .map((response) -> { + if (!response.isValid()) { + // Request failure... + } + + ClientResponseField field = response.field("project"); + if (field.getValue() == null) { + if (field.getErrors().isEmpty()) { + // Optional field set to null... + } + else { + // Field failure... + } + } + + return field.toEntity(String.class); + }); + // end::subscriptionExecute[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/retrieve/RetrieveSubscription.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/retrieve/RetrieveSubscription.java new file mode 100644 index 00000000..b0c58148 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/retrieve/RetrieveSubscription.java @@ -0,0 +1,33 @@ +/* + * Copyright 2020-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.docs.client.subscriptions.retrieve; + +import reactor.core.publisher.Flux; + +import org.springframework.graphql.client.GraphQlClient; + +public class RetrieveSubscription { + + void subscriptionRetrieve() { + GraphQlClient client = null; + // tag::subscriptionRetrieve[] + Flux greetingFlux = client.document("subscription { greetings }") + .retrieveSubscription("greeting") + .toEntity(String.class); + // end::subscriptionRetrieve[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/websocketgraphqlclient/WebSocketClientUsage.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/websocketgraphqlclient/WebSocketClientUsage.java new file mode 100644 index 00000000..72ca3a9b --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/websocketgraphqlclient/WebSocketClientUsage.java @@ -0,0 +1,67 @@ +/* + * Copyright 2020-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.docs.client.websocketgraphqlclient; + +import java.time.Duration; + +import org.springframework.graphql.client.WebSocketGraphQlClient; +import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; +import org.springframework.web.reactive.socket.client.WebSocketClient; + +public class WebSocketClientUsage { + + void create() { + // tag::create[] + String url = "wss://spring.io/graphql"; + WebSocketClient client = new ReactorNettyWebSocketClient(); + + WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client).build(); + // end::create[] + } + + void mutate() { + // tag::mutate[] + String url = "wss://spring.io/graphql"; + WebSocketClient client = new ReactorNettyWebSocketClient(); + + WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client) + .headers((headers) -> headers.setBasicAuth("joe", "...")) + .build(); + + // Use graphQlClient... + + WebSocketGraphQlClient anotherGraphQlClient = graphQlClient.mutate() + .headers((headers) -> headers.setBasicAuth("peter", "...")) + .build(); + + // Use anotherGraphQlClient... + + // end::mutate[] + } + + void keepAlive() { + // tag::keepAlive[] + String url = "wss://spring.io/graphql"; + WebSocketClient client = new ReactorNettyWebSocketClient(); + + WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client) + .keepAlive(Duration.ofSeconds(30)) + .build(); + // end::keepAlive[] + } + +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/errors/TestErrors.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/errors/TestErrors.java new file mode 100644 index 00000000..9cad4f86 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/errors/TestErrors.java @@ -0,0 +1,79 @@ +/* + * Copyright 2020-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.docs.testing.errors; + +import org.springframework.graphql.server.WebGraphQlHandler; +import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.graphql.test.tester.WebGraphQlTester; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestErrors { + + void verifyErrors() { + GraphQlTester graphQlTester = null; + String query = ""; + // tag::verifyErrors[] + graphQlTester.document(query) + .execute() + .errors() + .filter((error) -> error.getMessage().equals("ignored error")) + .verify() + .path("project.releases[*].version") + .entityList(String.class) + .hasSizeGreaterThan(1); + // end::verifyErrors[] + } + + void setupErrorFilter() { + WebGraphQlHandler handler = null; + // tag::setupErrorFilter[] + WebGraphQlTester graphQlTester = WebGraphQlTester.builder(handler) + .errorFilter((error) -> error.getMessage().equals("ignored error")) + .build(); + // end::setupErrorFilter[] + } + + void expectedErrors() { + GraphQlTester graphQlTester = null; + String query = ""; + // tag::expectedErrors[] + graphQlTester.document(query) + .execute() + .errors() + .expect((error) -> error.getMessage().equals("expected error")) + .verify() + .path("project.releases[*].version") + .entityList(String.class) + .hasSizeGreaterThan(1); + // end::expectedErrors[] + } + + void satisfyErrors() { + GraphQlTester graphQlTester = null; + String document = ""; + // tag::satisfyErrors[] + graphQlTester.document(document) + .execute() + .errors() + .satisfy((errors) -> + assertThat(errors) + .anyMatch((error) -> error.getMessage().contains("ignored error")) + ); + // end::satisfyErrors[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/graphqlservicetester/ServiceSetup.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/graphqlservicetester/ServiceSetup.java new file mode 100644 index 00000000..efceb298 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/graphqlservicetester/ServiceSetup.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020-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.docs.testing.graphqlservicetester; + +import graphql.execution.ExecutionId; + +import org.springframework.graphql.ExecutionGraphQlService; +import org.springframework.graphql.test.tester.ExecutionGraphQlServiceTester; + +public class ServiceSetup { + + void serviceSetup() { + // tag::serviceSetup[] + ExecutionGraphQlService service = /**/ null; + ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.create(service); + // end::serviceSetup[] + } + + void customServiceSetup() { + // tag::customServiceSetup[] + ExecutionGraphQlService service = /**/ null; + ExecutionId executionId = ExecutionId.generate(); + ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.builder(service) + .configureExecutionInput((executionInput, builder) -> builder.executionId(executionId).build()) + .build(); + // end::customServiceSetup[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/httpgraphqltester/HttpSetup.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/httpgraphqltester/HttpSetup.java new file mode 100644 index 00000000..04f3df6a --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/httpgraphqltester/HttpSetup.java @@ -0,0 +1,88 @@ +/* + * Copyright 2020-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.docs.testing.httpgraphqltester; + +import org.springframework.graphql.test.tester.HttpGraphQlTester; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.servlet.client.MockMvcWebTestClient; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +public class HttpSetup { + + void webTestClient() { + // tag::webTestClient[] + AnnotationConfigWebApplicationContext context = /**/ new AnnotationConfigWebApplicationContext(); + + WebTestClient client = + WebTestClient.bindToApplicationContext(context) + .configureClient() + .baseUrl("/graphql") + .build(); + + HttpGraphQlTester tester = HttpGraphQlTester.create(client); + // end::webTestClient[] + } + + void mockMvc() { + // tag::mockMvc[] + AnnotationConfigWebApplicationContext context = /**/ new AnnotationConfigWebApplicationContext(); + + WebTestClient client = + MockMvcWebTestClient.bindToApplicationContext(context) + .configureClient() + .baseUrl("/graphql") + .build(); + + HttpGraphQlTester tester = HttpGraphQlTester.create(client); + // end::mockMvc[] + } + + void liveServer() { + // tag::liveServer[] + WebTestClient client = + WebTestClient.bindToServer() + .baseUrl("http://localhost:8080/graphql") + .build(); + + HttpGraphQlTester tester = HttpGraphQlTester.create(client); + // end::liveServer[] + } + + void executeRequests() { + // tag::executeRequests[] + WebTestClient.Builder clientBuilder = + WebTestClient.bindToServer() + .baseUrl("http://localhost:8080/graphql"); + + HttpGraphQlTester tester = HttpGraphQlTester.builder(clientBuilder) + .headers((headers) -> headers.setBasicAuth("joe", "...")) + .build(); + + // Use tester... + + HttpGraphQlTester anotherTester = tester.mutate() + .headers((headers) -> headers.setBasicAuth("peter", "...")) + .build(); + + // Use anotherTester... + // end::executeRequests[] + } + + static class Configuration { + + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/TesterRequests.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/TesterRequests.java new file mode 100644 index 00000000..eebff4ed --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/TesterRequests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2020-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.docs.testing.requests; + +import org.springframework.graphql.test.tester.GraphQlTester; + +public class TesterRequests { + + void inlineDocument() { + GraphQlTester graphQlTester = null; + // tag::inlineDocument[] + String document = + """ + { + project(slug:"spring-framework") { + releases { + version + } + } + } + """; + + graphQlTester.document(document) + .execute() + .path("project.releases[*].version") + .entityList(String.class) + .hasSizeGreaterThan(1); + // end::inlineDocument[] + } + + void documentName() { + GraphQlTester graphQlTester = null; + // tag::documentName[] + graphQlTester.documentName("projectReleases") // <1> + .variable("slug", "spring-framework") // <2> + .execute() + .path("projectReleases.project.releases[*].version") + .entityList(String.class) + .hasSizeGreaterThan(1); + // end::documentName[] + } + + void fragment() { + GraphQlTester graphQlTester = null; + // tag::fragment[] + graphQlTester.documentName("projectReleases") // <1> + .fragmentName("releases") // <2> + .execute() + .path("frameworkReleases.project.releases[*].version") + .entityList(String.class) + .hasSizeGreaterThan(1); + // end::fragment[] + } + + record Project() { + + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/nestedpaths/NestedPaths.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/nestedpaths/NestedPaths.java new file mode 100644 index 00000000..a95fba58 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/nestedpaths/NestedPaths.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020-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.docs.testing.requests.nestedpaths; + +import org.springframework.graphql.test.tester.GraphQlTester; + +public class NestedPaths { + + void nestedPaths() { + GraphQlTester graphQlTester = null; + String document = ""; + // tag::nestedPaths[] + graphQlTester.document(document) + .execute() + .path("project", (project) -> project // <1> + .path("name").entity(String.class).isEqualTo("spring-framework") + .path("releases[*].version").entityList(String.class).hasSizeGreaterThan(1)); + // end::nestedPaths[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/rsocketgraphqltester/RSocketSetup.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/rsocketgraphqltester/RSocketSetup.java new file mode 100644 index 00000000..79b09e28 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/rsocketgraphqltester/RSocketSetup.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020-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.docs.testing.rsocketgraphqltester; + +import java.net.URI; + +import io.rsocket.transport.netty.client.WebsocketClientTransport; + +import org.springframework.graphql.test.tester.RSocketGraphQlTester; + +public class RSocketSetup { + + void rSocketSetup() { + // tag::rsocketSetup[] + URI url = URI.create("wss://localhost:8080/rsocket"); + WebsocketClientTransport transport = WebsocketClientTransport.create(url); + + RSocketGraphQlTester client = RSocketGraphQlTester.builder() + .clientTransport(transport) + .build(); + // end::rsocketSetup[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/subscriptions/TestSubscriptions.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/subscriptions/TestSubscriptions.java new file mode 100644 index 00000000..051a08ca --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/subscriptions/TestSubscriptions.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020-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.docs.testing.subscriptions; + +import reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +import org.springframework.graphql.test.tester.GraphQlTester; + +public class TestSubscriptions { + + void testSubscriptions() { + GraphQlTester tester = null; + // tag::testSubscriptions[] + Flux greetingFlux = tester.document("subscription { greetings }") + .executeSubscription() + .toFlux("greetings", String.class); // decode at JSONPath + + StepVerifier.create(greetingFlux) + .expectNext("Hi") + .expectNext("Bonjour") + .expectNext("Hola") + .verifyComplete(); + // end::testSubscriptions[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/webgraphqltester/WebSetup.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/webgraphqltester/WebSetup.java new file mode 100644 index 00000000..1081e899 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/webgraphqltester/WebSetup.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020-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.docs.testing.webgraphqltester; + +import org.springframework.graphql.server.WebGraphQlHandler; +import org.springframework.graphql.test.tester.WebGraphQlTester; + +public class WebSetup { + + void webSetup() { + // tag::webSetup[] + WebGraphQlHandler handler = /**/ null; + WebGraphQlTester tester = WebGraphQlTester.create(handler); + // end::webSetup[] + } + + void customWebSetup() { + // tag::customWebSetup[] + WebGraphQlHandler handler = /**/ null; + WebGraphQlTester tester = WebGraphQlTester.builder(handler) + .headers((headers) -> headers.setBasicAuth("joe", "...")) + .build(); + // end::customWebSetup[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/websocketgraphqltester/WsSetup.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/websocketgraphqltester/WsSetup.java new file mode 100644 index 00000000..0405a901 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/websocketgraphqltester/WsSetup.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020-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.docs.testing.websocketgraphqltester; + +import java.net.URI; + +import org.springframework.graphql.test.tester.WebSocketGraphQlTester; +import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; +import org.springframework.web.reactive.socket.client.WebSocketClient; + +public class WsSetup { + + void setup() { + // tag::setup[] + String url = "http://localhost:8080/graphql"; + WebSocketClient client = new ReactorNettyWebSocketClient(); + + WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client).build(); + // end::setup[] + } + + void customSetup() { + // tag::customSetup[] + URI url = URI.create("ws://localhost:8080/graphql"); + WebSocketClient client = new ReactorNettyWebSocketClient(); + + WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client) + .headers((headers) -> headers.setBasicAuth("joe", "...")) + .build(); + + // Use tester... + + WebSocketGraphQlTester anotherTester = tester.mutate() + .headers((headers) -> headers.setBasicAuth("peter", "...")) + .build(); + + // Use anotherTester... + // end::customSetup[] + } +}