Programing/API

Swagger Springboot 3.x Response 설정 방법

리커니 2024. 3. 25. 09:25
반응형

Swagger Springboot 3.x Response 설정 방법입니다.

 

Swagger 의 다양한 설정 방법은 아래의 Link를 확인하세요!

 

Link : https://aljjabaegi.tistory.com/713

 

Swagger Springboot 3.x 의존성 주입 및 Information 설정, 기본 설정

Swagger Springboot 3.x 의존성 주입 및 Information 설정, 초기 세팅 방법입니다. 의존성 추가 Gradle 에 Spring-web starter 와 Swagger dependency를 추가합니다. dependencies { implementation 'org.springframework.boot:spring-boot-sta

aljjabaegi.tistory.com

 

Link : https://aljjabaegi.tistory.com/714

 

Swagger Springboot 3.x Operation 설정 방법

Swagger Springboot 3.x Operation 설정 방법입니다. 초기 설정 방법은 아래의 Link를 확인하세요. Link : https://aljjabaegi.tistory.com/713 Swagger Springboot 3.x 의존성 주입 및 Information 설정, 기본 설정 Swagger Springboot 3

aljjabaegi.tistory.com

 

Link : https://aljjabaegi.tistory.com/716

 

Swagger Springboot 3.x Spring.properties 설정 옵션

Swagger Springboot 3.x Spring.properties 설정 옵션입니다. 초기세팅 및 Information, Operation, Response 설정은 아래의 Link 를 확인하세요! Link : https://aljjabaegi.tistory.com/713 Swagger Springboot 3.x 의존성 주입 및 Informati

aljjabaegi.tistory.com

 

Link : https://aljjabaegi.tistory.com/717

 

Swagger Springboot 3.x JWT 인증 적용 방법

Swagger Springboot 3.x JWT 인증 적용 방법입니다. Swagger 초기 설정 및 Operation, Response, application.properties 관련 방법은 아래의 Link를 확인하세요! Link : https://aljjabaegi.tistory.com/713 Swagger Springboot 3.x 의존성

aljjabaegi.tistory.com

 

Link : https://aljjabaegi.tistory.com/718

 

Swagger Springboot 3.x Grouping 방법

Swagger Springboot 3.x Grouping 방법입니다. Swagger에 대한 다양한 설정 방법은 아래의 Link를 확인하세요! Link : https://aljjabaegi.tistory.com/713 Swagger Springboot 3.x 의존성 주입 및 Information 설정, 기본 설정 Swagger

aljjabaegi.tistory.com

 

Responses 설정

Operation 하단에는 해당 API 의 응답 정보가 표출됩니다.

기본적으로는 ResponseEntity 의 Generic type 이 추가 됩니다.

{key 변수명, value type}

 

 

정상 Response 자동 추가

200 code에 대한 정보는 record에서 변경할 수 있습니다.

클래스와 속성 위치에 둘 다 @Schema 를 사용하여 설정합니다.

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

@Schema(description = "멤버 조회 응답")
public record MemberResponse(
        @Schema(description = "멤버 ID", example = "geonlee")
        @NotNull
        @Size(min = 2, max = 50)
        String memberId,
        
        @Schema(description = "멤버 이름", example = "이건")
        @Size(min = 2, max = 100)
        String memberName) {
}

 

 

코드를 보시면 아시겠지만, Swagger에서는 Validation annotation 과 연계하여 동작합니다.

 

오류 Response 자동 추가

Exception 에 대한 응답 처리는 @ControllerAdvice와 연계하여 동작합니다.

확인을 위해 GlobalExceptionHandler 를 구현해 보겠습니다.

 

GlobalExceptionHandler.java

import com.aljjabaegi.swagger.api.config.exception.record.ErrorResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.NoHandlerFoundException;

import java.io.IOException;

@ControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(value = {IOException.class, NoSuchMethodError.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseEntity<ErrorResponse> handleInternalServerError(Exception e) {
        LOGGER.error(e.getMessage(), e);
        return ResponseEntity.ok()
                .header("Content-type", String.valueOf(MediaType.APPLICATION_JSON))
                .body(new ErrorResponse(500, "서버 오류입니다."));
    }

    @ExceptionHandler(value = NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseEntity<ErrorResponse> handleNotFoundError(Exception e) {
        LOGGER.error(e.getMessage(), e);
        return ResponseEntity.ok()
                .header("Content-type", String.valueOf(MediaType.APPLICATION_JSON))
                .body(new ErrorResponse(400, "요청하신 페이지를 찾을 수 없습니다."));
    }
}

 

@ControllerAdvice 의  @ResponseStatus 와 연계하여 자동으로 Respones 가 추가되게 됩니다.

하지만 ErrorResponse의 example로 표출되기 때문에 정확한 정보를 전달하기 어렵죠.

 

Response 수동 추가

자동으로 추가하지 않고, 따로 설정하고 싶을 때에는 메서드에 @ApiResponses, @ApiResponse 를 사용합니다.

@RestController
@Tag(name = "[API-001] 멤버 정보 편집", description = "[담당자 : GEONLEE]")
public class MemberController {

    @GetMapping(value = "/members/{memberId}", produces = MediaType.APPLICATION_JSON_VALUE)
    @Operation(summary = "멤버 정보 조회", description = """
            # Parameters
            - memberId [멤버 ID] <font color='red'>*</font>
            - memberName [멤버 이름]
            """
    )
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = """
                    ### OK
                     - 정상 응답.
                    """
                    , content = @Content(schema = @Schema(implementation = MemberResponse.class))),
            @ApiResponse(responseCode = "404", description = """
                    ### Not Found
                     - 요청한 'URI' 를 찾을 수 없을 경우 발생.
                     - 'URI' 가 올바른지 확인.
                    """
                    , content = @Content(schema = @Schema(implementation = ErrorResponse.class)
                    , examples = @ExampleObject(value = """ 
                        {
                            "status" : 404,
                            "message" : "요청하신 페이지를 찾을 수 없습니다."
                        }
                    """))),
            @ApiResponse(responseCode = "500", description = """
                    ### Internal Server Error
                     - 서버 오류.
                     - API 담당자에게 로그 확인을 요청.
                    """
                    , content = @Content(schema = @Schema(implementation = ErrorResponse.class)
                    , examples = @ExampleObject(value = """ 
                        {
                            "status" : 500,
                            "message" : "서버 오류가 발생했습니다."
                        }
                    """)))
    })
    public ResponseEntity<MemberResponse> getMember(@PathVariable("memberId")
                                                    @Parameter(name = "memberId", description = "멤버 ID", example = "geonlee")
                                                    String memberId) {
        return ResponseEntity.ok(new MemberResponse("geonlee", "이건"));
    }
}

 

