Skip to content

Commit 11943c9

Browse files
Merge pull request #8640 from ProcessMaker/observation/FOUR-28341
FOUR-28341: Improve Error Handling for Smart Extract Subprocess
2 parents de1cc02 + 0cb910b commit 11943c9

2 files changed

Lines changed: 121 additions & 3 deletions

File tree

ProcessMaker/Jobs/ErrorHandling.php

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,59 @@ public function setDefaultsFromDataSourceConfig(array $config)
210210
public static function convertResponseToException($result)
211211
{
212212
if ($result['status'] === 'error') {
213-
if (str_starts_with($result['message'], 'Command exceeded timeout of')) {
214-
throw new ScriptTimeoutException($result['message']);
213+
$rawMessage = $result['message'] ?? '';
214+
if (str_starts_with((string) $rawMessage, 'Command exceeded timeout of')) {
215+
throw new ScriptTimeoutException((string) $rawMessage);
216+
}
217+
218+
$message = self::extractScriptErrorMessage($result);
219+
220+
if (empty($message)) {
221+
$message = $rawMessage ?: 'Script execution failed with unknown error';
222+
}
223+
224+
throw new ScriptException($message);
225+
}
226+
}
227+
228+
/**
229+
* Extract a concise error message from the microservice response.
230+
*/
231+
private static function extractScriptErrorMessage(array $result): string
232+
{
233+
$candidates = [
234+
$result['output']['error'] ?? null,
235+
$result['output']['exception'] ?? null,
236+
$result['output']['stderr'] ?? null,
237+
$result['output']['stdout'] ?? null,
238+
$result['message'] ?? null,
239+
];
240+
241+
foreach ($candidates as $candidate) {
242+
if (is_string($candidate) || is_numeric($candidate)) {
243+
$short = self::shortenMessage((string) $candidate);
244+
if (!empty($short)) {
245+
return $short;
246+
}
215247
}
216-
throw new ScriptException($result['message']);
217248
}
249+
250+
return '';
251+
}
252+
253+
/**
254+
* Keep only the first line of the error and limit its length to avoid noisy traces.
255+
*/
256+
private static function shortenMessage(string $message): string
257+
{
258+
$firstLine = strtok($message, "\n");
259+
$firstLine = $firstLine === false ? $message : $firstLine;
260+
$trimmed = trim($firstLine);
261+
262+
if (strlen($trimmed) > 400) {
263+
return substr($trimmed, 0, 400) . '';
264+
}
265+
266+
return $trimmed;
218267
}
219268
}

tests/unit/ErrorHandlingTest.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
namespace Tests\Unit;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use ProcessMaker\Exception\ScriptException;
7+
use ProcessMaker\Exception\ScriptTimeoutException;
8+
use ProcessMaker\Jobs\ErrorHandling;
9+
10+
class ErrorHandlingTest extends TestCase
11+
{
12+
public function testUsesOutputErrorOverGenericMessage(): void
13+
{
14+
$result = [
15+
'status' => 'error',
16+
'message' => 'Generic failure',
17+
'output' => [
18+
'error' => 'SMART_EXTRACT_API_HOST is required but could not be resolved',
19+
],
20+
];
21+
22+
$this->expectException(ScriptException::class);
23+
$this->expectExceptionMessage('SMART_EXTRACT_API_HOST is required but could not be resolved');
24+
25+
ErrorHandling::convertResponseToException($result);
26+
}
27+
28+
public function testPrefersStderrAndKeepsOnlyFirstLine(): void
29+
{
30+
$result = [
31+
'status' => 'error',
32+
'message' => 'fallback message',
33+
'output' => [
34+
'stderr' => "First line of error\nstack trace line 2\nstack trace line 3",
35+
],
36+
];
37+
38+
$this->expectException(ScriptException::class);
39+
$this->expectExceptionMessage('First line of error');
40+
41+
ErrorHandling::convertResponseToException($result);
42+
}
43+
44+
public function testTimeoutErrorThrowsScriptTimeoutException(): void
45+
{
46+
$result = [
47+
'status' => 'error',
48+
'message' => 'Command exceeded timeout of 120 seconds',
49+
];
50+
51+
$this->expectException(ScriptTimeoutException::class);
52+
$this->expectExceptionMessage('Command exceeded timeout of 120 seconds');
53+
54+
ErrorHandling::convertResponseToException($result);
55+
}
56+
57+
public function testFallsBackToRawMessageWhenNoOutputPresent(): void
58+
{
59+
$result = [
60+
'status' => 'error',
61+
'message' => 'Plain failure',
62+
];
63+
64+
$this->expectException(ScriptException::class);
65+
$this->expectExceptionMessage('Plain failure');
66+
67+
ErrorHandling::convertResponseToException($result);
68+
}
69+
}

0 commit comments

Comments
 (0)