Swagger 작성 가이드 : 예시와 함께 이해하는 API Swagger 작성 방법

0. swagger 확인할 수 있는 url 경로

  • 로컬 경로 뒤에 /swagger-ui/index.html를 붙여준다.
<Example>

http://localhost:5100/swagger-ui/index.html

1. Controller

<POST Example>

@Operation(summary = "회원가입", method = "POST", description = "사용자가 회원가입을 한다.")
@ResponseStatus(code = HttpStatus.OK)
@ApiResponseWithErrorCode({
    @ResponseDataAnnotation(
            statusCode = INVALID_INPUT_DATA,
            description = "입력한 데이터가 유효하지 않을 경우"),
    @ResponseDataAnnotation(
            statusCode = DUPLICATED,
            description = "중복된 데이터로 회원가입 요청을 했을 경우")
})
@PostMapping(path = "/user/signUp")
@ResponseBody
public ResponseEntity<ResponseData<Void>> signUp(
        @RequestBody @Valid SignUpRequestDTO signUpRequestDTO) throws Exception {

    userService.signUp(signUpRequestDTO);

    return ResponseUtility.createPostSyncSuccessResponse();
}
<GET Example>

@Operation(
        summary = "get user list",
        method = "GET",
        description = "검색어를 받아 유저 리스트를 페이지 단위로 조회하고 반환한다.",
        parameters = {
            @Parameter(name = "userName"),
            @Parameter(name = "userGrade", description = "user의 등급 ex)VIP, PREMIUM"),
            @Parameter(name = "page", description = "page number ex)1"),
            @Parameter(name = "size", description = "paging size ex)10"),
            @Parameter(name = "sort", description = "sort ex)lastUpdateDatetime,desc"),
        })
@GetMapping(path = "/user/users")
@ResponseBody
public ResponseEntity<ResponsePagingData<UserListResponseDTO>> retrieveUserList(
        @RequestParam(required = false) String userName,
        @RequestParam(required = false) String userGrade,
        @RequestParam(required = false, defaultValue = "1") Integer page,
        @RequestParam(required = false, defaultValue = "10") Integer size,
        @RequestParam(required = false, defaultValue = "lastUpdateDatetime,desc") String sort) {

        Pageable pageRequest = CustomPageRequest.of(page, size, sort);
        return ResponseUtility.createSuccessPagingResponse(
                userService.retrieveUserList(
                        userName, userGrade, pageRequest));

}

1.1 @Operation

@Operation(
        summary = "get user list",
        method = "GET",
        description = "검색어를 받아 유저 리스트를 페이지 단위로 조회하고 반환한다.",
        parameters = {
            @Parameter(name = "userName"),
            @Parameter(name = "userGrade", description = "user의 등급 ex)VIP, PREMIUM"),
            @Parameter(name = "page", description = "page number ex)1"),
            @Parameter(name = "size", description = "paging size ex)10"),
            @Parameter(name = "sort", description = "sort ex)lastUpdateDatetime,desc"),
        })

1.2 @ApiResponse

@ApiResponse(responseCode = "201", description = "CREATED")
  • responseCode 가 200이 아닌 경우 아래 코드 추가 필요
@ResponseStatus(code = HttpStatus.CREATED)
  • custom annotation for error code format. (에러 케이스 표현에 사용. 메소드 내에서 발생 가능한 모든 에러 케이스를 작성할 것.)
@ApiResponseWithErrorCode({
    @ResponseDataAnnotation(
            statusCode = INVALID_INPUT_DATA,
            description = "입력한 데이터가 유효하지 않을 경우"),
    @ResponseDataAnnotation(
            statusCode = DUPLICATED,
            description = "중복된 데이터로 회원가입 요청을 했을 경우")
})
  • response의 schema를 직접 정의 가능 (꼭 필요한 경우에만 사용 권장. 기본은 컨트롤러 함수에 리턴 타입 명시.)
@ApiResponse(
        responseCode = "201",
        description = "CREATED",
        content =
                @Content(
                        mediaType = "application/json",
                        schema =
                                @Schema(
                                        implementation =
                                                IdSignInResponseDTO.class)))

1.3 @Parameter

생략...
        parameters = {
            @Parameter(name = "userName"),
            @Parameter(name = "userGrade", description = "user의 등급 ex)VIP, PREMIUM"),
            @Parameter(name = "page", description = "page number ex)1"),
            @Parameter(name = "size", description = "paging size ex)10"),
            @Parameter(name = "sort", description = "sort ex)lastUpdateDatetime,desc"),
        })
