diff --git a/build.gradle b/build.gradle index ef09e161..4f150724 100644 --- a/build.gradle +++ b/build.gradle @@ -1,87 +1,40 @@ -project(':spring-restdocs') { - - ext { - hamcrestVersion = '1.3' - jacksonVersion = '2.3.4' - jacocoVersion = '0.7.2.201409121644' - junitVersion = '4.11' - servletApiVersion = '3.1.0' - springHateoasVersion = '0.17.0.RELEASE' - springVersion = '4.1.4.RELEASE' - mockitoVersion = '1.10.19' - } - - group = 'org.springframework.restdocs' - - apply plugin: 'java' - apply plugin: 'eclipse' - apply plugin: 'maven' - apply plugin: 'sonar-runner' - - sonarRunner { - sonarProperties { - property 'sonar.jacoco.reportPath', "${buildDir.name}/jacoco.exec" - property 'sonar.java.coveragePlugin', 'jacoco' - property 'sonar.links.ci', 'https://build.spring.io/browse/SRD' - property 'sonar.links.homepage', 'https://github.com/spring-projects/spring-restdocs' - property 'sonar.links.issue', 'https://github.com/spring-projects/spring-restdocs' - property 'sonar.links.scm', 'https://github.com/spring-projects/spring-restdocs' - } - } - - configurations { - jacoco - } - - sourceCompatibility = 1.7 - targetCompatibility = 1.7 - +buildscript { repositories { jcenter() } - - task sourcesJar(type: Jar) { - classifier = 'sources' - from project.sourceSets.main.allSource - } - - task javadocJar(type: Jar) { - classifier = "javadoc" - from javadoc - } - - artifacts { - archives sourcesJar - archives javadocJar - } - - eclipseJdt.onlyIf { false } - cleanEclipseJdt.onlyIf { false } - dependencies { - compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" - compile "junit:junit:$junitVersion" - compile "org.springframework:spring-test:$springVersion" - compile "org.springframework:spring-web:$springVersion" - compile "org.springframework:spring-webmvc:$springVersion" - compile "javax.servlet:javax.servlet-api:$servletApiVersion" - jacoco "org.jacoco:org.jacoco.agent:$jacocoVersion:runtime" - testCompile "org.springframework.hateoas:spring-hateoas:$springHateoasVersion" - testCompile "org.mockito:mockito-core:$mockitoVersion" - testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion" - testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion" - } - - test { - jvmArgs "-javaagent:${configurations.jacoco.asPath}=destfile=${buildDir}/jacoco.exec,includes=org.springframework.restdocs.*" - testLogging { - exceptionFormat "full" - } + classpath 'io.spring.gradle:dependency-management-plugin:0.5.1.RELEASE' } } apply plugin: 'samples' +ext { + springVersion = '4.1.5.RELEASE' +} + +subprojects { + apply plugin: 'io.spring.dependency-management' + apply plugin: 'java' + apply plugin: 'eclipse' + + dependencyManagement { + imports { + mavenBom "org.springframework:spring-framework-bom:$springVersion" + } + dependencies { + dependency 'com.fasterxml.jackson.core:jackson-databind:2.4.6' + dependency 'javax.servlet:javax.servlet-api:3.1.0' + dependency 'junit:junit:4.12' + dependency 'org.hamcrest:hamcrest-core:1.3' + dependency 'org.hamcrest:hamcrest-library:1.3' + dependency 'org.mockito:mockito-core:1.10.19' + dependency 'org.springframework.hateoas:spring-hateoas:0.17.0.RELEASE' + dependency 'org.jacoco:org.jacoco.agent:0.7.2.201409121644' + } + } +} + samples { dependOn 'spring-restdocs:install' @@ -94,6 +47,26 @@ samples { } } -wrapper { - gradleVersion = '2.3' +task docsZip(type: Zip, dependsOn: [':docs:asciidoctor', ':spring-restdocs:javadoc']) { + group = 'Distribution' + baseName = 'spring-restdocs' + classifier = 'docs' + description = 'Builds -${classifier} archive containing API and reference documentation' + destinationDir = file("${project.buildDir}/distributions") + + from (project.tasks.findByPath(':docs:asciidoctor')) { + into 'reference' + } + + from (project.tasks.findByPath(':spring-restdocs:javadoc')) { + into 'api' + } +} + +configurations { + archives +} + +artifacts { + archives docsZip } \ No newline at end of file diff --git a/docs/build.gradle b/docs/build.gradle new file mode 100644 index 00000000..f075eb28 --- /dev/null +++ b/docs/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'org.asciidoctor.convert' version '1.5.2' +} + +dependencies { + compile 'org.springframework:spring-webmvc' + + testCompile project(':spring-restdocs') + testCompile 'junit:junit' + testCompile 'org.springframework:spring-test' +} + +tasks.findByPath("artifactoryPublish")?.enabled = false + +asciidoctor { + sources { + include 'index.adoc' + } +} \ No newline at end of file diff --git a/docs/src/docs/asciidoc/configuration.adoc b/docs/src/docs/asciidoc/configuration.adoc new file mode 100644 index 00000000..fb92299b --- /dev/null +++ b/docs/src/docs/asciidoc/configuration.adoc @@ -0,0 +1,67 @@ +[[configuration]] +== Configuration + + + +[[configuration-uris]] +=== Documented URIs + +The default configuration for URIs documented by Spring REST docs is: + +|=== +|Setting |Default + +|Scheme +|`http` + +|Host +|`localhost` + +|Port +|`8080` + +|Context path +|Empty string +|=== + +This configuration is applied by `RestDocumentationConfigurer`. You can use its API to +change one or more of the defaults to suit your needs: + +[source,java,indent=0] +---- +include::{examples-dir}com/example/CustomUriConfiguration.java[tags=custom-uri-configuration] +---- + + + +[[configuration-snippet-encoding]] +=== Snippet encoding + +The default encoding used by Asciidoc is `UTF-8`. Spring REST docs adopts the same +default for the snippets that it generated. If you require an encoding other than `UTF-8`, +use `RestDocumentationConfigurer` to configure it: + +[source,java,indent=0] +---- +include::{examples-dir}com/example/CustomEncoding.java[tags=custom-encoding] +---- + + + +[[configuration-output-directory]] +=== Snippet output directory + +As described in <> the snippet output directory is +configured in your `pom.xml` or `build.gradle` file. This configuration applies to builds +on the command line, but it may not apply when running your tests in your IDE. In the +absence of the property, Spring REST Docs will write the generated snippets to standard +out. + +If you'd prefer that your IDE writes the snippets to disk you can use a file in +`src/test/resources` named `documentation.properties` to specify the output directory that +should be used: + +[source,properties] +---- +org.springframework.restdocs.outputDir: target/generated-snippets +---- \ No newline at end of file diff --git a/docs/src/docs/asciidoc/customizing-responses.adoc b/docs/src/docs/asciidoc/customizing-responses.adoc new file mode 100644 index 00000000..08613f93 --- /dev/null +++ b/docs/src/docs/asciidoc/customizing-responses.adoc @@ -0,0 +1,45 @@ +[[customizing-responses]] +== Customizing responses + +There may be situations where you do not want to document a response exactly as received. +Spring REST Docs provides a number of response post processors that can be used to modify +a response after it is received but before it's documented. + +Response modification is configured using a `ResponseModifier`. An instance can be +obtained using the static `modifyResponseTo` method on `RestDocumentation`. Once the +response modifications have been provided, documentation can be configured as usual +via the `andDocument` method: + +[source,java,indent=0] +---- +include::{examples-dir}/com/example/ResponsePostProcessing.java[tags=general] +---- +<1> Call `modifyResponseTo` to configure response modifications, passing in one or more +`ResponsePostProcessor` implementations. +<2> Proceed with documenting the call + + +[[customizing-responses-pretty-printing]] +=== Pretty printing + +`prettyPrintContent` on `ResponsePostProcessors` formats the body of the response to +make it easier to read. + +[[customizing-responses-masking-links]] +=== Masking links + +If you're documenting a Hypermedia-based API, you may want to encourage clients to +navigate the API using links rather than through the use of hard coded URIs. One way to do +this is to limit the use of URIs in the documentation. `maskLinks` on +`ResponsePostProcessors` replaces the `href` of any links in the response with `...`. A +different replacement can also be specified if you wish. + +=== Removing headers + +`removeHeaders` on `ResponsePostProcessors` removes any occurrences of the named headers +from the response. + +=== Replacing patterns + +`replacePattern` on `ResponsePostProcessors` provides a general purpose mechanism for +replacing content in a response. Any occurrences of a regular expression are replaced. \ No newline at end of file diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc new file mode 100644 index 00000000..dad31532 --- /dev/null +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -0,0 +1,263 @@ +[[documenting-your-api]] +== Documenting your API + +This section provides more details about using Spring REST Docs to document your API. + + + +[[documenting-your-api-hypermedia]] +=== Hypermedia + +Spring REST Docs provides support for documenting the links in a +https://en.wikipedia.org/wiki/HATEOAS[Hypermedia-based] API: + +[source,java,indent=0] +---- +include::{examples-dir}/com/example/Hypermedia.java[tag=links] +---- +<1> `withLinks` is used to describe the expected links +<2> Expects a link whose rel is `alpha`. Uses the static `linkWithRel` method on +`org.springframework.restdocs.hypermedia.HypermediaDocumentation`. +<3> Expects a link whose rel is `bravo` + +The result is a snippet named `links.adoc` that contains a table describing the resource's +links. + +When documenting links, the test will fail if an undocumented link is found in the +response. Similarly, the test will also fail if a documented link is not found in the +response. + + + +[[documenting-your-api-hypermedia-link-formats]] +==== Hypermedia link formats + +Two link formats are understood by default: + + * Atom – links are expected to be in an array named `links`. Used by default when the + content type of the response is compatible with `application/json`. + * HAL – links are expected to be in a map named `_links`. Used by default when the + content type of the response is compatible with `application/hal+json`. + +If you are using Atom or HAL-format links but with a different content type you can +provide one of the built-in `LinkExtractor` implementations to `withLinks`. For example: + +[source,java,indent=0] +---- +include::{examples-dir}/com/example/Hypermedia.java[tag=explicit-extractor] +---- +<1> Indicate that the links are in HAL format using the `halLinks` static method on +`org.springframework.restdocs.hypermedia.LinkExtractors`. + +If your API represents its links in a format other than Atom or HAL you can provide your +own implementation of the `LinkExtractor` interface to extract the links from the +response. + +[[documenting-your-api-request-response-payloads]] +=== Request and response payloads + +In addition to the Hypermedia-specific support <>, support for general documentation of request and response payloads is also +provided: + +[source,java,indent=0] +---- +include::{examples-dir}/com/example/Payload.java[tags=response] +---- +<1> `withResponseFields` is used to describe the expected fields in the response payload. +To document a request `withRequestFields` can be used. +<2> Expects a field with the path `contact`. Uses the static `fieldWithPath` method on +`org.springframework.restdocs.payload.PayloadDocumentation`. +<3> Expects a field with the path `contact.email` + +The result is a snippet that contains a table describing the fields. For requests this +snippet is named `request-fields.adoc`. For responses this snippet is named +`response-fields.adoc`. + +When documenting fields, the test will fail if an undocumented field is found in the +payload. Similarly, the test will also fail if a documented field is not found in the +payload and the field has not be marked as optional. For payloads with a hierarchical +structure, when a point in the hierarchy is documented any fields beneath that point are +also deemed to have been documented. This means that you do not have to document the +entire hierarchy, but you may do so if you wish. + + + +[[documenting-your-api-request-response-payloads-field-paths]] +==== Field paths + +When documenting request and response payloads, fields are identified using a path. Paths +use `.` to descend into a child object and `[]` to identify an array. For example, with +this JSON payload: + +[source,json,indent=0] +---- + { + "a":{ + "b":[ + { + "c":"one" + }, + { + "c":"two" + }, + { + "d":"three" + } + ] + } + } +---- + +The following paths are all present: + +[cols="1,3"] +|=== +|Path | Value + +|`a` +|An object containing `b` + +|`a.b` +|An array containing three objects + +|`a.b[]` +|An array containing three objects + +|`a.b[].c` +|An array containing the strings `one` and `two` + +|`a.b[].d` +|The string `three` +|=== + + + +[[documenting-your-api-request-response-payloads-field-types]] +==== Field types + +When a field is documented, Spring REST docs will attempt to determine its type by +examining the payload. Seven different types are supported: + +[cols="1,3"] +|=== +| Type | Description + +| array +| The value of each occurrence of the field is an array + +| boolean +| The value of each occurrence of the field is a boolean (`true` or `false`) + +| object +| The value of each occurrence of the field is an object + +| number +| The value of each occurrence of the field is a number + +| null +| The value of each occurrence of the field is `null` + +| string +| The value of each occurrence of the field is a string + +| varies +| The field occurs multiple times in the payload with a variety of different types +|=== + +The type can also be set explicitly using the `type(FieldType)` method on +`FieldDescriptor`: + +[source,java,indent=0] +---- +include::{examples-dir}/com/example/Payload.java[tags=explicit-type] +---- +<1> Set the field's type to `string`. + + + +[[documenting-your-api-query-parameters]] +=== Query parameters + +A request's query parameters can be documented using `withQueryParameters` + +[source,java,indent=0] +---- +include::{examples-dir}/com/example/QueryParameters.java[tags=query-parameters] +---- +<1> `withQueryParameters` is used to describe the query parameters +<2> Documents a parameter named `page`. Uses the static `parameterWithName` method on +`org.springframework.restdocs.request.RequestDocumentation`. +<3> Documents a parameter named `per_page` + +The result is a snippet named `query-parameters.adoc` that contains a table describing +the query parameters that are supported by the resource. + +When documenting query parameters, the test will fail if an undocumented query parameter +is used in the request. Similarly, the test will also fail if a documented query parameter +is not found in the request. + + + +[[documenting-your-api-default-snippets]] +=== Default snippets + +A number of snippets are produced automatically when you document a call to +`MockMvc.perform`: + +[cols="1,3"] +|=== +|Snippet | Description + +| `curl-request.adoc` +| Contains the http://curl.haxx.se[`curl`] command that is equivalent to the `MockMvc` +call that is being documented + +| `http-request.adoc` +| Contains the HTTP request that is equivalent to the `MockMvc` call that is being +documented + +| `http-response.adoc` +| Contains the HTTP response that was returned +|=== + + + +[[documentating-your-api-parameterized-output-directories]] +=== Using parameterized output directories + +The output directory used by `document` can be parameterized. The following parameters +are supported: + +[cols="1,3"] +|=== +| Parameter | Description + +| {methodName} +| The name of the test method, formatted using camelCase + +| {method-name} +| The name of the test method, formatted using kebab-case + +| {method_name} +| The name of the test method, formatted using snake_case + +| {step} +| The count of calls to MockMvc.perform in the current test +|=== + +For example, `document("{method-name}")` in a test method named `creatingANote` will write +snippets into a directory named `creating-a-note`. + +The `{step}` parameter is particularly useful in combination with Spring MVC Test's +`alwaysDo` functionality. It allows documentation to be configured once in a setup method: + +[source,java,indent=0] +---- +include::{examples-dir}/com/example/AlwaysDo.java[tags=always-do] +---- + +With this configuration in place, every call to `MockMvc.perform` will produce +the <> without any further +configuration. Take a look at the `GettingStartedDocumentation` classes in each of the +sample applications to see this functionality in action. \ No newline at end of file diff --git a/docs/src/docs/asciidoc/getting-started.adoc b/docs/src/docs/asciidoc/getting-started.adoc new file mode 100644 index 00000000..614036d6 --- /dev/null +++ b/docs/src/docs/asciidoc/getting-started.adoc @@ -0,0 +1,219 @@ +[[getting-started]] +== Getting started + +This section describes how to get started with Spring REST docs. + + + +[[getting-started-sample-applications]] +=== Sample applications + +If you want to jump straight in, there are two sample applications available. +{hateoas-sample}[One sample uses Spring HATEOAS] and {data-rest-sample}[the other uses +Spring Data REST]. Both samples use Spring REST Docs to produce a detailed API guide +and a getting started walkthrough. You can use either Gradle or Maven to build them. + +In each sample the source for the documentation can be found in `src/main/asciidoc`. +`api-guide.adoc` produces an API guide for the service. `getting-started-guide.adoc` +produces a getting started guide that provides an introductory walkthrough. + +The code that produces the generated snippets can be found in `src/test/java`. +`ApiDocumentation.java` produces the snippets for the API guide. +`GettingStartedDocumentation.java` produces the snippets for the getting started guide. + + + +[[getting-started-build-configuration]] +=== Build configuration + +The first step in using Spring REST Docs is to configure your project's build. + + + +[[getting-started-build-configuration-gradle]] +==== Gradle build configuration + +Both {samples}[sample applications] contain `build.gradle` files that you may wish to +use as a reference. The key parts of the configuration are described below. + + + +[source,groovy,indent=0] +---- + plugins { <1> + id "org.asciidoctor.convert" version "1.5.2" + } + + dependencies { <2> + testCompile 'org.springframework.restdocs:spring-restdocs:0.1.0.BUILD-SNAPSHOT' + } + + ext { <3> + snippetsDir = file('build/generated-snippets') + } + + test { <4> + systemProperty 'org.springframework.restdocs.outputDir', snippetsDir + outputs.dir snippetsDir + } + + asciidoctor { <5> + attributes 'snippets': snippetsDir + inputs.dir snippetsDir + dependsOn test + } +---- +<1> Apply the Asciidoctor plugin +<2> Add a dependency on spring-restdocs in the `testCompile` configuration: +<3> Configure a property to define the output location for generated snippets: +<4> Configure the `test` task with the `org.springframework.restdocs.outputDir` system +property. This property controls the location into which Spring REST Docs will write the +snippets that it generates. +<5> Configure the `asciidoctor` task and define an attribute named `snippets`. You can +then use this attribute when including the generated snippets in your documentation. + + + +[[getting-started-build-configuration-maven]] +==== Maven build configuration + +Both {samples}[sample applications] contain `pom.xml` files that you may wish to +use as a reference. The key parts of the configuration are described below. + + + +[source,xml,indent=0] +---- + <1> + org.springframework.restdocs + spring-restdocs + 0.1.0.BUILD-SNAPSHOT + test + + + <2> + ${project.build.directory}/generated-snippets + + + + + <3> + org.apache.maven.plugins + maven-surefire-plugin + + + **/*Documentation.java + + + + ${snippetsDirectory} + + + + + <4> + org.asciidoctor + asciidoctor-maven-plugin + 1.5.2 + + + generate-docs + package + + process-asciidoc + + + html + book + + ${snippetsDirectory} + + + + + + + + +---- +<1> Add a dependency on `spring-restdocs` in the `test` scope +<2> Configure a property to define the output location for generated snippets +<3> Configure the SureFire plugin with the `org.springframework.restdocs.outputDir` system +property. This property controls the location into which Spring REST docs will write the +snippets that it generates. The plugin is also configured to include files whose names end +with `Documentation.java`: +<4> Configure the Asciidoctor plugin and define an attribute named `snippets`. You can +then use this attribute when including the generated snippets in your documentation. + + +[[getting-started-documentation-snippets]] +=== Generating documentation snippets +Spring REST Docs uses {spring-mvc-test-docs}[Spring's MVC Test] to make requests to the +service that you are documenting. It then produces documentation snippets for the +result's request and response. + + + +[[getting-started-documentation-snippets-setup]] +==== Setting up Spring MVC test + +The first step in generating documentation snippets is to provide an `@Before` method +that creates a `MockMvc` instance: + +[source,java,indent=0] +---- +include::{examples-dir}com/example/ExampleApplicationTests.java[tags=mock-mvc-setup] +---- + +The `MockMvc` instance is configured using a `RestDocumentationConfigurer`. An instance +of this class can be obtained from the static `documentationConfiguration()` method on +`org.springframework.restdocs.RestDocumentation`. `RestDocumentationConfigurer` applies +sensible defaults and also provides an API for customizing the configuration. Refer to the +<> for more information. + + + +[[getting-started-documentation-snippets-setup]] +==== Invoking the RESTful service + +Now that a `MockMvc` instance has been created, it can be used to invoke the RESTful +service and document the request and response. + +[source,java,indent=0] +---- +include::{examples-dir}com/example/InvokeService.java[tags=invoke-service] +---- +<1> Invoke the root (`/`) of the service an indicate that an `application/json` response +is required +<2> Assert that the service is produced the expected response +<3> Document the call to the service, writing the snippets into a directory named `index` +that will be located beneath the configured output directory. The snippets are written by +a `RestDocumentationResultHandler`. An instance of this class can be obtained from the +static `document` method on `org.springframework.restdocs.RestDocumentation`. + +By default, three snippets a written: + + * `/index/curl-request.adoc` + * `/index/http-request.adoc` + * `/index/http-response.adoc` + +Refer to <> for more information about these and other snippets +that can be produced by Spring REST Docs. + + + +[[getting-started-using-the-snippets]] +=== Using the snippets + +The generated snippets can be included in your documentation using the +http://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#include-files[include macro]. +The `snippets` attribute specified in the <> can be used to reference the snippets output directory, for example: + +[source,adoc,indent=0] +.... +[source,bash] +---- +\include::{snippets}/index/curl-request.adoc[] +---- +.... diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc new file mode 100644 index 00000000..f5f245c9 --- /dev/null +++ b/docs/src/docs/asciidoc/index.adoc @@ -0,0 +1,23 @@ += Spring REST Docs +Andy Wilkinson +:doctype: book +:toc: left +:toclevels: 3 +:source-highlighter: highlightjs +:spring-mvc-test-docs: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#spring-mvc-test-framework +:samples: https://github.com/spring-projects/spring-restdocs/tree/master/samples +:hateoas-sample: https://github.com/spring-projects/spring-restdocs/tree/master/samples/rest-notes-spring-hateoas +:data-rest-sample: https://github.com/spring-projects/spring-restdocs/tree/master/samples/rest-notes-spring-data-rest +:examples-dir: ../../test/java/ +:icons: font + +[[abstract]] + +Document RESTful services by combining hand-written documentation with auto-generated +snippets produced with Spring MVC Test. + +include::introduction.adoc[] +include::getting-started.adoc[] +include::documenting-your-api.adoc[] +include::customizing-responses.adoc[] +include::configuration.adoc[] \ No newline at end of file diff --git a/docs/src/docs/asciidoc/introduction.adoc b/docs/src/docs/asciidoc/introduction.adoc new file mode 100644 index 00000000..8fceeb5e --- /dev/null +++ b/docs/src/docs/asciidoc/introduction.adoc @@ -0,0 +1,23 @@ +[[introduction]] +== Introduction + +The aim of Spring REST Docs is to help you to produce documentation for your RESTful +services that is accurate and readable. + +Writing high-quality documentation is difficult. One way to ease that difficulty is to use +tools that are well-suited to the job. To this end, Spring REST Docs uses +http://asciidoctor.org[Asciidoctor]. Asciidoctor processes plain text and produces +HTML styled and layed out to suit your needs. + +Spring REST Docs makes use of snippets produced by tests written with +{spring-mvc-test-docs}[Spring MVC Test]. This test-driven approach helps to guarantee the +accuracy of your service's documentation. If a snippet is incorrect the test that +produces it will fail. + +Documenting a RESTful service is largely about describing its resources. Two key parts +of each resource's description are the details of the HTTP requests that it consumes +and the HTTP responses that it produces. Spring REST Docs allows you to work with these +resources and the HTTP requests and responses, shielding your documentation +from the inner-details of your service's implementation. This separation helps you to +document your service's API rather than its implementation. It also frees you to evolve +the implementation without having to rework the documentation. \ No newline at end of file diff --git a/docs/src/test/java/com/example/AlwaysDo.java b/docs/src/test/java/com/example/AlwaysDo.java new file mode 100644 index 00000000..9962b217 --- /dev/null +++ b/docs/src/test/java/com/example/AlwaysDo.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014-2015 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 + * + * http://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 com.example; + +import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; + +import org.junit.Before; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +public class AlwaysDo { + + private MockMvc mockMvc; + + private WebApplicationContext context; + + // tag::always-do[] + @Before + public void setUp() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(documentationConfiguration()) + .alwaysDo(document("{method-name}/{step}/")) + .build(); + } + // end::always-do[] +} diff --git a/docs/src/test/java/com/example/CustomEncoding.java b/docs/src/test/java/com/example/CustomEncoding.java new file mode 100644 index 00000000..997ab9c3 --- /dev/null +++ b/docs/src/test/java/com/example/CustomEncoding.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014-2015 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 + * + * http://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 com.example; + +import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; + +import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +public class CustomEncoding { + + @Autowired + private WebApplicationContext context; + + private MockMvc mockMvc; + + @Before + public void setUp() { + // tag::custom-encoding[] + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(documentationConfiguration().snippets() + .withEncoding("ISO-8859-1")) + .build(); + // end::custom-encoding[] + } + +} diff --git a/docs/src/test/java/com/example/CustomUriConfiguration.java b/docs/src/test/java/com/example/CustomUriConfiguration.java new file mode 100644 index 00000000..845481d4 --- /dev/null +++ b/docs/src/test/java/com/example/CustomUriConfiguration.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014-2015 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 + * + * http://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 com.example; + +import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; + +import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +public class CustomUriConfiguration { + + @Autowired + private WebApplicationContext context; + + private MockMvc mockMvc; + + @Before + public void setUp() { + // tag::custom-uri-configuration[] + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(documentationConfiguration().uris() + .withScheme("https") + .withHost("example.com") + .withPort(443) + .withContextPath("/api")) + .build(); + // end::custom-uri-configuration[] + } + +} diff --git a/docs/src/test/java/com/example/ExampleApplicationTests.java b/docs/src/test/java/com/example/ExampleApplicationTests.java new file mode 100644 index 00000000..2ac5f6b9 --- /dev/null +++ b/docs/src/test/java/com/example/ExampleApplicationTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014-2015 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 + * + * http://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 com.example; + +import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.restdocs.RestDocumentation.documentationConfiguration; + +public class ExampleApplicationTests { + + // tag::mock-mvc-setup[] + @Autowired + private WebApplicationContext context; + + private MockMvc mockMvc; + + @Before + public void setUp() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(documentationConfiguration()) + .build(); + } + // end::mock-mvc-setup[] +} \ No newline at end of file diff --git a/docs/src/test/java/com/example/Hypermedia.java b/docs/src/test/java/com/example/Hypermedia.java new file mode 100644 index 00000000..8c69f2db --- /dev/null +++ b/docs/src/test/java/com/example/Hypermedia.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014-2015 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 + * + * http://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 com.example; + +import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.LinkExtractors.halLinks; + +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +public class Hypermedia { + + private MockMvc mockMvc; + + public void links() throws Exception { + // tag::links[] + this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(document("index").withLinks( // <1> + linkWithRel("alpha").description("Link to the alpha resource"), // <2> + linkWithRel("bravo").description("Link to the bravo resource"))); // <3> + // end::links[] + } + + public void explicitExtractor() throws Exception { + this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + //tag::explicit-extractor[] + .andDo(document("index").withLinks(halLinks(), // <1> + linkWithRel("alpha").description("Link to the alpha resource"), + linkWithRel("bravo").description("Link to the bravo resource"))); + // end::explicit-extractor[] + } + + +} diff --git a/docs/src/test/java/com/example/InvokeService.java b/docs/src/test/java/com/example/InvokeService.java new file mode 100644 index 00000000..7a863eab --- /dev/null +++ b/docs/src/test/java/com/example/InvokeService.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014-2015 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 + * + * http://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 com.example; + +import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +public class InvokeService { + + private MockMvc mockMvc; + + public void invokeService() throws Exception { + // tag::invoke-service[] + this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) // <1> + .andExpect(status().isOk()) // <2> + .andDo(document("index")); // <3> + // end::invoke-service[] + } + +} diff --git a/docs/src/test/java/com/example/Payload.java b/docs/src/test/java/com/example/Payload.java new file mode 100644 index 00000000..ddfd7579 --- /dev/null +++ b/docs/src/test/java/com/example/Payload.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014-2015 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 + * + * http://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 com.example; + +import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.FieldType; +import org.springframework.test.web.servlet.MockMvc; + +public class Payload { + +private MockMvc mockMvc; + + public void response() throws Exception { + // tag::response[] + this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(document("index").withResponseFields( // <1> + fieldWithPath("contact").description("The user's contact details"), // <2> + fieldWithPath("contact.email").description("The user's email address"))); // <3> + // end::response[] + } + + public void explicitType() throws Exception { + this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + // tag::explicit-type[] + .andDo(document("index").withResponseFields( + fieldWithPath("contact.email") + .type(FieldType.STRING) // <1> + .optional() + .description("The user's email address"))); + // end::explicit-type[] + } + +} diff --git a/docs/src/test/java/com/example/QueryParameters.java b/docs/src/test/java/com/example/QueryParameters.java new file mode 100644 index 00000000..682d2387 --- /dev/null +++ b/docs/src/test/java/com/example/QueryParameters.java @@ -0,0 +1,41 @@ +/* + * Copyright 2014-2015 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 + * + * http://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 com.example; + +import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.springframework.test.web.servlet.MockMvc; + +public class QueryParameters { + + private MockMvc mockMvc; + + public void queryParameters() throws Exception { + // tag::query-parameters[] + this.mockMvc.perform(get("/users?page=2&per_page=100")) + .andExpect(status().isOk()) + .andDo(document("users").withQueryParameters( // <1> + parameterWithName("page").description("The page to retrieve"), // <2> + parameterWithName("per_page").description("Entries per page") // <3> + )); + // end::query-parameters[] + } + +} diff --git a/docs/src/test/java/com/example/ResponsePostProcessing.java b/docs/src/test/java/com/example/ResponsePostProcessing.java new file mode 100644 index 00000000..605b4534 --- /dev/null +++ b/docs/src/test/java/com/example/ResponsePostProcessing.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014-2015 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 + * + * http://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 com.example; + +import static org.springframework.restdocs.RestDocumentation.modifyResponseTo; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.springframework.test.web.servlet.MockMvc; + +public class ResponsePostProcessing { + + private MockMvc mockMvc; + + public void general() throws Exception { + // tag::general[] + this.mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andDo(modifyResponseTo(/* ... */) // <1> + .andDocument("index")); // <2> + // end::general[] + } + +} diff --git a/samples/rest-notes-spring-data-rest/build.gradle b/samples/rest-notes-spring-data-rest/build.gradle index 0acf3af5..faa47de7 100644 --- a/samples/rest-notes-spring-data-rest/build.gradle +++ b/samples/rest-notes-spring-data-rest/build.gradle @@ -39,18 +39,18 @@ dependencies { } ext { - generatedDocumentation = file('build/generated-snippets') + snippetsDir = file('build/generated-snippets') } test { - systemProperty 'org.springframework.restdocs.outputDir', generatedDocumentation - outputs.dir generatedDocumentation + systemProperty 'org.springframework.restdocs.outputDir', snippetsDir + outputs.dir snippetsDir } asciidoctor { sourceDir 'src/main/asciidoc' // Align with Maven's default location - attributes 'generated': generatedDocumentation - inputs.dir generatedDocumentation + attributes 'snippets': snippetsDir + inputs.dir snippetsDir dependsOn test } diff --git a/samples/rest-notes-spring-data-rest/pom.xml b/samples/rest-notes-spring-data-rest/pom.xml index a12bcf06..9ccf53f5 100644 --- a/samples/rest-notes-spring-data-rest/pom.xml +++ b/samples/rest-notes-spring-data-rest/pom.xml @@ -18,6 +18,7 @@ UTF-8 1.7 + ${project.build.directory}/generated-snippets @@ -68,7 +69,7 @@ **/*Documentation.java - ${project.build.directory}/generated-snippets + ${snippetsDirectory} @@ -87,7 +88,7 @@ html book - ${project.build.directory}/generated-snippets + ${snippetsDirectory} diff --git a/samples/rest-notes-spring-data-rest/src/main/asciidoc/api-guide.adoc b/samples/rest-notes-spring-data-rest/src/main/asciidoc/api-guide.adoc index 9b14b377..58cb3ad5 100644 --- a/samples/rest-notes-spring-data-rest/src/main/asciidoc/api-guide.adoc +++ b/samples/rest-notes-spring-data-rest/src/main/asciidoc/api-guide.adoc @@ -64,12 +64,12 @@ use of HTTP status codes. Whenever an error response (status code >= 400) is returned, the body will contain a JSON object that describes the problem. The error object has the following structure: -include::{generated}/error-example/response-fields.adoc[] +include::{snippets}/error-example/response-fields.adoc[] For example, a request that attempts to apply a non-existent tag to a note will produce a `400 Bad Request` response: -include::{generated}/error-example/http-response.adoc[] +include::{snippets}/error-example/http-response.adoc[] [[overview-hypermedia]] == Hypermedia @@ -99,18 +99,18 @@ A `GET` request is used to access the index ==== Response structure -include::{generated}/index-example/response-fields.adoc[] +include::{snippets}/index-example/response-fields.adoc[] ==== Example response -include::{generated}/index-example/http-response.adoc[] +include::{snippets}/index-example/http-response.adoc[] [[resources-index-links]] ==== Links -include::{generated}/index-example/links.adoc[] +include::{snippets}/index-example/links.adoc[] @@ -128,15 +128,15 @@ A `GET` request will list all of the service's notes. ==== Response structure -include::{generated}/notes-list-example/response-fields.adoc[] +include::{snippets}/notes-list-example/response-fields.adoc[] ==== Example request -include::{generated}/notes-list-example/curl-request.adoc[] +include::{snippets}/notes-list-example/curl-request.adoc[] ==== Example response -include::{generated}/notes-list-example/http-response.adoc[] +include::{snippets}/notes-list-example/http-response.adoc[] @@ -147,15 +147,15 @@ A `POST` request is used to create a note ==== Request structure -include::{generated}/notes-create-example/request-fields.adoc[] +include::{snippets}/notes-create-example/request-fields.adoc[] ==== Example request -include::{generated}/notes-create-example/curl-request.adoc[] +include::{snippets}/notes-create-example/curl-request.adoc[] ==== Example response -include::{generated}/notes-create-example/http-response.adoc[] +include::{snippets}/notes-create-example/http-response.adoc[] @@ -173,15 +173,15 @@ A `GET` request will list all of the service's tags. ==== Response structure -include::{generated}/tags-list-example/response-fields.adoc[] +include::{snippets}/tags-list-example/response-fields.adoc[] ==== Example request -include::{generated}/tags-list-example/curl-request.adoc[] +include::{snippets}/tags-list-example/curl-request.adoc[] ==== Example response -include::{generated}/tags-list-example/http-response.adoc[] +include::{snippets}/tags-list-example/http-response.adoc[] @@ -192,15 +192,15 @@ A `POST` request is used to create a note ==== Request structure -include::{generated}/tags-create-example/request-fields.adoc[] +include::{snippets}/tags-create-example/request-fields.adoc[] ==== Example request -include::{generated}/tags-create-example/curl-request.adoc[] +include::{snippets}/tags-create-example/curl-request.adoc[] ==== Example response -include::{generated}/tags-create-example/http-response.adoc[] +include::{snippets}/tags-create-example/http-response.adoc[] @@ -214,7 +214,7 @@ The Note resource is used to retrieve, update, and delete individual notes [[resources-note-links]] === Links -include::{generated}/note-get-example/links.adoc[] +include::{snippets}/note-get-example/links.adoc[] @@ -225,15 +225,15 @@ A `GET` request will retrieve the details of a note ==== Response structure -include::{generated}/note-get-example/response-fields.adoc[] +include::{snippets}/note-get-example/response-fields.adoc[] ==== Example request -include::{generated}/note-get-example/curl-request.adoc[] +include::{snippets}/note-get-example/curl-request.adoc[] ==== Example response -include::{generated}/note-get-example/http-response.adoc[] +include::{snippets}/note-get-example/http-response.adoc[] @@ -244,17 +244,17 @@ A `PATCH` request is used to update a note ==== Request structure -include::{generated}/note-update-example/request-fields.adoc[] +include::{snippets}/note-update-example/request-fields.adoc[] To leave an attribute of a note unchanged, any of the above may be omitted from the request. ==== Example request -include::{generated}/note-update-example/curl-request.adoc[] +include::{snippets}/note-update-example/curl-request.adoc[] ==== Example response -include::{generated}/note-update-example/http-response.adoc[] +include::{snippets}/note-update-example/http-response.adoc[] @@ -268,7 +268,7 @@ The Tag resource is used to retrieve, update, and delete individual tags [[resources-tag-links]] === Links -include::{generated}/tag-get-example/links.adoc[] +include::{snippets}/tag-get-example/links.adoc[] @@ -279,15 +279,15 @@ A `GET` request will retrieve the details of a tag ==== Response structure -include::{generated}/tag-get-example/response-fields.adoc[] +include::{snippets}/tag-get-example/response-fields.adoc[] ==== Example request -include::{generated}/tag-get-example/curl-request.adoc[] +include::{snippets}/tag-get-example/curl-request.adoc[] ==== Example response -include::{generated}/tag-get-example/http-response.adoc[] +include::{snippets}/tag-get-example/http-response.adoc[] @@ -298,12 +298,12 @@ A `PATCH` request is used to update a tag ==== Request structure -include::{generated}/tag-update-example/request-fields.adoc[] +include::{snippets}/tag-update-example/request-fields.adoc[] ==== Example request -include::{generated}/tag-update-example/curl-request.adoc[] +include::{snippets}/tag-update-example/curl-request.adoc[] ==== Example response -include::{generated}/tag-update-example/http-response.adoc[] +include::{snippets}/tag-update-example/http-response.adoc[] diff --git a/samples/rest-notes-spring-data-rest/src/main/asciidoc/getting-started-guide.adoc b/samples/rest-notes-spring-data-rest/src/main/asciidoc/getting-started-guide.adoc index f9d9788c..a6678bb5 100644 --- a/samples/rest-notes-spring-data-rest/src/main/asciidoc/getting-started-guide.adoc +++ b/samples/rest-notes-spring-data-rest/src/main/asciidoc/getting-started-guide.adoc @@ -42,12 +42,12 @@ $ java -jar build/libs/*.jar You can check that the service is up and running by executing a simple request using cURL: -include::{generated}/index/1/curl-request.adoc[] +include::{snippets}/index/1/curl-request.adoc[] This request should yield the following response in the http://stateless.co/hal_specification.html[Hypertext Application Language (HAL)] format: -include::{generated}/index/1/http-response.adoc[] +include::{snippets}/index/1/http-response.adoc[] Note the `_links` in the JSON response. They are key to navigating the API. @@ -59,26 +59,26 @@ Now that you've started the service and verified that it works, the next step is it to create a new note. As you saw above, the URI for working with notes is included as a link when you perform a `GET` request against the root of the service: -include::{generated}/index/1/http-response.adoc[] +include::{snippets}/index/1/http-response.adoc[] To create a note, you need to execute a `POST` request to this URI including a JSON payload containing the title and body of the note: -include::{generated}/creating-a-note/1/curl-request.adoc[] +include::{snippets}/creating-a-note/1/curl-request.adoc[] The response from this request should have a status code of `201 Created` and contain a `Location` header whose value is the URI of the newly created note: -include::{generated}/creating-a-note/1/http-response.adoc[] +include::{snippets}/creating-a-note/1/http-response.adoc[] To work with the newly created note you use the URI in the `Location` header. For example, you can access the note's details by performing a `GET` request: -include::{generated}/creating-a-note/2/curl-request.adoc[] +include::{snippets}/creating-a-note/2/curl-request.adoc[] This request will produce a response with the note's details in its body: -include::{generated}/creating-a-note/2/http-response.adoc[] +include::{snippets}/creating-a-note/2/http-response.adoc[] Note the `tags` link which we'll make use of later. @@ -92,26 +92,26 @@ to tag a note, you must first create the tag. Referring back to the response for the service's index, the URI for working with tags is include as a link: -include::{generated}/index/1/http-response.adoc[] +include::{snippets}/index/1/http-response.adoc[] To create a tag you need to execute a `POST` request to this URI, including a JSON payload containing the name of the tag: -include::{generated}/creating-a-note/3/curl-request.adoc[] +include::{snippets}/creating-a-note/3/curl-request.adoc[] The response from this request should have a status code of `201 Created` and contain a `Location` header whose value is the URI of the newly created tag: -include::{generated}/creating-a-note/3/http-response.adoc[] +include::{snippets}/creating-a-note/3/http-response.adoc[] To work with the newly created tag you use the URI in the `Location` header. For example you can access the tag's details by performing a `GET` request: -include::{generated}/creating-a-note/4/curl-request.adoc[] +include::{snippets}/creating-a-note/4/curl-request.adoc[] This request will produce a response with the tag's details in its body: -include::{generated}/creating-a-note/4/http-response.adoc[] +include::{snippets}/creating-a-note/4/http-response.adoc[] @@ -132,25 +132,25 @@ with it. Once again we execute a `POST` request. However, this time, in an array named tags, we include the URI of the tag we just created: -include::{generated}/creating-a-note/5/curl-request.adoc[] +include::{snippets}/creating-a-note/5/curl-request.adoc[] Once again, the response's `Location` header tells us the URI of the newly created note: -include::{generated}/creating-a-note/5/http-response.adoc[] +include::{snippets}/creating-a-note/5/http-response.adoc[] As before, a `GET` request executed against this URI will retrieve the note's details: -include::{generated}/creating-a-note/6/curl-request.adoc[] -include::{generated}/creating-a-note/6/http-response.adoc[] +include::{snippets}/creating-a-note/6/curl-request.adoc[] +include::{snippets}/creating-a-note/6/http-response.adoc[] To verify that the tag has been associated with the note, we can perform a `GET` request against the URI from the `tags` link: -include::{generated}/creating-a-note/7/curl-request.adoc[] +include::{snippets}/creating-a-note/7/curl-request.adoc[] The response embeds information about the tag that we've just associated with the note: -include::{generated}/creating-a-note/7/http-response.adoc[] +include::{snippets}/creating-a-note/7/http-response.adoc[] @@ -160,18 +160,18 @@ An existing note can be tagged by executing a `PATCH` request against the note's a body that contains the array of tags to be associated with the note. We'll used the URI of the untagged note that we created earlier: -include::{generated}/creating-a-note/8/curl-request.adoc[] +include::{snippets}/creating-a-note/8/curl-request.adoc[] This request should produce a `204 No Content` response: -include::{generated}/creating-a-note/8/http-response.adoc[] +include::{snippets}/creating-a-note/8/http-response.adoc[] When we first created this note, we noted the tags link included in its details: -include::{generated}/creating-a-note/2/http-response.adoc[] +include::{snippets}/creating-a-note/2/http-response.adoc[] We can use that link now and execute a `GET` request to see that the note now has a single tag: -include::{generated}/creating-a-note/9/curl-request.adoc[] -include::{generated}/creating-a-note/9/http-response.adoc[] +include::{snippets}/creating-a-note/9/curl-request.adoc[] +include::{snippets}/creating-a-note/9/http-response.adoc[] diff --git a/samples/rest-notes-spring-hateoas/build.gradle b/samples/rest-notes-spring-hateoas/build.gradle index d8b78d53..f1a6120c 100644 --- a/samples/rest-notes-spring-hateoas/build.gradle +++ b/samples/rest-notes-spring-hateoas/build.gradle @@ -41,18 +41,18 @@ dependencies { } ext { - generatedDocumentation = file('build/generated-snippets') + snippetsDir = file('build/generated-snippets') } test { - systemProperty 'org.springframework.restdocs.outputDir', generatedDocumentation - outputs.dir generatedDocumentation + systemProperty 'org.springframework.restdocs.outputDir', snippetsDir + outputs.dir snippetsDir } asciidoctor { sourceDir 'src/main/asciidoc' // Align with Maven's default location - attributes 'generated': generatedDocumentation - inputs.dir generatedDocumentation + attributes 'snippets': snippetsDir + inputs.dir snippetsDir dependsOn test } diff --git a/samples/rest-notes-spring-hateoas/pom.xml b/samples/rest-notes-spring-hateoas/pom.xml index 7484e485..52127a16 100644 --- a/samples/rest-notes-spring-hateoas/pom.xml +++ b/samples/rest-notes-spring-hateoas/pom.xml @@ -18,6 +18,7 @@ UTF-8 1.7 + ${project.build.directory}/generated-snippets @@ -84,7 +85,7 @@ **/*Documentation.java - ${project.build.directory}/generated-snippets + ${snippetsDirectory} @@ -103,7 +104,7 @@ html book - ${project.build.directory}/generated-snippets + ${snippetsDirectory} diff --git a/samples/rest-notes-spring-hateoas/src/main/asciidoc/api-guide.adoc b/samples/rest-notes-spring-hateoas/src/main/asciidoc/api-guide.adoc index 71227a70..bf6c25ea 100644 --- a/samples/rest-notes-spring-hateoas/src/main/asciidoc/api-guide.adoc +++ b/samples/rest-notes-spring-hateoas/src/main/asciidoc/api-guide.adoc @@ -64,12 +64,12 @@ use of HTTP status codes. Whenever an error response (status code >= 400) is returned, the body will contain a JSON object that describes the problem. The error object has the following structure: -include::{generated}/error-example/response-fields.adoc[] +include::{snippets}/error-example/response-fields.adoc[] For example, a request that attempts to apply a non-existent tag to a note will produce a `400 Bad Request` response: -include::{generated}/error-example/http-response.adoc[] +include::{snippets}/error-example/http-response.adoc[] [[overview-hypermedia]] == Hypermedia @@ -99,18 +99,18 @@ A `GET` request is used to access the index ==== Response structure -include::{generated}/index-example/response-fields.adoc[] +include::{snippets}/index-example/response-fields.adoc[] ==== Example response -include::{generated}/index-example/http-response.adoc[] +include::{snippets}/index-example/http-response.adoc[] [[resources-index-links]] ==== Links -include::{generated}/index-example/links.adoc[] +include::{snippets}/index-example/links.adoc[] @@ -128,15 +128,15 @@ A `GET` request will list all of the service's notes. ==== Response structure -include::{generated}/notes-list-example/response-fields.adoc[] +include::{snippets}/notes-list-example/response-fields.adoc[] ==== Example request -include::{generated}/notes-list-example/curl-request.adoc[] +include::{snippets}/notes-list-example/curl-request.adoc[] ==== Example response -include::{generated}/notes-list-example/http-response.adoc[] +include::{snippets}/notes-list-example/http-response.adoc[] @@ -147,15 +147,15 @@ A `POST` request is used to create a note ==== Request structure -include::{generated}/notes-create-example/request-fields.adoc[] +include::{snippets}/notes-create-example/request-fields.adoc[] ==== Example request -include::{generated}/notes-create-example/curl-request.adoc[] +include::{snippets}/notes-create-example/curl-request.adoc[] ==== Example response -include::{generated}/notes-create-example/http-response.adoc[] +include::{snippets}/notes-create-example/http-response.adoc[] @@ -173,15 +173,15 @@ A `GET` request will list all of the service's tags. ==== Response structure -include::{generated}/tags-list-example/response-fields.adoc[] +include::{snippets}/tags-list-example/response-fields.adoc[] ==== Example request -include::{generated}/tags-list-example/curl-request.adoc[] +include::{snippets}/tags-list-example/curl-request.adoc[] ==== Example response -include::{generated}/tags-list-example/http-response.adoc[] +include::{snippets}/tags-list-example/http-response.adoc[] @@ -192,15 +192,15 @@ A `POST` request is used to create a note ==== Request structure -include::{generated}/tags-create-example/request-fields.adoc[] +include::{snippets}/tags-create-example/request-fields.adoc[] ==== Example request -include::{generated}/tags-create-example/curl-request.adoc[] +include::{snippets}/tags-create-example/curl-request.adoc[] ==== Example response -include::{generated}/tags-create-example/http-response.adoc[] +include::{snippets}/tags-create-example/http-response.adoc[] @@ -214,7 +214,7 @@ The Note resource is used to retrieve, update, and delete individual notes [[resources-note-links]] === Links -include::{generated}/note-get-example/links.adoc[] +include::{snippets}/note-get-example/links.adoc[] @@ -225,15 +225,15 @@ A `GET` request will retrieve the details of a note ==== Response structure -include::{generated}/note-get-example/response-fields.adoc[] +include::{snippets}/note-get-example/response-fields.adoc[] ==== Example request -include::{generated}/note-get-example/curl-request.adoc[] +include::{snippets}/note-get-example/curl-request.adoc[] ==== Example response -include::{generated}/note-get-example/http-response.adoc[] +include::{snippets}/note-get-example/http-response.adoc[] @@ -244,17 +244,17 @@ A `PATCH` request is used to update a note ==== Request structure -include::{generated}/note-update-example/request-fields.adoc[] +include::{snippets}/note-update-example/request-fields.adoc[] To leave an attribute of a note unchanged, any of the above may be omitted from the request. ==== Example request -include::{generated}/note-update-example/curl-request.adoc[] +include::{snippets}/note-update-example/curl-request.adoc[] ==== Example response -include::{generated}/note-update-example/http-response.adoc[] +include::{snippets}/note-update-example/http-response.adoc[] [[resources-tag]] @@ -267,7 +267,7 @@ The Tag resource is used to retrieve, update, and delete individual tags [[resources-tag-links]] === Links -include::{generated}/tag-get-example/links.adoc[] +include::{snippets}/tag-get-example/links.adoc[] @@ -278,15 +278,15 @@ A `GET` request will retrieve the details of a tag ==== Response structure -include::{generated}/tag-get-example/response-fields.adoc[] +include::{snippets}/tag-get-example/response-fields.adoc[] ==== Example request -include::{generated}/tag-get-example/curl-request.adoc[] +include::{snippets}/tag-get-example/curl-request.adoc[] ==== Example response -include::{generated}/tag-get-example/http-response.adoc[] +include::{snippets}/tag-get-example/http-response.adoc[] @@ -297,12 +297,12 @@ A `PATCH` request is used to update a tag ==== Request structure -include::{generated}/tag-update-example/request-fields.adoc[] +include::{snippets}/tag-update-example/request-fields.adoc[] ==== Example request -include::{generated}/tag-update-example/curl-request.adoc[] +include::{snippets}/tag-update-example/curl-request.adoc[] ==== Example response -include::{generated}/tag-update-example/http-response.adoc[] +include::{snippets}/tag-update-example/http-response.adoc[] diff --git a/samples/rest-notes-spring-hateoas/src/main/asciidoc/getting-started-guide.adoc b/samples/rest-notes-spring-hateoas/src/main/asciidoc/getting-started-guide.adoc index 93b37296..676a4615 100644 --- a/samples/rest-notes-spring-hateoas/src/main/asciidoc/getting-started-guide.adoc +++ b/samples/rest-notes-spring-hateoas/src/main/asciidoc/getting-started-guide.adoc @@ -42,11 +42,11 @@ $ java -jar build/libs/*.jar You can check that the service is up and running by executing a simple request using cURL: -include::{generated}/index/1/curl-request.adoc[] +include::{snippets}/index/1/curl-request.adoc[] This request should yield the following response: -include::{generated}/index/1/http-response.adoc[] +include::{snippets}/index/1/http-response.adoc[] Note the `_links` in the JSON response. They are key to navigating the API. @@ -58,26 +58,26 @@ Now that you've started the service and verified that it works, the next step is it to create a new note. As you saw above, the URI for working with notes is included as a link when you perform a `GET` request against the root of the service: -include::{generated}/index/1/http-response.adoc[] +include::{snippets}/index/1/http-response.adoc[] To create a note you need to execute a `POST` request to this URI, including a JSON payload containing the title and body of the note: -include::{generated}/creating-a-note/1/curl-request.adoc[] +include::{snippets}/creating-a-note/1/curl-request.adoc[] The response from this request should have a status code of `201 Created` and contain a `Location` header whose value is the URI of the newly created note: -include::{generated}/creating-a-note/1/http-response.adoc[] +include::{snippets}/creating-a-note/1/http-response.adoc[] To work with the newly created note you use the URI in the `Location` header. For example you can access the note's details by performing a `GET` request: -include::{generated}/creating-a-note/2/curl-request.adoc[] +include::{snippets}/creating-a-note/2/curl-request.adoc[] This request will produce a response with the note's details in its body: -include::{generated}/creating-a-note/2/http-response.adoc[] +include::{snippets}/creating-a-note/2/http-response.adoc[] Note the `note-tags` link which we'll make use of later. @@ -91,26 +91,26 @@ to tag a note, you must first create the tag. Referring back to the response for the service's index, the URI for working with tags is include as a link: -include::{generated}/index/1/http-response.adoc[] +include::{snippets}/index/1/http-response.adoc[] To create a tag you need to execute a `POST` request to this URI, including a JSON payload containing the name of the tag: -include::{generated}/creating-a-note/3/curl-request.adoc[] +include::{snippets}/creating-a-note/3/curl-request.adoc[] The response from this request should have a status code of `201 Created` and contain a `Location` header whose value is the URI of the newly created tag: -include::{generated}/creating-a-note/3/http-response.adoc[] +include::{snippets}/creating-a-note/3/http-response.adoc[] To work with the newly created tag you use the URI in the `Location` header. For example you can access the tag's details by performing a `GET` request: -include::{generated}/creating-a-note/4/curl-request.adoc[] +include::{snippets}/creating-a-note/4/curl-request.adoc[] This request will produce a response with the tag's details in its body: -include::{generated}/creating-a-note/4/http-response.adoc[] +include::{snippets}/creating-a-note/4/http-response.adoc[] @@ -131,25 +131,25 @@ with it. Once again we execute a `POST` request, but this time, in an array named tags, we include the URI of the tag we just created: -include::{generated}/creating-a-note/5/curl-request.adoc[] +include::{snippets}/creating-a-note/5/curl-request.adoc[] Once again, the response's `Location` header tells use the URI of the newly created note: -include::{generated}/creating-a-note/5/http-response.adoc[] +include::{snippets}/creating-a-note/5/http-response.adoc[] As before, a `GET` request executed against this URI will retrieve the note's details: -include::{generated}/creating-a-note/6/curl-request.adoc[] -include::{generated}/creating-a-note/6/http-response.adoc[] +include::{snippets}/creating-a-note/6/curl-request.adoc[] +include::{snippets}/creating-a-note/6/http-response.adoc[] To see the note's tags, execute a `GET` request against the URI of the note's `note-tags` link: -include::{generated}/creating-a-note/7/curl-request.adoc[] +include::{snippets}/creating-a-note/7/curl-request.adoc[] The response shows that, as expected, the note has a single tag: -include::{generated}/creating-a-note/7/http-response.adoc[] +include::{snippets}/creating-a-note/7/http-response.adoc[] @@ -159,18 +159,18 @@ An existing note can be tagged by executing a `PATCH` request against the note's a body that contains the array of tags to be associated with the note. We'll use the URI of the untagged note that we created earlier: -include::{generated}/creating-a-note/8/curl-request.adoc[] +include::{snippets}/creating-a-note/8/curl-request.adoc[] This request should produce a `204 No Content` response: -include::{generated}/creating-a-note/8/http-response.adoc[] +include::{snippets}/creating-a-note/8/http-response.adoc[] When we first created this note, we noted the `note-tags` link included in its details: -include::{generated}/creating-a-note/2/http-response.adoc[] +include::{snippets}/creating-a-note/2/http-response.adoc[] We can use that link now and execute a `GET` request to see that the note now has a single tag: -include::{generated}/creating-a-note/9/curl-request.adoc[] -include::{generated}/creating-a-note/9/http-response.adoc[] +include::{snippets}/creating-a-note/9/curl-request.adoc[] +include::{snippets}/creating-a-note/9/http-response.adoc[] diff --git a/settings.gradle b/settings.gradle index 80e754ca..ad08f6b3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,4 @@ rootProject.name = 'spring-restdocs-build' +include 'docs' include 'spring-restdocs' \ No newline at end of file diff --git a/spring-restdocs/build.gradle b/spring-restdocs/build.gradle new file mode 100644 index 00000000..0cca8f02 --- /dev/null +++ b/spring-restdocs/build.gradle @@ -0,0 +1,89 @@ +ext { + hamcrestVersion = '1.3' + jacocoVersion = '0.7.2.201409121644' +} + +group = 'org.springframework.restdocs' + +apply plugin: 'maven' +apply plugin: 'sonar-runner' + +sourceCompatibility = 1.7 +targetCompatibility = 1.7 + +eclipseJdt.onlyIf { false } +cleanEclipseJdt.onlyIf { false } + +repositories { + jcenter() +} + +sonarRunner { + sonarProperties { + property 'sonar.jacoco.reportPath', "${buildDir.name}/jacoco.exec" + property 'sonar.java.coveragePlugin', 'jacoco' + property 'sonar.links.ci', 'https://build.spring.io/browse/SRD' + property 'sonar.links.homepage', 'https://github.com/spring-projects/spring-restdocs' + property 'sonar.links.issue', 'https://github.com/spring-projects/spring-restdocs' + property 'sonar.links.scm', 'https://github.com/spring-projects/spring-restdocs' + } +} + +configurations { + jacoco +} + +ext { + javadocLinks = [ + 'http://docs.oracle.com/javase/8/docs/api/', + "http://docs.spring.io/spring-framework/docs/$springVersion/javadoc-api/" + ] as String[] +} + +dependencies { + compile 'com.fasterxml.jackson.core:jackson-databind' + compile 'junit:junit' + compile 'org.springframework:spring-core' + compile 'org.springframework:spring-test' + compile 'org.springframework:spring-web' + compile 'org.springframework:spring-webmvc' + compile 'javax.servlet:javax.servlet-api' + jacoco 'org.jacoco:org.jacoco.agent::runtime' + testCompile 'org.springframework.hateoas:spring-hateoas' + testCompile 'org.mockito:mockito-core' + testCompile 'org.hamcrest:hamcrest-core' + testCompile 'org.hamcrest:hamcrest-library' +} + +task sourcesJar(type: Jar) { + classifier = 'sources' + from project.sourceSets.main.allSource +} + +javadoc { + description = "Generates project-level javadoc for use in -javadoc jar" + + options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED + options.author = true + options.header = "Spring REST Docs $version" + options.docTitle = "${options.header} API" + options.links(project.ext.javadocLinks) + options.addStringOption('-quiet') +} + +task javadocJar(type: Jar) { + classifier = "javadoc" + from javadoc +} + +artifacts { + archives sourcesJar + archives javadocJar +} + +test { + jvmArgs "-javaagent:${configurations.jacoco.asPath}=destfile=${buildDir}/jacoco.exec,includes=org.springframework.restdocs.*" + testLogging { + exceptionFormat "full" + } +} \ No newline at end of file