Spring Rest Docs 사용기

mynameisjaehoon·2023년 12월 28일
0
post-thumbnail

서론

기존에 프로젝트를 진행하면서 API문서를 레포지토리 Wiki에 작성하고 있었다. 하지만 API가 변경될 때마다 직접 Wiki를 수정해주어야 했고, 심지어는 중간에 잘못된 내용이 들어가는 문제도 발생하였다. 그래서 자동으로 API 문서를 만들어주는 도구를 사용해야겠다고 생각했고 그 후보군에 Swagger와 Spring Rest Docs 두가지가 들어왔다.

사용하기 전에 먼저 두 도구에 대해 조사해보니 확연한 차이가 있었다.
간단히 두 도구에 대한 차이점을 말하자면

  • Swagger
    - 운영코드에 문서화 코드가 포함된다.
    • Spring Rest Docs에 비해 비교적 간단함.
    • 문서가 완성되면 직접 동작을 테스트해볼 수 있다.
    • UI가 이쁘더라
  • Spring Rest Docs
    - 테스트 코드에 문서화 코드가 포함된다.
    • 테스트 코드를 작성하고, 테스트가 통과되어야만 문서를 작성할 수 있다.
    • Swagger와는 다르게 기본적으로 동작테스트는 안된다. 하려면 추가적인 작업이 필요하다고 하더라.

처음에는 작성하기 더 쉽다는 Swagger를 선택하려 했다. 하지만 운영코드에 문서화 코드가 들어간다는 점이 마음에 들지 않았다. 몇몇 블로그에서 예시 코드들을 봤는데 운영코드에 Swagger 관련 애노테이션들이 덕지덕지 붙어있는 모습을 보고 마음을 돌렸다.
Spring Rest Docs는 테스트를 통과해야만 문서를 작성할 수 있고, 테스트 코드에 문서화 관련 코드를 적어야하기 때문에 작업량이 많다. 하지만 테스트를 꼭 통과해야한다는 점이 API문서의 신뢰도를 높여준다는 특징 때문에 Spring Rest Docs를 사용하기로 마음먹었다.

처음으로 Spring Rest Docs를 사용하면서 삽질을 굉장히 많이 했다. 그 과정에서 배운점이나, 나뿐만 아니라 다른 사람들도 흔하게 겪을 수 있는 오류에 대해서 정리해보려 한다.
이곳에 나오지 않은 추가적인 내용은 Spring Rest Docs 공식문서를 참고하자.

프로젝트 설정하기

build.gradle

plugins { 
    id "org.asciidoctor.jvm.convert" version "3.3.2"
}

configurations {
    asciidoctorExt 
}

dependencies {
    asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' 
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' 
}

ext { 
    snippetsDir = file('build/generated-snippets')
}

test { 
    outputs.dir snippetsDir
}

asciidoctor { 
    inputs.dir snippetsDir 
    configurations 'asciidoctorExt' 
    dependsOn test 
}

bootJar {
    dependsOn asciidoctor 
    from ("${asciidoctor.outputDir}/html5") { 
        into 'static/docs'
    }
}

전부 다 공식문서에 나와있는 내용이고, 플러그인과 설정부분은 넘어가고 그 다음부터 설명해보자.

의존관계 추가 부분을 먼저 살펴보면

asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'

asciidoctor 확장 라이브러리를 의존관계로 추가한다.
테스트 코드가 통과되면 테스트 코드에 포함되어 있는 문서화 코드를 바탕으로 .adoc 파일을 만들어준다.

  • .adoc 파일을 자동으로 만들어주고
  • .adoc 파일에서 매크로를 사용하여 스니펫 조각들을 연동하고
  • 최종적으로 .adoc 파일을 HTML로 만들어 export 하는 역할을 하는 라이브러리이다.

테스트가 통과되었을 때 만들어지는 .adoc 파일들의 위치는

ext { 
	snippetsDir = file('build/generated-snippets')
}

