diff --git a/src/main/java/com/haedal/haedalweb/config/SwaggerConfig.java b/src/main/java/com/haedal/haedalweb/config/SwaggerConfig.java new file mode 100644 index 0000000..b6c08f4 --- /dev/null +++ b/src/main/java/com/haedal/haedalweb/config/SwaggerConfig.java @@ -0,0 +1,214 @@ +package com.haedal.haedalweb.config; + +import com.haedal.haedalweb.constants.ErrorCode; +import com.haedal.haedalweb.constants.ResponseCode; +import com.haedal.haedalweb.constants.SuccessCode; +import com.haedal.haedalweb.dto.ErrorResponse; +import com.haedal.haedalweb.dto.SuccessResponse; +import com.haedal.haedalweb.swagger.ApiErrorCodeExample; +import com.haedal.haedalweb.swagger.ApiErrorCodeExamples; +import com.haedal.haedalweb.swagger.ApiSuccessCodeExample; +import com.haedal.haedalweb.swagger.ApiSuccessCodeExamples; +import com.haedal.haedalweb.swagger.ExampleHolder; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.examples.Example; +import io.swagger.v3.oas.models.media.Content; +import io.swagger.v3.oas.models.media.MediaType; +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; +import org.springframework.http.HttpHeaders; +import org.springframework.validation.FieldError; +import org.springframework.web.method.HandlerMethod; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + + +@OpenAPIDefinition( + info = @Info(title = "HAEDAL-WEB API 명세서", + description = "해달 웹 백엔드 API", + version = "1.0") + +) +@Configuration +public class SwaggerConfig { + private static final String BEARER_TOKEN_PREFIX = "Bearer"; + + @Bean + public OpenAPI openAPI() { + String accessToken = "Access Token (Bearer)"; + + SecurityRequirement securityRequirement = new SecurityRequirement() + .addList(accessToken); + + SecurityScheme accessTokenSecurityScheme = new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme(BEARER_TOKEN_PREFIX) + .bearerFormat("JWT") + .in(SecurityScheme.In.HEADER) + .name(HttpHeaders.AUTHORIZATION); + + Components components = new Components() + .addSecuritySchemes(accessToken, accessTokenSecurityScheme); + + return new OpenAPI() + .addSecurityItem(securityRequirement) + .components(components); + } + + @Bean + public OperationCustomizer customize() { + return (Operation operation, HandlerMethod handlerMethod) -> { + ApiErrorCodeExamples apiErrorCodeExamples = handlerMethod.getMethodAnnotation( + ApiErrorCodeExamples.class); + + // @ApiErrorCodeExamples 어노테이션이 붙어있다면 + if (apiErrorCodeExamples != null) { + generateResponseCodeResponseExample(operation, apiErrorCodeExamples.value()); + } else { + ApiErrorCodeExample apiErrorCodeExample = handlerMethod.getMethodAnnotation( + ApiErrorCodeExample.class); + + // @ApiErrorCodeExamples 어노테이션이 붙어있지 않고 + // @ApiErrorCodeExample 어노테이션이 붙어있다면 + if (apiErrorCodeExample != null) { + generateResponseCodeResponseExample(operation, apiErrorCodeExample.value()); + } + } + + 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; + }; + } + + private void generateResponseCodeResponseExample(Operation operation, ResponseCode[] responseCodes) { + ApiResponses responses = operation.getResponses(); + + Map> 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); + } + + // ErrorResponseDto 형태의 예시 객체 생성 + private Example getSwaggerExample(ResponseCode responseCode) { + Example example = new Example(); + + if (responseCode instanceof ErrorCode) { + List validationErrorList = new ArrayList<>(); + + 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; + } + + private void addExamplesToResponses(ApiResponses responses, + Map> statusWithExampleHolders) { + + if (responses != null && responses.containsKey("200")) { + responses.remove("200"); + } + + statusWithExampleHolders.forEach( + (status, v) -> { + Content content = new Content(); + MediaType mediaType = new MediaType(); + ApiResponse apiResponse = new ApiResponse(); + + v.forEach( + exampleHolder -> mediaType.addExamples( + exampleHolder.getName(), + exampleHolder.getHolder() + ) + ); + content.addMediaType("application/json", mediaType); + apiResponse.setContent(content); + responses.addApiResponse(String.valueOf(status), apiResponse); + } + ); + } + + private void addExamplesToResponses(ApiResponses responses, ExampleHolder exampleHolder) { + Content content = new Content(); + MediaType mediaType = new MediaType(); + ApiResponse apiResponse = new ApiResponse(); + + mediaType.addExamples(exampleHolder.getName(), exampleHolder.getHolder()); + content.addMediaType("application/json", mediaType); + apiResponse.content(content); + + if (responses != null && responses.containsKey("200")) { + responses.remove("200"); + } + + responses.addApiResponse(String.valueOf(exampleHolder.getHttpStatus()), apiResponse); + } +} \ No newline at end of file diff --git a/src/main/java/com/haedal/haedalweb/swagger/ApiErrorCodeExample.java b/src/main/java/com/haedal/haedalweb/swagger/ApiErrorCodeExample.java new file mode 100644 index 0000000..64321b0 --- /dev/null +++ b/src/main/java/com/haedal/haedalweb/swagger/ApiErrorCodeExample.java @@ -0,0 +1,15 @@ +package com.haedal.haedalweb.swagger; + +import com.haedal.haedalweb.constants.ErrorCode; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiErrorCodeExample { + + ErrorCode value(); +} \ No newline at end of file diff --git a/src/main/java/com/haedal/haedalweb/swagger/ApiErrorCodeExamples.java b/src/main/java/com/haedal/haedalweb/swagger/ApiErrorCodeExamples.java new file mode 100644 index 0000000..4ab265c --- /dev/null +++ b/src/main/java/com/haedal/haedalweb/swagger/ApiErrorCodeExamples.java @@ -0,0 +1,15 @@ +package com.haedal.haedalweb.swagger; + +import com.haedal.haedalweb.constants.ErrorCode; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiErrorCodeExamples { + + ErrorCode[] value(); +} diff --git a/src/main/java/com/haedal/haedalweb/swagger/ApiSuccessCodeExample.java b/src/main/java/com/haedal/haedalweb/swagger/ApiSuccessCodeExample.java new file mode 100644 index 0000000..a044421 --- /dev/null +++ b/src/main/java/com/haedal/haedalweb/swagger/ApiSuccessCodeExample.java @@ -0,0 +1,15 @@ +package com.haedal.haedalweb.swagger; + +import com.haedal.haedalweb.constants.SuccessCode; +import io.swagger.v3.oas.annotations.responses.ApiResponse; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiSuccessCodeExample { + SuccessCode value(); +} diff --git a/src/main/java/com/haedal/haedalweb/swagger/ApiSuccessCodeExamples.java b/src/main/java/com/haedal/haedalweb/swagger/ApiSuccessCodeExamples.java new file mode 100644 index 0000000..a0f514c --- /dev/null +++ b/src/main/java/com/haedal/haedalweb/swagger/ApiSuccessCodeExamples.java @@ -0,0 +1,14 @@ +package com.haedal.haedalweb.swagger; + +import com.haedal.haedalweb.constants.SuccessCode; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiSuccessCodeExamples { + SuccessCode[] value(); +} diff --git a/src/main/java/com/haedal/haedalweb/swagger/ExampleHolder.java b/src/main/java/com/haedal/haedalweb/swagger/ExampleHolder.java new file mode 100644 index 0000000..5db0df2 --- /dev/null +++ b/src/main/java/com/haedal/haedalweb/swagger/ExampleHolder.java @@ -0,0 +1,14 @@ +package com.haedal.haedalweb.swagger; + +import io.swagger.v3.oas.models.examples.Example; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ExampleHolder { + + private Example holder; + private String name; + private int httpStatus; +} \ No newline at end of file