init: 클린 기반 auth 서버 설계
@@ -0,0 +1,51 @@
|
||||
# presentation AGENTS
|
||||
|
||||
Role:
|
||||
- own HTTP contract only
|
||||
- handle controller, request/response dto, validation, response/error mapping
|
||||
|
||||
Allowed:
|
||||
- controller
|
||||
- request/response dto
|
||||
- validation annotation
|
||||
- API response model
|
||||
- HTTP status mapping
|
||||
- controller advice
|
||||
- argument resolver for controller parameter injection only
|
||||
|
||||
Forbidden:
|
||||
- repository access
|
||||
- external API call
|
||||
- business rule decision
|
||||
- direct dependency on domain/infrastructure
|
||||
- MDC/clock logic inside dto
|
||||
|
||||
Rules:
|
||||
- controller handles HTTP only
|
||||
- dto must stay pure data
|
||||
- validation at boundary, invariant in domain
|
||||
- use ArgumentResolver only for controller parameter injection
|
||||
- do not hardcode provider/business meaning in mapper
|
||||
- response shape must stay centralized and consistent
|
||||
|
||||
Read first:
|
||||
- `/docs/architecture/README.md`
|
||||
- `/docs/standards/web/api-controller.md`
|
||||
- `/docs/standards/web/request-response-dto.md`
|
||||
- `/docs/standards/web/response-format.md`
|
||||
- `/docs/standards/web/error-code-http-status-separation.md`
|
||||
- `/docs/standards/web/serialization-jackson.md`
|
||||
- `/docs/standards/web/authentication-object-access.md`
|
||||
- `/docs/standards/spring/filter-interceptor-resolver-advice.md`
|
||||
- `/docs/standards/spring/validation-location.md`
|
||||
- `/docs/standards/testing/mock-usage.md`
|
||||
- `/docs/standards/testing/springboottest-usage.md`
|
||||
|
||||
Examples:
|
||||
- `/docs/examples/web/api-controller.md`
|
||||
- `/docs/examples/web/request-response-dto.md`
|
||||
- `/docs/examples/web/response-format.md`
|
||||
- `/docs/examples/web/error-code-http-status-separation.md`
|
||||
- `/docs/examples/web/authentication-object-access.md`
|
||||
- `/docs/examples/testing/mock-usage.md`
|
||||
- `/docs/examples/testing/springboottest-usage.md`
|
||||
@@ -0,0 +1,8 @@
|
||||
dependencies {
|
||||
implementation project(':application')
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
compileOnly 'org.springframework.security:spring-security-core'
|
||||
compileOnly libs.swagger.annotations.jakarta
|
||||
testImplementation 'org.springframework.security:spring-security-core'
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AuthenticatedUserController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.html" class="el_package">com.project.auth.presentation.auth.controller</a> > <span class="el_class">AuthenticatedUserController</span></div><h1>AuthenticatedUserController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">42 of 42</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="AuthenticatedUserController.java.html#L43" class="el_method">me(AuthenticatedUser)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="27" alt="27"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">8</td><td class="ctr2" id="i0">8</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="AuthenticatedUserController.java.html#L34" class="el_method">AuthenticatedUserController(LoadKeycloakUserUseCase, AuthenticatedUserPresentationMapper, ApiResultFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="12" alt="12"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="AuthenticatedUserController.java.html#L24" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AuthenticatedUserController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.source.html" class="el_package">com.project.auth.presentation.auth.controller</a> > <span class="el_source">AuthenticatedUserController.java</span></div><h1>AuthenticatedUserController.java</h1><pre class="source lang-java linenums">package com.project.auth.presentation.auth.controller;
|
||||
|
||||
import com.project.auth.application.auth.identity.KeycloakUserClaims;
|
||||
import com.project.auth.application.auth.identity.LoadedKeycloakUser;
|
||||
import com.project.auth.application.auth.identity.LoadKeycloakUserUseCase;
|
||||
import com.project.auth.presentation.auth.current.AuthenticatedUser;
|
||||
import com.project.auth.presentation.auth.current.CurrentUser;
|
||||
import com.project.auth.presentation.auth.docs.AuthenticatedUserApiDocs;
|
||||
import com.project.auth.presentation.auth.dto.AuthenticatedUserResponse;
|
||||
import com.project.auth.presentation.auth.mapper.AuthenticatedUserPresentationMapper;
|
||||
import com.project.auth.presentation.support.response.ApiResult;
|
||||
import com.project.auth.presentation.support.response.ApiResultFactory;
|
||||
import com.project.auth.presentation.support.response.ApiSuccessCode;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/auth")
|
||||
@Tag(name = "Auth", description = "Keycloak 토큰 기반 인증 API")
|
||||
public class AuthenticatedUserController implements AuthenticatedUserApiDocs {
|
||||
|
||||
<span class="nc" id="L24"> private static final ApiSuccessCode USER_LOADED = ApiSuccessCode.AUTHENTICATED_USER_LOADED;</span>
|
||||
|
||||
private final LoadKeycloakUserUseCase loadKeycloakUserUseCase;
|
||||
private final AuthenticatedUserPresentationMapper authenticatedUserPresentationMapper;
|
||||
private final ApiResultFactory apiResultFactory;
|
||||
|
||||
public AuthenticatedUserController(
|
||||
LoadKeycloakUserUseCase loadKeycloakUserUseCase,
|
||||
AuthenticatedUserPresentationMapper authenticatedUserPresentationMapper,
|
||||
ApiResultFactory apiResultFactory
|
||||
<span class="nc" id="L34"> ) {</span>
|
||||
<span class="nc" id="L35"> this.loadKeycloakUserUseCase = loadKeycloakUserUseCase;</span>
|
||||
<span class="nc" id="L36"> this.authenticatedUserPresentationMapper = authenticatedUserPresentationMapper;</span>
|
||||
<span class="nc" id="L37"> this.apiResultFactory = apiResultFactory;</span>
|
||||
<span class="nc" id="L38"> }</span>
|
||||
|
||||
@Override
|
||||
@GetMapping("/me")
|
||||
public ApiResult<AuthenticatedUserResponse> me(@CurrentUser AuthenticatedUser currentUser) {
|
||||
<span class="nc" id="L43"> LoadedKeycloakUser user = loadKeycloakUserUseCase.load(new KeycloakUserClaims(</span>
|
||||
<span class="nc" id="L44"> currentUser.subject(),</span>
|
||||
<span class="nc" id="L45"> currentUser.email(),</span>
|
||||
<span class="nc" id="L46"> currentUser.name()</span>
|
||||
));
|
||||
|
||||
<span class="nc" id="L49"> return apiResultFactory.success(</span>
|
||||
<span class="nc" id="L50"> USER_LOADED.code(),</span>
|
||||
<span class="nc" id="L51"> USER_LOADED.message(),</span>
|
||||
<span class="nc" id="L52"> authenticatedUserPresentationMapper.toResponse(user, currentUser.authorities())</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>com.project.auth.presentation.auth.controller</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <span class="el_package">com.project.auth.presentation.auth.controller</span></div><h1>com.project.auth.presentation.auth.controller</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">42 of 42</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="AuthenticatedUserController.html" class="el_class">AuthenticatedUserController</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="42" alt="42"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">14</td><td class="ctr2" id="i0">14</td><td class="ctr1" id="j0">3</td><td class="ctr2" id="k0">3</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>com.project.auth.presentation.auth.controller</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <span class="el_package">com.project.auth.presentation.auth.controller</span></div><h1>com.project.auth.presentation.auth.controller</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">42 of 42</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="AuthenticatedUserController.java.html" class="el_source">AuthenticatedUserController.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="42" alt="42"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">14</td><td class="ctr2" id="i0">14</td><td class="ctr1" id="j0">3</td><td class="ctr2" id="k0">3</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AuthenticatedUser</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.html" class="el_package">com.project.auth.presentation.auth.current</a> > <span class="el_class">AuthenticatedUser</span></div><h1>AuthenticatedUser</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">22 of 22</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="AuthenticatedUser.java.html#L12" class="el_method">AuthenticatedUser(String, String, String, Set)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AuthenticatedUser.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.source.html" class="el_package">com.project.auth.presentation.auth.current</a> > <span class="el_source">AuthenticatedUser.java</span></div><h1>AuthenticatedUser.java</h1><pre class="source lang-java linenums">package com.project.auth.presentation.auth.current;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public record AuthenticatedUser(
|
||||
String subject,
|
||||
String email,
|
||||
String name,
|
||||
Set<String> authorities
|
||||
) {
|
||||
|
||||
<span class="nc" id="L12"> public AuthenticatedUser {</span>
|
||||
<span class="nc bnc" id="L13" title="All 2 branches missed."> authorities = authorities == null ? Set.of() : Set.copyOf(authorities);</span>
|
||||
<span class="nc" id="L14"> }</span>
|
||||
}
|
||||
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>com.project.auth.presentation.auth.current</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <span class="el_package">com.project.auth.presentation.auth.current</span></div><h1>com.project.auth.presentation.auth.current</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">22 of 22</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="AuthenticatedUser.html" class="el_class">AuthenticatedUser</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>com.project.auth.presentation.auth.current</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <span class="el_package">com.project.auth.presentation.auth.current</span></div><h1>com.project.auth.presentation.auth.current</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">22 of 22</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="AuthenticatedUser.java.html" class="el_source">AuthenticatedUser.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AuthenticatedUserPresentationMapper</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.html" class="el_package">com.project.auth.presentation.auth.mapper</a> > <span class="el_class">AuthenticatedUserPresentationMapper</span></div><h1>AuthenticatedUserPresentationMapper</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 18</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">7</td><td class="ctr1">0</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="AuthenticatedUserPresentationMapper.java.html#L13" class="el_method">toResponse(LoadedKeycloakUser, Set)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="15" alt="15"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">6</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="AuthenticatedUserPresentationMapper.java.html#L10" class="el_method">AuthenticatedUserPresentationMapper()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="24" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AuthenticatedUserPresentationMapper.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.source.html" class="el_package">com.project.auth.presentation.auth.mapper</a> > <span class="el_source">AuthenticatedUserPresentationMapper.java</span></div><h1>AuthenticatedUserPresentationMapper.java</h1><pre class="source lang-java linenums">package com.project.auth.presentation.auth.mapper;
|
||||
|
||||
import com.project.auth.application.auth.identity.LoadedKeycloakUser;
|
||||
import com.project.auth.presentation.auth.dto.AuthenticatedUserResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
<span class="fc" id="L10">public class AuthenticatedUserPresentationMapper {</span>
|
||||
|
||||
public AuthenticatedUserResponse toResponse(LoadedKeycloakUser user, Set<String> authorities) {
|
||||
<span class="fc" id="L13"> return new AuthenticatedUserResponse(</span>
|
||||
<span class="fc" id="L14"> user.userId(),</span>
|
||||
<span class="fc" id="L15"> user.providerSubject(),</span>
|
||||
<span class="fc" id="L16"> user.email(),</span>
|
||||
<span class="fc" id="L17"> user.name(),</span>
|
||||
<span class="fc" id="L18"> user.provider(),</span>
|
||||
authorities
|
||||
);
|
||||
}
|
||||
}
|
||||
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>com.project.auth.presentation.auth.mapper</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <span class="el_package">com.project.auth.presentation.auth.mapper</span></div><h1>com.project.auth.presentation.auth.mapper</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 18</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">7</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="AuthenticatedUserPresentationMapper.html" class="el_class">AuthenticatedUserPresentationMapper</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="18" alt="18"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">7</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">2</td><td class="ctr1" id="l0">0</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>com.project.auth.presentation.auth.mapper</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <span class="el_package">com.project.auth.presentation.auth.mapper</span></div><h1>com.project.auth.presentation.auth.mapper</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 18</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">7</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="AuthenticatedUserPresentationMapper.java.html" class="el_source">AuthenticatedUserPresentationMapper.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="18" alt="18"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">7</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">2</td><td class="ctr1" id="l0">0</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApiErrorHttpStatusMapper.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.source.html" class="el_package">com.project.auth.presentation.support.exception</a> > <span class="el_source">ApiErrorHttpStatusMapper.java</span></div><h1>ApiErrorHttpStatusMapper.java</h1><pre class="source lang-java linenums">package com.project.auth.presentation.support.exception;
|
||||
|
||||
import com.project.auth.application.support.exception.AuthErrorCode;
|
||||
import com.project.auth.application.support.exception.ClientFacingErrorCode;
|
||||
import com.project.auth.application.support.exception.CommonErrorCode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public final class ApiErrorHttpStatusMapper {
|
||||
|
||||
<span class="fc" id="L12"> private static final Logger log = LoggerFactory.getLogger(ApiErrorHttpStatusMapper.class);</span>
|
||||
|
||||
private ApiErrorHttpStatusMapper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* default 분기가 존재하는 이유: {@link ClientFacingErrorCode}가 non-sealed이므로
|
||||
* (application/presentation 외 모듈도 레이어 경계를 넘지 않고 자체 구현을 추가할 수 있다)
|
||||
* 컴파일러가 exhaustiveness를 강제할 수 없다. 새로운 구현이 매핑 테이블에 누락된 채
|
||||
* 들어오면 조용히 500이 되는 대신 WARN 로그로 드러내어 운영 대시보드에서 식별 가능하게 한다.
|
||||
*
|
||||
* "새 ClientFacingErrorCode 구현은 반드시 매핑 테이블에 등록해야 한다"는 아키텍처 차원의
|
||||
* 게이트는 ApiErrorHttpStatusMapperClientFacingCoverageTest에 있다.
|
||||
*/
|
||||
public static HttpStatus map(ClientFacingErrorCode errorCode) {
|
||||
<span class="fc bfc" id="L27" title="All 4 branches covered."> return switch (errorCode) {</span>
|
||||
<span class="fc" id="L28"> case CommonErrorCode commonErrorCode -> mapCommon(commonErrorCode);</span>
|
||||
<span class="fc" id="L29"> case AuthErrorCode authErrorCode -> mapAuth(authErrorCode);</span>
|
||||
<span class="fc" id="L30"> case PresentationErrorCode presentationErrorCode -> mapPresentation(presentationErrorCode);</span>
|
||||
default -> {
|
||||
<span class="fc" id="L32"> log.warn("Unmapped ClientFacingErrorCode reached default branch. type={} code={} -> falling back to 500",</span>
|
||||
<span class="fc" id="L33"> errorCode.getClass().getName(), errorCode.code());</span>
|
||||
<span class="fc" id="L34"> yield HttpStatus.INTERNAL_SERVER_ERROR;</span>
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static HttpStatus mapCommon(CommonErrorCode errorCode) {
|
||||
<span class="fc" id="L40"> return switch (errorCode) {</span>
|
||||
<span class="fc" id="L41"> case INTERNAL_SERVER_ERROR -> HttpStatus.INTERNAL_SERVER_ERROR;</span>
|
||||
};
|
||||
}
|
||||
|
||||
private static HttpStatus mapPresentation(PresentationErrorCode errorCode) {
|
||||
<span class="fc bfc" id="L46" title="All 8 branches covered."> return switch (errorCode) {</span>
|
||||
<span class="fc" id="L47"> case INVALID_INPUT, INVALID_REQUEST_BODY, MISSING_PARAMETER, CONSTRAINT_VIOLATION, MISSING_HEADER, REQUEST_BINDING_FAILED, TYPE_MISMATCH, INVALID_PARAMETER -> HttpStatus.BAD_REQUEST;</span>
|
||||
<span class="fc" id="L48"> case METHOD_NOT_ALLOWED -> HttpStatus.METHOD_NOT_ALLOWED;</span>
|
||||
<span class="fc" id="L49"> case RESOURCE_NOT_FOUND -> HttpStatus.NOT_FOUND;</span>
|
||||
<span class="fc" id="L50"> case UNSUPPORTED_MEDIA_TYPE -> HttpStatus.UNSUPPORTED_MEDIA_TYPE;</span>
|
||||
<span class="fc" id="L51"> case NOT_ACCEPTABLE -> HttpStatus.NOT_ACCEPTABLE;</span>
|
||||
<span class="fc" id="L52"> case PAYLOAD_TOO_LARGE -> HttpStatus.CONTENT_TOO_LARGE;</span>
|
||||
<span class="fc" id="L53"> case MESSAGE_NOT_WRITABLE -> HttpStatus.INTERNAL_SERVER_ERROR;</span>
|
||||
<span class="fc" id="L54"> case UNHANDLED_CLIENT_ERROR -> HttpStatus.BAD_REQUEST;</span>
|
||||
};
|
||||
}
|
||||
|
||||
private static HttpStatus mapAuth(AuthErrorCode errorCode) {
|
||||
<span class="fc bfc" id="L59" title="All 5 branches covered."> return switch (errorCode) {</span>
|
||||
<span class="fc" id="L60"> case AUTHENTICATION_REQUIRED -> HttpStatus.UNAUTHORIZED;</span>
|
||||
<span class="fc" id="L61"> case ACCESS_DENIED -> HttpStatus.FORBIDDEN;</span>
|
||||
<span class="fc" id="L62"> case KEYCLOAK_CLAIMS_INVALID -> HttpStatus.BAD_REQUEST;</span>
|
||||
<span class="fc" id="L63"> case KEYCLOAK_USER_NOT_FOUND -> HttpStatus.NOT_FOUND;</span>
|
||||
<span class="fc" id="L64"> case KEYCLOAK_ACCOUNT_CONFLICT -> HttpStatus.CONFLICT;</span>
|
||||
};
|
||||
}
|
||||
}
|
||||
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationExceptionHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.source.html" class="el_package">com.project.auth.presentation.support.exception</a> > <span class="el_source">ApplicationExceptionHandler.java</span></div><h1>ApplicationExceptionHandler.java</h1><pre class="source lang-java linenums">package com.project.auth.presentation.support.exception;
|
||||
|
||||
import com.project.auth.application.support.exception.BusinessException;
|
||||
import com.project.auth.application.support.exception.CommonErrorCode;
|
||||
import com.project.auth.application.support.logging.LogSanitizer;
|
||||
import com.project.auth.presentation.support.response.ApiResult;
|
||||
import com.project.auth.presentation.support.response.ApiResultFactory;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
@RestControllerAdvice
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public class ApplicationExceptionHandler {
|
||||
|
||||
<span class="fc" id="L24"> private static final Logger log = LoggerFactory.getLogger(ApplicationExceptionHandler.class);</span>
|
||||
|
||||
private final ApiResultFactory apiResultFactory;
|
||||
|
||||
<span class="fc" id="L28"> public ApplicationExceptionHandler(ApiResultFactory apiResultFactory) {</span>
|
||||
<span class="fc" id="L29"> this.apiResultFactory = apiResultFactory;</span>
|
||||
<span class="fc" id="L30"> }</span>
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleBusinessException(
|
||||
BusinessException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
<span class="fc" id="L37"> log.warn(</span>
|
||||
"Business exception. exceptionType={} errorCode={} method={} requestPath={}",
|
||||
<span class="fc" id="L39"> exception.getClass().getSimpleName(),</span>
|
||||
<span class="fc" id="L40"> exception.getErrorCode().code(),</span>
|
||||
<span class="fc" id="L41"> request.getMethod(),</span>
|
||||
<span class="fc" id="L42"> LogSanitizer.requestPath(request.getRequestURI())</span>
|
||||
);
|
||||
|
||||
<span class="fc" id="L45"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(exception.getErrorCode()))</span>
|
||||
<span class="fc" id="L46"> .body(apiResultFactory.failure(exception.getErrorCode().code(), exception.getErrorCode().message()));</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* 응답 직렬화 실패에 대한 정직한 처리.
|
||||
*
|
||||
* 응답이 이미 커밋된 상태(이미 바이트가 wire에 나간 상태)라면 회복 경로가 없다.
|
||||
* 같은 직렬화 경로로 본문을 또 쓰려고 하면 동일한 실패가 재발하거나 무시된다.
|
||||
* 이때는 본문 없는 빈 ResponseEntity(500)를 반환한다 — @ExceptionHandler가 null을
|
||||
* 반환하면 Spring은 "응답이 작성되지 않음"으로 해석해 리졸버 체인으로 재진입할 수 있고,
|
||||
* 우리가 막으려는 재귀 실패가 바로 그것이다.
|
||||
*
|
||||
* 응답이 아직 커밋되지 않았다면 표준 ApiResult 본문을 시도한다. 직렬화 실패가
|
||||
* 구조적인 원인이면 2차 시도도 같은 이유로 실패할 수 있는데, 그때는
|
||||
* @ExceptionHandler(Exception.class) 안전망이 다시 잡아낸다.
|
||||
*/
|
||||
@ExceptionHandler(HttpMessageNotWritableException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleMessageNotWritableException(
|
||||
HttpMessageNotWritableException exception,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
<span class="fc" id="L68"> log.error(</span>
|
||||
"Response body not writable. committed={} method={} requestPath={}",
|
||||
<span class="fc" id="L70"> response.isCommitted(),</span>
|
||||
<span class="fc" id="L71"> request.getMethod(),</span>
|
||||
<span class="fc" id="L72"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
exception
|
||||
);
|
||||
|
||||
<span class="fc bfc" id="L76" title="All 2 branches covered."> if (response.isCommitted()) {</span>
|
||||
<span class="fc" id="L77"> return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();</span>
|
||||
}
|
||||
|
||||
<span class="fc" id="L80"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.MESSAGE_NOT_WRITABLE))</span>
|
||||
<span class="fc" id="L81"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L82"> PresentationErrorCode.MESSAGE_NOT_WRITABLE.code(),</span>
|
||||
<span class="fc" id="L83"> PresentationErrorCode.MESSAGE_NOT_WRITABLE.message()</span>
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 더 구체적인 @ExceptionHandler에 매칭되지 않은 모든 예외에 대한 최후의 안전망.
|
||||
*
|
||||
* 이 핸들러가 없으면 매칭되지 않은 예외는 advice를 우회해 {@code /error}로 흘러가고,
|
||||
* Spring Boot 기본 응답 형태가 반환되어 {@link ApiResult} 계약이 깨진다.
|
||||
*
|
||||
* 항상 500 + COMMON-999를 반환하고, 알 수 없는 장애 모드를 운영이 식별할 수 있도록
|
||||
* ERROR 레벨로 전체 스택트레이스를 남긴다.
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleUncaughtException(
|
||||
Exception exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
<span class="fc" id="L101"> log.error(</span>
|
||||
"Uncaught exception reached @ExceptionHandler safety net. exceptionType={} method={} requestPath={}",
|
||||
<span class="fc" id="L103"> exception.getClass().getName(),</span>
|
||||
<span class="fc" id="L104"> request.getMethod(),</span>
|
||||
<span class="fc" id="L105"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
exception
|
||||
);
|
||||
|
||||
<span class="fc" id="L109"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(CommonErrorCode.INTERNAL_SERVER_ERROR))</span>
|
||||
<span class="fc" id="L110"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L111"> CommonErrorCode.INTERNAL_SERVER_ERROR.code(),</span>
|
||||
<span class="fc" id="L112"> CommonErrorCode.INTERNAL_SERVER_ERROR.message()</span>
|
||||
));
|
||||
}
|
||||
}
|
||||
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PresentationErrorCode</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.html" class="el_package">com.project.auth.presentation.support.exception</a> > <span class="el_class">PresentationErrorCode</span></div><h1>PresentationErrorCode</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 140</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">4</td><td class="ctr1">0</td><td class="ctr2">22</td><td class="ctr1">0</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a3"><a href="PresentationErrorCode.java.html#L37" class="el_method">static {...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="123" alt="123"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">16</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="PresentationErrorCode.java.html#L57" class="el_method">PresentationErrorCode(String, int, String, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="10" height="10" title="11" alt="11"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="PresentationErrorCode.java.html#L64" class="el_method">code()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/greenbar.gif" width="2" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">100%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">0</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">0</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">0</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="PresentationErrorCode.java.html#L69" class="el_method">message()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/greenbar.gif" width="2" height="10" title="3" alt="3"/></td><td class="ctr2" id="c3">100%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">0</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">0</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">0</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PresentationErrorCode.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.source.html" class="el_package">com.project.auth.presentation.support.exception</a> > <span class="el_source">PresentationErrorCode.java</span></div><h1>PresentationErrorCode.java</h1><pre class="source lang-java linenums">package com.project.auth.presentation.support.exception;
|
||||
|
||||
import com.project.auth.application.support.exception.ClientFacingErrorCode;
|
||||
|
||||
/**
|
||||
* 클라이언트에 반환되는 presentation 계층 에러 코드.
|
||||
*
|
||||
* 클라이언트 SDK가 원인별로 분기 로직을 짤 수 있도록 트리거별로 의도적으로 코드를 분리했다.
|
||||
* <ul>
|
||||
* <li>{@link #INVALID_INPUT} — 요청 본문 빈 검증 실패
|
||||
* ({@code @Valid @RequestBody}, MethodArgumentNotValidException).</li>
|
||||
* <li>{@link #INVALID_REQUEST_BODY} — 요청 본문 파싱 실패
|
||||
* (HttpMessageNotReadableException, 예: 잘못된 JSON).</li>
|
||||
* <li>{@link #MISSING_PARAMETER} — 필수 query/form 파라미터 누락
|
||||
* (MissingServletRequestParameterException).</li>
|
||||
* <li>{@link #CONSTRAINT_VIOLATION} — 컨트롤러 메서드 인자 범위 밖에서 트리거된
|
||||
* Jakarta Bean Validation (ConstraintViolationException, 주로 @Validated 서비스
|
||||
* 또는 @RequestParam 제약).</li>
|
||||
* <li>{@link #TYPE_MISMATCH} — query/path/form 값을 대상 타입으로 변환 실패
|
||||
* (TypeMismatchException, 예: Long에 {@code id=abc}).</li>
|
||||
* <li>{@link #INVALID_PARAMETER} — 핸들러 메서드 레벨 검증 실패
|
||||
* (HandlerMethodValidationException, 예: {@code @RequestParam @Size}).</li>
|
||||
* <li>{@link #MISSING_HEADER} — 필수 @RequestHeader 누락.</li>
|
||||
* <li>{@link #REQUEST_BINDING_FAILED} — 더 구체적인 코드로 분류되지 않은
|
||||
* 일반 ServletRequestBindingException.</li>
|
||||
* <li>{@link #METHOD_NOT_ALLOWED} — 라우트가 지원하지 않는 HTTP 메서드.</li>
|
||||
* <li>{@link #RESOURCE_NOT_FOUND} — 요청 경로에 매칭되는 라우트/정적 리소스 없음.</li>
|
||||
* <li>{@link #UNSUPPORTED_MEDIA_TYPE} — 라우트가 수용하지 않는 Content-Type.</li>
|
||||
* <li>{@link #NOT_ACCEPTABLE} — Accept 헤더를 만족시킬 수 없음.</li>
|
||||
* <li>{@link #PAYLOAD_TOO_LARGE} — multipart 업로드가 허용 한도 초과.</li>
|
||||
* <li>{@link #MESSAGE_NOT_WRITABLE} — 응답 직렬화 실패 (5xx).</li>
|
||||
* <li>{@link #UNHANDLED_CLIENT_ERROR} — 더 구체적인 매핑이 없는 4xx 상태 캐리어
|
||||
* (ResponseStatusException, ErrorResponseException) 또는 컨테이너 레벨 4xx 응답에
|
||||
* 대한 폴백.</li>
|
||||
* </ul>
|
||||
*/
|
||||
<span class="fc" id="L37">public enum PresentationErrorCode implements ClientFacingErrorCode {</span>
|
||||
<span class="fc" id="L38"> INVALID_INPUT("PRES-001", "요청 값이 올바르지 않습니다."),</span>
|
||||
<span class="fc" id="L39"> INVALID_REQUEST_BODY("PRES-002", "요청 본문을 읽을 수 없습니다."),</span>
|
||||
<span class="fc" id="L40"> METHOD_NOT_ALLOWED("PRES-003", "지원하지 않는 HTTP 메서드입니다."),</span>
|
||||
<span class="fc" id="L41"> MISSING_PARAMETER("PRES-004", "필수 요청 파라미터가 누락되었습니다."),</span>
|
||||
<span class="fc" id="L42"> RESOURCE_NOT_FOUND("PRES-005", "요청한 리소스를 찾을 수 없습니다."),</span>
|
||||
<span class="fc" id="L43"> CONSTRAINT_VIOLATION("PRES-006", "요청 값이 제약 조건을 위반했습니다."),</span>
|
||||
<span class="fc" id="L44"> UNSUPPORTED_MEDIA_TYPE("PRES-007", "지원하지 않는 Content-Type입니다."),</span>
|
||||
<span class="fc" id="L45"> NOT_ACCEPTABLE("PRES-008", "요청한 응답 형식을 제공할 수 없습니다."),</span>
|
||||
<span class="fc" id="L46"> MISSING_HEADER("PRES-009", "필수 요청 헤더가 누락되었습니다."),</span>
|
||||
<span class="fc" id="L47"> REQUEST_BINDING_FAILED("PRES-010", "요청 바인딩에 실패했습니다."),</span>
|
||||
<span class="fc" id="L48"> MESSAGE_NOT_WRITABLE("PRES-011", "응답 데이터를 처리할 수 없습니다."),</span>
|
||||
<span class="fc" id="L49"> PAYLOAD_TOO_LARGE("PRES-012", "요청 본문 크기가 허용 한도를 초과했습니다."),</span>
|
||||
<span class="fc" id="L50"> UNHANDLED_CLIENT_ERROR("PRES-013", "처리하지 못한 클라이언트 오류입니다."),</span>
|
||||
<span class="fc" id="L51"> TYPE_MISMATCH("PRES-014", "요청 파라미터 타입이 올바르지 않습니다."),</span>
|
||||
<span class="fc" id="L52"> INVALID_PARAMETER("PRES-015", "요청 파라미터 검증에 실패했습니다.");</span>
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
||||
<span class="fc" id="L57"> PresentationErrorCode(String code, String message) {</span>
|
||||
<span class="fc" id="L58"> this.code = code;</span>
|
||||
<span class="fc" id="L59"> this.message = message;</span>
|
||||
<span class="fc" id="L60"> }</span>
|
||||
|
||||
@Override
|
||||
public String code() {
|
||||
<span class="fc" id="L64"> return code;</span>
|
||||
}
|
||||
|
||||
@Override
|
||||
public String message() {
|
||||
<span class="fc" id="L69"> return message;</span>
|
||||
}
|
||||
}
|
||||
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1,266 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RequestExceptionHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.source.html" class="el_package">com.project.auth.presentation.support.exception</a> > <span class="el_source">RequestExceptionHandler.java</span></div><h1>RequestExceptionHandler.java</h1><pre class="source lang-java linenums">package com.project.auth.presentation.support.exception;
|
||||
|
||||
import com.project.auth.application.support.exception.CommonErrorCode;
|
||||
import com.project.auth.application.support.logging.LogSanitizer;
|
||||
import com.project.auth.presentation.support.response.ApiResult;
|
||||
import com.project.auth.presentation.support.response.ApiResultFactory;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.web.ErrorResponse;
|
||||
import org.springframework.web.ErrorResponseException;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.MissingRequestHeaderException;
|
||||
import org.springframework.web.bind.ServletRequestBindingException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||
|
||||
@RestControllerAdvice
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
|
||||
public class RequestExceptionHandler {
|
||||
|
||||
<span class="fc" id="L34"> private static final Logger log = LoggerFactory.getLogger(RequestExceptionHandler.class);</span>
|
||||
|
||||
private final ApiResultFactory apiResultFactory;
|
||||
|
||||
<span class="fc" id="L38"> public RequestExceptionHandler(ApiResultFactory apiResultFactory) {</span>
|
||||
<span class="fc" id="L39"> this.apiResultFactory = apiResultFactory;</span>
|
||||
<span class="fc" id="L40"> }</span>
|
||||
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleMessageNotReadableException(
|
||||
HttpMessageNotReadableException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
<span class="fc" id="L47"> log.warn("Request body not readable. method={} requestPath={} errorCode={}",</span>
|
||||
<span class="fc" id="L48"> request.getMethod(),</span>
|
||||
<span class="fc" id="L49"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
<span class="fc" id="L50"> PresentationErrorCode.INVALID_REQUEST_BODY.code());</span>
|
||||
|
||||
<span class="fc" id="L52"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.INVALID_REQUEST_BODY))</span>
|
||||
<span class="fc" id="L53"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L54"> PresentationErrorCode.INVALID_REQUEST_BODY.code(),</span>
|
||||
<span class="fc" id="L55"> PresentationErrorCode.INVALID_REQUEST_BODY.message()</span>
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleMethodNotSupportedException(
|
||||
HttpRequestMethodNotSupportedException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
<span class="fc" id="L64"> log.warn("Method not supported. method={} requestPath={} attemptedMethod={} errorCode={}",</span>
|
||||
<span class="fc" id="L65"> request.getMethod(),</span>
|
||||
<span class="fc" id="L66"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
<span class="fc" id="L67"> LogSanitizer.normalize(exception.getMethod()),</span>
|
||||
<span class="fc" id="L68"> PresentationErrorCode.METHOD_NOT_ALLOWED.code());</span>
|
||||
|
||||
<span class="fc" id="L70"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.METHOD_NOT_ALLOWED))</span>
|
||||
<span class="fc" id="L71"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L72"> PresentationErrorCode.METHOD_NOT_ALLOWED.code(),</span>
|
||||
<span class="fc" id="L73"> PresentationErrorCode.METHOD_NOT_ALLOWED.message()</span>
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleMissingParameterException(
|
||||
MissingServletRequestParameterException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
<span class="fc" id="L82"> log.warn("Missing request parameter. method={} requestPath={} parameter={} errorCode={}",</span>
|
||||
<span class="fc" id="L83"> request.getMethod(),</span>
|
||||
<span class="fc" id="L84"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
<span class="fc" id="L85"> LogSanitizer.normalize(exception.getParameterName()),</span>
|
||||
<span class="fc" id="L86"> PresentationErrorCode.MISSING_PARAMETER.code());</span>
|
||||
|
||||
<span class="fc" id="L88"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.MISSING_PARAMETER))</span>
|
||||
<span class="fc" id="L89"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L90"> PresentationErrorCode.MISSING_PARAMETER.code(),</span>
|
||||
<span class="fc" id="L91"> PresentationErrorCode.MISSING_PARAMETER.message()</span>
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(TypeMismatchException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleTypeMismatchException(
|
||||
TypeMismatchException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
<span class="fc" id="L100"> log.warn("Type mismatch for parameter. method={} requestPath={} parameter={} errorCode={}",</span>
|
||||
<span class="fc" id="L101"> request.getMethod(),</span>
|
||||
<span class="fc" id="L102"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
<span class="fc" id="L103"> LogSanitizer.normalize(exception.getPropertyName()),</span>
|
||||
<span class="fc" id="L104"> PresentationErrorCode.TYPE_MISMATCH.code());</span>
|
||||
|
||||
<span class="fc" id="L106"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.TYPE_MISMATCH))</span>
|
||||
<span class="fc" id="L107"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L108"> PresentationErrorCode.TYPE_MISMATCH.code(),</span>
|
||||
<span class="fc" id="L109"> PresentationErrorCode.TYPE_MISMATCH.message()</span>
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleMediaTypeNotSupportedException(
|
||||
HttpMediaTypeNotSupportedException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
<span class="fc" id="L118"> log.warn("Unsupported media type. method={} requestPath={} contentType={} errorCode={}",</span>
|
||||
<span class="fc" id="L119"> request.getMethod(),</span>
|
||||
<span class="fc" id="L120"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
<span class="fc" id="L121"> LogSanitizer.normalize(String.valueOf(exception.getContentType())),</span>
|
||||
<span class="fc" id="L122"> PresentationErrorCode.UNSUPPORTED_MEDIA_TYPE.code());</span>
|
||||
|
||||
<span class="fc" id="L124"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.UNSUPPORTED_MEDIA_TYPE))</span>
|
||||
<span class="fc" id="L125"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L126"> PresentationErrorCode.UNSUPPORTED_MEDIA_TYPE.code(),</span>
|
||||
<span class="fc" id="L127"> PresentationErrorCode.UNSUPPORTED_MEDIA_TYPE.message()</span>
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleMediaTypeNotAcceptableException(
|
||||
HttpMediaTypeNotAcceptableException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
<span class="fc" id="L136"> log.warn("Not acceptable media type. method={} requestPath={} errorCode={}",</span>
|
||||
<span class="fc" id="L137"> request.getMethod(),</span>
|
||||
<span class="fc" id="L138"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
<span class="fc" id="L139"> PresentationErrorCode.NOT_ACCEPTABLE.code());</span>
|
||||
|
||||
<span class="fc" id="L141"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.NOT_ACCEPTABLE))</span>
|
||||
<span class="fc" id="L142"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L143"> PresentationErrorCode.NOT_ACCEPTABLE.code(),</span>
|
||||
<span class="fc" id="L144"> PresentationErrorCode.NOT_ACCEPTABLE.message()</span>
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(MissingRequestHeaderException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleMissingRequestHeaderException(
|
||||
MissingRequestHeaderException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
<span class="fc" id="L153"> log.warn("Missing request header. method={} requestPath={} header={} errorCode={}",</span>
|
||||
<span class="fc" id="L154"> request.getMethod(),</span>
|
||||
<span class="fc" id="L155"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
<span class="fc" id="L156"> LogSanitizer.normalize(exception.getHeaderName()),</span>
|
||||
<span class="fc" id="L157"> PresentationErrorCode.MISSING_HEADER.code());</span>
|
||||
|
||||
<span class="fc" id="L159"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.MISSING_HEADER))</span>
|
||||
<span class="fc" id="L160"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L161"> PresentationErrorCode.MISSING_HEADER.code(),</span>
|
||||
<span class="fc" id="L162"> PresentationErrorCode.MISSING_HEADER.message()</span>
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(ServletRequestBindingException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleServletRequestBindingException(
|
||||
ServletRequestBindingException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
<span class="fc" id="L171"> log.warn("Request binding failed. method={} requestPath={} errorCode={}",</span>
|
||||
<span class="fc" id="L172"> request.getMethod(),</span>
|
||||
<span class="fc" id="L173"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
<span class="fc" id="L174"> PresentationErrorCode.REQUEST_BINDING_FAILED.code());</span>
|
||||
|
||||
<span class="fc" id="L176"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.REQUEST_BINDING_FAILED))</span>
|
||||
<span class="fc" id="L177"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L178"> PresentationErrorCode.REQUEST_BINDING_FAILED.code(),</span>
|
||||
<span class="fc" id="L179"> PresentationErrorCode.REQUEST_BINDING_FAILED.message()</span>
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(NoResourceFoundException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleNoResourceFoundException(
|
||||
NoResourceFoundException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
<span class="fc" id="L188"> log.warn("No resource found. method={} requestPath={} resourcePath={} errorCode={}",</span>
|
||||
<span class="fc" id="L189"> request.getMethod(),</span>
|
||||
<span class="fc" id="L190"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
<span class="fc" id="L191"> LogSanitizer.requestPath(exception.getResourcePath()),</span>
|
||||
<span class="fc" id="L192"> PresentationErrorCode.RESOURCE_NOT_FOUND.code());</span>
|
||||
|
||||
<span class="fc" id="L194"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.RESOURCE_NOT_FOUND))</span>
|
||||
<span class="fc" id="L195"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L196"> PresentationErrorCode.RESOURCE_NOT_FOUND.code(),</span>
|
||||
<span class="fc" id="L197"> PresentationErrorCode.RESOURCE_NOT_FOUND.message()</span>
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(MaxUploadSizeExceededException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleMaxUploadSizeExceededException(
|
||||
MaxUploadSizeExceededException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
<span class="fc" id="L206"> log.warn("Upload payload too large. method={} requestPath={} maxBytes={} errorCode={}",</span>
|
||||
<span class="fc" id="L207"> request.getMethod(),</span>
|
||||
<span class="fc" id="L208"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
<span class="fc" id="L209"> exception.getMaxUploadSize(),</span>
|
||||
<span class="fc" id="L210"> PresentationErrorCode.PAYLOAD_TOO_LARGE.code());</span>
|
||||
|
||||
<span class="fc" id="L212"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.PAYLOAD_TOO_LARGE))</span>
|
||||
<span class="fc" id="L213"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L214"> PresentationErrorCode.PAYLOAD_TOO_LARGE.code(),</span>
|
||||
<span class="fc" id="L215"> PresentationErrorCode.PAYLOAD_TOO_LARGE.message()</span>
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 더 구체적인 핸들러로 잡히지 않은, 프레임워크가 직접 던진 상태 캐리어 예외 처리.
|
||||
* 상위에서 결정된 4xx vs 5xx 분기가 클라이언트까지 보존되도록 carriedStatus를 그대로 사용한다.
|
||||
*
|
||||
* 본문 분류: 5xx는 내부 분류 노출을 막기 위해 COMMON-999로 정규화하고, 4xx는
|
||||
* UNHANDLED_CLIENT_ERROR로 폴백한다. 클라이언트 SDK가 "요청 잘못" vs "서버 결함"을
|
||||
* 여전히 구분할 수 있게 하기 위함이다.
|
||||
*
|
||||
* ResponseStatusException과 ErrorResponseException은 둘 다 {@link ErrorResponse}를 구현하면서
|
||||
* 동시에 {@link Throwable}을 상속하므로 로그 호출부의 (Throwable) 캐스트가 안전하다.
|
||||
* 향후 Throwable이 아닌 ErrorResponse 구현체를 이 @ExceptionHandler 목록에 추가하면
|
||||
* 런타임 ClassCastException이 발생하므로, 이 핸들러는 명시한 두 예외 타입으로만 한정한다.
|
||||
*/
|
||||
@ExceptionHandler({ResponseStatusException.class, ErrorResponseException.class})
|
||||
public ResponseEntity<ApiResult<Void>> handleErrorResponseException(
|
||||
ErrorResponse exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
<span class="fc" id="L237"> HttpStatusCode carriedStatus = exception.getStatusCode();</span>
|
||||
<span class="fc" id="L238"> int statusValue = carriedStatus.value();</span>
|
||||
|
||||
<span class="fc bfc" id="L240" title="All 2 branches covered."> if (statusValue >= 500) {</span>
|
||||
<span class="fc" id="L241"> log.error("Framework-thrown 5xx status. method={} requestPath={} status={}",</span>
|
||||
<span class="fc" id="L242"> request.getMethod(),</span>
|
||||
<span class="fc" id="L243"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
<span class="fc" id="L244"> statusValue,</span>
|
||||
(Throwable) exception);
|
||||
<span class="fc" id="L246"> return ResponseEntity.status(carriedStatus)</span>
|
||||
<span class="fc" id="L247"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L248"> CommonErrorCode.INTERNAL_SERVER_ERROR.code(),</span>
|
||||
<span class="fc" id="L249"> CommonErrorCode.INTERNAL_SERVER_ERROR.message()</span>
|
||||
));
|
||||
}
|
||||
|
||||
<span class="fc" id="L253"> log.warn("Framework-thrown 4xx status. method={} requestPath={} status={} errorCode={}",</span>
|
||||
<span class="fc" id="L254"> request.getMethod(),</span>
|
||||
<span class="fc" id="L255"> LogSanitizer.requestPath(request.getRequestURI()),</span>
|
||||
<span class="fc" id="L256"> statusValue,</span>
|
||||
<span class="fc" id="L257"> PresentationErrorCode.UNHANDLED_CLIENT_ERROR.code());</span>
|
||||
<span class="fc" id="L258"> return ResponseEntity.status(carriedStatus)</span>
|
||||
<span class="fc" id="L259"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L260"> PresentationErrorCode.UNHANDLED_CLIENT_ERROR.code(),</span>
|
||||
<span class="fc" id="L261"> PresentationErrorCode.UNHANDLED_CLIENT_ERROR.message()</span>
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1,230 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ValidationExceptionHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.source.html" class="el_package">com.project.auth.presentation.support.exception</a> > <span class="el_source">ValidationExceptionHandler.java</span></div><h1>ValidationExceptionHandler.java</h1><pre class="source lang-java linenums">package com.project.auth.presentation.support.exception;
|
||||
|
||||
import com.project.auth.presentation.support.response.ApiResult;
|
||||
import com.project.auth.presentation.support.response.ApiResultFactory;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import jakarta.validation.Path;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.context.MessageSourceResolvable;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.HandlerMethodValidationException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestControllerAdvice
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
|
||||
public class ValidationExceptionHandler {
|
||||
|
||||
/**
|
||||
* 필드와 연결되지 않은 클래스 레벨/cross-field 검증 실패(예: DTO의 {@code @AssertTrue},
|
||||
* 커스텀 클래스 레벨 {@code ConstraintValidator})를 담는 버킷 키.
|
||||
*
|
||||
* 이 버킷이 없으면 ObjectError가 조용히 폐기되어 클라이언트는 {@code errors: {}}만 받고
|
||||
* 어디가 잘못됐는지 알 수 없다.
|
||||
*/
|
||||
public static final String GLOBAL_ERROR_KEY = "__global__";
|
||||
|
||||
/**
|
||||
* {@code getDefaultMessage()}와 {@code error.code()}가 모두 null/공백인 경우의 폴백 메시지.
|
||||
*
|
||||
* Bean Validation은 MessageSource로 해석되는 메시지 코드만 정의된 케이스를 허용하며,
|
||||
* 이때 getDefaultMessage()는 null을 반환할 수 있다. 이 sentinel이 없으면 응답 errors에
|
||||
* {@code [null]} 항목이 그대로 들어간다.
|
||||
*/
|
||||
static final String UNRESOLVED_VIOLATION_MESSAGE = "validation failed";
|
||||
|
||||
<span class="fc" id="L48"> private static final Logger log = LoggerFactory.getLogger(ValidationExceptionHandler.class);</span>
|
||||
|
||||
private final ApiResultFactory apiResultFactory;
|
||||
|
||||
<span class="fc" id="L52"> public ValidationExceptionHandler(ApiResultFactory apiResultFactory) {</span>
|
||||
<span class="fc" id="L53"> this.apiResultFactory = apiResultFactory;</span>
|
||||
<span class="fc" id="L54"> }</span>
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleValidationException(
|
||||
MethodArgumentNotValidException exception
|
||||
) {
|
||||
<span class="fc" id="L60"> Map<String, List<String>> errors = new LinkedHashMap<>();</span>
|
||||
<span class="fc bfc" id="L61" title="All 2 branches covered."> for (FieldError fieldError : exception.getBindingResult().getFieldErrors()) {</span>
|
||||
<span class="fc" id="L62"> errors.computeIfAbsent(fieldFieldToJsonPointer(fieldError.getField()), k -> new ArrayList<>())</span>
|
||||
<span class="fc" id="L63"> .add(resolveMessage(fieldError.getDefaultMessage(), fieldError.getCode()));</span>
|
||||
<span class="fc" id="L64"> }</span>
|
||||
<span class="fc bfc" id="L65" title="All 2 branches covered."> for (ObjectError globalError : exception.getBindingResult().getGlobalErrors()) {</span>
|
||||
<span class="fc" id="L66"> errors.computeIfAbsent(GLOBAL_ERROR_KEY, k -> new ArrayList<>())</span>
|
||||
<span class="fc" id="L67"> .add(resolveMessage(globalError.getDefaultMessage(), globalError.getCode()));</span>
|
||||
<span class="fc" id="L68"> }</span>
|
||||
|
||||
<span class="fc" id="L70"> log.warn("Validation failed: {}", errors);</span>
|
||||
|
||||
<span class="fc" id="L72"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.INVALID_INPUT))</span>
|
||||
<span class="fc" id="L73"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L74"> PresentationErrorCode.INVALID_INPUT.code(),</span>
|
||||
<span class="fc" id="L75"> PresentationErrorCode.INVALID_INPUT.message(),</span>
|
||||
errors
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleConstraintViolationException(
|
||||
ConstraintViolationException exception
|
||||
) {
|
||||
<span class="fc" id="L84"> Map<String, List<String>> errors = new LinkedHashMap<>();</span>
|
||||
<span class="fc bfc" id="L85" title="All 2 branches covered."> for (ConstraintViolation<?> violation : exception.getConstraintViolations()) {</span>
|
||||
<span class="fc" id="L86"> String pointer = toJsonPointer(violation.getPropertyPath());</span>
|
||||
<span class="fc" id="L87"> errors.computeIfAbsent(pointer, k -> new ArrayList<>())</span>
|
||||
<span class="fc" id="L88"> .add(resolveMessage(violation.getMessage(), null));</span>
|
||||
<span class="fc" id="L89"> }</span>
|
||||
|
||||
<span class="fc" id="L91"> log.warn("Constraint violation: {}", errors);</span>
|
||||
|
||||
<span class="fc" id="L93"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.CONSTRAINT_VIOLATION))</span>
|
||||
<span class="fc" id="L94"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L95"> PresentationErrorCode.CONSTRAINT_VIOLATION.code(),</span>
|
||||
<span class="fc" id="L96"> PresentationErrorCode.CONSTRAINT_VIOLATION.message(),</span>
|
||||
errors
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(HandlerMethodValidationException.class)
|
||||
public ResponseEntity<ApiResult<Void>> handleHandlerMethodValidationException(
|
||||
HandlerMethodValidationException exception
|
||||
) {
|
||||
<span class="fc" id="L105"> Map<String, List<String>> errors = new LinkedHashMap<>();</span>
|
||||
<span class="fc" id="L106"> int unnamedCounter = 0;</span>
|
||||
<span class="fc bfc" id="L107" title="All 2 branches covered."> for (var result : exception.getValueResults()) {</span>
|
||||
<span class="fc" id="L108"> String paramName = result.getMethodParameter().getParameterName();</span>
|
||||
String key;
|
||||
<span class="fc bfc" id="L110" title="All 2 branches covered."> if (paramName != null) {</span>
|
||||
<span class="fc" id="L111"> key = fieldFieldToJsonPointer(paramName);</span>
|
||||
} else {
|
||||
<span class="fc" id="L113"> key = fieldFieldToJsonPointer("unknown_" + result.getMethodParameter().getParameterIndex());</span>
|
||||
<span class="fc" id="L114"> unnamedCounter++;</span>
|
||||
}
|
||||
<span class="fc bfc" id="L116" title="All 2 branches covered."> for (MessageSourceResolvable error : result.getResolvableErrors()) {</span>
|
||||
<span class="fc" id="L117"> String[] codes = error.getCodes();</span>
|
||||
<span class="pc bpc" id="L118" title="2 of 4 branches missed."> String firstCode = codes != null && codes.length > 0 ? codes[0] : null;</span>
|
||||
<span class="fc" id="L119"> errors.computeIfAbsent(key, k -> new ArrayList<>())</span>
|
||||
<span class="fc" id="L120"> .add(resolveMessage(error.getDefaultMessage(), firstCode));</span>
|
||||
<span class="fc" id="L121"> }</span>
|
||||
<span class="fc" id="L122"> }</span>
|
||||
<span class="fc bfc" id="L123" title="All 2 branches covered."> if (unnamedCounter > 0) {</span>
|
||||
<span class="fc" id="L124"> log.warn("Handler method validation: {} unnamed parameter(s); compile with -parameters to recover names",</span>
|
||||
<span class="fc" id="L125"> unnamedCounter);</span>
|
||||
}
|
||||
|
||||
<span class="fc" id="L128"> log.warn("Handler method validation failed: {}", errors);</span>
|
||||
|
||||
<span class="fc" id="L130"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(PresentationErrorCode.INVALID_PARAMETER))</span>
|
||||
<span class="fc" id="L131"> .body(apiResultFactory.failure(</span>
|
||||
<span class="fc" id="L132"> PresentationErrorCode.INVALID_PARAMETER.code(),</span>
|
||||
<span class="fc" id="L133"> PresentationErrorCode.INVALID_PARAMETER.message(),</span>
|
||||
errors
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bean Validation의 propertyPath를 JSON Pointer(RFC 6901) 형식으로 변환한다.
|
||||
* 예) {@code users[0].email} -> {@code /users/0/email}
|
||||
*
|
||||
* 서로 다른 파라미터가 우연히 같은 필드명으로 끝나도 충돌이 발생하지 않으며,
|
||||
* 클라이언트 입장에서 모호함이 없는 형식이다.
|
||||
* 메서드 파라미터 접두사(예: {@code findUser.id})는 출처 파라미터를 식별하므로
|
||||
* 첫 pointer 세그먼트로 보존한다.
|
||||
*/
|
||||
private static String toJsonPointer(Path path) {
|
||||
<span class="fc" id="L148"> StringBuilder builder = new StringBuilder();</span>
|
||||
<span class="fc bfc" id="L149" title="All 2 branches covered."> for (Path.Node node : path) {</span>
|
||||
<span class="fc" id="L150"> String name = node.getName();</span>
|
||||
<span class="pc bpc" id="L151" title="1 of 2 branches missed."> if (name == null) {</span>
|
||||
<span class="nc" id="L152"> continue;</span>
|
||||
}
|
||||
<span class="fc" id="L154"> builder.append('/').append(escapeJsonPointerSegment(name));</span>
|
||||
<span class="fc" id="L155"> Integer index = node.getIndex();</span>
|
||||
<span class="pc bpc" id="L156" title="1 of 2 branches missed."> if (index != null) {</span>
|
||||
<span class="nc" id="L157"> builder.append('/').append(index);</span>
|
||||
<span class="pc bpc" id="L158" title="1 of 2 branches missed."> } else if (node.getKey() != null) {</span>
|
||||
<span class="nc" id="L159"> builder.append('/').append(escapeJsonPointerSegment(String.valueOf(node.getKey())));</span>
|
||||
}
|
||||
<span class="fc" id="L161"> }</span>
|
||||
<span class="pc bpc" id="L162" title="1 of 2 branches missed."> return builder.length() == 0 ? "/" : builder.toString();</span>
|
||||
}
|
||||
|
||||
private static String escapeJsonPointerSegment(String segment) {
|
||||
<span class="fc" id="L166"> return segment.replace("~", "~0").replace("/", "~1");</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* Spring {@code BindingResult.FieldError#getField()} 형식(예: {@code user.email},
|
||||
* {@code items[0].name})을 JSON Pointer(RFC 6901)로 변환한다.
|
||||
* ConstraintViolationException 핸들러와 키 형식을 통일하여, 응답 errors 맵의 키 규약을
|
||||
* 단일화한다(클라이언트가 두 가지 형식을 분기 처리할 필요가 없게 한다).
|
||||
*/
|
||||
static String fieldFieldToJsonPointer(String field) {
|
||||
<span class="fc bfc" id="L176" title="All 4 branches covered."> if (field == null || field.isEmpty()) {</span>
|
||||
<span class="fc" id="L177"> return "/";</span>
|
||||
}
|
||||
<span class="fc" id="L179"> StringBuilder builder = new StringBuilder();</span>
|
||||
<span class="fc" id="L180"> int index = 0;</span>
|
||||
<span class="fc" id="L181"> int length = field.length();</span>
|
||||
<span class="fc bfc" id="L182" title="All 2 branches covered."> while (index < length) {</span>
|
||||
<span class="fc" id="L183"> char ch = field.charAt(index);</span>
|
||||
<span class="fc bfc" id="L184" title="All 2 branches covered."> if (ch == '.') {</span>
|
||||
<span class="fc" id="L185"> index++;</span>
|
||||
<span class="fc" id="L186"> continue;</span>
|
||||
}
|
||||
<span class="fc bfc" id="L188" title="All 2 branches covered."> if (ch == '[') {</span>
|
||||
<span class="fc" id="L189"> int end = field.indexOf(']', index);</span>
|
||||
<span class="pc bpc" id="L190" title="1 of 2 branches missed."> if (end == -1) {</span>
|
||||
<span class="nc" id="L191"> builder.append('/').append(escapeJsonPointerSegment(field.substring(index)));</span>
|
||||
<span class="nc" id="L192"> break;</span>
|
||||
}
|
||||
<span class="fc" id="L194"> builder.append('/').append(escapeJsonPointerSegment(field.substring(index + 1, end)));</span>
|
||||
<span class="fc" id="L195"> index = end + 1;</span>
|
||||
<span class="fc" id="L196"> continue;</span>
|
||||
}
|
||||
<span class="fc" id="L198"> int nextDot = field.indexOf('.', index);</span>
|
||||
<span class="fc" id="L199"> int nextBracket = field.indexOf('[', index);</span>
|
||||
<span class="fc" id="L200"> int next = minNonNegative(nextDot, nextBracket);</span>
|
||||
<span class="fc bfc" id="L201" title="All 2 branches covered."> if (next == -1) {</span>
|
||||
<span class="fc" id="L202"> next = length;</span>
|
||||
}
|
||||
<span class="fc" id="L204"> builder.append('/').append(escapeJsonPointerSegment(field.substring(index, next)));</span>
|
||||
<span class="fc" id="L205"> index = next;</span>
|
||||
<span class="fc" id="L206"> }</span>
|
||||
<span class="pc bpc" id="L207" title="1 of 2 branches missed."> return builder.length() == 0 ? "/" : builder.toString();</span>
|
||||
}
|
||||
|
||||
private static int minNonNegative(int a, int b) {
|
||||
<span class="fc bfc" id="L211" title="All 2 branches covered."> if (a < 0) {</span>
|
||||
<span class="fc" id="L212"> return b;</span>
|
||||
}
|
||||
<span class="fc bfc" id="L214" title="All 2 branches covered."> if (b < 0) {</span>
|
||||
<span class="fc" id="L215"> return a;</span>
|
||||
}
|
||||
<span class="fc" id="L217"> return Math.min(a, b);</span>
|
||||
}
|
||||
|
||||
private static String resolveMessage(String defaultMessage, String fallbackCode) {
|
||||
<span class="pc bpc" id="L221" title="1 of 4 branches missed."> if (defaultMessage != null && !defaultMessage.isBlank()) {</span>
|
||||
<span class="fc" id="L222"> return defaultMessage;</span>
|
||||
}
|
||||
<span class="pc bpc" id="L224" title="2 of 4 branches missed."> if (fallbackCode != null && !fallbackCode.isBlank()) {</span>
|
||||
<span class="fc" id="L225"> return fallbackCode;</span>
|
||||
}
|
||||
<span class="nc" id="L227"> return UNRESOLVED_VIOLATION_MESSAGE;</span>
|
||||
}
|
||||
}
|
||||
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApiResult</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.html" class="el_package">com.project.auth.presentation.support.response</a> > <span class="el_class">ApiResult</span></div><h1>ApiResult</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 24</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">1</td><td class="ctr1">0</td><td class="ctr2">1</td><td class="ctr1">0</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ApiResult.java.html#L22" class="el_method">ApiResult(boolean, String, String, Object, Map, String, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="24" alt="24"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApiResult.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.source.html" class="el_package">com.project.auth.presentation.support.response</a> > <span class="el_source">ApiResult.java</span></div><h1>ApiResult.java</h1><pre class="source lang-java linenums">package com.project.auth.presentation.support.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 모든 API 응답의 통일된 응답 봉투(envelope).
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code data}: 성공 응답의 페이로드. 실패 시에는 null.</li>
|
||||
* <li>{@code errors}: 검증/cross-field 에러 메시지의 키-리스트 맵. 검증 실패에서만
|
||||
* 채워진다. 필드 레벨 진단이 없으면 null. {@code data}와 분리해서 OpenAPI 스펙이
|
||||
* {@code data}를 도메인 타입과 에러 맵의 oneOf로 모델링할 필요가 없게 한다.</li>
|
||||
* </ul>
|
||||
*
|
||||
* null 필드는 JSON 직렬화 시 제외하여, 성공 응답에 {@code "errors": null} 잡음이나
|
||||
* 실패 응답에 {@code "data": null}이 따라붙지 않도록 한다.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
<span class="fc" id="L22">public record ApiResult<T>(</span>
|
||||
boolean success,
|
||||
String code,
|
||||
String message,
|
||||
T data,
|
||||
Map<String, List<String>> errors,
|
||||
String traceId,
|
||||
String timestamp
|
||||
) {
|
||||
}
|
||||
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApiSuccessCode</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.html" class="el_package">com.project.auth.presentation.support.response</a> > <span class="el_class">ApiSuccessCode</span></div><h1>ApiSuccessCode</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">28 of 28</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">8</td><td class="ctr2">8</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a0"><a href="ApiSuccessCode.java.html#L9" class="el_method">ApiSuccessCode(String, int, String, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="11" alt="11"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="ApiSuccessCode.java.html#L3" class="el_method">static {...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="11" alt="11"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="ApiSuccessCode.java.html#L15" class="el_method">code()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="ApiSuccessCode.java.html#L19" class="el_method">message()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="3" alt="3"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApiSuccessCode.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <a href="index.source.html" class="el_package">com.project.auth.presentation.support.response</a> > <span class="el_source">ApiSuccessCode.java</span></div><h1>ApiSuccessCode.java</h1><pre class="source lang-java linenums">package com.project.auth.presentation.support.response;
|
||||
|
||||
<span class="nc" id="L3">public enum ApiSuccessCode {</span>
|
||||
<span class="nc" id="L4"> AUTHENTICATED_USER_LOADED("AUTHENTICATED_USER_LOADED", "현재 사용자 조회가 완료되었습니다.");</span>
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
||||
<span class="nc" id="L9"> ApiSuccessCode(String code, String message) {</span>
|
||||
<span class="nc" id="L10"> this.code = code;</span>
|
||||
<span class="nc" id="L11"> this.message = message;</span>
|
||||
<span class="nc" id="L12"> }</span>
|
||||
|
||||
public String code() {
|
||||
<span class="nc" id="L15"> return code;</span>
|
||||
}
|
||||
|
||||
public String message() {
|
||||
<span class="nc" id="L19"> return message;</span>
|
||||
}
|
||||
}
|
||||
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>com.project.auth.presentation.support.response</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <span class="el_package">com.project.auth.presentation.support.response</span></div><h1>com.project.auth.presentation.support.response</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">28 of 52</td><td class="ctr2">46%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">4</td><td class="ctr2">5</td><td class="ctr1">8</td><td class="ctr2">9</td><td class="ctr1">4</td><td class="ctr2">5</td><td class="ctr1">1</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="ApiSuccessCode.html" class="el_class">ApiSuccessCode</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="28" alt="28"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">8</td><td class="ctr2" id="i0">8</td><td class="ctr1" id="j0">4</td><td class="ctr2" id="k0">4</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a0"><a href="ApiResult.html" class="el_class">ApiResult</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="102" height="10" title="24" alt="24"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td><td class="ctr1" id="l1">0</td><td class="ctr2" id="m1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>com.project.auth.presentation.support.response</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">presentation</a> > <span class="el_package">com.project.auth.presentation.support.response</span></div><h1>com.project.auth.presentation.support.response</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">28 of 52</td><td class="ctr2">46%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">4</td><td class="ctr2">5</td><td class="ctr1">8</td><td class="ctr2">9</td><td class="ctr1">4</td><td class="ctr2">5</td><td class="ctr1">1</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="ApiSuccessCode.java.html" class="el_source">ApiSuccessCode.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="28" alt="28"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">8</td><td class="ctr2" id="i0">8</td><td class="ctr1" id="j0">4</td><td class="ctr2" id="k0">4</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a0"><a href="ApiResult.java.html" class="el_source">ApiResult.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="102" height="10" title="24" alt="24"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td><td class="ctr1" id="l1">0</td><td class="ctr2" id="m1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
|
||||
|
After Width: | Height: | Size: 91 B |
|
After Width: | Height: | Size: 91 B |
|
After Width: | Height: | Size: 91 B |
|
After Width: | Height: | Size: 709 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 67 B |
|
After Width: | Height: | Size: 91 B |
|
After Width: | Height: | Size: 351 B |
|
After Width: | Height: | Size: 193 B |
|
After Width: | Height: | Size: 227 B |