From e5db3d958eef8013c6f7039224eb1216179a6f77 Mon Sep 17 00:00:00 2001 From: Gunnar Hillert Date: Sun, 21 Aug 2016 22:32:50 -1000 Subject: [PATCH] gh-824 Add Spring Restdocs Infrastructure --- spring-cloud-dataflow-docs/pom.xml | 46 +++++- .../src/main/asciidoc/api-guide.adoc | 147 ++++++++++++++++++ .../src/main/asciidoc/index.adoc | 1 + .../rest/documentation/ApiDocumentation.java | 110 +++++++++++++ .../AppRegistryDocumentation.java | 41 +++++ .../rest/documentation/BaseDocumentation.java | 65 ++++++++ spring-cloud-dataflow-server-core/pom.xml | 8 +- spring-cloud-dataflow-server-local/pom.xml | 11 ++ .../server/local/LocalDataflowResource.java | 6 + 9 files changed, 426 insertions(+), 9 deletions(-) create mode 100644 spring-cloud-dataflow-docs/src/main/asciidoc/api-guide.adoc create mode 100644 spring-cloud-dataflow-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/ApiDocumentation.java create mode 100644 spring-cloud-dataflow-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/AppRegistryDocumentation.java create mode 100644 spring-cloud-dataflow-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/BaseDocumentation.java diff --git a/spring-cloud-dataflow-docs/pom.xml b/spring-cloud-dataflow-docs/pom.xml index 107de6072e..d6e1dffdca 100644 --- a/spring-cloud-dataflow-docs/pom.xml +++ b/spring-cloud-dataflow-docs/pom.xml @@ -53,12 +53,47 @@ org.springframework.cloud spring-cloud-starter-dataflow-server-local + + + org.springframework.cloud + spring-cloud-dataflow-server-local + ${project.version} + test-jar + test + + + org.springframework.restdocs + spring-restdocs-mockmvc + 1.1.2.RELEASE + test + + + org.springframework.restdocs + spring-restdocs-core + 1.1.2.RELEASE + test + + + com.jayway.jsonpath + json-path + test + full + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*Documentation.java + **/*Tests.java + + + org.apache.maven.plugins maven-javadoc-plugin @@ -95,7 +130,7 @@ generate-docbook - generate-resources + prepare-package process-asciidoc @@ -109,6 +144,7 @@ warn ${project.version} ${project.artifactId} + ${basedir}/target/generated-snippets @@ -158,7 +194,7 @@ generate-html - generate-resources + prepare-package ${basedir}/src/main/docbook/xsl/html-singlepage.xsl ${basedir}/target/docbook/htmlsingle @@ -182,7 +218,7 @@ generate-html - generate-resources + prepare-package ${basedir}/src/main/docbook/xsl/html-multipage.xsl ${basedir}/target/docbook/html @@ -207,7 +243,7 @@ generate-pdf - generate-resources + prepare-package ${basedir}/src/main/docbook/xsl/pdf.xsl ${basedir}/target/docbook/pdf @@ -226,7 +262,7 @@ generate-epub3 - generate-resources + prepare-package ${basedir}/src/main/docbook/xsl/epub.xsl ${basedir}/target/docbook/epub diff --git a/spring-cloud-dataflow-docs/src/main/asciidoc/api-guide.adoc b/spring-cloud-dataflow-docs/src/main/asciidoc/api-guide.adoc new file mode 100644 index 0000000000..b302bbc8d8 --- /dev/null +++ b/spring-cloud-dataflow-docs/src/main/asciidoc/api-guide.adoc @@ -0,0 +1,147 @@ +[[api-guide]] += REST API Guide + +[partintro] +-- +In this section you will learn all about the Spring Cloud Data Flow REST API. +-- + +[[api-guide-overview]] +== Overview + +Spring Cloud Data Flow provides a REST API allowing you to access all aspects of +the server. In fact the Spring Cloud Data Flow Shell is a first-class consumer of +that API. + +TIP: If you plan on using the REST API using Java, please also consider using the +provided Java client (DataflowTemplate) that uses the REST API internally. + +[[api-guide-overview-http-verbs]] +=== HTTP verbs + +Spring Cloud Data Flow tries to adhere as closely as possible to standard HTTP and REST conventions in its use of HTTP verbs. + +|=== +| Verb | Usage + +| `GET` +| Used to retrieve a resource + +| `POST` +| Used to create a new resource + +| `PUT` +| Used to update an existing resource, including partial updates. Also used for +resources that imply the concept of `restarts` such as Tasks. + +| `DELETE` +| Used to delete an existing resource +|=== + +[[api-guide-overview-http-status-codes]] +=== HTTP status codes + +RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its use of HTTP status codes. + +|=== +| Status code | Usage + +| `200 OK` +| The request completed successfully + +| `201 Created` +| A new resource has been created successfully. The resource's URI is available from the response's `Location` header + +| `204 No Content` +| An update to an existing resource has been applied successfully + +| `400 Bad Request` +| The request was malformed. The response body will include an error providing further information + +| `404 Not Found` +| The requested resource did not exist + +| `409 Conflict` +| The requested resource already exists, e.g. the task already exists or the stream +was already being deployed + +| `422 Unprocessable Entity` +| Returned in cases the Job Execution cannot be stopped or restarted + +|=== + +[[api-guide-overview-headers]] +=== Headers + +Every response has the following header(s): + +include::{snippets}/api-documentation/headers/response-headers.adoc[] + +[[api-guide-overview-errors]] +=== Errors + +include::{snippets}/api-documentation/errors/response-fields.adoc[] + +[[api-guide-overview-hypermedia]] +=== Hypermedia + +Spring Cloud Data Flow uses hypermedia and resources include links to other resources +in their responses. Responses are in http://stateless.co/hal_specification.html[Hypertext Application from resource to resource Language (HAL)] format. Links can be found beneath the `_links` key. Users of the API should not create URIs themselves, instead they should use the above-described links to navigate. + +[[api-guide-resources]] +== Resources + +[[api-guide-resources-index]] +=== Index + +The index provides the entry point into Spring Cloud Data Flow's REST API. + +[[api-guide-resources-index-access]] +==== Accessing the index + +A `GET` request is used to access the index + +===== Request structure + +include::{snippets}/api-documentation/index/http-request.adoc[] + +===== Example request + +include::{snippets}/api-documentation/index/curl-request.adoc[] + +===== Response structure + +include::{snippets}/api-documentation/index/response-fields.adoc[] + +===== Example response + +include::{snippets}/api-documentation/index/http-response.adoc[] + +[[api-guide-resources-index-links]] +===== Links + +The main element of the index are the links as they allow you to traverse the API +and execute the desired functionality: + +include::{snippets}/api-documentation/index/links.adoc[] + +[[resources-app-registry-list]] +=== Listing Applications + +A `GET` request will list all applications known to Spring Cloud Data Flow. + +==== Request structure + +include::{snippets}/app-registry-documentation/get-applications-filtered/http-request.adoc[] + +==== Request parameters + +include::{snippets}/app-registry-documentation/get-applications-filtered/request-parameters.adoc[] + +==== Example request + +include::{snippets}/app-registry-documentation/get-applications-filtered/curl-request.adoc[] + +==== Response structure + +include::{snippets}/app-registry-documentation/get-applications-filtered/http-response.adoc[] diff --git a/spring-cloud-dataflow-docs/src/main/asciidoc/index.adoc b/spring-cloud-dataflow-docs/src/main/asciidoc/index.adoc index ab506b4fed..4996483e64 100644 --- a/spring-cloud-dataflow-docs/src/main/asciidoc/index.adoc +++ b/spring-cloud-dataflow-docs/src/main/asciidoc/index.adoc @@ -26,6 +26,7 @@ include::streams.adoc[] include::tasks.adoc[] include::dashboard.adoc[] include::howto.adoc[] +include::api-guide.adoc[] include::appendix.adoc[] diff --git a/spring-cloud-dataflow-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/ApiDocumentation.java b/spring-cloud-dataflow-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/ApiDocumentation.java new file mode 100644 index 0000000000..a69cc65695 --- /dev/null +++ b/spring-cloud-dataflow-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/ApiDocumentation.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016 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 org.springframework.cloud.dataflow.server.rest.documentation; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import javax.servlet.RequestDispatcher; + +import org.junit.Test; + +/** + * @author Gunnar Hillert + */ +public class ApiDocumentation extends BaseDocumentation { + + @Test + public void headers() throws Exception { + this.mockMvc + .perform(get("/")) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + responseHeaders( + headerWithName("Content-Type").description("The Content-Type of the payload, e.g. `application/hal+json`")))); + } + + @Test + public void errors() throws Exception { + this.mockMvc + .perform(get("/error") + .requestAttr(RequestDispatcher.ERROR_STATUS_CODE, 400) + .requestAttr(RequestDispatcher.ERROR_REQUEST_URI, "/apps") + .requestAttr(RequestDispatcher.ERROR_MESSAGE, "The app 'http://localhost:8080/apps/123' does not exist")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("error", is("Bad Request"))) + .andExpect(jsonPath("timestamp", is(notNullValue()))) + .andExpect(jsonPath("status", is(400))) + .andExpect(jsonPath("path", is(notNullValue()))) + .andDo(this.documentationHandler.document( + responseFields( + fieldWithPath("error").description("The HTTP error that occurred, e.g. `Bad Request`"), + fieldWithPath("message").description("A description of the cause of the error"), + fieldWithPath("path").description("The path to which the request was made"), + fieldWithPath("status").description("The HTTP status code, e.g. `400`"), + fieldWithPath("timestamp").description("The time, in milliseconds, at which the error occurred")))); + } + + @Test + public void index() throws Exception { + this.mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + links( + linkWithRel("apps").description("Handle registered applications"), + linkWithRel("completions/stream").description("Exposes the DSL completion features"), + linkWithRel("jobs/executions").description("Provides the JobExecution resource"), + linkWithRel("jobs/executions/execution").description("Provides details for a specific JobExecution"), + linkWithRel("jobs/executions/execution/steps").description("Provides the steps for a JobExecution"), + linkWithRel("jobs/executions/execution/steps/step").description("Returns the details for a specific step"), + linkWithRel("jobs/executions/execution/steps/step/progress").description("Provides progress information for a specific step"), + linkWithRel("jobs/executions/name").description("Retrieve Job Executions by Job name"), + linkWithRel("jobs/instances/instance").description("Provides the job instance resource for a specific job instance"), + linkWithRel("jobs/instances/name").description("Provides the Job instance resource for a specific job name"), + linkWithRel("runtime/apps").description("Provides the runtime application resource"), + linkWithRel("runtime/apps/app").description("Exposes the runtime status for a specific app"), + linkWithRel("runtime/apps/instances").description("Provides the status for app instances"), + linkWithRel("tasks/definitions").description("Provides the task definition resource"), + linkWithRel("tasks/definitions/definition").description("Provides details for a specific task definition"), + linkWithRel("tasks/deployments").description("Provides the resource for deployment operations"), + linkWithRel("tasks/deployments/deployment").description("Launch a task"), + linkWithRel("tasks/executions").description("Returns Task executions"), + linkWithRel("tasks/executions/name").description("Returns all task executions for a given Task name"), + linkWithRel("tasks/executions/execution").description("Provides details for a specific task execution"), + linkWithRel("streams/definitions").description("Exposes the Streams resource"), + linkWithRel("streams/definitions/definition").description("Handle a specific Stream definition"), + linkWithRel("streams/deployments").description("Provides Stream deployment operations"), + linkWithRel("streams/deployments/deployment").description("Request (un-)deployment of an existing stream definition"), + linkWithRel("counters").description("Exposes the resource for dealing with Counters"), + linkWithRel("counters/counter").description("Handle a specific counter"), + linkWithRel("aggregate-counters").description("Provides the resource for dealing with aggregate counters"), + linkWithRel("aggregate-counters/counter").description("Handle a specific aggregate counter"), + linkWithRel("field-value-counters").description("Provides the resource for dealing with field-value-counters"), + linkWithRel("field-value-counters/counter").description("Handle a specific field-value-counter")), + responseFields( + fieldWithPath("_links").description("Links to other resources")))); + } +} diff --git a/spring-cloud-dataflow-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/AppRegistryDocumentation.java b/spring-cloud-dataflow-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/AppRegistryDocumentation.java new file mode 100644 index 0000000000..747e8addc5 --- /dev/null +++ b/spring-cloud-dataflow-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/AppRegistryDocumentation.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 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 org.springframework.cloud.dataflow.server.rest.documentation; + +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; + +/** + * @author Gunnar Hillert + */ +public class AppRegistryDocumentation extends BaseDocumentation { + + @Test + public void getApplicationsFiltered() throws Exception { + this.mockMvc.perform(get("/apps").param("type", "source") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document(requestParameters( + parameterWithName("type").description("Restrict the returned apps to the type of the app.")))); + } +} diff --git a/spring-cloud-dataflow-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/BaseDocumentation.java b/spring-cloud-dataflow-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/BaseDocumentation.java new file mode 100644 index 0000000000..6e0e4e398c --- /dev/null +++ b/spring-cloud-dataflow-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/BaseDocumentation.java @@ -0,0 +1,65 @@ +/* + * Copyright 2016 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 org.springframework.cloud.dataflow.server.rest.documentation; + +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.springframework.cloud.dataflow.server.local.LocalDataflowResource; +import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +/** + * @author Gunnar Hillert + */ +public abstract class BaseDocumentation { + + protected String TARGET_DIRECTORY = "target/generated-snippets"; + + @Rule + public JUnitRestDocumentation restDocumentation = + new JUnitRestDocumentation(TARGET_DIRECTORY); + + @ClassRule + public final static LocalDataflowResource springDataflowServer = + new LocalDataflowResource(null); + + protected MockMvc mockMvc; + protected RestDocumentationResultHandler documentationHandler; + + @Before + public void setupMocks() { + prepareDocumentationTests(restDocumentation); + } + + protected void prepareDocumentationTests(JUnitRestDocumentation restDocumentation) { + this.documentationHandler = document("{class-name}/{method-name}", + preprocessResponse(prettyPrint())); + this.mockMvc = MockMvcBuilders.webAppContextSetup(springDataflowServer.getWebApplicationContext()) + .apply(documentationConfiguration(restDocumentation)) + .alwaysDo(this.documentationHandler) + .build(); + } + +} diff --git a/spring-cloud-dataflow-server-core/pom.xml b/spring-cloud-dataflow-server-core/pom.xml index b8926be021..3f7527d718 100644 --- a/spring-cloud-dataflow-server-core/pom.xml +++ b/spring-cloud-dataflow-server-core/pom.xml @@ -163,9 +163,9 @@ - pl.project13.maven - git-commit-id-plugin - - + pl.project13.maven + git-commit-id-plugin + + diff --git a/spring-cloud-dataflow-server-local/pom.xml b/spring-cloud-dataflow-server-local/pom.xml index d2b7d09f7e..61c7e36474 100644 --- a/spring-cloud-dataflow-server-local/pom.xml +++ b/spring-cloud-dataflow-server-local/pom.xml @@ -40,6 +40,17 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + diff --git a/spring-cloud-dataflow-server-local/src/test/java/org/springframework/cloud/dataflow/server/local/LocalDataflowResource.java b/spring-cloud-dataflow-server-local/src/test/java/org/springframework/cloud/dataflow/server/local/LocalDataflowResource.java index 679826ef01..72bffaf6b4 100644 --- a/spring-cloud-dataflow-server-local/src/test/java/org/springframework/cloud/dataflow/server/local/LocalDataflowResource.java +++ b/spring-cloud-dataflow-server-local/src/test/java/org/springframework/cloud/dataflow/server/local/LocalDataflowResource.java @@ -85,4 +85,10 @@ public MockMvc getMockMvc() { public String getDataflowPort() { return dataflowPort; } + + public WebApplicationContext getWebApplicationContext() { + return configurableApplicationContext; + } + } +