Vedant Sanjay Jadhav commited on
Commit
441ddfc
·
1 Parent(s): bf39fe7

fix: improve server and client error extraction to eliminate HTTP 200 generic message

Browse files
frontend/lib/api.ts CHANGED
@@ -18,6 +18,15 @@ function generateRequestId(): string {
18
  return `REQ_${Math.random().toString(36).substring(2, 8).toUpperCase()}`;
19
  }
20
 
 
 
 
 
 
 
 
 
 
21
  /**
22
  * Health check probe via /api/health
23
  */
@@ -28,12 +37,15 @@ export async function checkBackendHealth(): Promise<BackendHealthStatus> {
28
  const res = await fetch("/api/health", { cache: "no-store" });
29
  const latency = Math.round(performance.now() - start);
30
  if (res.ok) {
31
- return {
32
- status: "CONNECTED",
33
- endpoint: "/api/health (Vercel Server Proxy)",
34
- last_sync_at: new Date().toISOString(),
35
- roundtrip_latency_ms: latency,
36
- };
 
 
 
37
  }
38
  } catch (err: any) {
39
  console.warn("Health check error via /api/health:", err);
@@ -68,7 +80,7 @@ export async function analyzeTransaction(
68
  const json = await res.json();
69
 
70
  if (!res.ok || !json.success || !json.data) {
71
- throw new Error(json?.error?.message || `Transaction analysis failed with HTTP ${res.status}`);
72
  }
73
 
74
  const parsed: AnalyzeTransactionResponse = json.data;
@@ -104,7 +116,7 @@ export async function runScenarioReplay(
104
  const json = await res.json();
105
 
106
  if (!res.ok || !json.success || !json.data) {
107
- throw new Error(json?.error?.message || `Scenario replay failed with HTTP ${res.status}`);
108
  }
109
 
110
  const parsed: ScenarioReplayResult = json.data;
@@ -137,7 +149,7 @@ export async function queryMerchantState(merchantId: string): Promise<MerchantSt
137
  const json = await res.json();
138
 
139
  if (!res.ok || !json.success || !json.data) {
140
- throw new Error(json?.error?.message || `Merchant query failed with HTTP ${res.status}`);
141
  }
142
 
143
  const parsed: MerchantStateQueryResponse = json.data;
@@ -164,7 +176,7 @@ export async function explainEvidencePayload(evidenceJson: string): Promise<any>
164
 
165
  const json = await res.json();
166
  if (!res.ok || !json.success) {
167
- throw new Error(json?.error?.message || `Explanation failed with HTTP ${res.status}`);
168
  }
169
 
170
  return json.data;
@@ -186,7 +198,7 @@ export async function resetDemoState(): Promise<{ status: string; message: strin
186
  const json = await res.json();
187
 
188
  if (!res.ok || !json.success || !json.data) {
189
- throw new Error(json?.error?.message || `Reset demo state failed with HTTP ${res.status}`);
190
  }
191
 
192
  const parsed = json.data;
 
18
  return `REQ_${Math.random().toString(36).substring(2, 8).toUpperCase()}`;
19
  }
20
 
21
+ function extractErrorMessage(json: any, fallback: string): string {
22
+ if (!json) return fallback;
23
+ if (typeof json.error === "string") return json.error;
24
+ if (json.error?.message) return json.error.message;
25
+ if (json.error?.details) return json.error.details;
26
+ if (json.message) return json.message;
27
+ return fallback;
28
+ }
29
+
30
  /**
31
  * Health check probe via /api/health
32
  */
 
37
  const res = await fetch("/api/health", { cache: "no-store" });
38
  const latency = Math.round(performance.now() - start);
39
  if (res.ok) {
40
+ const json = await res.json();
41
+ if (json.success) {
42
+ return {
43
+ status: "CONNECTED",
44
+ endpoint: "/api/health (Vercel Server Proxy)",
45
+ last_sync_at: new Date().toISOString(),
46
+ roundtrip_latency_ms: latency,
47
+ };
48
+ }
49
  }
50
  } catch (err: any) {
51
  console.warn("Health check error via /api/health:", err);
 
80
  const json = await res.json();
81
 
82
  if (!res.ok || !json.success || !json.data) {
83
+ throw new Error(extractErrorMessage(json, `Transaction analysis failed with HTTP ${res.status}`));
84
  }
85
 
86
  const parsed: AnalyzeTransactionResponse = json.data;
 
116
  const json = await res.json();
117
 
118
  if (!res.ok || !json.success || !json.data) {
119
+ throw new Error(extractErrorMessage(json, `Scenario replay failed with HTTP ${res.status}`));
120
  }
121
 
122
  const parsed: ScenarioReplayResult = json.data;
 
149
  const json = await res.json();
150
 
151
  if (!res.ok || !json.success || !json.data) {
152
+ throw new Error(extractErrorMessage(json, `Merchant query failed with HTTP ${res.status}`));
153
  }
154
 
155
  const parsed: MerchantStateQueryResponse = json.data;
 
176
 
177
  const json = await res.json();
178
  if (!res.ok || !json.success) {
179
+ throw new Error(extractErrorMessage(json, `Explanation failed with HTTP ${res.status}`));
180
  }
181
 
182
  return json.data;
 
198
  const json = await res.json();
199
 
200
  if (!res.ok || !json.success || !json.data) {
201
+ throw new Error(extractErrorMessage(json, `Reset demo state failed with HTTP ${res.status}`));
202
  }
203
 
204
  const parsed = json.data;
frontend/lib/server/razorshield.ts CHANGED
@@ -86,9 +86,20 @@ async function callGradioApi<T = any>(apiName: string, data: any[]): Promise<T>
86
  export async function healthCheck(): Promise<ApiResponse<{ status: string; service: string; model: string; slm_loaded: boolean; policy_mode: string }>> {
87
  try {
88
  const start = performance.now();
89
- await callGradioApi("analyze_merchant", ["M_HEALTH_CHECK"]);
90
  const latency = Math.round(performance.now() - start);
91
 
 
 
 
 
 
 
 
 
 
 
 
92
  return {
93
  success: true,
94
  data: {
@@ -141,6 +152,17 @@ export async function analyzeTransaction(
141
  policyMode,
142
  ]);
143
 
 
 
 
 
 
 
 
 
 
 
 
144
  return {
145
  success: true,
146
  data: parsed,
@@ -151,7 +173,7 @@ export async function analyzeTransaction(
151
  success: false,
152
  error: {
153
  code: "TRANSACTION_ANALYSIS_FAILED",
154
- message: "Failed to evaluate transaction through backend risk engine.",
155
  details: err.message,
156
  },
157
  };
@@ -166,6 +188,18 @@ export async function analyzeMerchant(
166
  ): Promise<ApiResponse<MerchantStateQueryResponse>> {
167
  try {
168
  const parsed = await callGradioApi<MerchantStateQueryResponse>("analyze_merchant", [merchantId]);
 
 
 
 
 
 
 
 
 
 
 
 
169
  return {
170
  success: true,
171
  data: parsed,
@@ -176,7 +210,7 @@ export async function analyzeMerchant(
176
  success: false,
177
  error: {
178
  code: "MERCHANT_QUERY_FAILED",
179
- message: "Failed to query merchant incident state from backend.",
180
  details: err.message,
181
  },
182
  };
@@ -192,6 +226,18 @@ export async function runScenario(
192
  ): Promise<ApiResponse<ScenarioReplayResult>> {
193
  try {
194
  const parsed = await callGradioApi<ScenarioReplayResult>("run_scenario", [scenarioName, policyMode]);
 
 
 
 
 
 
 
 
 
 
 
 
195
  return {
196
  success: true,
197
  data: parsed,
@@ -202,7 +248,7 @@ export async function runScenario(
202
  success: false,
203
  error: {
204
  code: "SCENARIO_REPLAY_FAILED",
205
- message: "Failed to execute scenario replay through backend.",
206
  details: err.message,
207
  },
208
  };
@@ -217,6 +263,18 @@ export async function explainEvidence(
217
  ): Promise<ApiResponse<any>> {
218
  try {
219
  const parsed = await callGradioApi("explain_evidence", [evidenceJson]);
 
 
 
 
 
 
 
 
 
 
 
 
220
  return {
221
  success: true,
222
  data: parsed,
@@ -227,7 +285,7 @@ export async function explainEvidence(
227
  success: false,
228
  error: {
229
  code: "EXPLANATION_GENERATION_FAILED",
230
- message: "Failed to generate explanation for evidence.",
231
  details: err.message,
232
  },
233
  };
@@ -240,6 +298,18 @@ export async function explainEvidence(
240
  export async function resetDemoState(): Promise<ApiResponse<{ status: string; message: string }>> {
241
  try {
242
  const parsed = await callGradioApi<{ status: string; message: string }>("reset_demo_state", []);
 
 
 
 
 
 
 
 
 
 
 
 
243
  return {
244
  success: true,
245
  data: parsed,
@@ -250,7 +320,7 @@ export async function resetDemoState(): Promise<ApiResponse<{ status: string; me
250
  success: false,
251
  error: {
252
  code: "RESET_FAILED",
253
- message: "Failed to reset demo state on backend.",
254
  details: err.message,
255
  },
256
  };
 
86
  export async function healthCheck(): Promise<ApiResponse<{ status: string; service: string; model: string; slm_loaded: boolean; policy_mode: string }>> {
87
  try {
88
  const start = performance.now();
89
+ const res = await callGradioApi("analyze_merchant", ["M_HEALTH_CHECK"]);
90
  const latency = Math.round(performance.now() - start);
91
 
92
+ if ((res as any)?.error) {
93
+ return {
94
+ success: false,
95
+ error: {
96
+ code: "BACKEND_ERROR",
97
+ message: (res as any).error,
98
+ details: (res as any).details || (res as any).error,
99
+ },
100
+ };
101
+ }
102
+
103
  return {
104
  success: true,
105
  data: {
 
152
  policyMode,
153
  ]);
154
 
155
+ if ((parsed as any)?.error) {
156
+ return {
157
+ success: false,
158
+ error: {
159
+ code: "BACKEND_ERROR",
160
+ message: (parsed as any).error,
161
+ details: (parsed as any).details || (parsed as any).error,
162
+ },
163
+ };
164
+ }
165
+
166
  return {
167
  success: true,
168
  data: parsed,
 
173
  success: false,
174
  error: {
175
  code: "TRANSACTION_ANALYSIS_FAILED",
176
+ message: err.message || "Failed to evaluate transaction through backend risk engine.",
177
  details: err.message,
178
  },
179
  };
 
188
  ): Promise<ApiResponse<MerchantStateQueryResponse>> {
189
  try {
190
  const parsed = await callGradioApi<MerchantStateQueryResponse>("analyze_merchant", [merchantId]);
191
+
192
+ if ((parsed as any)?.error) {
193
+ return {
194
+ success: false,
195
+ error: {
196
+ code: "BACKEND_ERROR",
197
+ message: (parsed as any).error,
198
+ details: (parsed as any).details || (parsed as any).error,
199
+ },
200
+ };
201
+ }
202
+
203
  return {
204
  success: true,
205
  data: parsed,
 
210
  success: false,
211
  error: {
212
  code: "MERCHANT_QUERY_FAILED",
213
+ message: err.message || "Failed to query merchant incident state from backend.",
214
  details: err.message,
215
  },
216
  };
 
226
  ): Promise<ApiResponse<ScenarioReplayResult>> {
227
  try {
228
  const parsed = await callGradioApi<ScenarioReplayResult>("run_scenario", [scenarioName, policyMode]);
229
+
230
+ if ((parsed as any)?.error) {
231
+ return {
232
+ success: false,
233
+ error: {
234
+ code: "BACKEND_ERROR",
235
+ message: (parsed as any).error,
236
+ details: (parsed as any).details || (parsed as any).error,
237
+ },
238
+ };
239
+ }
240
+
241
  return {
242
  success: true,
243
  data: parsed,
 
248
  success: false,
249
  error: {
250
  code: "SCENARIO_REPLAY_FAILED",
251
+ message: err.message || "Failed to execute scenario replay through backend.",
252
  details: err.message,
253
  },
254
  };
 
263
  ): Promise<ApiResponse<any>> {
264
  try {
265
  const parsed = await callGradioApi("explain_evidence", [evidenceJson]);
266
+
267
+ if ((parsed as any)?.error) {
268
+ return {
269
+ success: false,
270
+ error: {
271
+ code: "BACKEND_ERROR",
272
+ message: (parsed as any).error,
273
+ details: (parsed as any).details || (parsed as any).error,
274
+ },
275
+ };
276
+ }
277
+
278
  return {
279
  success: true,
280
  data: parsed,
 
285
  success: false,
286
  error: {
287
  code: "EXPLANATION_GENERATION_FAILED",
288
+ message: err.message || "Failed to generate explanation for evidence.",
289
  details: err.message,
290
  },
291
  };
 
298
  export async function resetDemoState(): Promise<ApiResponse<{ status: string; message: string }>> {
299
  try {
300
  const parsed = await callGradioApi<{ status: string; message: string }>("reset_demo_state", []);
301
+
302
+ if ((parsed as any)?.error) {
303
+ return {
304
+ success: false,
305
+ error: {
306
+ code: "BACKEND_ERROR",
307
+ message: (parsed as any).error,
308
+ details: (parsed as any).details || (parsed as any).error,
309
+ },
310
+ };
311
+ }
312
+
313
  return {
314
  success: true,
315
  data: parsed,
 
320
  success: false,
321
  error: {
322
  code: "RESET_FAILED",
323
+ message: err.message || "Failed to reset demo state on backend.",
324
  details: err.message,
325
  },
326
  };