...중략...
public ResponseEntity<ResponsePagingData<UserListResponseDTO>> retrieveUserList(
        @RequestParam(required = false) String userName,
        @RequestParam(required = false) String userGrade,
        @RequestParam(required = false, defaultValue = "1") Integer page,
        @RequestParam(required = false, defaultValue = "10") Integer size,
        @RequestParam(required = false, defaultValue = "lastUpdateDatetime,desc") String sort) {
...생략
  • RequestParam과 함께 정의하여 사용. (기본 1:1 매핑, description 등 필요 없으면 생략 가능)
  • Parameter는 @Operation 내에 정의하도록 함.
  • x-correlation-id, x-language-code는 모두 공통이므로 @Parameter 입력하지 않음
  • 컨트롤러 함수의 리턴 값을 명시하여 swagger 상 표현되도록 함.
  • 이름만으로 용도를 알기 어려운 데이터는 description 상에 용도를 표현
  • 필수 파라미터 여부를 required로 명시할 것.

1.4 @io.swagger.v3.oas.annotations.parameters.RequestBody

public ResponseEntity<ResponseData<CollectionRequestResponseDTO>> updateCollection(
        @io.swagger.v3.oas.annotations.parameters.RequestBody(
                        content =
                                @Content(
                                        schema =
                                                @Schema(
                                                        implementation =
                                                                CollectionRequestResponseDTO
                                                                        .class)))
                @RequestBody
                HashMap<String, Object> changes,
        @PathVariable("collectionId") Integer collectionId)

Map의 사용은 지양하되, 부득이 사용하는 경우 @RequestBody를 직접 명시하여 swagger 상 표현되도록 함.

2. DTO

package io.naemo.centralwallet.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(callSuper = false)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class GoogleOTPKeyResponseDTO {

    @Schema(
            name = "googleOtpKey",
            description = "32자의 google OTP secret key.",
            example = "QGXKNGCQ3L36PTMFQQP5SHW3MYDEPS3J")
    private String googleOtpKey;

    @Schema(
            name = "googleOtpQRCodeUrl",
            description = "google OTP secret key의 QR 코드 주소. FE에서 QR코드 이미지로 변환해서 노출됨.",
            example =
                    "otpauth://totp/NAEMO%3Amp_member01%40gmail.com?secret=QGXKNGCQ3L36PTMFQQP5SHW3MYDEPS3J&issuer=NAEMO")
    private String googleOtpQRCodeUrl;

    @Builder
    public GoogleOTPKeyResponseDTO(String googleOtpKey, String googleOtpQRCodeUrl) {
        this.googleOtpKey = googleOtpKey;
        this.googleOtpQRCodeUrl = googoleOtpQRCodeUrl;
    }
}
  • API의 request, response class
  • @NotBlank, @NotNull, @Positive와 같은 @Valid는 request에서 사용.
  • 위와 같은 @Valid가 사용되었다면 MANDATORY_PARAM_ERR와 같은 @ApiResponseWithErrorCode 챙길 것.
  • 서로 다른 API에서 DTO를 공통으로 사용할 수 없다면 분리.
  • 코드성 데이터는 enum 타입으로 정의 혹은 description 상에 표현( ex. “N_ETHEREUM/N_SOLANA”)
  • 이름만으로 용도를 알기 어려운 데이터는 description 상에 용도를 표현

2.1 @Schema

@Schema(
        name = "googleOtpKey",
        description = "32자의 google OTP secret key.",
        example = "QGXKNGCQ3L36PTMFQQP5SHW3MYDEPS3J")

3. 작성시 주의 사항

  • INVALID_MEBER_INFORMATION → 공통 description으로 Member information is not founded by session. 사용
    • 사용 예시
@ApiResponseWithErrorCode({
    @ResponseDataAnnotation(
            statusCode = INVALID_MEMBER_INFORMATION,
            description = "Session에서 사용자 정보를 가져오지 못했을 경우")
})
  • 모든 예시 적어주기 - YN, true/false 포함 (...과 같은 축약어 사용하지 말 것.)

    • 사용예시

      ```java
      @Schema(name = "useYn", description = "해당 2FA 사용여부 ex) Y/N", example = "Y")
      @NotEmpty@Setterprivate String useYn;
      ```
      
    • description 상에 예시 작성

  • input data 관련 에러 리턴인 경우, input의 이름과 맞춰주기

    • ex) input으로 들어온 actionType에 대한 에러 처리 → INVALID_TRANSACTION가 아니라 INVALID_ACTION_TYPE으로 수정
    • 필요시 연관 로직/모듈테스트 함께 수정
  • 공통의 DTO를 사용하게 되면서 사용하지 않는 파라미터들의 불필요한 노출

    • 필요하다면 DTO 분리
    • 노출되지 않아도 된다면 @Schema(hidden = true)
    • 사용하지 않는 파라미터는 제거
    • enum 타입으로 선언하여 불필요한 코드값이 같이 노출되는 경우, description으로 사용 가능한 코드값 명시
  • 이름만으로 용도 확인이 가능하다면 추가 description을 달지 않아도 됨

    • 이름만으로 알 수 없다면 자세한 description 추가
    • 코드성의 데이터는 enum 사용 혹은 description/example 등에 모든 값 명시
    • paging의 sort도 가능한 정렬 조건 모두 표기
  • API에서 핸들링하는 exception 모두 표기

    • 필요한 경우 exception 내용 변경 또는 구체화

참고) OpenAPI Specification - Version 3.0.3 | Swagger OpenAPI 3 Library for spring-boot (springdoc.org)

댓글남기기