package com.baiyue.common.web; import com.baiyue.common.core.api.ApiResponse; import com.fasterxml.jackson.core.JsonProcessingException; import feign.FeignException; import java.util.Collections; import java.util.IdentityHashMap; import java.util.Set; /** Explicit failure classification and bounded, credential-free delivery diagnostics. */ public class EventDeliveryFailure extends IllegalStateException { private static final Set PERMANENT_CODES = Set.of("A0401", "A0403", "EVT_PROTOCOL"); private final boolean permanent; public EventDeliveryFailure(boolean permanent, String message) { super(message); this.permanent = permanent; } public static void requireSuccessful(ApiResponse response) { requireSuccessful(response, Set.of()); } public static void requireSuccessful(ApiResponse response, Set additionalPermanentCodes) { if (response == null) throw new EventDeliveryFailure(false, "Consumer returned an empty response"); if (!response.successful()) { throw new EventDeliveryFailure(response.code() != null && (PERMANENT_CODES.contains(response.code()) || additionalPermanentCodes.contains(response.code())), "Consumer rejected event: code=" + response.code() + ", message=" + response.message() + ", requestId=" + response.requestId() + ", traceId=" + response.traceId()); } if (response.data() == null) throw new EventDeliveryFailure(false, "Consumer ACK is missing"); } public static boolean isPermanent(Throwable failure) { Set seen = Collections.newSetFromMap(new IdentityHashMap<>()); for (Throwable current = failure; current != null && seen.add(current); current = current.getCause()) { if (current instanceof EventDeliveryFailure delivery && delivery.permanent) return true; if (current instanceof FeignException http && (http.status() == 401 || http.status() == 403)) return true; } return false; } public static String diagnostic(Throwable failure, String internalToken) { Throwable current = failure; Set seen = Collections.newSetFromMap(new IdentityHashMap<>()); while (current.getCause() != null && seen.add(current) && ("NoFallbackAvailableException".equals(current.getClass().getSimpleName()) || "No fallback available.".equals(current.getMessage()))) current = current.getCause(); String message; if (current instanceof feign.codec.DecodeException) message = "Invalid consumer response contract"; else if (current instanceof FeignException http) message = "HTTP " + http.status(); else if (current instanceof JsonProcessingException) message = "Invalid event JSON"; else message = current.getMessage() == null ? "unknown" : current.getMessage(); String text = current.getClass().getSimpleName() + ": " + message; if (internalToken != null && !internalToken.isBlank()) text = text.replace(internalToken, "***"); text = text.replaceAll("(?i)Bearer\\s+[^\\s,;\\\"']+", "Bearer ***") .replaceAll("(?i)((?:X-Internal-Service-Token|Authorization|access[_-]?token|refresh[_-]?token)\\s*[=:]\\s*)[^\\s,;]+", "$1***") .replaceAll("[\\r\\n\\t]+", " "); return text.length() > 500 ? text.substring(0, 500) : text; } }