• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FIWARE / contract-management / #103

30 Sep 2026 02:39PM UTC coverage: 4.528% (+0.7%) from 3.796%
#103

Pull #28

vramperez
Address review: keep the standard stacktrace, only add the downstream answer

- DownstreamError no longer rewrites the cause chain into a custom format; it
  only adds what the stacktrace lacks: status and body of the downstream
  answer a failure was caused by
- The single place logging a failure attaches the exception, so the standard
  stacktrace is printed once per failure (subscription retries and faulty
  client requests stay one line)
- Removed remaining duplicate logs: the ContractManagementController and
  Oid4VpAuthHandler logged errors that are propagated and logged again; the
  OID4VP token failure is wrapped with client and scope instead

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pull Request #28: Add failure reasons, downstream error descriptions and configurable log output

213 of 403 new or added lines in 35 files covered. (52.85%)

11 existing lines in 7 files now uncovered.

1621 of 35799 relevant lines covered (4.53%)

0.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.89
/src/main/java/org/fiware/iam/logging/DownstreamError.java
1
package org.fiware.iam.logging;
2

3
import io.micronaut.http.client.exceptions.HttpClientResponseException;
4

5
import java.util.Optional;
6

7
/**
8
 * Adds what a stacktrace does not show to a log line: the status and body a downstream service answered with. That
9
 * body usually holds the actual reason of a failure (e.g. a validation message of the PAP or the TIL), while the
10
 * trace of a {@link HttpClientResponseException} only carries the status.
11
 * <p>
12
 * Log the throwable itself next to it, so the standard stacktrace is printed as well.
13
 */
14
public final class DownstreamError {
15

16
        static final int MAX_BODY_LENGTH = 1000;
17
        private static final int MAX_CAUSE_DEPTH = 10;
18

19
        private DownstreamError() {
20
        }
21

22
        /**
23
         * The message of the throwable, followed by status and body of the downstream answer it was caused by - if any.
24
         * E.g. {@code [pap_rejected_policy] The PAP rejected policy x. - downstream answered with status=400 body={"detail":"..."}}
25
         */
26
        public static String reason(Throwable throwable) {
27
                String message = Optional.ofNullable(throwable.getMessage()).orElseGet(() -> throwable.getClass().getSimpleName());
1 ✔
28
                return httpError(throwable)
1 ✔
29
                                .map(httpError -> "%s - downstream answered with %s".formatted(message, httpError))
1 ✔
30
                                .orElse(message);
1 ✔
31
        }
32

33
        /**
34
         * Status and body of the first downstream http error in the causes of the throwable, empty if it was not caused by one.
35
         */
36
        public static Optional<String> httpError(Throwable throwable) {
37
                Throwable current = throwable;
1 ✔
38
                for (int depth = 0; current != null && depth < MAX_CAUSE_DEPTH; depth++) {
1 ✔
39
                        if (current instanceof HttpClientResponseException hcre) {
1 ✔
40
                                return Optional.of("status=%s body=%s".formatted(hcre.getStatus().getCode(), body(hcre)));
1 ✔
41
                        }
42
                        current = current.getCause();
1 ✔
43
                }
44
                return Optional.empty();
1 ✔
45
        }
46

47
        private static String body(HttpClientResponseException hcre) {
48
                String body;
49
                try {
50
                        body = hcre.getResponse().getBody(String.class).orElse("");
1 ✔
NEW
51
                } catch (RuntimeException e) {
×
NEW
52
                        body = "";
×
53
                }
1 ✔
54
                if (body.isBlank()) {
1 ✔
55
                        return "<empty>";
1 ✔
56
                }
57
                body = body.replaceAll("\\s*\\R\\s*", " ");
1 ✔
58
                return body.length() > MAX_BODY_LENGTH ? body.substring(0, MAX_BODY_LENGTH) + "...(truncated)" : body;
1 ✔
59
        }
60
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc