Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

DS's TechBlog

[Spring] Swagger 공통 응답 코드 처리 및 Enum으로 정의한 응답 코드 사용하기 본문

Java & Spring

[Spring] Swagger 공통 응답 코드 처리 및 Enum으로 정의한 응답 코드 사용하기

dsjo 2024. 5. 3. 18:07

Swagger로 API 문서를 작성하는 데 몇 가지 문제가 있었습니다.

1. 공통 응답 코드는 Swagger-ui에 표시되지 않습니다.

2. Enum으로 정의한 응답 코드와 메시지를 @ApiResponse에서 사용할 수 없습니다.

문제 코드

문제 상황

위와 같이, 중복 응답 코드에 대해서는 먼저 작성한 구문에 대해서만 동작하는 것을 볼 수 있습니다.

해결 방법

https://leeeeeyeon-dev.tistory.com/92#google_vignette

 

[Packy] Swagger @ApiResponses를 커스텀 어노테이션으로 대체하기

1. @ApiResponses의 한계 Swagger에서 정상 응답값과 함께 에러 응답값도 명시해주어야 클라이언트 단에서도 에러 응답값에 대한 예외 처리를 할 수 있다. Swagger에서는 기본적으로 아래와 같이 @ApiRespon

leeeeeyeon-dev.tistory.com

위 블로그에서 이와 같은 고민들을 다루고 있었습니다. 

기본적인 뼈대는 똑같습니다. 에러 코드 뿐 아니라, 성공 코드도 반환할 수 있도록 코드를 추가하였습니다.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiSuccessCodeExample {
    SuccessCode value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiSuccessCodeExamples {
    SuccessCode[] value();
}

note) SuccessCode는 HttpStatus와 성공여부, 메시지를 변수로 가지는 enum 클래스입니다.

@Configuration
public class SwaggerConfig {
    
    /* 생략 */

    @Bean
    public OperationCustomizer customize() {
        return (Operation operation, HandlerMethod handlerMethod) -> {
        
        /* 생략 */
        
            ApiSuccessCodeExamples apiSuccessCodeExamples = handlerMethod.getMethodAnnotation(
                    ApiSuccessCodeExamples.class);

            if (apiSuccessCodeExamples != null) {
                generateResponseCodeResponseExample(operation, apiSuccessCodeExamples.value());
            } else {
                ApiSuccessCodeExample apiSuccessCodeExample = handlerMethod.getMethodAnnotation(
                        ApiSuccessCodeExample.class);

                if (apiSuccessCodeExample != null) {
                    generateResponseCodeResponseExample(operation, apiSuccessCodeExample.value());
                }
            }

            return operation;
        };
    }

	// ResponseCode는 SuccessCode와 ErrorCode의 인터페이스입니다.
    private void generateResponseCodeResponseExample(Operation operation, ResponseCode[] responseCodes) {
        ApiResponses responses = operation.getResponses();

        Map<Integer, List<ExampleHolder>> statusWithExampleHolders = Arrays.stream(responseCodes)
                .map(
                        responseCode -> ExampleHolder.builder()
                                .holder(getSwaggerExample(responseCode))
                                .httpStatus(responseCode.getHttpStatus().value())
                                .name(responseCode.name())
                                .build()
                )
                .collect(Collectors.groupingBy(ExampleHolder::getHttpStatus));

        addExamplesToResponses(responses, statusWithExampleHolders);
    }

    private void generateResponseCodeResponseExample(Operation operation, ResponseCode responseCode) {
        ApiResponses responses = operation.getResponses();

        // ExampleHolder 객체 생성 및 ApiResponses에 추가
        ExampleHolder exampleHolder = ExampleHolder.builder()
                .holder(getSwaggerExample(responseCode))
                .name(responseCode.name())
                .httpStatus(responseCode.getHttpStatus().value())
                .build();

        addExamplesToResponses(responses, exampleHolder);
    }

    private Example getSwaggerExample(ResponseCode responseCode) {
        Example example = new Example();

        if (responseCode instanceof ErrorCode) {
            List<ErrorResponse.ValidationError> validationErrorList = new ArrayList<>();
            
            // Contoller의 @Valid에서 생긴 에러는 ErrorResponse에 에러 세부 필드를 추가해줍니다.
            if (responseCode == ErrorCode.INVALID_PARAMETER) {
                FieldError fieldError = new FieldError("objectName", "field", "defaultMessage");
                validationErrorList.add(ErrorResponse.ValidationError.of(fieldError));
            }

            ErrorResponse errorResponse = ErrorResponse.builder()
                    .code(((ErrorCode) responseCode).getCode())
                    .message(responseCode.getMessage())
                    .errors(validationErrorList)
                    .build();

            example.setValue(errorResponse);

            return example;
        }


        SuccessResponse successResponse = SuccessResponse.builder()
                .success(((SuccessCode) responseCode).getSuccess())
                .message(responseCode.getMessage())
                .build();

        example.setValue(successResponse);

        return example;
    }
    
     /* 생략 */
}

전체적인 코드와 흐름에 대한 설명은 위 블로그에 자세하게 작성되어 있어서, 추가하거나 변경한 부분만 작성했습니다.

 

코드 변경점

  1. 에러 코드와 성공 코드를 같이 처리하기 위해서 ErrorCode SuccessCode를 구현체로 가지는 ResponseCode 인터페이스를 작성해야 합니다.
  2. 성공 코드를 반환할 때 사용할 ApiSuccessCodeExampleApiSuccessCodeExamples 를 작성해줍니다.
  3. @Valid에서 발생하는 에러에 추가 되는 errors 필드를 응답에 포함하기 위해서, FiledError 를 추가했습니다. (errors 필드는 @JsonInclude(JsonInclude.Include.NON_EMPTY) 이 추가되어 있어서, 값이 없을 때는 응답에 포함되지 않습니다.)

적용 코드

    @ApiSuccessCodeExample(SuccessCode.JOIN_SUCCESS)
    @ApiErrorCodeExamples({ErrorCode.DUPLICATED_USER_ID, ErrorCode.DUPLICATED_STUDENT_NUMBER, ErrorCode.INVALID_PARAMETER})
    @PostMapping
    public ResponseEntity<SuccessResponse> resisterUser(@RequestBody @Valid JoinDTO joinDTO) {
        joinService.createUserAccount(joinDTO);

        return ResponseUtil.buildSuccessResponseEntity(SuccessCode.JOIN_SUCCESS);
    }

적용 결과

이렇게 해서, Swagger-UI에 공통 응답 코드를 처리하는 방법과 Enum으로 정의한 응답 코드와 메시지를 바로 사용할 수 있습니다.