모든 Operation에 공통 Response 추가

@ApiResponses 를 사용하여 모든 Operation 에 추가하는 것은 무리가 있습니다. @ApiResponse 는 특정 Operation 에서 발생하는 Response를 추가할 때 사용하고 모든 Operation 공통 Response는 SwaggerConfig 에 아래와 같이 OpeartionCustomizer 를 등록하여 사용하도록 합니다.

 

@Configuration
public class SwaggerConfig {
	/**
     * Operation 의 기존 ApiResponse 에 공통 응답 추가
     */
    @Bean
    public OperationCustomizer operationCustomizer() {
        return (operation, handlerMethod) -> {
            ApiResponses apiResponses = operation.getResponses();
            if (apiResponses == null) {
                apiResponses = new ApiResponses();
                operation.setResponses(apiResponses);
            }
            apiResponses.putAll(getCommonResponses());
            return operation;
        };
    }

    /**
     * 공통 응답 정보를 생성하여 맵으로 리턴한다.
     *
     * @return LinkedHashMap<String, ApiResponse> ApiResponse Map
     */
    private Map<String, ApiResponse> getCommonResponses() {
        LinkedHashMap<String, ApiResponse> responses = new LinkedHashMap<>();
        responses.put("404", notFoundResponse());
        responses.put("500", internalServerResponse());
        return responses;
    }

    /**
     * 404 Response 를 생성하여 리턴
     *
     * @return ApiResponse 404 응답 객체
     */
    private ApiResponse notFoundResponse() {
        ApiResponse apiResponse = new ApiResponse();
        apiResponse.setDescription("""
                Not Found
                - 요청한 URI 가 올바른지 확인한다.
                """);
        addContent(apiResponse, 404, "Not Found");
        return apiResponse;
    }

    /**
     * 500 Response 를 생성하여 리턴
     *
     * @return ApiResponse 500 응답 객체
     */
    private ApiResponse internalServerResponse() {
        ApiResponse apiResponse = new ApiResponse();
        apiResponse.setDescription("""
                Internal Server Error (Unchecked Exception)
                - API 담당자에게 오류 확인을 요청한다.
                """);
        addContent(apiResponse, 500, "Internal Server Error");
        return apiResponse;
    }

    /**
     * ApiResponse 의 Content 정보를 추가
     *
     * @param apiResponse Api 응답 객체
     * @param status      응답 상태 코드
     * @param message     응답 메시지
     */
    @SuppressWarnings("rawtypes")
    private void addContent(ApiResponse apiResponse, int status, String message) {
        Content content = new Content();
        MediaType mediaType = new MediaType();
        Schema schema = new Schema<>();
        schema.$ref("#/components/schemas/ErrorResponse");
        mediaType.schema(schema).example(new ErrorResponse(status, message));
        content.addMediaType("application/json", mediaType);
        apiResponse.setContent(content);
    }
}

 

swagger에 등록되는 Schema 는 $ref("/components/chemas/스키마명") 으로 접근 할 수 있습니다.

 

※참고로 Schema 는 @Operation의 요청/응답 객체만 등록이 되기 때문에, GlobalExceptionHandler 에서 하나를 등록하고 (ErrorResponse가 schema 에 등록) OperationCustomizer에서 override 되도록 합니다. (ApiResponses는 Map)

 

등록이 되지 않은 Schema에 접근 할 경우 아래와 같은 에러를 보게됩니다.

 

참고로 2.3.0, 2.4.0 버전에서 OperationCustomizer 로 추가 할 경우 기존 exmaple 이 깨져 "string" 으로 표출되는 문제가 발견되었습니다. 2.2.0 버전에서는 발견되지 않은 문제이니 참고 바랍니다.

 

Github Swagger Project

https://github.com/aljjabaegiProgrammer/swagger-api

 

GitHub - aljjabaegiProgrammer/swagger-api: swagger API project

swagger API project. Contribute to aljjabaegiProgrammer/swagger-api development by creating an account on GitHub.

github.com

 

반응형