Fix #436: Add cause message to RestClientException#437
Fix #436: Add cause message to RestClientException#437
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses issue #436 by enriching RestClientException messages thrown by DefaultRestClient so they include the underlying exception message, and it adds/adjusts tests to validate the new message behavior.
Changes:
- Append the caught exception message to
RestClientExceptionmessages for GET/POST/PUT/DELETE/PATCH/HEAD failures. - Add new tests asserting the updated failure message includes DNS resolution details.
- Refactor an existing error-response test to use
assertThrowsand add an assertion for the exception message.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| rest-client-base/src/main/java/com/wultra/core/rest/client/base/DefaultRestClient.java | Updates thrown RestClientException messages to include the underlying exception message. |
| rest-client-base/src/test/java/com/wultra/core/rest/client/base/DefaultRestClientTest.java | Adds message assertions for request-failure scenarios and improves one error-response test structure. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| throw (RestClientException) ex.getCause(); | ||
| } | ||
| throw new RestClientException("HTTP GET request failed", ex); | ||
| throw new RestClientException("HTTP GET request failed; " + ex.getMessage(), ex); |
There was a problem hiding this comment.
The new exception message concatenates ex.getMessage() unconditionally. For some exception types getMessage() can be null, resulting in messages like HTTP GET request failed; null, which is not helpful. Consider appending the cause message only when present/non-blank (or falling back to ex.getClass().getSimpleName()/ex.toString()), and apply the same pattern across the other HTTP methods updated in this PR.
There was a problem hiding this comment.
IMHO there should be always some message for exception and we should just put it into RestClientException, no matter it is null, empty string ... So the implementation in this PR is correct
| void testGet_causeMessage() throws Exception { | ||
| final DefaultRestClient localRestClient = new DefaultRestClient("http://nonexistent.invalid"); | ||
| final RestClientException exception = assertThrows(RestClientException.class, () -> | ||
| localRestClient.get("/", new ParameterizedTypeReference<>() {})); | ||
|
|
||
| assertTrue( | ||
| exception.getMessage().startsWith("HTTP GET request failed; Failed to resolve 'nonexistent.invalid'"), | ||
| () -> "Unexpected message: " + exception.getMessage()); |
There was a problem hiding this comment.
These tests make real network calls and assert a very specific DNS-resolution error message. This is likely to be flaky/slow in CI (network may be blocked) and the message text varies across OS/JDK/Reactor Netty versions. To make the test stable, avoid external DNS and instead inject/mock the WebClient/connector to throw a deterministic exception, or assert more loosely (e.g., that the message starts with HTTP <METHOD> request failed; and that the cause message is non-empty/contains the hostname).
There was a problem hiding this comment.
I agree that mocking webClient would be better. On the other hand it would require quite a lot of stubbing and these tests would be hard to read. So usage real localRestClient is acceptable for this use-case.
| void testGet_causeMessage() throws Exception { | ||
| final DefaultRestClient localRestClient = new DefaultRestClient("http://nonexistent.invalid"); | ||
| final RestClientException exception = assertThrows(RestClientException.class, () -> | ||
| localRestClient.get("/", new ParameterizedTypeReference<>() {})); | ||
|
|
||
| assertTrue( | ||
| exception.getMessage().startsWith("HTTP GET request failed; Failed to resolve 'nonexistent.invalid'"), | ||
| () -> "Unexpected message: " + exception.getMessage()); | ||
| } | ||
|
|
||
| @Test | ||
| void testHead_causeMessage() throws Exception { | ||
| final DefaultRestClient localRestClient = new DefaultRestClient("http://nonexistent.invalid"); | ||
| final RestClientException exception = assertThrows(RestClientException.class, () -> | ||
| localRestClient.head("/", new ParameterizedTypeReference<>() {})); | ||
|
|
||
| assertTrue( | ||
| exception.getMessage().startsWith("HTTP HEAD request failed; Failed to resolve 'nonexistent.invalid'"), | ||
| () -> "Unexpected message: " + exception.getMessage()); | ||
| } | ||
|
|
||
| @Test | ||
| void testDelete_causeMessage() throws Exception { | ||
| final DefaultRestClient localRestClient = new DefaultRestClient("http://nonexistent.invalid"); | ||
| final RestClientException exception = assertThrows(RestClientException.class, () -> | ||
| localRestClient.delete("/", new ParameterizedTypeReference<>() {})); | ||
|
|
||
| assertTrue( | ||
| exception.getMessage().startsWith("HTTP DELETE request failed; Failed to resolve 'nonexistent.invalid'"), | ||
| () -> "Unexpected message: " + exception.getMessage()); | ||
| } | ||
|
|
||
| @Test | ||
| void testPut_causeMessage() throws Exception { | ||
| final DefaultRestClient localRestClient = new DefaultRestClient("http://nonexistent.invalid"); | ||
| final RestClientException exception = assertThrows(RestClientException.class, () -> | ||
| localRestClient.put("/", null, new ParameterizedTypeReference<>() {})); | ||
|
|
||
| assertTrue( | ||
| exception.getMessage().startsWith("HTTP PUT request failed; Failed to resolve 'nonexistent.invalid'"), | ||
| () -> "Unexpected message: " + exception.getMessage()); | ||
| } | ||
|
|
||
| @Test | ||
| void testPost_causeMessage() throws Exception { | ||
| final DefaultRestClient localRestClient = new DefaultRestClient("http://nonexistent.invalid"); | ||
| final RestClientException exception = assertThrows(RestClientException.class, () -> | ||
| localRestClient.post("/", null, new ParameterizedTypeReference<>() {})); | ||
|
|
||
| assertTrue( | ||
| exception.getMessage().startsWith("HTTP POST request failed; Failed to resolve 'nonexistent.invalid'"), | ||
| () -> "Unexpected message: " + exception.getMessage()); | ||
| } |
There was a problem hiding this comment.
The five *_causeMessage tests are nearly identical aside from the HTTP method. Consider converting them to a single parameterized test (e.g., method name + lambda) to reduce duplication and make future message changes easier to update consistently.
michal-rozehnal-w
left a comment
There was a problem hiding this comment.
Nice PR, it will definitely help us in any future investigations
| throw (RestClientException) ex.getCause(); | ||
| } | ||
| throw new RestClientException("HTTP GET request failed", ex); | ||
| throw new RestClientException("HTTP GET request failed; " + ex.getMessage(), ex); |
There was a problem hiding this comment.
IMHO there should be always some message for exception and we should just put it into RestClientException, no matter it is null, empty string ... So the implementation in this PR is correct
| void testGet_causeMessage() throws Exception { | ||
| final DefaultRestClient localRestClient = new DefaultRestClient("http://nonexistent.invalid"); | ||
| final RestClientException exception = assertThrows(RestClientException.class, () -> | ||
| localRestClient.get("/", new ParameterizedTypeReference<>() {})); | ||
|
|
||
| assertTrue( | ||
| exception.getMessage().startsWith("HTTP GET request failed; Failed to resolve 'nonexistent.invalid'"), | ||
| () -> "Unexpected message: " + exception.getMessage()); |
There was a problem hiding this comment.
I agree that mocking webClient would be better. On the other hand it would require quite a lot of stubbing and these tests would be hard to read. So usage real localRestClient is acceptable for this use-case.
romanstrobl
left a comment
There was a problem hiding this comment.
Looks OK, I just don't like the ; in the error message.
No description provided.