From 0cb89f9506e136818b03a36e93260344991c887d Mon Sep 17 00:00:00 2001 From: seungyeop-lee Date: Wed, 5 Jun 2024 23:27:20 +0900 Subject: [PATCH 1/5] =?UTF-8?q?chore:=20=EC=95=BD=EA=B4=80=20txt=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20#62?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...47\221_\354\235\264\354\232\251_\354\225\275\352\264\200.txt" | 1 - ...35\274_\354\210\230\354\213\240_\354\225\275\352\264\200.txt" | 1 - .../init/\354\235\264\354\232\251\354\225\275\352\264\200.txt" | 1 - 3 files changed, 3 deletions(-) delete mode 100644 "api/src/main/resources/init/\352\260\234\354\235\270\354\240\225\353\263\264_\354\210\230\354\247\221_\354\235\264\354\232\251_\354\225\275\352\264\200.txt" delete mode 100644 "api/src/main/resources/init/\353\247\210\354\274\200\355\214\205_\353\251\224\354\235\274_\354\210\230\354\213\240_\354\225\275\352\264\200.txt" delete mode 100644 "api/src/main/resources/init/\354\235\264\354\232\251\354\225\275\352\264\200.txt" diff --git "a/api/src/main/resources/init/\352\260\234\354\235\270\354\240\225\353\263\264_\354\210\230\354\247\221_\354\235\264\354\232\251_\354\225\275\352\264\200.txt" "b/api/src/main/resources/init/\352\260\234\354\235\270\354\240\225\353\263\264_\354\210\230\354\247\221_\354\235\264\354\232\251_\354\225\275\352\264\200.txt" deleted file mode 100644 index 592165b..0000000 --- "a/api/src/main/resources/init/\352\260\234\354\235\270\354\240\225\353\263\264_\354\210\230\354\247\221_\354\235\264\354\232\251_\354\225\275\352\264\200.txt" +++ /dev/null @@ -1 +0,0 @@ -개인정보 수집 이용 약관 내용 diff --git "a/api/src/main/resources/init/\353\247\210\354\274\200\355\214\205_\353\251\224\354\235\274_\354\210\230\354\213\240_\354\225\275\352\264\200.txt" "b/api/src/main/resources/init/\353\247\210\354\274\200\355\214\205_\353\251\224\354\235\274_\354\210\230\354\213\240_\354\225\275\352\264\200.txt" deleted file mode 100644 index 7422efb..0000000 --- "a/api/src/main/resources/init/\353\247\210\354\274\200\355\214\205_\353\251\224\354\235\274_\354\210\230\354\213\240_\354\225\275\352\264\200.txt" +++ /dev/null @@ -1 +0,0 @@ -마케팅 메일 수신 약관 내용 diff --git "a/api/src/main/resources/init/\354\235\264\354\232\251\354\225\275\352\264\200.txt" "b/api/src/main/resources/init/\354\235\264\354\232\251\354\225\275\352\264\200.txt" deleted file mode 100644 index a9814da..0000000 --- "a/api/src/main/resources/init/\354\235\264\354\232\251\354\225\275\352\264\200.txt" +++ /dev/null @@ -1 +0,0 @@ -이용약관 내용 From 87f7a079b78eca15f6d443632f44c5d7f4fd2e11 Mon Sep 17 00:00:00 2001 From: seungyeop-lee Date: Wed, 5 Jun 2024 23:35:01 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=EA=B3=B5=ED=86=B5=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EA=B4=80=EB=A0=A8=20=EB=B6=80=EB=B6=84=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/web/common/CommonApiException.java | 18 ++++++- .../common/GlobalRestControllerAdvice.java | 4 +- .../GlobalOperationCustomizerImpl.java | 49 ++++++++++++------- 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/api/src/main/java/vook/server/api/web/common/CommonApiException.java b/api/src/main/java/vook/server/api/web/common/CommonApiException.java index 89f7404..17c97b3 100644 --- a/api/src/main/java/vook/server/api/web/common/CommonApiException.java +++ b/api/src/main/java/vook/server/api/web/common/CommonApiException.java @@ -23,6 +23,9 @@ public Exception(String message, Throwable cause) { } public static class BadRequest extends Exception { + + private static final int STATUS_CODE = 400; + public BadRequest(String message, Throwable cause) { super(message, cause); } @@ -33,18 +36,29 @@ public BadRequest(String message) { @Override public CommonApiResponse response() { - return CommonApiResponse.noResult(400, message); + return CommonApiResponse.noResult(STATUS_CODE, message); } } public static class ServerError extends Exception { + + private static final int STATUS_CODE = 500; + + public ServerError(String message, Throwable cause) { + super(message, cause); + } + public ServerError(Throwable cause) { super(cause); } @Override public CommonApiResponse response() { - return CommonApiResponse.noResult(500, "처리되지 않은 서버 에러가 발생하였습니다."); + return CommonApiResponse.noResult(STATUS_CODE, message); + } + + public CommonApiResponse response(String message) { + return CommonApiResponse.noResult(STATUS_CODE, message); } } } diff --git a/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java b/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java index ba60963..7d698df 100644 --- a/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java +++ b/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java @@ -22,7 +22,9 @@ public ResponseEntity handleCommonApiException(CommonApiException.Exception e public ResponseEntity handleException(Exception e) { log.error(e.getMessage(), e); - CommonApiResponse response = new CommonApiException.ServerError(e).response(); + CommonApiResponse response = new CommonApiException + .ServerError("처리되지 않은 서버 에러가 발생하였습니다.", e) + .response(); return ResponseEntity.status(response.getCode()).body(response); } diff --git a/api/src/main/java/vook/server/api/web/swagger/GlobalOperationCustomizerImpl.java b/api/src/main/java/vook/server/api/web/swagger/GlobalOperationCustomizerImpl.java index 0449495..67878d7 100644 --- a/api/src/main/java/vook/server/api/web/swagger/GlobalOperationCustomizerImpl.java +++ b/api/src/main/java/vook/server/api/web/swagger/GlobalOperationCustomizerImpl.java @@ -10,7 +10,7 @@ import org.springframework.web.method.HandlerMethod; import vook.server.api.web.common.CommonApiResponse; -import java.util.Map; +import java.util.HashMap; public class GlobalOperationCustomizerImpl implements GlobalOperationCustomizer { @@ -21,21 +21,36 @@ public Operation customize(Operation operation, HandlerMethod handlerMethod) { } private static void applyInternalServerErrorApiResponse(Operation operation) { - ApiResponse internalServerErrorApiResponse = new ApiResponse() - .description("처리되지 않은 서버 에러") - .content(new Content().addMediaType( - "application/json", - new MediaType() - .schema(new Schema().$ref("#/components/schemas/CommonApiResponse")) - .examples(Map.of("서버 에러", new Example().value( - """ - { - "code": 500, - "message": "처리되지 않은 서버 에러가 발생하였습니다." - }""" - ))) - )); - - operation.getResponses().addApiResponse("500", internalServerErrorApiResponse); + MediaType jsonType = prepareOrGetJsonMediaType(operation); + + if (jsonType.getSchema() == null) { + jsonType.setSchema(new Schema().$ref("#/components/schemas/CommonApiResponse")); + } + + jsonType.getExamples().put("처리되지 않은 서버 에러", + new Example() + .description("처리되지 않은 서버 에러") + .value(""" + { + "code": 500, + "message": "처리되지 않은 서버 에러가 발생하였습니다." + }""") + ); + } + + private static MediaType prepareOrGetJsonMediaType(Operation operation) { + ApiResponse apiResponse = operation.getResponses().computeIfAbsent("500", k -> new ApiResponse()); + + if (apiResponse.getContent() == null) { + apiResponse.setContent(new Content()); + } + + MediaType jsonType = apiResponse.getContent().computeIfAbsent("application/json", k -> new MediaType()); + + if (jsonType.getExamples() == null) { + jsonType.setExamples(new HashMap<>()); + } + + return jsonType; } } From a8f3925aad24f68d51b8d7e8a2646ad85ceda346 Mon Sep 17 00:00:00 2001 From: seungyeop-lee Date: Thu, 6 Jun 2024 09:57:38 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20bean=20validator=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81,=20=EA=B8=B0=EB=B0=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#64?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/build.gradle | 5 +- .../api/web/common/CommonApiResponse.java | 12 +++- .../common/GlobalRestControllerAdvice.java | 10 +++ .../web/routes/user/UserRestController.java | 3 +- .../user/reqres/UserRegisterRequest.java | 4 ++ .../swagger/GlobalOpenApiCustomizerImpl.java | 5 +- .../api/testhelper/HttpEntityBuilder.java | 10 ++- .../api/testhelper/IntegrationTestBase.java | 5 ++ .../server/api/testhelper/WebApiTest.java | 10 --- .../server/api/testhelper/WebServiceTest.java | 7 -- .../routes/health/HealthControllerTest.java | 4 +- .../routes/user/UserRestControllerTest.java | 67 ++++++++++++++++--- .../web/routes/user/UserWebServiceTest.java | 6 +- 13 files changed, 111 insertions(+), 37 deletions(-) delete mode 100644 api/src/test/java/vook/server/api/testhelper/WebApiTest.java delete mode 100644 api/src/test/java/vook/server/api/testhelper/WebServiceTest.java diff --git a/api/build.gradle b/api/build.gradle index 0b82ab7..66ed837 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -27,8 +27,11 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-validation' developmentOnly 'org.springframework.boot:spring-boot-devtools' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'com.vaadin.external.google', module: 'android-json' + } // lombok compileOnly 'org.projectlombok:lombok' diff --git a/api/src/main/java/vook/server/api/web/common/CommonApiResponse.java b/api/src/main/java/vook/server/api/web/common/CommonApiResponse.java index 39bfb69..4c74d9b 100644 --- a/api/src/main/java/vook/server/api/web/common/CommonApiResponse.java +++ b/api/src/main/java/vook/server/api/web/common/CommonApiResponse.java @@ -8,9 +8,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class CommonApiResponse { - @Schema(description = "응답 코드") + @Schema(description = "응답 코드", requiredMode = Schema.RequiredMode.REQUIRED) private Integer code; - @Schema(description = "응답 메시지") + @Schema(description = "응답 메시지", requiredMode = Schema.RequiredMode.REQUIRED) private String message; private T result; @@ -30,4 +30,12 @@ public static CommonApiResponse noResult(Integer code, String message) { response.message = message; return response; } + + public static CommonApiResponse withResult(Integer code, String message, T result) { + CommonApiResponse response = new CommonApiResponse<>(); + response.code = code; + response.message = message; + response.result = result; + return response; + } } diff --git a/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java b/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java index 7d698df..32a3372 100644 --- a/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java +++ b/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java @@ -2,6 +2,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -18,6 +19,15 @@ public ResponseEntity handleCommonApiException(CommonApiException.Exception e return ResponseEntity.status(response.getCode()).body(response); } + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + CommonApiResponse response = new CommonApiException + .BadRequest("요청이 잘못되었습니다.", e) + .response(); + + return ResponseEntity.status(response.getCode()).body(response); + } + @ExceptionHandler(Exception.class) public ResponseEntity handleException(Exception e) { log.error(e.getMessage(), e); diff --git a/api/src/main/java/vook/server/api/web/routes/user/UserRestController.java b/api/src/main/java/vook/server/api/web/routes/user/UserRestController.java index e4382cf..c463fe4 100644 --- a/api/src/main/java/vook/server/api/web/routes/user/UserRestController.java +++ b/api/src/main/java/vook/server/api/web/routes/user/UserRestController.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import vook.server.api.config.auth.common.VookLoginUser; import vook.server.api.web.common.CommonApiResponse; @@ -31,7 +32,7 @@ public CommonApiResponse userInfo( @PostMapping("/register") public CommonApiResponse register( @AuthenticationPrincipal VookLoginUser user, - @RequestBody UserRegisterRequest request + @Validated @RequestBody UserRegisterRequest request ) { service.register(user, request); return CommonApiResponse.ok(); diff --git a/api/src/main/java/vook/server/api/web/routes/user/reqres/UserRegisterRequest.java b/api/src/main/java/vook/server/api/web/routes/user/reqres/UserRegisterRequest.java index 02d84fe..1eec34c 100644 --- a/api/src/main/java/vook/server/api/web/routes/user/reqres/UserRegisterRequest.java +++ b/api/src/main/java/vook/server/api/web/routes/user/reqres/UserRegisterRequest.java @@ -1,13 +1,17 @@ package vook.server.api.web.routes.user.reqres; +import jakarta.validation.constraints.NotBlank; import lombok.Data; import vook.server.api.app.user.data.RegisterCommand; @Data public class UserRegisterRequest { + @NotBlank private String nickname; + private boolean requiredTermsAgree; + private boolean marketingEmailOptIn; public RegisterCommand toCommand(String userUid) { diff --git a/api/src/main/java/vook/server/api/web/swagger/GlobalOpenApiCustomizerImpl.java b/api/src/main/java/vook/server/api/web/swagger/GlobalOpenApiCustomizerImpl.java index f1cafa0..81701d4 100644 --- a/api/src/main/java/vook/server/api/web/swagger/GlobalOpenApiCustomizerImpl.java +++ b/api/src/main/java/vook/server/api/web/swagger/GlobalOpenApiCustomizerImpl.java @@ -17,8 +17,9 @@ public void customise(OpenAPI openApi) { private static void applyCommonApiResponseSchema(OpenAPI openApi) { openApi.getComponents() .addSchemas("CommonApiResponse", new Schema>() - .addProperty("code", new IntegerSchema().description("응답 코드")) - .addProperty("message", new StringSchema().description("응답 메시지")) + .addProperty("code", new IntegerSchema().description("응답 코드")).addRequiredItem("code") + .addProperty("message", new StringSchema().description("응답 메시지")).addRequiredItem("message") + .addProperty("result", new StringSchema().description("응답 결과")) ); } } diff --git a/api/src/test/java/vook/server/api/testhelper/HttpEntityBuilder.java b/api/src/test/java/vook/server/api/testhelper/HttpEntityBuilder.java index b7d5842..6396a65 100644 --- a/api/src/test/java/vook/server/api/testhelper/HttpEntityBuilder.java +++ b/api/src/test/java/vook/server/api/testhelper/HttpEntityBuilder.java @@ -5,14 +5,20 @@ public class HttpEntityBuilder { + private Object body; private final HttpHeaders headers = new HttpHeaders(); - public HttpEntityBuilder addHeader(String key, String value) { + public HttpEntityBuilder header(String key, String value) { headers.add(key, value); return this; } + public HttpEntityBuilder body(Object body) { + this.body = body; + return this; + } + public HttpEntity build() { - return new HttpEntity<>(headers); + return new HttpEntity<>(body, headers); } } diff --git a/api/src/test/java/vook/server/api/testhelper/IntegrationTestBase.java b/api/src/test/java/vook/server/api/testhelper/IntegrationTestBase.java index 44e6479..2ec5fb7 100644 --- a/api/src/test/java/vook/server/api/testhelper/IntegrationTestBase.java +++ b/api/src/test/java/vook/server/api/testhelper/IntegrationTestBase.java @@ -1,7 +1,9 @@ package vook.server.api.testhelper; import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -31,6 +33,9 @@ public abstract class IntegrationTestBase { meilisearchContainer.start(); } + @Autowired + protected TestRestTemplate rest; + @BeforeEach void init() { TimeZone.setDefault(TimeZone.getTimeZone(DEFAULT_TIME_ZONE)); diff --git a/api/src/test/java/vook/server/api/testhelper/WebApiTest.java b/api/src/test/java/vook/server/api/testhelper/WebApiTest.java deleted file mode 100644 index d8abc27..0000000 --- a/api/src/test/java/vook/server/api/testhelper/WebApiTest.java +++ /dev/null @@ -1,10 +0,0 @@ -package vook.server.api.testhelper; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.web.client.TestRestTemplate; - -public abstract class WebApiTest extends IntegrationTestBase { - - @Autowired - protected TestRestTemplate rest; -} diff --git a/api/src/test/java/vook/server/api/testhelper/WebServiceTest.java b/api/src/test/java/vook/server/api/testhelper/WebServiceTest.java deleted file mode 100644 index d223b7d..0000000 --- a/api/src/test/java/vook/server/api/testhelper/WebServiceTest.java +++ /dev/null @@ -1,7 +0,0 @@ -package vook.server.api.testhelper; - -import org.springframework.transaction.annotation.Transactional; - -@Transactional -public abstract class WebServiceTest extends IntegrationTestBase { -} diff --git a/api/src/test/java/vook/server/api/web/routes/health/HealthControllerTest.java b/api/src/test/java/vook/server/api/web/routes/health/HealthControllerTest.java index 5dd285d..cc877b8 100644 --- a/api/src/test/java/vook/server/api/web/routes/health/HealthControllerTest.java +++ b/api/src/test/java/vook/server/api/web/routes/health/HealthControllerTest.java @@ -4,11 +4,11 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import vook.server.api.testhelper.WebApiTest; +import vook.server.api.testhelper.IntegrationTestBase; import static org.assertj.core.api.Assertions.assertThat; -class HealthControllerTest extends WebApiTest { +class HealthControllerTest extends IntegrationTestBase { @Test @DisplayName("헬스체크") diff --git a/api/src/test/java/vook/server/api/web/routes/user/UserRestControllerTest.java b/api/src/test/java/vook/server/api/web/routes/user/UserRestControllerTest.java index 677f683..97b2a02 100644 --- a/api/src/test/java/vook/server/api/web/routes/user/UserRestControllerTest.java +++ b/api/src/test/java/vook/server/api/web/routes/user/UserRestControllerTest.java @@ -1,19 +1,25 @@ package vook.server.api.web.routes.user; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import vook.server.api.app.auth.data.GeneratedToken; import vook.server.api.model.user.User; -import vook.server.api.model.user.UserStatus; import vook.server.api.testhelper.HttpEntityBuilder; +import vook.server.api.testhelper.IntegrationTestBase; import vook.server.api.testhelper.TestDataCreator; -import vook.server.api.testhelper.WebApiTest; + +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; -class UserRestControllerTest extends WebApiTest { +class UserRestControllerTest extends IntegrationTestBase { + + @MockBean + UserWebService webService; @Autowired TestDataCreator testDataCreator; @@ -29,16 +35,61 @@ void userInfo() { "/user/info", HttpMethod.GET, new HttpEntityBuilder() - .addHeader("Authorization", "Bearer " + token.getAccessToken()) + .header("Authorization", "Bearer " + token.getAccessToken()) .build(), UserApi.UserApiUerInfoResponse.class ); // then assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(res.getBody()).isNotNull(); - assertThat(res.getBody().getResult().getUid()).isEqualTo(unregisteredUser.getUid()); - assertThat(res.getBody().getResult().getEmail()).isEqualTo(unregisteredUser.getEmail()); - assertThat(res.getBody().getResult().getStatus()).isEqualTo(UserStatus.SOCIAL_LOGIN_COMPLETED); + } + + @Test + @DisplayName("회원 가입 - 정상") + void register() { + // given + User unregisteredUser = testDataCreator.createUnregisteredUser(); + GeneratedToken token = testDataCreator.createToken(unregisteredUser); + + // when + var res = rest.exchange( + "/user/register", + HttpMethod.POST, + new HttpEntityBuilder() + .header("Authorization", "Bearer " + token.getAccessToken()) + .body(Map.of( + "nickname", "testNickname", + "onboardingComplete", true + )) + .build(), + String.class + ); + + // then + assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + @DisplayName("회원 가입 - 닉네임 누락") + void registerWithoutNickname() { + // given + User unregisteredUser = testDataCreator.createUnregisteredUser(); + GeneratedToken token = testDataCreator.createToken(unregisteredUser); + + // when + var res = rest.exchange( + "/user/register", + HttpMethod.POST, + new HttpEntityBuilder() + .header("Authorization", "Bearer " + token.getAccessToken()) + .body(Map.of( + "onboardingComplete", true + )) + .build(), + String.class + ); + + // then + assertThat(res.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); } } diff --git a/api/src/test/java/vook/server/api/web/routes/user/UserWebServiceTest.java b/api/src/test/java/vook/server/api/web/routes/user/UserWebServiceTest.java index daa3a69..6ee11ba 100644 --- a/api/src/test/java/vook/server/api/web/routes/user/UserWebServiceTest.java +++ b/api/src/test/java/vook/server/api/web/routes/user/UserWebServiceTest.java @@ -3,19 +3,21 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import vook.server.api.app.user.UserService; import vook.server.api.config.auth.common.VookLoginUser; import vook.server.api.model.user.User; import vook.server.api.model.user.UserStatus; +import vook.server.api.testhelper.IntegrationTestBase; import vook.server.api.testhelper.TestDataCreator; -import vook.server.api.testhelper.WebServiceTest; import vook.server.api.web.routes.user.reqres.UserInfoResponse; import vook.server.api.web.routes.user.reqres.UserOnboardingCompleteRequest; import vook.server.api.web.routes.user.reqres.UserRegisterRequest; import static org.assertj.core.api.Assertions.assertThat; -class UserWebServiceTest extends WebServiceTest { +@Transactional +class UserWebServiceTest extends IntegrationTestBase { @Autowired UserWebService userWebService; From a8cc031c5c7fe696293a397d565e0ab487328f83 Mon Sep 17 00:00:00 2001 From: seungyeop-lee Date: Thu, 6 Jun 2024 10:24:46 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20API=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=ED=98=95=EC=8B=9D=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?#65?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/web/common/CommonApiException.java | 53 +++++++------------ .../api/web/common/CommonApiResponse.java | 14 ++--- .../common/GlobalRestControllerAdvice.java | 20 ++----- .../swagger/GlobalOpenApiCustomizerImpl.java | 6 +-- .../GlobalOperationCustomizerImpl.java | 3 +- 5 files changed, 33 insertions(+), 63 deletions(-) diff --git a/api/src/main/java/vook/server/api/web/common/CommonApiException.java b/api/src/main/java/vook/server/api/web/common/CommonApiException.java index 17c97b3..596babe 100644 --- a/api/src/main/java/vook/server/api/web/common/CommonApiException.java +++ b/api/src/main/java/vook/server/api/web/common/CommonApiException.java @@ -2,63 +2,50 @@ public class CommonApiException { public static abstract class Exception extends RuntimeException { - protected String message; + + protected String code; - public Exception(Throwable cause) { - super(cause); - this.message = cause.getMessage(); - } - - public Exception(String message) { - super(message); - this.message = message; - } - - public Exception(String message, Throwable cause) { - super(message, cause); - this.message = message; + public Exception(String code, Throwable cause) { + super(code, cause); + this.code = code; } abstract CommonApiResponse response(); + + abstract int statusCode(); } public static class BadRequest extends Exception { - private static final int STATUS_CODE = 400; - - public BadRequest(String message, Throwable cause) { - super(message, cause); + public BadRequest(String code, Throwable cause) { + super(code, cause); } - public BadRequest(String message) { - super(message); + @Override + public CommonApiResponse response() { + return CommonApiResponse.noResult(code); } @Override - public CommonApiResponse response() { - return CommonApiResponse.noResult(STATUS_CODE, message); + int statusCode() { + return 400; } } public static class ServerError extends Exception { - private static final int STATUS_CODE = 500; - - public ServerError(String message, Throwable cause) { - super(message, cause); - } - - public ServerError(Throwable cause) { - super(cause); + public ServerError(String code, Throwable cause) { + super(code, cause); } @Override public CommonApiResponse response() { - return CommonApiResponse.noResult(STATUS_CODE, message); + return CommonApiResponse.noResult(code); } - public CommonApiResponse response(String message) { - return CommonApiResponse.noResult(STATUS_CODE, message); + @Override + int statusCode() { + return 500; } } } diff --git a/api/src/main/java/vook/server/api/web/common/CommonApiResponse.java b/api/src/main/java/vook/server/api/web/common/CommonApiResponse.java index 4c74d9b..cc7ee7d 100644 --- a/api/src/main/java/vook/server/api/web/common/CommonApiResponse.java +++ b/api/src/main/java/vook/server/api/web/common/CommonApiResponse.java @@ -8,14 +8,12 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class CommonApiResponse { - @Schema(description = "응답 코드", requiredMode = Schema.RequiredMode.REQUIRED) - private Integer code; - @Schema(description = "응답 메시지", requiredMode = Schema.RequiredMode.REQUIRED) - private String message; + @Schema(description = "결과 코드", requiredMode = Schema.RequiredMode.REQUIRED) + private String code; private T result; public static CommonApiResponse ok() { - return noResult(200, "API 요청이 성공했습니다."); + return noResult("success"); } public static CommonApiResponse okWithResult(T result) { @@ -24,17 +22,15 @@ public static CommonApiResponse okWithResult(T result) { return response; } - public static CommonApiResponse noResult(Integer code, String message) { + public static CommonApiResponse noResult(String code) { CommonApiResponse response = new CommonApiResponse<>(); response.code = code; - response.message = message; return response; } - public static CommonApiResponse withResult(Integer code, String message, T result) { + public static CommonApiResponse withResult(String code, T result) { CommonApiResponse response = new CommonApiResponse<>(); response.code = code; - response.message = message; response.result = result; return response; } diff --git a/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java b/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java index 32a3372..e4d8668 100644 --- a/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java +++ b/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java @@ -13,29 +13,19 @@ public class GlobalRestControllerAdvice { @ExceptionHandler(CommonApiException.Exception.class) public ResponseEntity handleCommonApiException(CommonApiException.Exception e) { log.error(e.getMessage(), e); - - CommonApiResponse response = e.response(); - - return ResponseEntity.status(response.getCode()).body(response); + return ResponseEntity.status(e.statusCode()).body(e.response()); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { - CommonApiResponse response = new CommonApiException - .BadRequest("요청이 잘못되었습니다.", e) - .response(); - - return ResponseEntity.status(response.getCode()).body(response); + CommonApiException.BadRequest badRequest = new CommonApiException.BadRequest("INVALID_PARAMETER", e); + return ResponseEntity.status(badRequest.statusCode()).body(badRequest.response()); } @ExceptionHandler(Exception.class) public ResponseEntity handleException(Exception e) { log.error(e.getMessage(), e); - - CommonApiResponse response = new CommonApiException - .ServerError("처리되지 않은 서버 에러가 발생하였습니다.", e) - .response(); - - return ResponseEntity.status(response.getCode()).body(response); + CommonApiException.ServerError serverError = new CommonApiException.ServerError("UNHANDLED_ERROR", e); + return ResponseEntity.status(serverError.statusCode()).body(serverError.response()); } } diff --git a/api/src/main/java/vook/server/api/web/swagger/GlobalOpenApiCustomizerImpl.java b/api/src/main/java/vook/server/api/web/swagger/GlobalOpenApiCustomizerImpl.java index 81701d4..6828eff 100644 --- a/api/src/main/java/vook/server/api/web/swagger/GlobalOpenApiCustomizerImpl.java +++ b/api/src/main/java/vook/server/api/web/swagger/GlobalOpenApiCustomizerImpl.java @@ -1,7 +1,6 @@ package vook.server.api.web.swagger; import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.media.IntegerSchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; import org.springdoc.core.customizers.GlobalOpenApiCustomizer; @@ -17,9 +16,8 @@ public void customise(OpenAPI openApi) { private static void applyCommonApiResponseSchema(OpenAPI openApi) { openApi.getComponents() .addSchemas("CommonApiResponse", new Schema>() - .addProperty("code", new IntegerSchema().description("응답 코드")).addRequiredItem("code") - .addProperty("message", new StringSchema().description("응답 메시지")).addRequiredItem("message") - .addProperty("result", new StringSchema().description("응답 결과")) + .addProperty("code", new StringSchema().description("결과 코드")).addRequiredItem("code") + .addProperty("result", new Schema<>()) ); } } diff --git a/api/src/main/java/vook/server/api/web/swagger/GlobalOperationCustomizerImpl.java b/api/src/main/java/vook/server/api/web/swagger/GlobalOperationCustomizerImpl.java index 67878d7..7740986 100644 --- a/api/src/main/java/vook/server/api/web/swagger/GlobalOperationCustomizerImpl.java +++ b/api/src/main/java/vook/server/api/web/swagger/GlobalOperationCustomizerImpl.java @@ -32,8 +32,7 @@ private static void applyInternalServerErrorApiResponse(Operation operation) { .description("처리되지 않은 서버 에러") .value(""" { - "code": 500, - "message": "처리되지 않은 서버 에러가 발생하였습니다." + "code": "UNHANDLED_ERROR" }""") ); } From da0ccf3f8981a45d5b133cd4accba918c4bd983e Mon Sep 17 00:00:00 2001 From: seungyeop-lee Date: Fri, 7 Jun 2024 23:23:28 +0900 Subject: [PATCH 5/5] =?UTF-8?q?docs:=20OpenAPI=EC=9D=98=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EC=84=B1=EA=B3=B5=20=EC=9D=91=EB=8B=B5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?#66?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/web/common/ApiResponseCode.java | 36 ++++++++++++++++ .../api/web/common/CommonApiException.java | 12 +++--- .../api/web/common/CommonApiResponse.java | 12 +++--- .../common/GlobalRestControllerAdvice.java | 4 +- .../server/api/web/routes/auth/AuthApi.java | 2 +- .../server/api/web/routes/demo/DemoApi.java | 2 +- .../api/web/routes/health/HealthApi.java | 4 +- .../server/api/web/routes/init/InitApi.java | 11 +---- .../api/web/routes/init/InitController.java | 4 +- .../server/api/web/routes/user/UserApi.java | 18 ++++---- .../api/web/swagger/ComponentRefConsts.java | 17 ++++++++ .../swagger/GlobalOpenApiCustomizerImpl.java | 29 ++++++++++++- .../GlobalOperationCustomizerImpl.java | 42 ++++++++++++++----- 13 files changed, 145 insertions(+), 48 deletions(-) create mode 100644 api/src/main/java/vook/server/api/web/common/ApiResponseCode.java create mode 100644 api/src/main/java/vook/server/api/web/swagger/ComponentRefConsts.java diff --git a/api/src/main/java/vook/server/api/web/common/ApiResponseCode.java b/api/src/main/java/vook/server/api/web/common/ApiResponseCode.java new file mode 100644 index 0000000..8d3d231 --- /dev/null +++ b/api/src/main/java/vook/server/api/web/common/ApiResponseCode.java @@ -0,0 +1,36 @@ +package vook.server.api.web.common; + +public interface ApiResponseCode { + + String code(); + + enum Ok implements ApiResponseCode { + + SUCCESS; + + @Override + public String code() { + return this.name(); + } + } + + enum BadRequest implements ApiResponseCode { + + INVALID_PARAMETER; + + @Override + public String code() { + return this.name(); + } + } + + enum ServerError implements ApiResponseCode { + + UNHANDLED_ERROR; + + @Override + public String code() { + return this.name(); + } + } +} diff --git a/api/src/main/java/vook/server/api/web/common/CommonApiException.java b/api/src/main/java/vook/server/api/web/common/CommonApiException.java index 596babe..2c01757 100644 --- a/api/src/main/java/vook/server/api/web/common/CommonApiException.java +++ b/api/src/main/java/vook/server/api/web/common/CommonApiException.java @@ -2,11 +2,11 @@ public class CommonApiException { public static abstract class Exception extends RuntimeException { - - protected String code; - public Exception(String code, Throwable cause) { - super(code, cause); + protected ApiResponseCode code; + + public Exception(ApiResponseCode code, Throwable cause) { + super(code.code(), cause); this.code = code; } @@ -17,7 +17,7 @@ public Exception(String code, Throwable cause) { public static class BadRequest extends Exception { - public BadRequest(String code, Throwable cause) { + public BadRequest(ApiResponseCode code, Throwable cause) { super(code, cause); } @@ -34,7 +34,7 @@ int statusCode() { public static class ServerError extends Exception { - public ServerError(String code, Throwable cause) { + public ServerError(ApiResponseCode code, Throwable cause) { super(code, cause); } diff --git a/api/src/main/java/vook/server/api/web/common/CommonApiResponse.java b/api/src/main/java/vook/server/api/web/common/CommonApiResponse.java index cc7ee7d..b918c7a 100644 --- a/api/src/main/java/vook/server/api/web/common/CommonApiResponse.java +++ b/api/src/main/java/vook/server/api/web/common/CommonApiResponse.java @@ -8,12 +8,12 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class CommonApiResponse { - @Schema(description = "결과 코드", requiredMode = Schema.RequiredMode.REQUIRED) + @Schema(description = "결과 코드", requiredMode = Schema.RequiredMode.REQUIRED, example = "SUCCESS") private String code; private T result; public static CommonApiResponse ok() { - return noResult("success"); + return noResult(ApiResponseCode.Ok.SUCCESS); } public static CommonApiResponse okWithResult(T result) { @@ -22,15 +22,15 @@ public static CommonApiResponse okWithResult(T result) { return response; } - public static CommonApiResponse noResult(String code) { + public static CommonApiResponse noResult(ApiResponseCode code) { CommonApiResponse response = new CommonApiResponse<>(); - response.code = code; + response.code = code.code(); return response; } - public static CommonApiResponse withResult(String code, T result) { + public static CommonApiResponse withResult(ApiResponseCode code, T result) { CommonApiResponse response = new CommonApiResponse<>(); - response.code = code; + response.code = code.code(); response.result = result; return response; } diff --git a/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java b/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java index e4d8668..73f0d79 100644 --- a/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java +++ b/api/src/main/java/vook/server/api/web/common/GlobalRestControllerAdvice.java @@ -18,14 +18,14 @@ public ResponseEntity handleCommonApiException(CommonApiException.Exception e @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { - CommonApiException.BadRequest badRequest = new CommonApiException.BadRequest("INVALID_PARAMETER", e); + CommonApiException.BadRequest badRequest = new CommonApiException.BadRequest(ApiResponseCode.BadRequest.INVALID_PARAMETER, e); return ResponseEntity.status(badRequest.statusCode()).body(badRequest.response()); } @ExceptionHandler(Exception.class) public ResponseEntity handleException(Exception e) { log.error(e.getMessage(), e); - CommonApiException.ServerError serverError = new CommonApiException.ServerError("UNHANDLED_ERROR", e); + CommonApiException.ServerError serverError = new CommonApiException.ServerError(ApiResponseCode.ServerError.UNHANDLED_ERROR, e); return ResponseEntity.status(serverError.statusCode()).body(serverError.response()); } } diff --git a/api/src/main/java/vook/server/api/web/routes/auth/AuthApi.java b/api/src/main/java/vook/server/api/web/routes/auth/AuthApi.java index 239560c..96c2065 100644 --- a/api/src/main/java/vook/server/api/web/routes/auth/AuthApi.java +++ b/api/src/main/java/vook/server/api/web/routes/auth/AuthApi.java @@ -16,7 +16,7 @@ public interface AuthApi { summary = "토큰 갱신", description = """ 리프레시 토큰을 이용하여 엑세스 토큰과 리프레시 토큰을 갱신합니다. - 리프레시 토큰은 최상위 Description에 Authorzation 항목을 참고하세요.""" + 리프레시 토큰은 최상위 Description의 Authorzation 항목을 참고하세요.""" ) @ApiResponses(value = { @ApiResponse( diff --git a/api/src/main/java/vook/server/api/web/routes/demo/DemoApi.java b/api/src/main/java/vook/server/api/web/routes/demo/DemoApi.java index 4274ccb..b48c122 100644 --- a/api/src/main/java/vook/server/api/web/routes/demo/DemoApi.java +++ b/api/src/main/java/vook/server/api/web/routes/demo/DemoApi.java @@ -17,8 +17,8 @@ public interface DemoApi { @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "성공", content = @Content( + mediaType = "application/json", schema = @Schema(implementation = SearchApiTermResponse.class) ) ), diff --git a/api/src/main/java/vook/server/api/web/routes/health/HealthApi.java b/api/src/main/java/vook/server/api/web/routes/health/HealthApi.java index e107d6c..e7c35b3 100644 --- a/api/src/main/java/vook/server/api/web/routes/health/HealthApi.java +++ b/api/src/main/java/vook/server/api/web/routes/health/HealthApi.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; @@ -15,7 +16,8 @@ public interface HealthApi { @ApiResponse( responseCode = "200", content = @Content( - mediaType = "text/plain" + mediaType = "text/plain", + examples = @ExampleObject(name = "성공", value = "OK") ) ) String health(); diff --git a/api/src/main/java/vook/server/api/web/routes/init/InitApi.java b/api/src/main/java/vook/server/api/web/routes/init/InitApi.java index 0f819ae..b064ca5 100644 --- a/api/src/main/java/vook/server/api/web/routes/init/InitApi.java +++ b/api/src/main/java/vook/server/api/web/routes/init/InitApi.java @@ -1,9 +1,8 @@ package vook.server.api.web.routes.init; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import vook.server.api.web.common.CommonApiResponse; @Tag(name = "init", description = "초기화 API") public interface InitApi { @@ -12,11 +11,5 @@ public interface InitApi { summary = "데이터 초기화", description = "모든 데이터를 삭제하고, 데모용 데이터를 생성시킨 상태로 초기화 시킵니다." ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "성공" - ), - }) - void init(); + CommonApiResponse init(); } diff --git a/api/src/main/java/vook/server/api/web/routes/init/InitController.java b/api/src/main/java/vook/server/api/web/routes/init/InitController.java index c6335e8..0d842ae 100644 --- a/api/src/main/java/vook/server/api/web/routes/init/InitController.java +++ b/api/src/main/java/vook/server/api/web/routes/init/InitController.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import vook.server.api.devhelper.InitService; +import vook.server.api.web.common.CommonApiResponse; @Profile({"local", "dev", "stag"}) @RestController @@ -16,7 +17,8 @@ public class InitController implements InitApi { private final InitService initService; @PostMapping - public void init() { + public CommonApiResponse init() { initService.init(); + return CommonApiResponse.ok(); } } diff --git a/api/src/main/java/vook/server/api/web/routes/user/UserApi.java b/api/src/main/java/vook/server/api/web/routes/user/UserApi.java index 48dd646..57f7d9c 100644 --- a/api/src/main/java/vook/server/api/web/routes/user/UserApi.java +++ b/api/src/main/java/vook/server/api/web/routes/user/UserApi.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -12,6 +13,7 @@ import vook.server.api.web.routes.user.reqres.UserInfoResponse; import vook.server.api.web.routes.user.reqres.UserOnboardingCompleteRequest; import vook.server.api.web.routes.user.reqres.UserRegisterRequest; +import vook.server.api.web.swagger.ComponentRefConsts; @Tag(name = "user", description = "사용자 관련 API") public interface UserApi { @@ -25,8 +27,8 @@ public interface UserApi { @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "성공", content = @Content( + mediaType = "application/json", schema = @Schema(implementation = UserApiUerInfoResponse.class) ) ), @@ -44,8 +46,12 @@ class UserApiUerInfoResponse extends CommonApiResponse { ) @ApiResponses(value = { @ApiResponse( - responseCode = "200", - description = "성공" + responseCode = "400", + content = @Content( + mediaType = "application/json", + schema = @Schema(ref = ComponentRefConsts.Schema.COMMON_API_RESPONSE), + examples = @ExampleObject(name = "유효하지 않은 파라미터", ref = ComponentRefConsts.Example.INVALID_PARAMETER) + ) ), }) CommonApiResponse register(VookLoginUser user, UserRegisterRequest request); @@ -56,11 +62,5 @@ class UserApiUerInfoResponse extends CommonApiResponse { @SecurityRequirement(name = "AccessToken") } ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "성공" - ), - }) CommonApiResponse onboardingComplete(VookLoginUser user, UserOnboardingCompleteRequest request); } diff --git a/api/src/main/java/vook/server/api/web/swagger/ComponentRefConsts.java b/api/src/main/java/vook/server/api/web/swagger/ComponentRefConsts.java new file mode 100644 index 0000000..12890cc --- /dev/null +++ b/api/src/main/java/vook/server/api/web/swagger/ComponentRefConsts.java @@ -0,0 +1,17 @@ +package vook.server.api.web.swagger; + +/** + * OpenAPI ComponentRef 상수 + */ +public class ComponentRefConsts { + + public static class Schema { + public static final String COMMON_API_RESPONSE = "#/components/schemas/CommonApiResponse"; + } + + public static class Example { + public static final String SUCCESS = "#/components/examples/Success"; + public static final String INVALID_PARAMETER = "#/components/examples/InvalidParameter"; + public static final String UNHANDLED_ERROR = "#/components/examples/UnhandledError"; + } +} diff --git a/api/src/main/java/vook/server/api/web/swagger/GlobalOpenApiCustomizerImpl.java b/api/src/main/java/vook/server/api/web/swagger/GlobalOpenApiCustomizerImpl.java index 6828eff..1347211 100644 --- a/api/src/main/java/vook/server/api/web/swagger/GlobalOpenApiCustomizerImpl.java +++ b/api/src/main/java/vook/server/api/web/swagger/GlobalOpenApiCustomizerImpl.java @@ -1,9 +1,11 @@ package vook.server.api.web.swagger; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.examples.Example; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; import org.springdoc.core.customizers.GlobalOpenApiCustomizer; +import vook.server.api.web.common.ApiResponseCode; import java.util.Map; @@ -15,9 +17,34 @@ public void customise(OpenAPI openApi) { private static void applyCommonApiResponseSchema(OpenAPI openApi) { openApi.getComponents() - .addSchemas("CommonApiResponse", new Schema>() + .addSchemas(getKey(ComponentRefConsts.Schema.COMMON_API_RESPONSE), new Schema>() .addProperty("code", new StringSchema().description("결과 코드")).addRequiredItem("code") .addProperty("result", new Schema<>()) + ) + .addExamples(getKey(ComponentRefConsts.Example.SUCCESS), new Example() + .description("성공") + .value(String.format(""" + { + "code": "%s" + }""", ApiResponseCode.Ok.SUCCESS.code())) + ) + .addExamples(getKey(ComponentRefConsts.Example.INVALID_PARAMETER), new Example() + .description("유효하지 않은 파라미터") + .value(String.format(""" + { + "code": "%s" + }""", ApiResponseCode.BadRequest.INVALID_PARAMETER.code())) + ) + .addExamples(getKey(ComponentRefConsts.Example.UNHANDLED_ERROR), new Example() + .description("처리되지 않은 서버 에러") + .value(String.format(""" + { + "code": "%s" + }""", ApiResponseCode.ServerError.UNHANDLED_ERROR.code())) ); } + + private static String getKey(String refString) { + return refString.substring(refString.lastIndexOf('/') + 1); + } } diff --git a/api/src/main/java/vook/server/api/web/swagger/GlobalOperationCustomizerImpl.java b/api/src/main/java/vook/server/api/web/swagger/GlobalOperationCustomizerImpl.java index 7740986..b2974ee 100644 --- a/api/src/main/java/vook/server/api/web/swagger/GlobalOperationCustomizerImpl.java +++ b/api/src/main/java/vook/server/api/web/swagger/GlobalOperationCustomizerImpl.java @@ -8,7 +8,6 @@ import io.swagger.v3.oas.models.responses.ApiResponse; import org.springdoc.core.customizers.GlobalOperationCustomizer; import org.springframework.web.method.HandlerMethod; -import vook.server.api.web.common.CommonApiResponse; import java.util.HashMap; @@ -16,29 +15,50 @@ public class GlobalOperationCustomizerImpl implements GlobalOperationCustomizer @Override public Operation customize(Operation operation, HandlerMethod handlerMethod) { + applyDefaultOkApiResponse(operation); applyInternalServerErrorApiResponse(operation); return operation; } + private void applyDefaultOkApiResponse(Operation operation) { + ApiResponse apiResponse = operation.getResponses().computeIfAbsent( + "200", + k -> new ApiResponse().description("OK") + ); + + if (apiResponse.getContent() == null) { + apiResponse.setContent(new Content()); + } else { + // SpringDoc이 기본으로 제공하는 Content 제거 + apiResponse.getContent().remove("*/*"); + } + + // Content가 존재하면 종료 + if (!apiResponse.getContent().isEmpty()) { + return; + } + + // Content가 존재하지 않으면 기본 성공 응답 추가 + apiResponse.getContent().computeIfAbsent("application/json", k -> new MediaType() + .schema(new Schema<>().$ref(ComponentRefConsts.Schema.COMMON_API_RESPONSE)) + .addExamples("성공", new Example().$ref(ComponentRefConsts.Example.SUCCESS))); + } + private static void applyInternalServerErrorApiResponse(Operation operation) { MediaType jsonType = prepareOrGetJsonMediaType(operation); if (jsonType.getSchema() == null) { - jsonType.setSchema(new Schema().$ref("#/components/schemas/CommonApiResponse")); + jsonType.setSchema(new Schema<>().$ref(ComponentRefConsts.Schema.COMMON_API_RESPONSE)); } - jsonType.getExamples().put("처리되지 않은 서버 에러", - new Example() - .description("처리되지 않은 서버 에러") - .value(""" - { - "code": "UNHANDLED_ERROR" - }""") - ); + jsonType.getExamples().put("처리되지 않은 서버 에러", new Example().$ref(ComponentRefConsts.Example.UNHANDLED_ERROR)); } private static MediaType prepareOrGetJsonMediaType(Operation operation) { - ApiResponse apiResponse = operation.getResponses().computeIfAbsent("500", k -> new ApiResponse()); + ApiResponse apiResponse = operation.getResponses().computeIfAbsent( + "500", + k -> new ApiResponse().description("Internal Server Error") + ); if (apiResponse.getContent() == null) { apiResponse.setContent(new Content());