이 부분에서 설정된다. 여기서에서 만들어진 .adoc 파일은 build/generated-snippets에 위치하게 된다.

testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

  • restdocs-mockmvc의 testCompile을 구성하는 부분이다.
  • MockMvc를 이용해서 스니펫(snippet) 조각들을 뽑아낼 수 있도록 해준다.
  • MockMvc대신 WebTestClient를 사용하려면 sprint-restdocs-webtestclient를 추가하면 된다.
  • MockMvc대신 REST Assured를 사용하려면 spring-restdocs-restassured를 추가하면 된다.
asciidoctor { 
    inputs.dir snippetsDir 
    configurations 'asciidoctorExt' 
    dependsOn test 
}

asciidoctor 작업을 구성하는 부분이다.

  • snipeetsDir를 입력으로 구성하고
  • 위에서 작성한 asciidoctorExt 설정을 적용하고
  • test 이후에 작동하도록 하는 설정이다.
bootJar {
    dependsOn asciidoctor 
    from ("${asciidoctor.outputDir}/html5") { 
        into 'static/docs'
    }
}
  • 만들어진 .adoc 파일을 사용해서 만들어진 html 파일을 어느 디렉토리에 위치시킬지 설정하는 부분이다.
  • 이 방법을 사용하면 jar로 빌드될 때 문서가 옮겨지기 때문에 build 폴더내에서만 결과물을 확인할 수 있다.
    만약 IDE로 원하는 곳에서 바로 문서를 확인하고 싶다면 아래의 방법을 사용해야한다.

IDE에서 src/main/resources/static/docs 에서 완성된 html문서를 위치시키는 방법이다.

// static/docs 폴더 비우기
asciidoctor.doFirst {
    delete file('src/main/resources/static/docs')
}

// asccidoctor 작업 이후 생성된 HTML 파일을 static/docs 로 copy
task copyDocument(type: Copy) {
    dependsOn asciidoctor
    from file("build/docs/asciidoc")
    into file("src/main/resources/static/docs")
}

// build 의 의존작업 명시
build {
    dependsOn copyDocument
}

@AutoConfigureRestDocs

MockMvc를 이용해서 테스트할 때 테스트 코드에 @AutoConfigureRestDocs 애노테이션을 붙여주어야 한다. 테스트 클래스에 적용해서 Spring Rest Docs를 사용하는데 필요한 것들을 자동으로 설정해주는데 사용하는 애노테이션이다.
서블릿 웹 애플리케이션에서 MockMvc, WebTestClient, REST Assured 기반의 환경을 설정해준다. 만약 애노테이션을 붙이지 않으면 다음 에러가 발생한다.

java.lang.IllegalStateException: REST Docs configuration not found. Did you forget to apply a MockMvcRestDocumentationConfigurer when building the MockMvc instance?

Rest Docs 적용하기

내가 Rest Docs를 적용했던 사례들을 몇가지 가져오면서 어떤식으로 적용했는지 설명해보겠다.

경로변수 + 응답 바디로 JSON 반환

단일 퀴즈 조회 컨트롤러 메서드는 다음과 같다.

@GetMapping("/{quizId}")
public ResponseEntity<SingleQuizServiceResponse> findSingleQuiz(
    @PathVariable("quizId") Long quizId
) {
    //...
    return ResponseEntity
        .ok()
        .body(result);
}

간단하게 살펴보면

  • 경로 변수(Path Variable)이 존재한다.

  • 또 객체를 응답으로 전달하고있다. 응답으로 객체를 전달하면 JSON으로 변환되어 HTTP 응답메세지 바디에 담긴다.

    public class SingleQuizServiceResponse {
        private Long quizId;
        private String title;
        private String description;
        private String thumbnailData;
        private int likeCount;
        private int playTime;
        private List<QuestionServiceDto> questions;
        //...
    }
    
    public class QuestionServiceDto {
        private Long id;
        private String content;
        private int number;
        private String answer;
        private String attachmentData;
        private String attachmentType;
        private List<ChoiceServiceDto> choices;
    
        //...
    }
    
    public class ChoiceServiceDto {
        private int number;
        private String content;
    
        //...
    }   
  • 단순한 형태가 아니라 계층형구조의 다소 복잡한 JSON을 반환하는 경우이다.

