Add Grails sample app
Closes gh-250
This commit is contained in:
committed by
Andy Wilkinson
parent
c1d245f7d4
commit
64803b5fae
148
samples/rest-notes-grails/src/docs/index.adoc
Normal file
148
samples/rest-notes-grails/src/docs/index.adoc
Normal file
@@ -0,0 +1,148 @@
|
||||
= Grails RESTful Notes API Guide
|
||||
Andy Wilkinson; Jenn Strater
|
||||
:doctype: book
|
||||
:icons: font
|
||||
:source-highlighter: highlightjs
|
||||
:toc: left
|
||||
:toclevels: 4
|
||||
:sectlinks:
|
||||
|
||||
[[overview]]
|
||||
= Overview
|
||||
|
||||
[[overview-http-verbs]]
|
||||
== HTTP verbs
|
||||
|
||||
Grails RESTful notes 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
|
||||
|
||||
| `PATCH`
|
||||
| Used to update an existing resource, including partial updates
|
||||
|
||||
| `DELETE`
|
||||
| Used to delete an existing resource
|
||||
|===
|
||||
|
||||
[[overview-http-status-codes]]
|
||||
== HTTP status codes
|
||||
|
||||
Grails 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
|
||||
|===
|
||||
|
||||
[[resources]]
|
||||
= Resources
|
||||
|
||||
|
||||
[[resources-index]]
|
||||
== Index
|
||||
|
||||
The index provides the entry point into the service.
|
||||
|
||||
|
||||
|
||||
[[resources-index-access]]
|
||||
=== Accessing the index
|
||||
|
||||
A `GET` request is used to access the index
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{snippets}/index-example/curl-request.adoc[]
|
||||
|
||||
==== Response structure
|
||||
|
||||
include::{snippets}/index-example/response-fields.adoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{snippets}/index-example/http-response.adoc[]
|
||||
|
||||
|
||||
[[resources-notes]]
|
||||
== Notes
|
||||
|
||||
The Notes resources is used to create and list notes
|
||||
|
||||
|
||||
|
||||
[[resources-notes-list]]
|
||||
=== Listing notes
|
||||
|
||||
A `GET` request will list all of the service's notes.
|
||||
|
||||
==== Response structure
|
||||
|
||||
include::{snippets}/notes-list-example/response-fields.adoc[]
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{snippets}/notes-list-example/curl-request.adoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{snippets}/notes-list-example/http-response.adoc[]
|
||||
|
||||
|
||||
|
||||
[[resources-notes-create]]
|
||||
=== Creating a note
|
||||
|
||||
A `POST` request is used to create a note
|
||||
|
||||
==== Request structure
|
||||
|
||||
include::{snippets}/notes-create-example/request-fields.adoc[]
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{snippets}/notes-create-example/curl-request.adoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{snippets}/notes-create-example/http-response.adoc[]
|
||||
|
||||
[[resources-note-retrieve]]
|
||||
=== Retrieve a note
|
||||
|
||||
A `GET` request will retrieve the details of a note
|
||||
|
||||
==== Response structure
|
||||
|
||||
include::{snippets}/note-get-example/response-fields.adoc[]
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{snippets}/note-get-example/curl-request.adoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{snippets}/note-get-example/http-response.adoc[]
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.example
|
||||
|
||||
import org.springframework.restdocs.payload.JsonFieldType
|
||||
|
||||
import static com.jayway.restassured.RestAssured.given
|
||||
import static org.hamcrest.CoreMatchers.is
|
||||
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest
|
||||
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse
|
||||
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields
|
||||
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields
|
||||
import static org.springframework.restdocs.restassured.operation.preprocess.RestAssuredPreprocessors.modifyUris
|
||||
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document
|
||||
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration
|
||||
|
||||
import com.jayway.restassured.builder.RequestSpecBuilder
|
||||
import com.jayway.restassured.specification.RequestSpecification
|
||||
import grails.test.mixin.integration.Integration
|
||||
import grails.transaction.Rollback
|
||||
import org.junit.Rule
|
||||
import org.springframework.restdocs.JUnitRestDocumentation
|
||||
import org.springframework.http.MediaType
|
||||
import spock.lang.Specification
|
||||
|
||||
@Integration
|
||||
@Rollback
|
||||
class ApiDocumentationSpec extends Specification {
|
||||
@Rule
|
||||
JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation('src/docs/generated-snippets')
|
||||
|
||||
protected RequestSpecification documentationSpec
|
||||
|
||||
void setup() {
|
||||
this.documentationSpec = new RequestSpecBuilder()
|
||||
.addFilter(documentationConfiguration(restDocumentation))
|
||||
.build()
|
||||
}
|
||||
|
||||
void 'test and document get request for /index'() {
|
||||
expect:
|
||||
given(this.documentationSpec)
|
||||
.accept(MediaType.APPLICATION_JSON.toString())
|
||||
.filter(document('index-example',
|
||||
preprocessRequest(modifyUris()
|
||||
.host('api.example.com')
|
||||
.removePort()),
|
||||
preprocessResponse(prettyPrint()),
|
||||
responseFields(
|
||||
fieldWithPath('message').description('Welcome to Grails!'),
|
||||
fieldWithPath('environment').description("The running environment"),
|
||||
fieldWithPath('appversion').description('version of the app that is running'),
|
||||
fieldWithPath('grailsversion').description('the version of grails used in this project'),
|
||||
fieldWithPath('appprofile').description('the profile of grails used in this project'),
|
||||
fieldWithPath('groovyversion').description('the version of groovy used in this project'),
|
||||
fieldWithPath('jvmversion').description('the version of the jvm used in this project'),
|
||||
fieldWithPath('controllers').type(JsonFieldType.ARRAY).description('the list of available controllers'),
|
||||
fieldWithPath('plugins').type(JsonFieldType.ARRAY).description('the plugins active for this project'),
|
||||
)))
|
||||
.when()
|
||||
.port(8080)
|
||||
.get('/')
|
||||
.then()
|
||||
.assertThat()
|
||||
.statusCode(is(200))
|
||||
}
|
||||
|
||||
void 'test and document notes list request'() {
|
||||
expect:
|
||||
given(this.documentationSpec)
|
||||
.accept(MediaType.APPLICATION_JSON.toString())
|
||||
.filter(document('notes-list-example',
|
||||
preprocessRequest(modifyUris()
|
||||
.host('api.example.com')
|
||||
.removePort()),
|
||||
preprocessResponse(prettyPrint()),
|
||||
responseFields(
|
||||
fieldWithPath('[].class').description('the class of the resource'),
|
||||
fieldWithPath('[].id').description('the id of the note'),
|
||||
fieldWithPath('[].title').description('the title of the note'),
|
||||
fieldWithPath('[].body').description('the body of the note'),
|
||||
fieldWithPath('[].tags').type(JsonFieldType.ARRAY).description('the list of tags associated with the note'),
|
||||
)))
|
||||
.when()
|
||||
.port(8080)
|
||||
.get('/notes')
|
||||
.then()
|
||||
.assertThat()
|
||||
.statusCode(is(200))
|
||||
}
|
||||
|
||||
void 'test and document create new note'() {
|
||||
expect:
|
||||
given(this.documentationSpec)
|
||||
.accept(MediaType.APPLICATION_JSON.toString())
|
||||
.contentType(MediaType.APPLICATION_JSON.toString())
|
||||
.filter(document('notes-create-example',
|
||||
preprocessRequest(modifyUris()
|
||||
.host('api.example.com')
|
||||
.removePort()),
|
||||
preprocessResponse(prettyPrint()),
|
||||
requestFields(
|
||||
fieldWithPath('title').description('the title of the note'),
|
||||
fieldWithPath('body').description('the body of the note'),
|
||||
fieldWithPath('tags').type(JsonFieldType.ARRAY).description('a list of tags associated to the note')
|
||||
),
|
||||
responseFields(
|
||||
fieldWithPath('class').description('the class of the resource'),
|
||||
fieldWithPath('id').description('the id of the note'),
|
||||
fieldWithPath('title').description('the title of the note'),
|
||||
fieldWithPath('body').description('the body of the note'),
|
||||
fieldWithPath('tags').type(JsonFieldType.ARRAY).description('the list of tags associated with the note'),
|
||||
)))
|
||||
.body('{ "body": "My test example", "title": "Eureka!", "tags": [{"name": "testing123"}] }')
|
||||
.when()
|
||||
.port(8080)
|
||||
.post('/notes')
|
||||
.then()
|
||||
.assertThat()
|
||||
.statusCode(is(201))
|
||||
}
|
||||
|
||||
void 'test and document getting specific note'() {
|
||||
expect:
|
||||
given(this.documentationSpec)
|
||||
.accept(MediaType.APPLICATION_JSON.toString())
|
||||
.filter(document('note-get-example',
|
||||
preprocessRequest(modifyUris()
|
||||
.host('api.example.com')
|
||||
.removePort()),
|
||||
preprocessResponse(prettyPrint()),
|
||||
responseFields(
|
||||
fieldWithPath('class').description('the class of the resource'),
|
||||
fieldWithPath('id').description('the id of the note'),
|
||||
fieldWithPath('title').description('the title of the note'),
|
||||
fieldWithPath('body').description('the body of the note'),
|
||||
fieldWithPath('tags').type(JsonFieldType.ARRAY).description('the list of tags associated with the note'),
|
||||
)))
|
||||
.when()
|
||||
.port(8080)
|
||||
.get('/notes/1')
|
||||
.then()
|
||||
.assertThat()
|
||||
.statusCode(is(200))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user