RequestBoundApiResultFactory.java
package com.project.auth.config.web;
import com.project.auth.presentation.support.response.ApiResult;
import com.project.auth.presentation.support.response.ApiResultFactory;
import org.slf4j.MDC;
import java.time.Clock;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class RequestBoundApiResultFactory implements ApiResultFactory {
private static final String TRACE_ID_KEY = "traceId";
/**
* MDC에 traceId가 없을 때 사용하는 sentinel. JSON {@code null}로 그대로 두면
* TraceIdFilter가 누락/오설정된 사실이 가려지므로, 명시적인 placeholder 문자열로
* 로그·대시보드에서 즉시 식별 가능하게 한다.
*/
static final String MISSING_TRACE_ID = "-";
private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ISO_INSTANT;
private final Clock clock;
public RequestBoundApiResultFactory(Clock clock) {
this.clock = Objects.requireNonNull(clock, "clock must not be null");
}
@Override
public <T> ApiResult<T> success(String code, String message, T data) {
return result(true, code, message, data, null);
}
@Override
public ApiResult<Void> success(String code, String message) {
return result(true, code, message, null, null);
}
@Override
public ApiResult<Void> failure(String code, String message) {
return result(false, code, message, null, null);
}
@Override
public ApiResult<Void> failure(String code, String message, Map<String, List<String>> errors) {
return result(false, code, message, null, errors);
}
private <T> ApiResult<T> result(
boolean success,
String code,
String message,
T data,
Map<String, List<String>> errors
) {
String traceId = MDC.get(TRACE_ID_KEY);
return new ApiResult<>(
success,
code,
message,
data,
errors,
traceId == null || traceId.isBlank() ? MISSING_TRACE_ID : traceId,
TIMESTAMP_FORMATTER.format(clock.instant())
);
}
}