Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat, docs: bean validator 추가, API 공통 반환 형식 변경, OpenAPI 리팩토링 #67

Merged
merged 5 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
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
5 changes: 4 additions & 1 deletion api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
36 changes: 36 additions & 0 deletions api/src/main/java/vook/server/api/web/common/ApiResponseCode.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,50 @@

public class CommonApiException {
public static abstract class Exception extends RuntimeException {
protected String message;

public Exception(Throwable cause) {
super(cause);
this.message = cause.getMessage();
}

public Exception(String message) {
super(message);
this.message = message;
}
protected ApiResponseCode code;

public Exception(String message, Throwable cause) {
super(message, cause);
this.message = message;
public Exception(ApiResponseCode code, Throwable cause) {
super(code.code(), cause);
this.code = code;
}

abstract CommonApiResponse<?> response();

abstract int statusCode();
}

public static class BadRequest extends Exception {
public BadRequest(String message, Throwable cause) {
super(message, cause);
}

public BadRequest(String message) {
super(message);
public BadRequest(ApiResponseCode code, Throwable cause) {
super(code, cause);
}

@Override
public CommonApiResponse<?> response() {
return CommonApiResponse.noResult(400, message);
return CommonApiResponse.noResult(code);
}

@Override
int statusCode() {
return 400;
}
}

public static class ServerError extends Exception {
public ServerError(Throwable cause) {
super(cause);

public ServerError(ApiResponseCode code, Throwable cause) {
super(code, cause);
}

@Override
public CommonApiResponse<?> response() {
return CommonApiResponse.noResult(500, "처리되지 않은 서버 에러가 발생하였습니다.");
return CommonApiResponse.noResult(code);
}

@Override
int statusCode() {
return 500;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CommonApiResponse<T> {

@Schema(description = "응답 코드")
private Integer code;
@Schema(description = "응답 메시지")
private String message;
@Schema(description = "결과 코드", requiredMode = Schema.RequiredMode.REQUIRED, example = "SUCCESS")
private String code;
private T result;

public static <T> CommonApiResponse<T> ok() {
return noResult(200, "API 요청이 성공했습니다.");
return noResult(ApiResponseCode.Ok.SUCCESS);
}

public static <T> CommonApiResponse<T> okWithResult(T result) {
Expand All @@ -24,10 +22,16 @@ public static <T> CommonApiResponse<T> okWithResult(T result) {
return response;
}

public static <T> CommonApiResponse<T> noResult(Integer code, String message) {
public static <T> CommonApiResponse<T> noResult(ApiResponseCode code) {
CommonApiResponse<T> response = new CommonApiResponse<>();
response.code = code;
response.message = message;
response.code = code.code();
return response;
}

public static <T> CommonApiResponse<T> withResult(ApiResponseCode code, T result) {
CommonApiResponse<T> response = new CommonApiResponse<>();
response.code = code.code();
response.result = result;
return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -12,18 +13,19 @@ public class GlobalRestControllerAdvice {
@ExceptionHandler(CommonApiException.Exception.class)
public ResponseEntity<?> handleCommonApiException(CommonApiException.Exception e) {
log.error(e.getMessage(), e);
return ResponseEntity.status(e.statusCode()).body(e.response());
}

CommonApiResponse<?> response = e.response();

return ResponseEntity.status(response.getCode()).body(response);
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException 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);

CommonApiResponse<?> response = new CommonApiException.ServerError(e).response();

return ResponseEntity.status(response.getCode()).body(response);
CommonApiException.ServerError serverError = new CommonApiException.ServerError(ApiResponseCode.ServerError.UNHANDLED_ERROR, e);
return ResponseEntity.status(serverError.statusCode()).body(serverError.response());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public interface AuthApi {
summary = "토큰 갱신",
description = """
리프레시 토큰을 이용하여 엑세스 토큰과 리프레시 토큰을 갱신합니다.
리프레시 토큰은 최상위 Description에 Authorzation 항목을 참고하세요."""
리프레시 토큰은 최상위 Description의 Authorzation 항목을 참고하세요."""
)
@ApiResponses(value = {
@ApiResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public interface DemoApi {
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "성공",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = SearchApiTermResponse.class)
)
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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();
Expand Down
11 changes: 2 additions & 9 deletions api/src/main/java/vook/server/api/web/routes/init/InitApi.java
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -12,11 +11,5 @@ public interface InitApi {
summary = "데이터 초기화",
description = "모든 데이터를 삭제하고, 데모용 데이터를 생성시킨 상태로 초기화 시킵니다."
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "성공"
),
})
void init();
CommonApiResponse<Void> init();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,7 +17,8 @@ public class InitController implements InitApi {
private final InitService initService;

@PostMapping
public void init() {
public CommonApiResponse<Void> init() {
initService.init();
return CommonApiResponse.ok();
}
}
18 changes: 9 additions & 9 deletions api/src/main/java/vook/server/api/web/routes/user/UserApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -25,8 +27,8 @@ public interface UserApi {
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "성공",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = UserApiUerInfoResponse.class)
)
),
Expand All @@ -44,8 +46,12 @@ class UserApiUerInfoResponse extends CommonApiResponse<UserInfoResponse> {
)
@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<Void> register(VookLoginUser user, UserRegisterRequest request);
Expand All @@ -56,11 +62,5 @@ class UserApiUerInfoResponse extends CommonApiResponse<UserInfoResponse> {
@SecurityRequirement(name = "AccessToken")
}
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "성공"
),
})
CommonApiResponse<Void> onboardingComplete(VookLoginUser user, UserOnboardingCompleteRequest request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -31,7 +32,7 @@ public CommonApiResponse<UserInfoResponse> userInfo(
@PostMapping("/register")
public CommonApiResponse<Void> register(
@AuthenticationPrincipal VookLoginUser user,
@RequestBody UserRegisterRequest request
@Validated @RequestBody UserRegisterRequest request
) {
service.register(user, request);
return CommonApiResponse.ok();
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
}
}
Loading
Loading