eventmodeler 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,16 +11,7 @@ import { getValidAccessToken } from '../../lib/auth.js';
11
11
  */
12
12
  async function handleHttpError(response, path) {
13
13
  const text = await response.text().catch(() => '');
14
- let errorDetail = '';
15
- if (text.trim()) {
16
- try {
17
- const parsed = JSON.parse(text);
18
- errorDetail = parsed.error ?? parsed.message ?? '';
19
- }
20
- catch {
21
- // Response body isn't JSON (could be HTML error page, etc.)
22
- }
23
- }
14
+ const errorDetail = extractErrorDetail(text);
24
15
  switch (response.status) {
25
16
  case 401:
26
17
  throw new Error(`Authentication required. Run 'eventmodeler login' to authenticate.` +
@@ -34,6 +25,73 @@ async function handleHttpError(response, path) {
34
25
  throw new Error(errorDetail || `HTTP ${response.status}: ${response.statusText}`);
35
26
  }
36
27
  }
28
+ const GENERIC_ERROR_MESSAGES = new Set([
29
+ 'internal server error',
30
+ 'bad request',
31
+ 'unauthorized',
32
+ 'forbidden',
33
+ 'not found',
34
+ ]);
35
+ function isGenericErrorMessage(value) {
36
+ return GENERIC_ERROR_MESSAGES.has(value.trim().toLowerCase());
37
+ }
38
+ function toStringValue(value) {
39
+ return typeof value === 'string' ? value.trim() : '';
40
+ }
41
+ function collectCandidateMessages(node, out) {
42
+ if (!node || typeof node !== 'object')
43
+ return;
44
+ const obj = node;
45
+ // Prefer richer fields before generic "error".
46
+ for (const key of ['message', 'detail', 'reason', 'description', 'title', 'error']) {
47
+ const value = toStringValue(obj[key]);
48
+ if (value)
49
+ out.push(value);
50
+ }
51
+ const errors = obj.errors;
52
+ if (Array.isArray(errors)) {
53
+ for (const entry of errors) {
54
+ if (typeof entry === 'string') {
55
+ const value = entry.trim();
56
+ if (value)
57
+ out.push(value);
58
+ }
59
+ else if (entry && typeof entry === 'object') {
60
+ collectCandidateMessages(entry, out);
61
+ }
62
+ }
63
+ }
64
+ const cause = obj.cause;
65
+ if (cause && typeof cause === 'object') {
66
+ collectCandidateMessages(cause, out);
67
+ }
68
+ }
69
+ function extractErrorDetail(text) {
70
+ const body = text.trim();
71
+ if (!body)
72
+ return '';
73
+ try {
74
+ const parsed = JSON.parse(body);
75
+ if (typeof parsed === 'string') {
76
+ return parsed.trim();
77
+ }
78
+ const candidates = [];
79
+ collectCandidateMessages(parsed, candidates);
80
+ if (candidates.length > 0) {
81
+ const specific = candidates.find((value) => !isGenericErrorMessage(value));
82
+ return specific ?? candidates[0];
83
+ }
84
+ }
85
+ catch {
86
+ // Not JSON. Fall back to plain text below.
87
+ }
88
+ // Avoid dumping entire HTML pages from reverse proxies.
89
+ if (body.startsWith('<')) {
90
+ const title = body.match(/<title>([^<]+)<\/title>/i)?.[1]?.trim();
91
+ return title && !isGenericErrorMessage(title) ? title : '';
92
+ }
93
+ return body;
94
+ }
37
95
  async function cliGet(path, params) {
38
96
  const backendUrl = getBackendUrl();
39
97
  const accessToken = await getValidAccessToken();
@@ -4,8 +4,8 @@ import { getValidAccessToken } from './auth.js';
4
4
  * Handle HTTP error responses with user-friendly messages
5
5
  */
6
6
  async function handleHttpError(response) {
7
- const errorData = await response.json().catch(() => ({}));
8
- const errorDetail = errorData.error ?? errorData.message ?? '';
7
+ const text = await response.text().catch(() => '');
8
+ const errorDetail = extractErrorDetail(text);
9
9
  switch (response.status) {
10
10
  case 401:
11
11
  throw new Error(`Authentication required. Run 'eventmodeler login' to authenticate.` +
@@ -19,6 +19,73 @@ async function handleHttpError(response) {
19
19
  throw new Error(errorDetail || `HTTP ${response.status}: ${response.statusText}`);
20
20
  }
21
21
  }
22
+ const GENERIC_ERROR_MESSAGES = new Set([
23
+ 'internal server error',
24
+ 'bad request',
25
+ 'unauthorized',
26
+ 'forbidden',
27
+ 'not found',
28
+ ]);
29
+ function isGenericErrorMessage(value) {
30
+ return GENERIC_ERROR_MESSAGES.has(value.trim().toLowerCase());
31
+ }
32
+ function toStringValue(value) {
33
+ return typeof value === 'string' ? value.trim() : '';
34
+ }
35
+ function collectCandidateMessages(node, out) {
36
+ if (!node || typeof node !== 'object')
37
+ return;
38
+ const obj = node;
39
+ // Prefer specific fields before generic "error".
40
+ for (const key of ['message', 'detail', 'reason', 'description', 'title', 'error']) {
41
+ const value = toStringValue(obj[key]);
42
+ if (value)
43
+ out.push(value);
44
+ }
45
+ const errors = obj.errors;
46
+ if (Array.isArray(errors)) {
47
+ for (const entry of errors) {
48
+ if (typeof entry === 'string') {
49
+ const value = entry.trim();
50
+ if (value)
51
+ out.push(value);
52
+ }
53
+ else if (entry && typeof entry === 'object') {
54
+ collectCandidateMessages(entry, out);
55
+ }
56
+ }
57
+ }
58
+ const cause = obj.cause;
59
+ if (cause && typeof cause === 'object') {
60
+ collectCandidateMessages(cause, out);
61
+ }
62
+ }
63
+ function extractErrorDetail(text) {
64
+ const body = text.trim();
65
+ if (!body)
66
+ return '';
67
+ try {
68
+ const parsed = JSON.parse(body);
69
+ if (typeof parsed === 'string') {
70
+ return parsed.trim();
71
+ }
72
+ const candidates = [];
73
+ collectCandidateMessages(parsed, candidates);
74
+ if (candidates.length > 0) {
75
+ const specific = candidates.find((value) => !isGenericErrorMessage(value));
76
+ return specific ?? candidates[0];
77
+ }
78
+ }
79
+ catch {
80
+ // Not JSON. Fall back to plain text below.
81
+ }
82
+ // Avoid dumping full HTML error pages.
83
+ if (body.startsWith('<')) {
84
+ const title = body.match(/<title>([^<]+)<\/title>/i)?.[1]?.trim();
85
+ return title && !isGenericErrorMessage(title) ? title : '';
86
+ }
87
+ return body;
88
+ }
22
89
  /**
23
90
  * Create a cloud client for interacting with the Axon backend.
24
91
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eventmodeler",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "CLI tool for interacting with Event Model files - query, update, and export event models from the terminal",
5
5
  "type": "module",
6
6
  "bin": {