mvc.perform(
    RestDocumentationRequestBuilders.get("/api/v1/quiz/{quizId}", testQuizId)
)
.andExpect(status().isOk())
.andDo(
    document(
        "단일 퀴즈 조회",
        preprocessRequest(prettyPrint()),
        preprocessResponse(prettyPrint()),
        HeaderDocumentation.responseHeaders(

        ),
        pathParameters(
            parameterWithName("quizId").description("퀴즈 ID")
        ),
        responseFields(
            fieldWithPath("quizId").description("퀴즈 ID").type(NUMBER),
            fieldWithPath("title").description("퀴즈 제목").type(STRING),
            fieldWithPath("description").description("퀴즈 설명").type(STRING),
            fieldWithPath("thumbnailData").optional().description("퀴즈 썸네일이미지 데이터").type(STRING),
            fieldWithPath("likeCount").description("퀴즈 좋아요 수").type(NUMBER),
            fieldWithPath("playTime").description("퀴즈 플레이 횟수").type(NUMBER),
            fieldWithPath("questions").description("문제 목록"),
            fieldWithPath("questions[].id").description("문제 ID").type(NUMBER),
            fieldWithPath("questions[].content").description("문제 내용").type(STRING),
            fieldWithPath("questions[].number").description("문제 번호").type(NUMBER),
            fieldWithPath("questions[].answer").description("문제 정답").type(STRING),
            fieldWithPath("questions[].attachmentData").optional().description("문제 첨부파일 데이터").type(STRING),
            fieldWithPath("questions[].attachmentType").optional().description("문제 첨부파일 타입").type(STRING),
            fieldWithPath("questions[].choices[].number").description("선택지 번호").type(NUMBER),
            fieldWithPath("questions[].choices[].content").description("선택지 내용").type(STRING)
        )
    )
);

한줄 한줄 자세히 살펴보자.

RestDocumentationRequestBuilders

원래 MockMvc를 이용해서 테스트할 때는 perform 메서드 안에 요청정보를 넘겨주면서 요청 URI를 MockHttpServletRequestBuilder의 메서드로 전달한다. 하지만 이곳에서는 RestDocumentationRequestBuilders를 사용하고 있다.

RestDocumentationRequestBuildersMockMvcRequestBuilders를 확장한 클래스로, REST Docs의 문서화 기능을 추가적으로 제공하는 클래스이다.

MockMvcRequestBuilders와 함께 사용되면서 API의 경로변수를 문서화 하는데 특화되어 있다. 문서에 경로변수를 포함하기 위해서는 꼭 RestDocumentationRequestBuilders를 사용해서 URI, HTTP Method를 표현해야한다. 만약 기존처럼 MockHttpServletRequestBuilder를 사용하면 다음 에러를 맞이하게 된다.

java.lang.IllegalArgumentException: urlTemplate not found. If you are using MockMvc did you use RestDocumentationRequestBuilders to build the request?

.andExpect(status().isOk())

그냥 테스트 코드를 작성할 때도 볼수 있는 익숙한 코드이다. 응답으로 HTTP 200 OK 상태코드가 올것을 기대한다.

MockMvcRestDocumentation.document(...)

