옌의 로그

[Spring] Swagger에서 ErrorCode Enum 자동화하기 본문

스터디/스프링

[Spring] Swagger에서 ErrorCode Enum 자동화하기

dev-yen 2025. 8. 27. 09:27

1.  구현 배경

client 개발자들과 협업하며, api 마다 발생할 수 있는 에러 응답에 대해 ui로 편하게 볼 수 있게 할 목적으로 작업하게 되었다.

Swagger config 빈이 생성될 때 OperationCustomizer가 동작해 자동으로 ErrorType enum을 읽어와 각 API에 해당하는 에러 코드 예시를 자동 등록하고, UI에서 보기 쉽게 표시하는 구조를 만들게 되었다.

 


 

2. 에러 코드는 관리

에러 코드는 다음과 같이 enum으로 정의했다.

public enum ErrorType {
    INVALID_PARAMETER(HttpStatus.BAD_REQUEST, 1001, "잘못된 요청입니다."),
    UNAUTHORIZED(HttpStatus.UNAUTHORIZED, 1002, "인증이 필요합니다."),
    PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, 2001, "상품을 찾을 수 없습니다."),
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 9999, "알 수 없는 서버 오류입니다.");

    private final HttpStatus status;
    private final int code;
    private final String message;

    // ... getter 생략
}

 

 

 

3. 각 API에서 발생 가능한 에러 지정

각 API 메서드에는 커스텀 애너테이션을 활용했다.

@ApiErrorCodeExample({ErrorType.INVALID_PARAMETER, ErrorType.PRODUCT_NOT_FOUND})
@GetMapping("/products/{id}")
public ResponseEntity<ProductResponse> getProduct(@PathVariable Long id) {
    ...
}

이렇게 하면 Swagger 설정 클래스에서 해당 애너테이션을 읽어 자동으로 해당 API의 에러 예시를 구성할 수 있다.

 


 

4. SwaggerConfig 설정

SwaggerConfig의 주요 흐름

  • 모든 API 요청에 대해 Swagger Operation 커스터마이저가 동작
  • @ApiErrorCodeExample에 지정된 에러 타입을 읽음
@Bean
public OperationCustomizer customize() {
    return (Operation operation, HandlerMethod handlerMethod) -> {
        ApiErrorCodeExample apiErrorCodeExample = handlerMethod.getMethodAnnotation(ApiErrorCodeExample.class);

        if (apiErrorCodeExample != null) {
            generateErrorCodeResponseExample(operation, apiErrorCodeExample.value());
        }

        return operation;
    };
}

@ApiErrorCodeExample이 붙은 메서드만 골라서 ErrorType[]을 추출하고, 에러 응답을 만들어준다.

 

 

generateErrorCodeResponseExample

  • 각 에러에 대해 응답 예시를 구성하고 Swagger에 추가
  • INTERNAL_SERVER_ERROR는 항상 추가
private void generateErrorCodeResponseExample(Operation operation, ErrorType[] errorTypes) {
    ApiResponses responses = operation.getResponses();

    List<ErrorType> errorTypesList = Arrays.asList(errorTypes);

    ErrorType[] filteredErrorTypes = Arrays.stream(ErrorType.values())
        .filter(errorType -> errorTypesList.contains(errorType) ||
                             errorType.getStatus().equals(HttpStatus.INTERNAL_SERVER_ERROR))
        .sorted(Comparator.comparing(ErrorType::getStatus))
        .toArray(ErrorType[]::new);

    Map<HttpStatus, List<ExampleHolder>> statusWithExampleHolders =
        Arrays.stream(filteredErrorTypes)
            .map(errorCode -> ExampleHolder.builder()
                    .statusCode(errorCode.getStatus())
                    .holder(getSwaggerExample(errorCode))
                    .errorCode(errorCode.getCode())
                    .errorMessage(errorCode.getMessage())
                    .build())
            .collect(groupingBy(ExampleHolder::getStatusCode));

    addExamplesToResponses(responses, statusWithExampleHolders);
}

→ 각 HttpStatus별로 에러 코드를 그룹핑하여, Swagger UI에서 구분해 보여줄 수 있도록 구성한다.

 

 

addExamplesToResponses

private void addExamplesToResponses(ApiResponses responses, Map<HttpStatus, List<ExampleHolder>> statusWithExampleHolders) {
    statusWithExampleHolders.forEach((status, exampleHolders) -> {
        Content content = new Content();
        MediaType mediaType = new MediaType();
        ApiResponse apiResponse = new ApiResponse();

        exampleHolders.forEach(holder -> {
            mediaType.addExamples(String.valueOf(holder.getErrorCode()), holder.getHolder());
        });

        content.addMediaType("application/json", mediaType);
        apiResponse.setContent(content);

        responses.addApiResponse(status.toString(), apiResponse);
    });
}

 

 

 

 

결과 (Swagger UI)

API의 응답 예시 아래에, 아래와 같은 JSON이 상태코드별로 자동 표시된다

{
  "title": "상품을 찾을 수 없습니다.",
  "code": 4042001,
  "message": "상품을 찾을 수 없습니다."
}
  • 코드 계산 방식: HTTP 상태코드 * 10000 + 내부에러코드 → 예: 4042001
  • 클라이언트에서는 이 코드 기반으로 분기 처리 가능
  • Swagger UI에서도 에러코드 목록이 “example”로 자동 노출되어 확인 가능

 


 

 

마무리하며 . .  .

이번 작업을 통해 얻은 가장 큰 이점은:

 

  • 에러 코드를 관리하는 곳은 그대로 두되,
  • Swagger 문서가 해당 코드를 자동으로 API마다 예시로 표시해준다는 점이다.

 

클라이언트에서는 Swagger를 파싱해서 select box로 표시하거나, 자동화된 에러 메시지 대응에도 사용할 수 있다.

 

무엇보다도, 매번 @ApiResponse를 일일이 적지 않아도 된다는 점에서 유지보수성이 매우 높아졌고, 에러 코드의 통일성도 확보할 수 있었다. -_-v

 

Comments