Set up reference documentation and JavaDoc

Closes gh-76
This commit is contained in:
Brian Clozel
2021-06-30 17:55:33 +02:00
parent a1e8571853
commit 1727c7270a
9 changed files with 1167 additions and 74 deletions

View File

@@ -6,6 +6,8 @@ ext {
moduleProjects = subprojects.findAll { it.name.startsWith("spring-") }
}
description = "Spring GraphQL"
subprojects {
apply plugin: 'io.spring.dependency-management'

View File

@@ -90,6 +90,14 @@ jobs:
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
disable_checksum_uploads: true
threads: 8
artifact_set:
- include:
- "/**/spring-graphql-*-docs.zip"
properties:
"zip.name": "spring-graphql"
"zip.displayname": "Spring GraphQL"
"zip.deployed": "false"
"zip.type": "docs"
get_params:
threads: 8
- name: stage-milestone

152
docs/build.gradle Normal file
View File

@@ -0,0 +1,152 @@
plugins {
id 'org.asciidoctor.jvm.convert' version '3.1.0'
id 'de.undercouch.download' version '4.1.1'
}
description = "Spring GraphQL reference documentation"
configurations {
asciidoctorExt
}
repositories {
maven {
url "https://repo.spring.io/release"
mavenContent {
includeGroup "io.spring.asciidoctor"
}
}
}
dependencies {
asciidoctorExt 'io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.6.0'
}
ext.javadocLinks = [
"https://docs.oracle.com/javase/8/docs/api/",
"https://javadoc.io/doc/com.graphql-java/graphql-java/16.2/",
"https://docs.spring.io/spring-framework/docs/5.3.x/javadoc-api/"
] as String[]
/**
* Produce Javadoc for all Spring GraphQL modules in "build/docs/javadoc"
*/
task api(type: Javadoc) {
group = "Documentation"
description = "Generates aggregated Javadoc API documentation."
title = "${rootProject.description} ${version} API"
dependsOn {
moduleProjects.collect {
it.tasks.getByName("jar")
}
}
options {
encoding = "UTF-8"
memberLevel = JavadocMemberLevel.PROTECTED
author = true
header = rootProject.description
use = true
overview = "src/docs/api/overview.html"
stylesheetFile = file("src/docs/api/stylesheet.css")
splitIndex = true
links(project.ext.javadocLinks)
addStringOption('Xdoclint:none', '-quiet')
if(JavaVersion.current().isJava9Compatible()) {
addBooleanOption('html5', true)
}
}
source = moduleProjects.collect { project ->
project.sourceSets.main.allJava
}
classpath = moduleProjects.collect { project ->
project.sourceSets.main.compileClasspath
}.sum()
maxMemory = "1024m"
destinationDir = file("$buildDir/docs/javadoc")
}
task downloadResources(type: Download) {
def version = "0.2.5"
src "https://repo.spring.io/release/io/spring/docresources/" +
"spring-doc-resources/$version/spring-doc-resources-${version}.zip"
dest project.file("$buildDir/docs/spring-doc-resources.zip")
onlyIfModified true
useETag "all"
}
task extractDocResources(type: Copy, dependsOn: downloadResources) {
from project.zipTree(downloadResources.dest);
into "$buildDir/docs/spring-docs-resources/"
}
asciidoctorj {
version = '2.4.3'
fatalWarnings ".*"
options doctype: 'book', eruby: 'erubis'
attributes([
icons: 'font',
idprefix: '',
idseparator: '-',
docinfo: 'shared',
revnumber: project.version,
sectanchors: '',
sectnums: '',
'source-highlighter': 'highlight.js',
highlightjsdir: 'js/highlight',
'highlightjs-theme': 'googlecode',
stylesdir: 'css/',
stylesheet: 'stylesheet.css'
])
}
/**
* Generate the Spring GraphQL Reference documentation from "src/docs/asciidoc"
* in "build/docs/reference/html".
*/
asciidoctor {
baseDirFollowsSourceDir()
configurations 'asciidoctorExt'
sources {
include '*.adoc'
}
outputDir "$buildDir/docs/reference/html"
logDocuments = true
resources {
from(sourceDir) {
include 'images/*.png', 'css/**', 'js/**'
}
from extractDocResources
}
}
/**
* Zip all docs into a single archive
*/
task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor']) {
group = "Distribution"
description = "Builds -${archiveClassifier} archive containing api and reference " +
"for deployment at https://docs.spring.io/spring-graphql/docs."
archiveBaseName.set("spring-graphql")
archiveClassifier.set("docs")
from (api) {
into "javadoc-api"
}
from ("$asciidoctor.outputDir") {
into "reference/html"
}
}
apply from: "${rootDir}/gradle/publishing.gradle"
publishing {
publications {
mavenJava(MavenPublication) {
artifact docsZip
}
}
}