MockMvcRestDocumentation클래스의 document 메서드는 Spring Rest Docs를 작성할 때 가장 중요한 부분이다. 이 내부에 담기는 정보를 바탕으로 테스트가 성공했을 때 .adoc문서들이 생성된다.

  • 첫번째 매개변수로는 문서의 식별자를 전달한다. 여기서 적은 이름이 관련된 .adoc 문서가 담기는 폴더 이름이 된다.

  • 두번째와 세번째 매개변수로 전처리기를 전달할 수 있다.

    • OperationRequestProcessor
    • OperationResponseProcessor
    • 나는 Preprocessor 클래스의 preProcessorRequest, preProcessorResponse를 호출해서 전달하였다.
    • 매개변수로 넘겨진 prettyPrint()는 JSON을 형식에 맞추어 개행해주는등, 문서를 예쁘게 출력하는데 쓰인다. 만약 사용하지 않으면 JSON이 한줄로 출력된다.
  • 이후로는 스니펫과 관련된 부분이 나온다.

RequestDocumentation.pathParameters(...)

pathParameters(
    parameterWithName("quizId").description("퀴즈 ID")
),

경로변수에 대한 정보를 표현하는 스니펫이다. 나는 RequestDocumentation클래스의 parameterWithName 만 사용해서 ParameterDiscriptor만 전달해주었는데 List, Map으로도 속성을 전달할 수 있는 것 같다.

description 메서드로 연결해서 설명을 추가할 수 있다.

RequesetDocumentation.requestFields(...)

responseFields(
    fieldWithPath("quizId").description("퀴즈 ID").type(NUMBER),
    fieldWithPath("title").description("퀴즈 제목").type(STRING),
    fieldWithPath("description").description("퀴즈 설명").type(STRING),
    fieldWithPath("thumbnailData").optional().description("퀴즈 썸네일이미지 데이터").type(STRING),
    fieldWithPath("likeCount").description("퀴즈 좋아요 수").type(NUMBER),
    fieldWithPath("playTime").description("퀴즈 플레이 횟수").type(NUMBER),
    subsectionWithPath("questions").description("문제 목록"),
    fieldWithPath("questions[].id").description("문제 ID").type(NUMBER),
    fieldWithPath("questions[].content").description("문제 내용").type(STRING),
    fieldWithPath("questions[].number").description("문제 번호").type(NUMBER),
    fieldWithPath("questions[].answer").description("문제 정답").type(STRING),
    fieldWithPath("questions[].attachmentData").optional().description("문제 첨부파일 데이터").type(STRING),
    fieldWithPath("questions[].attachmentType").optional().description("문제 첨부파일 타입").type(STRING),
    fieldWithPath("questions[].choices[].number").description("선택지 번호").type(NUMBER),
    fieldWithPath("questions[].choices[].content").description("선택지 내용").type(STRING)
)

요청 JSON으로 필드를 나타내는 부분이다. PayloadDocumentation.fieldWithPath 메서드를 사용해서 표현하는데, 복잡한 JSON의 경우 표현식으로 나타내야한다. 표현식이 틀리면 에러가 발생한다.

공식문서에도 나와있듯이 만약 다음과 같은 JSON이 있을 때

{
	"a":{
		"b":[
			{
				"c":"one"
			},
			{
				"c":"two"
			},
			{
				"d":"three"
			}
		],
		"e.dot" : "four"
	}
}
  • a: b를 포함하고 있는 객체 a를 표현함
  • a.b: a객체 내부에 있는 b를 표현함
  • a.b[].c: a객체 내부에 있는 b리스트의 c를 표현함
  • a.b[].d: a객체 내부에 있는 b리스트의 d를 표현함
  • a[e.dot]: a객체 내부에 있는 e.dot 값을 표현함

이런식으로 정해져있는 표현식으로 JSON에서의 위치를 표현할 수 있다.

  • description을 통해서 속성에 대한 설명을 추가할 수 있다.
  • type을 통해서 속성의 타입을 추가할 수 있다.
    • type 메서드가 Object 타입을 파라미터로 받기 때문에 자바에서사용하는 Integer.class, Long.class, String.class ... 등을 사용해도 오류가 발생하지 않지만 나중에 문서로 보면 패키지명이 모두 표현되기 때문에 좋지 않다. 그리고 API를 사용하는 클라이언트 개발자 입장에서는 없는 타입일 수 있기 때문에 혼동을 야기할 수 있다. 그래서 보통은 JsonFieldType 열거형에 정의된 타입이름을 사용한다.
  • optional()을 통해서 속성이 필수가 아니라는 것을 표현할 수 있다.

