ApiErrorHttpStatusMapper.java

1
package com.project.auth.presentation.support.exception;
2
3
import com.project.auth.application.support.exception.AuthErrorCode;
4
import com.project.auth.application.support.exception.ClientFacingErrorCode;
5
import com.project.auth.application.support.exception.CommonErrorCode;
6
import org.slf4j.Logger;
7
import org.slf4j.LoggerFactory;
8
import org.springframework.http.HttpStatus;
9
10
public final class ApiErrorHttpStatusMapper {
11
12
    private static final Logger log = LoggerFactory.getLogger(ApiErrorHttpStatusMapper.class);
13
14
    private ApiErrorHttpStatusMapper() {
15
    }
16
17
    /**
18
     * default 분기가 존재하는 이유: {@link ClientFacingErrorCode}가 non-sealed이므로
19
     * (application/presentation 외 모듈도 레이어 경계를 넘지 않고 자체 구현을 추가할 수 있다)
20
     * 컴파일러가 exhaustiveness를 강제할 수 없다. 새로운 구현이 매핑 테이블에 누락된 채
21
     * 들어오면 조용히 500이 되는 대신 WARN 로그로 드러내어 운영 대시보드에서 식별 가능하게 한다.
22
     *
23
     * "새 ClientFacingErrorCode 구현은 반드시 매핑 테이블에 등록해야 한다"는 아키텍처 차원의
24
     * 게이트는 ApiErrorHttpStatusMapperClientFacingCoverageTest에 있다.
25
     */
26
    public static HttpStatus map(ClientFacingErrorCode errorCode) {
27 1 1. map : replaced return value with null for com/project/auth/presentation/support/exception/ApiErrorHttpStatusMapper::map → KILLED
        return switch (errorCode) {
28
            case CommonErrorCode commonErrorCode -> mapCommon(commonErrorCode);
29
            case AuthErrorCode authErrorCode -> mapAuth(authErrorCode);
30
            case PresentationErrorCode presentationErrorCode -> mapPresentation(presentationErrorCode);
31
            default -> {
32
                log.warn("Unmapped ClientFacingErrorCode reached default branch. type={} code={} -> falling back to 500",
33
                        errorCode.getClass().getName(), errorCode.code());
34
                yield HttpStatus.INTERNAL_SERVER_ERROR;
35
            }
36
        };
37
    }
38
39
    private static HttpStatus mapCommon(CommonErrorCode errorCode) {
40 1 1. mapCommon : replaced return value with null for com/project/auth/presentation/support/exception/ApiErrorHttpStatusMapper::mapCommon → KILLED
        return switch (errorCode) {
41
            case INTERNAL_SERVER_ERROR -> HttpStatus.INTERNAL_SERVER_ERROR;
42
        };
43
    }
44
45
    private static HttpStatus mapPresentation(PresentationErrorCode errorCode) {
46 1 1. mapPresentation : replaced return value with null for com/project/auth/presentation/support/exception/ApiErrorHttpStatusMapper::mapPresentation → KILLED
        return switch (errorCode) {
47
            case INVALID_INPUT, INVALID_REQUEST_BODY, MISSING_PARAMETER, CONSTRAINT_VIOLATION, MISSING_HEADER, REQUEST_BINDING_FAILED, TYPE_MISMATCH, INVALID_PARAMETER -> HttpStatus.BAD_REQUEST;
48
            case METHOD_NOT_ALLOWED -> HttpStatus.METHOD_NOT_ALLOWED;
49
            case RESOURCE_NOT_FOUND -> HttpStatus.NOT_FOUND;
50
            case UNSUPPORTED_MEDIA_TYPE -> HttpStatus.UNSUPPORTED_MEDIA_TYPE;
51
            case NOT_ACCEPTABLE -> HttpStatus.NOT_ACCEPTABLE;
52
            case PAYLOAD_TOO_LARGE -> HttpStatus.CONTENT_TOO_LARGE;
53
            case MESSAGE_NOT_WRITABLE -> HttpStatus.INTERNAL_SERVER_ERROR;
54
            case UNHANDLED_CLIENT_ERROR -> HttpStatus.BAD_REQUEST;
55
        };
56
    }
57
58
    private static HttpStatus mapAuth(AuthErrorCode errorCode) {
59 1 1. mapAuth : replaced return value with null for com/project/auth/presentation/support/exception/ApiErrorHttpStatusMapper::mapAuth → KILLED
        return switch (errorCode) {
60
            case AUTHENTICATION_REQUIRED -> HttpStatus.UNAUTHORIZED;
61
            case ACCESS_DENIED -> HttpStatus.FORBIDDEN;
62
            case KEYCLOAK_CLAIMS_INVALID -> HttpStatus.BAD_REQUEST;
63
            case KEYCLOAK_USER_NOT_FOUND -> HttpStatus.NOT_FOUND;
64
        };
65
    }
66
}

Mutations

27

1.1
Location : map
Killed by : com.project.auth.presentation.support.exception.ApiErrorHttpStatusMapperPropertyTest.[engine:jqwik]/[class:com.project.auth.presentation.support.exception.ApiErrorHttpStatusMapperPropertyTest]/[property:access_denied_is_always_403(com.project.auth.application.support.exception.AuthErrorCode)]
replaced return value with null for com/project/auth/presentation/support/exception/ApiErrorHttpStatusMapper::map → KILLED

40

1.1
Location : mapCommon
Killed by : com.project.auth.presentation.support.exception.ApiErrorHttpStatusMapperPropertyTest.[engine:jqwik]/[class:com.project.auth.presentation.support.exception.ApiErrorHttpStatusMapperPropertyTest]/[property:map_result_is_always_4xx_or_5xx(com.project.auth.application.support.exception.ClientFacingErrorCode)]
replaced return value with null for com/project/auth/presentation/support/exception/ApiErrorHttpStatusMapper::mapCommon → KILLED

46

1.1
Location : mapPresentation
Killed by : com.project.auth.presentation.support.exception.ApiErrorHttpStatusMapperPropertyTest.[engine:jqwik]/[class:com.project.auth.presentation.support.exception.ApiErrorHttpStatusMapperPropertyTest]/[property:map_result_is_always_4xx_or_5xx(com.project.auth.application.support.exception.ClientFacingErrorCode)]
replaced return value with null for com/project/auth/presentation/support/exception/ApiErrorHttpStatusMapper::mapPresentation → KILLED

59

1.1
Location : mapAuth
Killed by : com.project.auth.presentation.support.exception.ApiErrorHttpStatusMapperPropertyTest.[engine:jqwik]/[class:com.project.auth.presentation.support.exception.ApiErrorHttpStatusMapperPropertyTest]/[property:access_denied_is_always_403(com.project.auth.application.support.exception.AuthErrorCode)]
replaced return value with null for com/project/auth/presentation/support/exception/ApiErrorHttpStatusMapper::mapAuth → KILLED

Active mutators

Tests examined


Report generated by PIT 1.19.1