View File

@@ -0,0 +1,7 @@
<html lang="en">
<body>
<p>
This is the public API documentation for the <a href="https://spring.io/projects/spring-graphql" target="_top">Spring GraphQL project</a>.
</p>
</body>
</html>

View File

@@ -0,0 +1,596 @@
/* Javadoc style sheet */
/*
Overall document style
*/
body {
background-color:#ffffff;
color:#353833;
font-family:'DejaVu Sans', Arial, Helvetica, sans-serif;
font-size:14px;
margin:0;
}
a:link, a:visited {
text-decoration:none;
color:#4A6782;
}
a:hover, a:focus {
text-decoration:none;
color:#bb7a2a;
}
a:active {
text-decoration:none;
color:#4A6782;
}
a[name] {
color:#353833;
}
a[name]:hover {
text-decoration:none;
color:#353833;
}
pre {
font-family:'DejaVu Sans Mono', monospace;
font-size:14px;
}
h1 {
font-size:20px;
}
h2 {
font-size:18px;
}
h3 {
font-size:16px;
font-style:italic;
}
h4 {
font-size:13px;
}
h5 {
font-size:12px;
}
h6 {
font-size:11px;
}
ul {
list-style-type:disc;
}
code, tt {
font-family:'DejaVu Sans Mono', monospace;
font-size:14px;
padding-top:4px;
margin-top:8px;
line-height:1.4em;
}
dt code {
font-family:'DejaVu Sans Mono', monospace;
font-size:14px;
padding-top:4px;
}
table tr td dt code {
font-family:'DejaVu Sans Mono', monospace;
font-size:14px;
vertical-align:top;
padding-top:4px;
}
sup {
font-size:8px;
}
/*
Document title and Copyright styles
*/
.clear {
clear:both;
height:0px;
overflow:hidden;
}
.aboutLanguage {
float:right;
padding:0px 21px;
font-size:11px;
z-index:200;
margin-top:-9px;
}
.legalCopy {
margin-left:.5em;
}
.bar a, .bar a:link, .bar a:visited, .bar a:active {
color:#FFFFFF;
text-decoration:none;
}
.bar a:hover, .bar a:focus {
color:#bb7a2a;
}
.tab {
background-color:#0066FF;
color:#ffffff;
padding:8px;
width:5em;
font-weight:bold;
}
/*
Navigation bar styles
*/
.bar {
background-color:#4D7A97;
color:#FFFFFF;
padding:.8em .5em .4em .8em;
height:auto;/*height:1.8em;*/
font-size:11px;
margin:0;
}
.topNav {
background-color:#4D7A97;
color:#FFFFFF;
float:left;
padding:0;
width:100%;
clear:right;
height:2.8em;
padding-top:10px;
overflow:hidden;
font-size:12px;
}
.bottomNav {
margin-top:10px;
background-color:#4D7A97;
color:#FFFFFF;
float:left;
padding:0;
width:100%;
clear:right;
height:2.8em;
padding-top:10px;
overflow:hidden;
font-size:12px;
}
.subNav {
background-color:#dee3e9;
float:left;
width:100%;
overflow:hidden;
font-size:12px;
}
.subNav div {
clear:left;
float:left;
padding:0 0 5px 6px;
text-transform:uppercase;
}
ul.navList, ul.subNavList {
float:left;
margin:0 25px 0 0;
padding:0;
}
ul.navList li{
list-style:none;
float:left;
padding: 5px 6px;
text-transform:uppercase;
}
ul.subNavList li{
list-style:none;
float:left;
}
.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited {
color:#FFFFFF;
text-decoration:none;
text-transform:uppercase;
}
.topNav a:hover, .bottomNav a:hover {
text-decoration:none;
color:#bb7a2a;
text-transform:uppercase;
}
.navBarCell1Rev {
background-color:#F8981D;
color:#253441;
margin: auto 5px;
}
.skipNav {
position:absolute;
top:auto;
left:-9999px;
overflow:hidden;
}
/*
Page header and footer styles
*/
.header, .footer {
clear:both;
margin:0 20px;
padding:5px 0 0 0;
}
.indexHeader {
margin:10px;
position:relative;
}
.indexHeader span{
margin-right:15px;
}
.indexHeader h1 {
font-size:13px;
}
.title {
color:#2c4557;
margin:10px 0;
}
.subTitle {
margin:5px 0 0 0;
}
.header ul {
margin:0 0 15px 0;
padding:0;
}
.footer ul {
margin:20px 0 5px 0;
}
.header ul li, .footer ul li {
list-style:none;
font-size:13px;
}
/*
Heading styles
*/
div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 {
background-color:#dee3e9;
border:1px solid #d0d9e0;
margin:0 0 6px -8px;
padding:7px 5px;
}
ul.blockList ul.blockList ul.blockList li.blockList h3 {
background-color:#dee3e9;
border:1px solid #d0d9e0;
margin:0 0 6px -8px;
padding:7px 5px;
}
ul.blockList ul.blockList li.blockList h3 {
padding:0;
margin:15px 0;
}
ul.blockList li.blockList h2 {
padding:0px 0 20px 0;
}
/*
Page layout container styles
*/
.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer {
clear:both;
padding:10px 20px;
position:relative;
}
.indexContainer {
margin:10px;
position:relative;
font-size:12px;
}
.indexContainer h2 {
font-size:13px;
padding:0 0 3px 0;
}
.indexContainer ul {
margin:0;
padding:0;
}
.indexContainer ul li {
list-style:none;
padding-top:2px;
}
.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt {
font-size:12px;
font-weight:bold;
margin:10px 0 0 0;
color:#4E4E4E;
}
.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd {
margin:5px 0 10px 0px;
font-size:14px;
font-family:'DejaVu Sans Mono',monospace;
}
.serializedFormContainer dl.nameValue dt {
margin-left:1px;
font-size:1.1em;
display:inline;
font-weight:bold;
}
.serializedFormContainer dl.nameValue dd {
margin:0 0 0 1px;
font-size:1.1em;
display:inline;
}
/*
List styles
*/
ul.horizontal li {
display:inline;
font-size:0.9em;
}
ul.inheritance {
margin:0;
padding:0;
}
ul.inheritance li {
display:inline;
list-style:none;
}
ul.inheritance li ul.inheritance {
margin-left:15px;
padding-left:15px;
padding-top:1px;
}
ul.blockList, ul.blockListLast {
margin:10px 0 10px 0;
padding:0;
}
ul.blockList li.blockList, ul.blockListLast li.blockList {
list-style:none;
margin-bottom:15px;
line-height:1.4;
}
ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList {
padding:0px 20px 5px 10px;
border:1px solid #ededed;
background-color:#f8f8f8;
}
ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList {
padding:0 0 5px 8px;
background-color:#ffffff;
border:none;
}
ul.blockList ul.blockList ul.blockList ul.blockList li.blockList {
margin-left:0;
padding-left:0;
padding-bottom:15px;
border:none;
}
ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast {
list-style:none;
border-bottom:none;
padding-bottom:0;
}
table tr td dl, table tr td dl dt, table tr td dl dd {
margin-top:0;
margin-bottom:1px;
}
/*
Table styles
*/
.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary {
width:100%;
border-left:1px solid #EEE;
border-right:1px solid #EEE;
border-bottom:1px solid #EEE;
}
.overviewSummary, .memberSummary {
padding:0px;
}
.overviewSummary caption, .memberSummary caption, .typeSummary caption,
.useSummary caption, .constantsSummary caption, .deprecatedSummary caption {
position:relative;
text-align:left;
background-repeat:no-repeat;
color:#253441;
font-weight:bold;
clear:none;
overflow:hidden;
padding:0px;
padding-top:10px;
padding-left:1px;
margin:0px;
white-space:pre;
}
.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link,
.useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link,
.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover,
.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover,
.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active,
.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active,
.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited,
.useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited {
color:#FFFFFF;
}
.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span,
.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span {
white-space:nowrap;
padding-top:5px;
padding-left:12px;
padding-right:12px;
padding-bottom:7px;
display:inline-block;
float:left;
background-color:#F8981D;
border: none;
height:16px;
}
.memberSummary caption span.activeTableTab span {
white-space:nowrap;
padding-top:5px;
padding-left:12px;
padding-right:12px;
margin-right:3px;
display:inline-block;
float:left;
background-color:#F8981D;
height:16px;
}
.memberSummary caption span.tableTab span {
white-space:nowrap;
padding-top:5px;
padding-left:12px;
padding-right:12px;
margin-right:3px;
display:inline-block;
float:left;
background-color:#4D7A97;
height:16px;
}
.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab {
padding-top:0px;
padding-left:0px;
padding-right:0px;
background-image:none;
float:none;
display:inline;
}
.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd,
.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd {
display:none;
width:5px;
position:relative;
float:left;
background-color:#F8981D;
}
.memberSummary .activeTableTab .tabEnd {
display:none;
width:5px;
margin-right:3px;
position:relative;
float:left;
background-color:#F8981D;
}
.memberSummary .tableTab .tabEnd {
display:none;
width:5px;
margin-right:3px;
position:relative;
background-color:#4D7A97;
float:left;
}
.overviewSummary td, .memberSummary td, .typeSummary td,
.useSummary td, .constantsSummary td, .deprecatedSummary td {
text-align:left;
padding:0px 0px 12px 10px;
width:100%;
}
th.colOne, th.colFirst, th.colLast, .useSummary th, .constantsSummary th,
td.colOne, td.colFirst, td.colLast, .useSummary td, .constantsSummary td{
vertical-align:top;
padding-right:0px;
padding-top:8px;
padding-bottom:3px;
}
th.colFirst, th.colLast, th.colOne, .constantsSummary th {
background:#dee3e9;
text-align:left;
padding:8px 3px 3px 7px;
}
td.colFirst, th.colFirst {
white-space:nowrap;
font-size:13px;
}
td.colLast, th.colLast {
font-size:13px;
}
td.colOne, th.colOne {
font-size:13px;
}
.overviewSummary td.colFirst, .overviewSummary th.colFirst,
.overviewSummary td.colOne, .overviewSummary th.colOne,
.memberSummary td.colFirst, .memberSummary th.colFirst,
.memberSummary td.colOne, .memberSummary th.colOne,
.typeSummary td.colFirst{
width:25%;
vertical-align:top;
}
td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover {
font-weight:bold;
}
.tableSubHeadingColor {
background-color:#EEEEFF;
}
.altColor {
background-color:#FFFFFF;
}
.rowColor {
background-color:#EEEEEF;
}
/*
Content styles
*/
.description pre {
margin-top:0;
}
.deprecatedContent {
margin:0;
padding:10px 0;
}
.docSummary {
padding:0;
}
ul.blockList ul.blockList ul.blockList li.blockList h3 {
font-style:normal;
}
div.block {
font-size:14px;
font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif;
}
td.colLast div {
padding-top:0px;
}
td.colLast a {
padding-bottom:3px;
}
/*
Formatting effect styles
*/
.sourceLineNo {
color:green;
padding:0 30px 0 0;
}
h1.hidden {
visibility:hidden;
overflow:hidden;
font-size:10px;
}
.block {
display:block;
margin:3px 10px 2px 0px;
color:#474747;
}
.deprecatedLabel, .descfrmTypeLabel, .memberNameLabel, .memberNameLink,
.overrideSpecifyLabel, .packageHierarchyLabel, .paramLabel, .returnLabel,
.seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink {
font-weight:bold;
}
.deprecationComment, .emphasizedPhrase, .interfaceName {
font-style:italic;
}
div.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase,
div.block div.block span.interfaceName {
font-style:normal;
}
div.contentContainer ul.blockList li.blockList h2{
padding-bottom:0px;
}
/*
Spring
*/
pre.code {
background-color: #F8F8F8;
border: 1px solid #CCCCCC;
border-radius: 3px 3px 3px 3px;
overflow: auto;
padding: 10px;
margin: 4px 20px 2px 0px;
}
pre.code code, pre.code code * {
font-size: 1em;
}
pre.code code, pre.code code * {
padding: 0 !important;
margin: 0 !important;
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2020-2021 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.
*/
@import 'css/spring.css';
.listingblock .switch {
border-style: none;
display: inline-block;
position: relative;
bottom: -3px;
}
.listingblock .switch--item {
padding: 10px;
background-color: #e6e1dc;
color: #282c34;
display: inline-block;
cursor: pointer;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.listingblock .switch--item:not(:first-child) {
border-style: none;
}
.listingblock .switch--item.selected {
background-color: #282c34;
color: #e6e1dc;
}
.listingblock pre.highlightjs {
padding: 0;
}

View File

@@ -0,0 +1,277 @@
= Spring GraphQL Documentation
:toc: left
:toclevels: 4
:tabsize: 4
:docinfo1:
https://graphql.org/[GraphQL] support for Spring applications with https://github.com/graphql-java/graphql-java[GraphQL Java].
== Getting started
This project is tested against Spring Boot 2.4+.
You can start by creating a project on https://start.spring.io and select the `spring-boot-starter-web` or `spring-boot-starter-webflux` starter,
depending on the type of web application you'd like to build. Once the project is generated, you can manually add the
`org.springframework.experimental:graphql-spring-boot-starter` dependency.
`build.gradle` snippet:
[source,groovy,indent=0,subs="verbatim,quotes"]
----
dependencies {
implementation 'org.springframework.experimental:graphql-spring-boot-starter:1.0.0-SNAPSHOT'
// Spring Web MVC starter
implementation 'org.springframework.boot:spring-boot-starter-web'
// OR Spring WebFlux starter
implementation 'org.springframework.boot:spring-boot-starter-webflux'
}
repositories {
mavenCentral()
// don't forget to add spring milestone or snapshot repositories
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
----
`pom.xml` snippet:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<dependencies>
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<!-- Spring Web MVC starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- OR Spring WebFlux starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- ... -->
</dependencies>
<!-- Don't forget to add spring milestone or snapshot repositories -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
----
You can now add a GraphQL schema in `src/main/resources/graphql/schema.graphqls` such as:
[source,javascript,indent=0,subs="verbatim,quotes"]
----
type Query {
people: [Person]!
}
type Person {
id: ID!
name: String!
}
----
Then you should configure the data fetching process using a `RuntimeWiringCustomizer` and custom components like
Spring Data repositories, `WebClient` instances for Web APIs, a `@Service` bean, etc.
[source,java,indent=0,subs="verbatim,quotes"]
----
@Component
public class PersonDataWiring implements RuntimeWiringCustomizer {
private final PersonService personService;
public PersonDataWiring(PersonService personService) {
this.personService = personService;
}
@Override
public void customize(RuntimeWiring.Builder builder) {
builder.type("Query", typeWiring -> typeWiring
.dataFetcher("people", env -> this.personService.findAll()));
}
}
----
You can now start your application!
A GraphiQL web interface is available at `http://localhost:8080/graphql` and you can use GraphQL clients
to POST queries at the same location.
== Features
=== Core configuration
The Spring GraphQL project offers a few configuration properties to customize your application:
[source,properties,indent=0,subs="verbatim,quotes"]
----
# web path to the graphql endpoint
spring.graphql.path=/graphql
# locations of the graphql '*.graphqls' schema files
spring.graphql.schema.locations=classpath:graphql/
# schema printer endpoint configuration
# endpoint path is concatenated with the main path, so "/graphql/schema" by default
spring.graphql.schema.printer.enabled=false
spring.graphql.schema.printer.path=/schema
# GraphiQL UI configuration
spring.graphql.graphiql.enabled=true
spring.graphql.graphiql.path=/graphiql
# whether micrometer metrics should be collected for graphql queries
management.metrics.graphql.autotime.enabled=true
----
You can contribute `RuntimeWiringCustomizer` beans to the context in order to configure the runtime wiring of your GraphQL application.
=== WebSocket support
This project also supports WebSocket as a transport for GraphQL requests - you can use it to build [`Subscription` queries](http://spec.graphql.org/draft/#sec-Subscription).
This use case is powered by Reactor `Flux`, check out the `samples/webflux-websocket` sample application for more.
To enable this support, you need to configure the `spring.graphql.websocket.path` property in your application
and have the required dependencies on classpath. In the case of a Servlet application, adding the `spring-boot-starter-websocket` should be enough.
WebSocket support comes with dedicated properties:
[source,properties,indent=0,subs="verbatim,quotes"]
----
# Path of the GraphQL WebSocket subscription endpoint.
spring.graphql.websocket.path=/graphql/websocket
# Time within which the initial {@code CONNECTION_INIT} type message must be received.
spring.graphql.websocket.connection-init-timeout=60s
----
=== Extension points
You can contribute [`WebInterceptor` beans](https://github.com/spring-projects-experimental/spring-graphql/blob/master/spring-graphql/src/main/java/org/springframework/graphql/WebInterceptor.java)
to the application context, so as to customize the `ExecutionInput` or the `ExecutionResult` of the query.
A custom `WebInterceptor` can, for example, change the HTTP request/response headers.
=== Testing support
When the `spring-boot-starter-test` dependency is on the classpath, Spring GraphQL provides a testing infrastructure for your application.
Spring Boot allows you to test your web application with [with a mock environment](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.with-mock-environment)
or [with a running server](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.with-running-server).
In both cases, adding the `@AutoConfigureGraphQlTester` annotation on your test class will contribute a `GraphQlTester` bean you can inject and use in your tests:
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureGraphQlTester
public class MockMvcGraphQlTests {
@Autowired
private GraphQlTester graphQlTester;
@Test
void jsonPath() {
String query = "{" +
" project(slug:\"spring-framework\") {" +
" releases {" +
" version" +
" }" +
" }" +
"}";
this.graphQlTester.query(query)
.execute()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
}
}
----
=== Metrics
If the `spring-boot-starter-actuator` dependency is on the classpath, metrics will be collected for GraphQL requests.
You can see those metrics by exposing the metrics endpoint with `application.properties`:
[source,properties,indent=0,subs="verbatim,quotes"]
----
management.endpoints.web.exposure.include=health,metrics,info
----
==== GraphQL Request (timer)
A Request metric timer is available at `/actuator/metrics/graphql.request`.
[cols="1,2,2"]
|===
|Tag | Description| Sample values
|outcome
|Request outcome
|"SUCCESS", "ERROR"
|===
==== GraphQL Data Fetcher (timer)
A Data Fetcher metric timer is available at `/actuator/metrics/graphql.datafetcher`.
[cols="1,2,2"]
|===
|Tag | Description| Sample values
|path
|data fetcher path
|"Query.project"
|outcome
|data fetching outcome
|"SUCCESS", "ERROR"
|===
==== GraphQL Error (counter)
A counter metric counter is available at `/actuator/metrics/graphql.error`.
[cols="1,2,2"]
|===
|Tag | Description| Sample values
|errorType
|error type
|"DataFetchingException"
|errorPath
|error JSON Path
|"$.project"
|===
== Sample applications
This repository contains sample applications that the team is using to test new features and ideas.
You can run them by cloning this repository and typing on the command line:
[source,bash,indent=0,subs="verbatim,quotes"]
----
$ ./gradlew :samples:webmvc-http:bootRun
$ ./gradlew :samples:webflux-websocket:bootRun
----

View File

@@ -1,94 +1,97 @@
apply plugin: "maven-publish"
javadoc {
description = "Generates project-level javadoc for use in -javadoc jar"
plugins.withType(JavaPlugin) {
javadoc {
description = "Generates project-level javadoc for use in -javadoc jar"
options.encoding = "UTF-8"
options.memberLevel = JavadocMemberLevel.PROTECTED
options.author = true
options.header = project.name
options.use = true
options.addStringOption("Xdoclint:none", "-quiet")
options.encoding = "UTF-8"
options.memberLevel = JavadocMemberLevel.PROTECTED
options.author = true
options.header = project.name
options.use = true
options.addStringOption("Xdoclint:none", "-quiet")
// Suppress warnings due to cross-module @see and @link references.
// Note that global 'api' task does display all warnings.
logging.captureStandardError LogLevel.INFO
logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message
}
// Suppress warnings due to cross-module @see and @link references.
// Note that global 'api' task does display all warnings.
logging.captureStandardError LogLevel.INFO
logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message
}
task sourcesJar(type: Jar, dependsOn: classes) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveClassifier.set("sources")
from sourceSets.main.allSource
}
task sourcesJar(type: Jar, dependsOn: classes) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveClassifier.set("sources")
from sourceSets.main.allSource
}
task javadocJar(type: Jar) {
archiveClassifier.set("javadoc")
from javadoc
}
publishing {
publications {
mavenJava(MavenPublication) {
pom {
afterEvaluate {
name = project.description
description = project.description
}
url = "https://github.com/spring-projects/spring-graphql"
organization {
name = "Spring IO"
url = "https://spring.io/projects"
}
licenses {
license {
name = "Apache License, Version 2.0"
url = "https://www.apache.org/licenses/LICENSE-2.0"
distribution = "repo"
task javadocJar(type: Jar) {
archiveClassifier.set("javadoc")
from javadoc
}
publishing {
publications {
mavenJava(MavenPublication) {
pom {
afterEvaluate {
name = project.description
description = project.description
}
}
scm {
url = "https://github.com/spring-projects/spring-graphql"
connection = "scm:git:git://github.com/spring-projects/spring-graphql"
developerConnection = "scm:git:git://github.com/spring-projects/spring-graphql"
}
developers {
developer {
id = "andimarek"
name = "Andreas Marek"
email = "andimarek@fastmail.fm"
organization {
name = "Spring IO"
url = "https://spring.io/projects"
}
developer {
id = "rstoyanchev"
name = "Rossen Stoyanchev"
email = "rstoyanchev@vmware.com"
licenses {
license {
name = "Apache License, Version 2.0"
url = "https://www.apache.org/licenses/LICENSE-2.0"
distribution = "repo"
}
}
developer {
id = "bclozel"
name = "Brian Clozel"
email = "bclozel@vmware.com"
scm {
url = "https://github.com/spring-projects/spring-graphql"
connection = "scm:git:git://github.com/spring-projects/spring-graphql"
developerConnection = "scm:git:git://github.com/spring-projects/spring-graphql"
}
developers {
developer {
id = "andimarek"
name = "Andreas Marek"
email = "andimarek@fastmail.fm"
}
developer {
id = "rstoyanchev"
name = "Rossen Stoyanchev"
email = "rstoyanchev@vmware.com"
}
developer {
id = "bclozel"
name = "Brian Clozel"
email = "bclozel@vmware.com"
}
}
issueManagement {
system = "GitHub"
url = "https://github.com/spring-projects/spring-graphql/issues"
}
}
issueManagement {
system = "GitHub"
url = "https://github.com/spring-projects/spring-graphql/issues"
versionMapping {
usage('java-api') {
fromResolutionResult()
}
usage('java-runtime') {
fromResolutionResult()
}
}
from components.java
artifact sourcesJar
artifact javadocJar
}
versionMapping {
usage('java-api') {
fromResolutionResult()
}
usage('java-runtime') {
fromResolutionResult()
}
}
from components.java
artifact sourcesJar
artifact javadocJar
}
}
}
configureDeploymentRepository(project)
void configureDeploymentRepository(Project project) {

View File

@@ -21,4 +21,5 @@ include 'spring-graphql',
'samples:webmvc-http',
'samples:webmvc-http-security',
'samples:webflux-security',
'samples:webflux-websocket'
'samples:webflux-websocket',
'docs'