Skip to content

Commit c7c9728

Browse files
authored
Make Exception Message Parsing in JSONClient more flexible (#446)
1 parent db632ab commit c7c9728

File tree

2 files changed

+28
-13
lines changed

2 files changed

+28
-13
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,9 @@ for more information.
179179
180180
* Fixed broken log file downloads from the Admin Console.
181181
182+
* Enhance exception handling in `JSONClient` to capture messages returned as raw strings.
183+
184+
182185
## 28.1.0 - 2025-02-13
183186
184187
### 🎁 New Features

src/main/groovy/io/xh/hoist/http/JSONClient.groovy

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
package io.xh.hoist.http
99

1010
import groovy.transform.CompileStatic
11-
import groovy.util.logging.Slf4j
1211
import io.xh.hoist.exception.ExternalHttpException
1312
import io.xh.hoist.json.JSONParser
13+
import io.xh.hoist.util.StringUtils
1414
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse
1515
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase
1616
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient
@@ -146,32 +146,44 @@ class JSONClient {
146146
}
147147

148148
// Attempt to parse a reasonable exception from failed response, but never actually throw if not possible.
149-
private Throwable parseException(CloseableHttpResponse response){
149+
private Throwable parseException(CloseableHttpResponse response) {
150150
try {
151-
def ex = JSONParser.parseObject(response.entity.content),
152-
className = ex?.className,
153-
msg = ex?.message
151+
String content = response.entity.content.getText().trim()
152+
if (!content) return null
154153

155-
// Message is required
156-
if (msg instanceof String) {
154+
// [1] We have a valid json object (preferred)
155+
Map obj = safeParseObject(content)
156+
if (obj) {
157+
String msg = obj.message instanceof String ? obj.message : 'Unknown Error'
157158

158159
// Try to rehydrate exception of certain known and present classes
160+
def className = obj.className
159161
if (className instanceof String &&
160-
(className.contains('io.xh.hoist') || className.contains('java.lang'))) {
162+
(className.contains('io.xh.hoist') || className.contains('java.lang'))) {
161163
def cls = this.class.classLoader.loadClass(className),
162164
constructor = cls?.getConstructor(String)
163-
164165
if (constructor) {
165166
return (Throwable) constructor.newInstance(msg)
166167
}
167-
168168
}
169-
// otherwise fall back to a runtime exception...
169+
// otherwise use a runtime exception.
170170
return new RuntimeException(msg)
171171
}
172-
} catch (Exception ignored) {}
173172

174-
return null
173+
// [2] Or just interpret content as a raw string message
174+
return new RuntimeException(StringUtils.elide(content, 255))
175+
} catch (Exception ignored) {
176+
// [3] ..but in the worst case, just return null
177+
return null
178+
}
179+
}
180+
181+
private Map safeParseObject(String s) {
182+
try {
183+
return JSONParser.parseObject(s)
184+
} catch (Exception ignored) {
185+
return null
186+
}
175187
}
176188

177189
}

0 commit comments

Comments
 (0)