위 코드에서는 fieldWithPath만 사용해서 필드를 표현하였다. 하지만 모든 속성에 대한 요청을 표현할 필요가 없는 상황이 있을 수 있다. subSectionWithPath를 사용하면 대상 필드의 하위에 속성이 있을 때 표현하지 않아도 에러가 발생하지 않게 해준다.

responseFields(
    fieldWithPath("quizId").description("퀴즈 ID").type(NUMBER),
    fieldWithPath("title").description("퀴즈 제목").type(STRING),
    fieldWithPath("description").description("퀴즈 설명").type(STRING),
    fieldWithPath("thumbnailData").optional().description("퀴즈 썸네일이미지 데이터").type(STRING),
    fieldWithPath("likeCount").description("퀴즈 좋아요 수").type(NUMBER),
    fieldWithPath("playTime").description("퀴즈 플레이 횟수").type(NUMBER),
    subSectionWithPath("questions").description("문제 목록"),
)

questions에 subSectionWithPath를 사용함으로써 questions 하위의 속성들을 표현해주지 않아도 오류가 발생하지 않는다.
하지만 나는 이런 요청속성의 경우에는 클라이트트가 필요한 모든 것을 표현해주어야 한다고 생각해 사용하지 않았다. 중요하지 않다고 생각되는 응답 필드에만 간간히 사용해주었다.

이렇게 해서 단일 퀴즈 조회에 대한 문서화를 완료 했다. .adoc 파일을 만들고 문서를 조회하는 부분은 이후에 알아보고 우선은아직 다루지않은 내용들에 대해서 좀더 설명한다.

multipart/form-data 을 사용할 때

퀴즈를 생성할 때는 퀴즈 생성정보에 대한 폼 데이터와 퀴즈의 썸네일로 사용할 이미지를 모두 전달 받아야 했기 때문에 Content-Type으로 multipart/form-data를 받는다. 단순히 HTTP 요청메세지 바디로 폼 데이터를 담은 JSON만을 전달받는 것과 multipart/form-data를 사용하는 것과는 문서화 할때 차이점이 있다.

먼저 컨트롤러 코드를 살펴보자

