spring rest-doc-2015-11
TRANSCRIPT
references• github : https://github.com/spring-projects/spring-restdocs
• Docs : http://docs.spring.io/spring-restdocs/docs/current/reference/html5/
• API Guide : http://docs.spring.io/spring-restdocs/docs/current/samples/restful-notes/api-guide.html
• blog :
• https://www.opencredo.com/2015/07/28/rest-api-tooling-review/
• http://info.michael-simons.eu/2015/11/05/documenting-your-api-with-spring-rest-docs/
• Slide :https://speakerdeck.com/ankinson/documenting-restful-apis-webinar by Andy Wilkinson, Pivotal
Spring REST Docs
http://docs.spring.io/spring-restdocs/docs/1.0.x/reference/html5/#getting-started
Introduction• 2015/11
• 1.0.0 current, GA
• 1.0.1 snapshot
• asciidoctor 사용
• Spring MVC Test로 작성 :
• http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#spring-mvc-test-framework
http://docs.spring.io/spring-restdocs/docs/1.0.x/reference/html5/#getting-started
asciidoctor• http://asciidoctor.org/
• plain text을 HTML5, DocBook5 등으로 변환
• Ruby로 작성됨.
• Syntax : http://asciidoctor.org/docs/user-manual/
asciidoctor 사용 법$ vi test.adoc
= Hello, AsciiDoc! Doc Writer <[email protected]>
An introduction to http://asciidoc.org[AsciiDoc].
== First Section
* item 1 * item 2
[source,ruby] puts "Hello, World!”
$ asciidoctor test.doc -o test.html
Sample Application
• Spring Data REST : • https://github.com/spring-projects/spring-restdocs/tree/master/samples/rest-notes-spring-data-rest
• Spring HATEOAS : • https://github.com/spring-projects/spring-restdocs/tree/master/samples/rest-notes-spring-hateoas
• HATEOAS : Hypermedia As the Engine Of Application State
• api-guide.adoc : API guide
• ApiDocumentation.java
• getting-started-guide.adoc : getting started guide
• GettingStartedDocumentation.java
• Spring Data REST
Spring Data REST sample => 생성된 문서의 HTML은?
http://docs.spring.io/spring-restdocs/docs/current/samples/restful-notes/api-guide.html
<dependency> <groupId>org.springframework.restdocs</groupId> <artifactId>spring-restdocs-mockmvc</artifactId> <version>1.0.0.RELEASE</version> <scope>test</scope> </dependency>
<properties> <snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory> </properties>
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <includes> <include>**/*Documentation.java</include> </includes> </configuration> </plugin> <plugin> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctor-maven-plugin</artifactId> <version>1.5.2</version> <executions> <execution> <id>generate-docs</id> <phase>package</phase> <goals> <goal>process-asciidoc</goal> </goals> <configuration> <backend>html</backend> <doctype>book</doctype> <attributes> <snippets>${snippetsDirectory}</snippets> </attributes> </configuration> </execution> </executions> </plugin> </plugins> </build>
Spring Data REST sample : pom.xml
<dependency> <groupId>org.springframework.restdocs</groupId> <artifactId>spring-restdocs-mockmvc</artifactId> <version>1.0.0.RELEASE</version> <scope>test</scope> </dependency>
<properties> <snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory> </properties>
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <includes> <include>**/*Documentation.java</include> </includes> </configuration> </plugin>
1) test scope에 spring-restdocs-mockmvc 추가
2) 생성된 snippet 의 출력 위치
3) mvn test 시 사용하는 SureFire plugin 추가하고, Documentation.java 파일 찾아서 테스트 코드 실행
<plugin> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctor-maven-plugin</artifactId> <version>1.5.2</version> <executions> <execution> <id>generate-docs</id> <phase>package</phase> <goals> <goal>process-asciidoc</goal> </goals> <configuration> <backend>html</backend> <doctype>book</doctype> <attributes> <snippets>${snippetsDirectory}</snippets> </attributes> </configuration> </execution> </executions> </plugin> Add the Asciidoctor plugin
4) Asciidoctor plugin 추가
5) snippets 디렉토리 설정
6) jar 파일내에서 문서를 package하려면… prepare-package 표현식을 사용하면 됨.
Generating documentation snippets
• Spring REST Docs은 MVC Test framework을 사용
• MVC test framework doc
• http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#spring-mvc-test-framework
Setting up Spring MVC Test
@Rulepublic final RestDocumentation restDocumentation = new RestDocumentation("build/generated-snippets");
@Autowired private WebApplicationContext context;
private MockMvc mockMvc;
@Beforepublic void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); }
1) snippets을 생성하기위해 @Rule annotation을 이용, RestDocumentation 을 선언, “build/generated-snippets” pom.xml 에서 선언되어있음
2) MockMvc instance를 생성키위해 @Before에서 setUp() method 선언, MockMvc의 instance는 RestDocumentationMockMvcConfigurer 를 사용해서 설정할 수 있는데, static documentationConfiguration() method로 얻을 수 있다. documentationConfiguration의 method들?
@Rulepublic final RestDocumentation restDocumentation = new RestDocumentation("build/generated-snippets");
@Autowired private WebApplicationContext context;
private MockMvc mockMvc;
@Beforepublic void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); }
documentationConfiguration은 URIs 설정할 수 있는데, 아래와 같이 사용이 가능하다. documentationConfiguration(this.restDocumentation).uris() .withScheme(“https”) .withHost("example.com") .withPort(443)
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andDo(document("index"));
Invoking the RESTful service
1) root(/) 호출하고, application/json response 설정
2) 예상 결과를 위해 Assert
3) 서비스를 호출하고, 디렉토리에 snippets을 기록, “index”는 sub-path. RestDocumentationResultHandler 로 기록됨. 이 클래스의 인스턴스는org.springframework.restdocs.mockmvc.MockMvcRestDocumentation 의 static document method 에서 얻을 수 있음.
기본적으로, 세 가지 snippets이 기록된다. 1) <output-directory>/index/curl-request.adoc 2) <output-directory>/index/http-request.adoc 3) <output-directory>/index/http-response.adoc
pom.xml 의 <configuration> <backend>html</backend> <doctype>book</doctype> <attributes> <snippets>${snippetsDirectory}</snippets> </attributes> </configuration>
asciidoc 에서 아래와 같이 사용할 수 있다. include::{snippets}/index/curl-request.adoc[]
Using the snippets<snippets> attribute 에 지정된 값이 출력 디렉토리임.
Documenting your API• Request and response payloads
• JSON payloads • XML payloads
• Request parameters • Path parameters • HTTP headers • Documenting constraints • Default snippets • Using parameterised output directories • Customising the output • Hypermedia
2) Request and response payloads
this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andDo(document("index", responseFields( fieldWithPath("contact").description("The user's contact details"), fieldWithPath("contact.email").description("The user's email address"))));
response payload에 field를 기술하는 snippet을 생산하기위해 responseFields() 사용
For requests : request-fields.adoc For response : response-fields.adoc
“contact.email” path를 가진 field를 예상함.field를 기술하는
table을 포함하는 snippet
failed? undocumented field가 payload 에서 발견되는 경우, documented filed가 payload에 없는 경우
JSON payloads
{ "a":{ "b":[ { "c":"one" }, { "c":"two" }, { "d":"three" } ], "e.dot" : "four" } }
path내에 각 키를 구별하기위해 . 을 사용
[] : 각 키를 ‘ (single quotes)로 감싼다.
.andDo(document("index", responseFields( fieldWithPath("contact.email") .type(JsonFieldType.STRING) .optional() .description("The user's email address"))));
사용 법 : string 타입은 JsonFieldType.STRING enum을 사용
JSON field types
this.mockMvc.perform(get("/users?page=2&per_page=100")) .andExpect(status().isOk()) .andDo(document("users", requestParameters( parameterWithName("page").description("The page to retrieve"), parameterWithName("per_page").description("Entries per page") )));
1) GET 요청으로 page와 per_page 파라미터를 요청
request-parameters.adoc
3) Request parameters
2) requestParameters() 를 사용
3) page 파라미터, parameterWithName() method를 사용
parameter를 기술하는 table을 포함하는 snippet
this.mockMvc.perform(post("/users").param("username", "Tester")) .andExpect(status().isCreated()) .andDo(document("create-user", requestParameters( parameterWithName("username").description("The user's username") )));
1) username 을 가진 POST request임
request-parameters.adocfailed? undocumented request parameter가 request 에서 사용되는 경우, documented request parameter가 request에 없는 경우
parameter를 기술하는 table을 포함하는 snippet
4) Path parameters
this.mockMvc.perform(get("/locations/{latitude}/{longitude}", 51.5072, 0.1275)) .andExpect(status().isOk()) .andDo(document("locations", pathParameters( parameterWithName("latitude").description("The location's latitude"), parameterWithName("longitude").description("The location's longitude") )));
1) latitude, longitude의 두 path parameter
path-parameters.adoc
2) pathParameters() method 사용
path parameters를 기술하는 table을 포함하는 snippet
this.mockMvc
.perform(get("/people").header("Authorization", "Basic dXNlcjpzZWNyZXQ=")) .andExpect(status().isOk())
.andDo(document("headers",
requestHeaders(
headerWithName("Authorization").description(
"Basic auth credentials")),
responseHeaders(
headerWithName("X-RateLimit-Limit").description(
"The total number of requests permitted per period"),
headerWithName("X-RateLimit-Remaining").description(
"Remaining requests permitted in current period"),
headerWithName("X-RateLimit-Reset").description(
"Time at which the rate limit period will reset"))));
1) Authorization header를 가진 GET request
request-headers.doc, response-headers.adoc
2) requestHeaders method 사용하고, headerWithName method로 문서화 할 수 있다.
3) responseHeaders method 사용
headers를 기술하는 table을 포함하는 snippet
public void example() {
ConstraintDescriptions userConstraints = new ConstraintDescriptions(UserInput.class);
List<String> descriptions = userConstraints.descriptionsForProperty("name"); }
static class UserInput {
@NotNull
@Size(min = 1)
String name;
@NotNull
@Size(min = 8) String password;
1) ConstraintDescriptions의 instance를 생성
2) name property의 constraint를 가져온다. List 에는 NotNull 조건과 Size 조건이 있다.
Bean Validation 1.1의 constraints 모두 지원한다.
AssertFalse, AssertTrue, DecimalMax, DecimalMin, Digits, Future, Max, Min, NotNull, Null, Past, Pattern, Size
.andDo(document("create-user", requestFields( attributes( key("title").value("Fields for user creation")), fieldWithPath("name") .description("The user's name") .attributes( key("constraints").value("Must not be null. Must not be empty")), fieldWithPath("email") .description("The user's email address") .attributes( key("constraints").value("Must be a valid email address")))));
request field의 extra information으로 constraints를 문서화도 가능함
name field에 “constraints” 속성을 설정할 수 있다.
8) Using parameterised output directories
• output 디렉토리를 위해 아래 parameter를 지원.
_ ( userscore) 를 사용
-(dash) 를 사용
@Before public void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .alwaysDo(document("{method-name}/{step}/")) .build(); } alwaysDo 를 사용하고, method-name이
creatingANote 라면, 디렉토리는 creating-a-note/1/ 이라는 이름에 snippet이 저장된다.
9) Customising the output
• 9-1) Spring REST Docs : Mustache template를 사용
• ./spring-restdocs-core/build/resources/main/org/springframework/restdocs/templates/*.snippet
• curl-request.adoc 을 위해 template 을 사용하려면,
• src/test/resources/org/springframework/restdocs/templates/curl-request.snippet 을 만들어서 사용하면 됨.
.andDo(document("create-user", requestFields( attributes( key("title").value("Fields for user creation")), fieldWithPath("name") .description("The user's name") .attributes( key("constraints").value("Must not be null. Must not be empty")), fieldWithPath("email") .description("The user's email address") .attributes( key("constraints").value("Must be a valid email address")))));
9-2) including extra information
1) attributes() method를 사용하고, “title” attribute를 설정
2) “name” 의 constraints를 설정
3) “email” 의 constraints를 설정
custom template : request-fields.snippet 설정
custom template : request-fields.snippet .{{title}} |=== |Path|Type|Description|Constraints
{{#fields}} |{{path}} |{{type}} |{{description}} |{{constraints}}
{{/fields}} |===
1) 테이블에 title 추가
2) 새로운 column “Constraints” 추가
3) 테이블의 각 row 에 “constraints” 속성의 설명을 포함
this.mockMvc.perform(get("/")) .andExpect(status().isOk()) .andDo(document("index", preprocessRequest(removeHeaders("Foo")), preprocessResponse(prettyPrint())));
1) Preprocessing : document() 를 호출하면 된다. OperationRequestPreprocessor Instance는 preprocessRequest() 를 사용하면됨. 2) “Foo”라는 header를 삭제
Customizing requests and responses
모든 테스트에 동일한 preprocessor 적용하기 :
@Before public void setup() { this.document = document("{method-name}", preprocessRequest(removeHeaders("Foo")), preprocessResponse(prettyPrint())); this.mockMvc = MockMvcBuilders .webAppContextSetup(this.context) .alwaysDo(this.document) .build(); }
1) RestDocumentationResultHandler instance는 document() 로 생성할 수 있다. 2) Header “Foo” 삭제
MockMvc instance 생성
this.document.snippets( links(linkWithRel("self").description("Canonical self link"))); this.mockMvc.perform(get("/")) .andExpect(status().isOk());
테스트하려는 리소스에 링크를 지정
perform()은 setup()에서 alwasyDo()를 사용했기 때문에, 자동으로 문서를 만듬
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation).uris() .withScheme("https") .withHost("example.com") .withPort(443)) .build();
RestDocumentationMockMvcConfigurer 사용하거나 documentationConfiguration()를 호출 한다.
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation).snippets() .withEncoding("ISO-8859-1")) .build();
Asciidoctor에서 사용하는 Default encoding은 UTF-8 RestDocumentationMockMvcConfigurer 을 사용해서 설정할 수 있고, documentationConfiguration() Method를 이용하면 됨.
Snippet encoding
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation).snippets() .withDefaults(curlRequest())) .build();
Default snippets : 3 가지가 있다. curl-request http-request http-response
예: curl-request snippet 을 만들기 위해서는 withDefaults(curlRequest()) 설정
Default snippets
Working with Asciidoctor
• asciidoctor syntax
• http://asciidoctor.org/docs/asciidoc-syntax-quick-reference/
• asciidoctor user manual
• http://asciidoctor.org/docs/user-manual
2) Customising tables
• Formatting columns
• Configuring the title
• User manual :
• http://asciidoctor.org/docs/user-manual/#tables
[cols=1,3] include::{snippets}/index/links.adoc[]
테이블은 두 개 컬럼 두번째 컬럼이 3배 넓다
2-1) Formatting columns
2-2) Configuring the title
.Links include::{snippets}/index/links.adoc[]
테이블의 제목 : “Links” 임.
spring-boot 설치
• 추천 버전
• Spring Boot v1.1.4 release
• Mac OS X 환경에 설치 $ brew tap pivotal/tap $ brew install springboot
http://docs.spring.io/autorepo/docs/spring-boot/1.1.4.RELEASE/reference/html/getting-started-installing-spring-boot.html
java & mvn & intelliJ• 최소 스펙
• Java : 1.6 +
• maven : 3.0 +
• Mac OS X
• java : 1.7.0_79
• maven : 3.2.5
• intelliJ IDEA 14.1.5+
~/spring-restdocs/samples/rest-notes-spring-data-rest/src/main/asciidoc $ ls -rw-r--r-- 1 EricAhn staff 5.8K Nov 12 15:21 api-guide.adoc -rw-r--r-- 1 EricAhn staff 5.8K Nov 12 15:21 getting-started-guide.doc
$ asciidoctor api-guide.doc
=> generated api-guide.html
?
원하는 결과 :
… [[overview-errors]] == Errors
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::{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::{snippets}/error-example/http-response.adoc[]
[[overview-hypermedia]] …
api-guide.doc
MockHttpServletRequest: HTTP Method = GET Request URI = /error Parameters = {} Headers = {}
Handler: Type = org.springframework.boot.autoconfigure.web.BasicErrorController Method = public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
Async: Async started = false Async result = null
Resolved Exception: Type = null
ModelAndView: View name = null View = null Model = null
FlashMap:
MockHttpServletResponse: Status = 400 Error message = null Headers = {Content-Type=[application/json;charset=UTF-8]} Content type = application/json;charset=UTF-8 Body = { "timestamp" : 1447748756746, "status" : 400, "error" : "Bad Request", "message" : "The tag 'http://localhost:8080/tags/123' does not exist", "path" : "/notes"} Forwarded URL = null Redirected URL = null Cookies = []
Process finished with exit code 0
IntelliJ Output :
~/spring-restdocs/samples/rest-notes-spring-data-rest/target/generated-snippets/error-example$ ls -la total 32 drwxr-xr-x 6 EricAhn staff 204 Nov 17 17:25 . drwxr-xr-x 3 EricAhn staff 102 Nov 17 17:25 .. -rw-r--r-- 1 EricAhn staff 63 Nov 17 17:25 curl-request.adoc -rw-r--r-- 1 EricAhn staff 60 Nov 17 17:25 http-request.adoc -rw-r--r-- 1 EricAhn staff 287 Nov 17 17:25 http-response.adoc -rw-r--r-- 1 EricAhn staff 340 Nov 17 17:25 response-fields.adoc
$ cd spring-restdocs/samples/rest-notes-spring-data-rest/src/main/asciidoc $ cp -R target/generated-snippets/error-example . $ ls drwxr-xr-x 7 EricAhn staff 238 Nov 17 22:14 . drwxr-xr-x 5 EricAhn staff 170 Nov 12 15:21 .. -rw-r--r-- 1 EricAhn staff 5985 Nov 17 22:23 api-guide.adoc drwxr-xr-x 6 EricAhn staff 204 Nov 17 22:12 error-example -rw-r--r-- 1 EricAhn staff 5888 Nov 12 15:21 getting-started-guide.adoc
$ asciidoctor api-guide.adoc
$ ls drwxr-xr-x 7 EricAhn staff 238 Nov 17 22:14 . drwxr-xr-x 5 EricAhn staff 170 Nov 12 15:21 .. -rw-r--r-- 1 EricAhn staff 5985 Nov 17 22:23 api-guide.adoc -rw-r--r-- 1 EricAhn staff 52057 Nov 17 22:13 api-guide.html drwxr-xr-x 6 EricAhn staff 204 Nov 17 22:12 error-example -rw-r--r-- 1 EricAhn staff 5888 Nov 12 15:21 getting-started-guide.adoc
=> Open browser : api-guide.html
response-fields.adoc
http-response.adoc