From 1727c7270ad845f8dd6b07fe2e7131e7aba13647 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Wed, 30 Jun 2021 17:55:33 +0200 Subject: [PATCH] Set up reference documentation and JavaDoc Closes gh-76 --- build.gradle | 2 + ci/pipeline.yml | 8 + docs/build.gradle | 152 ++++++ docs/src/docs/api/overview.html | 7 + docs/src/docs/api/stylesheet.css | 596 ++++++++++++++++++++++ docs/src/docs/asciidoc/css/stylesheet.css | 47 ++ docs/src/docs/asciidoc/index.adoc | 277 ++++++++++ gradle/publishing.gradle | 149 +++--- settings.gradle | 3 +- 9 files changed, 1167 insertions(+), 74 deletions(-) create mode 100644 docs/build.gradle create mode 100644 docs/src/docs/api/overview.html create mode 100644 docs/src/docs/api/stylesheet.css create mode 100644 docs/src/docs/asciidoc/css/stylesheet.css create mode 100644 docs/src/docs/asciidoc/index.adoc diff --git a/build.gradle b/build.gradle index f614aa74..64bc08fb 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,8 @@ ext { moduleProjects = subprojects.findAll { it.name.startsWith("spring-") } } +description = "Spring GraphQL" + subprojects { apply plugin: 'io.spring.dependency-management' diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 03470837..44419176 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -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 diff --git a/docs/build.gradle b/docs/build.gradle new file mode 100644 index 00000000..388844b2 --- /dev/null +++ b/docs/build.gradle @@ -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 + } + } +} + + diff --git a/docs/src/docs/api/overview.html b/docs/src/docs/api/overview.html new file mode 100644 index 00000000..48993425 --- /dev/null +++ b/docs/src/docs/api/overview.html @@ -0,0 +1,7 @@ + + +

+ This is the public API documentation for the Spring GraphQL project. +

+ + diff --git a/docs/src/docs/api/stylesheet.css b/docs/src/docs/api/stylesheet.css new file mode 100644 index 00000000..dc19d6a4 --- /dev/null +++ b/docs/src/docs/api/stylesheet.css @@ -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; +} + diff --git a/docs/src/docs/asciidoc/css/stylesheet.css b/docs/src/docs/asciidoc/css/stylesheet.css new file mode 100644 index 00000000..e0a59111 --- /dev/null +++ b/docs/src/docs/asciidoc/css/stylesheet.css @@ -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; +} \ No newline at end of file diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc new file mode 100644 index 00000000..77588be1 --- /dev/null +++ b/docs/src/docs/asciidoc/index.adoc @@ -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"] +---- + + + org.springframework.experimental + graphql-spring-boot-starter + 1.0.0-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-webflux + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + +---- + +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 +---- diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle index d24607a3..5f981d07 100644 --- a/gradle/publishing.gradle +++ b/gradle/publishing.gradle @@ -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) { diff --git a/settings.gradle b/settings.gradle index 7527e400..90c9e383 100644 --- a/settings.gradle +++ b/settings.gradle @@ -21,4 +21,5 @@ include 'spring-graphql', 'samples:webmvc-http', 'samples:webmvc-http-security', 'samples:webflux-security', - 'samples:webflux-websocket' + 'samples:webflux-websocket', + 'docs'