@PostMapping(value = "/new", consumes = {MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<Void> createQuiz(
    @RequestPart(value = "form") QuizCreateRequestForm form,
    @RequestPart(value = "thumbnail", required = false) MultipartFile thumbnail
) {
    //...
    return ResponseEntity
        .created(URI.create("/api/v1/quiz/" + createdQuiz.getId()))
        .build();
}
  • multipart/form-data을 허용하고 있다.
  • form이라는 이름으로 QuizCreateRequestForm 형식의 JSON을 받고있다.
  • thumbnail이라는 이름으로 이미지 데이터를 MultiaprtFile로 받고있다. 필수 요청데이터가 아니다.

MockMultipartFile 만들기

테스트를 위해서 사용할 MultipartFile을 만들어야한다.

QuizCreateRequestForm requestForm = new QuizCreateRequestForm("quiz name", "12345", "example new quiz");
String formJson = objectMapper.writeValueAsString(requestForm);

MockMultipartFile formFile = new MockMultipartFile("form", "", "application/json", formJson.getBytes());
MockMultipartFile imageFile = new MockMultipartFile("thumbnail", "", "image/png", (byte[]) null);
mvc.perform(
        multipart("/api/v1/quiz/new")
            .file(formFile)
            .file(imageFile)
            .accept(APPLICATION_JSON, IMAGE_PNG, IMAGE_JPEG)
    )
    .andDo(print())
    .andExpect(status().isCreated())
    .andDo(
        document(
            "퀴즈 생성",
            preprocessRequest(prettyPrint()),
            preprocessResponse(prettyPrint()),
            requestPartBody("form"),
            requestPartFields(
                "form",
                fieldWithPath("title").type(STRING).description("퀴즈 이름"),
                fieldWithPath("password").type(STRING).description("퀴즈 비밀번호"),
                fieldWithPath("description").type(STRING).description("퀴즈 설명")
            )
        )
    );
  • multipart/form-data 를 테스트할 때는 요청 URL을 전달할 때 multipart() 메서드를 사용해서 file을 전달해야한다. 여기서는 경로변수가 없기 때문에 RestDocumentationRequestBuilders가 아니라 MockMvcRequestBuilders의 메서드를 사용하였다.
  • accept 메서드를 사용해서 허용하는 데이터 형식을 표시해주어야한다. 위 코드에서는 application/json, image/png, image/jpg 를 허용하고있다.
  • HTTP 요청 메세지 바디로 JSON만을 전달 받을 때와는 다르게 요청데이터를 문서로 나타내기 위해서 requestPartBody, requestPartFields를 사용해야한다.
  • 메서드의 첫번째 인자로는 요청 파트의 이름을 전달한다. 이 이름을 바탕으로 실제 요청의 파트를 찾아서 .adoc 문서에 포함시켜준다.
  • 전달한 요청파트의 이름이 생성되는 .adoc 파일의 이름을 결정한다.
    • request-part-${전달한 이름}-body.adoc
    • request-part-${전달한 이름}-fields.adoc

multipart/form-data, POST 이외의 메서드를 사용할 때

퀴즈 수정 컨트롤러는 퀴즈에 대한 정보와 함께 퀴즈의 썸네일 데이터를 받기 때문에 퀴즈 생성때와 동일하게 multipart/form-data를 허용한다. 다른점은 POST메서드 대신 PUT메서드를 사용한다는 점이다.

multipart/form-data를 사용할 때는 앞에서 보았던 대로 get(), post(), delete() 같은 메서드가 아니라 multipart() 메서드로 요청 정보를 표현하는 것에 대해 살펴보았다. 그런데 이 방법의 문제점은 multipart() 메서드가 항상 POST 로만 요청한다는 점이다. 만약 다른 메서드를 사용하고 싶다면 multipart() 메서드의 반환타입인 MockMultipartHttpServletRequestBuilder의 with 메서드를 사용해서 미리 구현한 다음 사용해야한다.

MockMultipartHttpServletRequestBuilder builder = RestDocumentationRequestBuilders.multipart("/api/v1/quiz/{quizId}/edit", testQuizId);

builder.with(request -> {
    request.setMethod("PUT");
    return request;
});

mvc.perform(
        builder
            .file(form)
            .file(thumbnail)
            .accept(APPLICATION_JSON, IMAGE_PNG, IMAGE_JPEG)
    )
    //...
);

경로 변수를 문서에서 표현해야하기 때문에 ResetDocumentationRequestBuilders의 메서드를 사용하는 것을 볼 수 있다.

문서 생성하기

모든 테스트 코드를 작성하고 나서 테스트를 수행하거나 ./gradlew clean build 명령으로 빌드를 했다면 테스트가 수행되면서 .adoc 문서파일이 생성된다. 우리는 build.gradle 설정파일에서 테스트를 마치면 build/generated-snippets 디렉토리에 문서파일이 생성되도록 설정했었다.

생성되는 .adoc 파일에는 기본적으로 생성되는 것이 있고, 특정한 경우에 생성되는 파일이 있다.
예를 들어서 경로 변수관련 정보가 있을 때만 path-parameters.adoc 문서가 생성된다.

기본적으로 생성되는 문서 목록은 다음과 같다.

  • curl-request.adoc
  • http-request.adoc
  • httpie-request.adoc
  • http-response.adoc
  • request-body.adoc
  • respons-body.adoc

테스트 코드에 따라 추가적으로 생성되는 문서목록은 다음과 같다. 이것 말고도 다른것들도 있다.

  • response-fields.adoc
  • request-parameters.adoc
  • path-parameters.adoc
  • request-part-${내가지정한 이름}-body.adoc
  • request-part-${내가지정한 이름}-fields.adoc

이제 이렇게 만들어진 조각(snippet)을 가지고 실제 문서를 만들어볼 차례이다.

IntelliJ에서 AsciiDoc 플러그인을 설치하면 편리하게 .adoc 문서를 편집할 수 있다.

  1. main/resource/static/docs 디렉토리를 만들어준다. 위에서 했던 gradle 설정에서 html 문서 경로설정을 했다면 완성된 html 파일이 이곳에 위치하게 된다.
  2. src/docs/asciidoc 디렉토리 안에 index.adoc 파일을 만들어준다. 이 index.adoc 파일을 바탕으로 html파일이 만들어진다.
  3. 그리고 나서 index.adoc파일을 수정한다.
= Harpseal API Docs
Harseal API 문서
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:sectlinks:

[[퀴즈-목록-조회]]
=== 퀴즈 목록 조회
operation::퀴즈 목록 조회[snippets='http-request,query-parameters,request-fields,http-response,response-fields']
  • :toc: left Table Of Content를 문서의 좌측에 둡니다.
  • :source-highlighter: highlightjs 문서에 표기되는 코드의 하이트라이트를 highlightjs를 사용합니다.
  • =, ==, ===: 마크다운의 #, ##, ###에 해당합니다.
  • [[텍스트]]: 해당 텍스트에 링크를 겁니다.
  • operation::디렉토리명[snippets='원하는 스니펫 조각']: 원하는 스니펫 조각을 가져와 반영합니다.
    • 예를 들어서 퀴즈 생성 디렉토리에 있는 http-request.snippet, http-response.snippet을 반영하고 싶다면
      operation::퀴즈 생성[snippet='http-request,http-response']
      처럼 작성하면 됩니다.

추가적으로 AsciiDoc 문법에 더 자세히 알고싶다면 AsciiDoc 기본 사용법을 참고해주세용

이렇게 문서를 편집하고 난후 빌드를 해주면 html 파일이 생성됩니다.

문서 커스텀하기

그런데 기본적으로 제공되는 내용말고도 추가적으로 정보를 제공하고 싶을 수도 있습니다.
테스트 코드에서 작성했지만 타입(type)이나 필수여부(optional)등이 문서에는 반영되어 있지 않습니다.

추가적인 정보를 제공하기 위해서 사용자가 원하는 문서 형식으로 커스텀해야 합니다.

src/test/resources/org/springframework/templates/asciidoctor 내부에 사용자가 원하는 .snippet 문서를 이름 규칙만 지켜서 정의해두면 테스트가 실행되면서 build/generated-snippets에 기본형식의 스니펫 대신 사용자가 커스텀한 형식의 스니펫을 등록해줍니다.

만약 http-request.adoc을 커스텀하고 싶다면 src/test/resources/org/springframework/templates/asciidoctorhttp-request.snippet을 정의하면됩니다.

참고로 저는 request-part-form-fields.adoc의 커스텀 형식이 그대로 이 이름을 따르는 줄 착각해 2시간을 날렸습니다. form은 등록한 문서의 이름으로 문서를 생성할 때 들어가는 이름이기 때문에 기본형식이 아니였기 때문입니다. 기본형식 이름은 request-part-fields.adoc 이였습니다. 커스텀 문서를 만들때는 항상 기본형식 이름을 사전에 확인합시다... 기본형식의 이름에서 default만 빼면 됩니다.

.snippet 문서는 mustache 문법을 사용한다고 한다.

|===
|파라미터|설명
{{#parameters}}
|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/parameters}}
|===

기본 스니펫들을 살펴보고 원하는 컬럼의 이름을 수정하거나 원하는 컬럼을 넣을 수 있다.

0개의 댓글