Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 62 additions & 3 deletions src/main/java/com/bop/youthpick/global/config/OpenApiConfig.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,85 @@
package com.bop.youthpick.global.config;

import com.bop.youthpick.global.error.ErrorResponse;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.core.converter.ResolvedSchema;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.customizers.OperationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* 성공 응답의 실제 타입은 컨트롤러 반환 타입에서 springdoc이 자동으로 뽑아내지만(예: {@code ApiResponseListPolicyCardResponse}),
* 에러 응답({@code global.error.ErrorResponse})은 예외 핸들러가 던지는 것이라 컨트롤러 시그니처에 드러나지 않는다. 그래서 스키마 등록과
* 4XX/5XX 기본 응답 노출을 여기서 전역으로 채워준다.
*/
@Configuration
public class OpenApiConfig {

private static final String BEARER_AUTH_SCHEME = "bearerAuth";
private static final String ERROR_SCHEMA_NAME = "ErrorResponse";

@Bean
OpenAPI openAPI() {
Components components =
new Components().addSecuritySchemes(BEARER_AUTH_SCHEME, bearerAuthScheme());
registerErrorSchema(components);

return new OpenAPI()
.info(apiInfo())
.addSecurityItem(new SecurityRequirement().addList(BEARER_AUTH_SCHEME))
.components(
new Components()
.addSecuritySchemes(BEARER_AUTH_SCHEME, bearerAuthScheme()));
.components(components);
}

@Bean
OperationCustomizer errorResponseCustomizer() {
Content errorContent =
new Content()
.addMediaType(
"application/json",
new MediaType()
.schema(
new Schema<>()
.$ref(
"#/components/schemas/"
+ ERROR_SCHEMA_NAME)));

return (operation, handlerMethod) -> {
ApiResponses responses = operation.getResponses();
responses.addApiResponse(
"4XX",
errorApiResponse(
errorContent,
"클라이언트 오류 — 공통 에러 응답 형식(ErrorResponse)으로 내려간다. code 필드로 원인을 구분한다(예: C001"
+ " 입력값 오류, A001 인증 실패)."));
responses.addApiResponse(
"5XX",
errorApiResponse(
errorContent, "서버 내부 오류 — 공통 에러 응답 형식(ErrorResponse)으로 내려간다(S001)."));
return operation;
};
}

private ApiResponse errorApiResponse(Content content, String description) {
return new ApiResponse().description(description).content(content);
}

private void registerErrorSchema(Components components) {
ResolvedSchema resolvedSchema =
ModelConverters.getInstance()
.resolveAsResolvedSchema(new AnnotatedType(ErrorResponse.class));
components.addSchemas(ERROR_SCHEMA_NAME, resolvedSchema.schema);
resolvedSchema.referencedSchemas.forEach(components::addSchemas);
}

private Info apiInfo() {
Expand Down
Loading