diff --git a/src/Common/QueryError.test.ts b/src/Common/QueryError.test.ts index 2eea29a62..7924c398d 100644 --- a/src/Common/QueryError.test.ts +++ b/src/Common/QueryError.test.ts @@ -36,7 +36,7 @@ describe("QueryError.tryParse", () => { code: "BadRequest", message: "Your query is bad, and you should feel bad", }; - const message = JSON.stringify(innerError); + const message = `Message: ${JSON.stringify(innerError)}\r\nActivity ID: 42`; const outerError = { code: "BadRequest", message, @@ -48,7 +48,7 @@ describe("QueryError.tryParse", () => { ]); }); - // Imitate the value coming from the backend, which has the syntax errors serialized as JSON in the message. + // Imitate the value coming from the backend, which has the syntax errors serialized as JSON in the message, along with a prefix and activity id. it("handles single-nested error", () => { const errors = [ { @@ -69,7 +69,7 @@ describe("QueryError.tryParse", () => { message: "Your query is bad, and you should feel bad", errors, }; - const message = JSON.stringify(innerError); + const message = `Message: ${JSON.stringify(innerError)}\r\nActivity ID: 42`; const outerError = { code: "BadRequest", message, @@ -91,4 +91,23 @@ describe("QueryError.tryParse", () => { ), ]); }); + + // Imitate another value we've gotten from the backend, which has a doubly-nested JSON payload. + it("handles double-nested error", () => { + const outerError = { + code: "BadRequest", + message: + '{"code":"BadRequest","message":"{\\"errors\\":[{\\"severity\\":\\"Error\\",\\"location\\":{\\"start\\":7,\\"end\\":18},\\"code\\":\\"SC2005\\",\\"message\\":\\"\'nonexistent\' is not a recognized built-in function name.\\"}]}\\r\\nActivityId: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa, Windows/10.0.20348 cosmos-netstandard-sdk/3.18.0"}', + }; + + const result = QueryError.tryParse(outerError, testErrorLocationResolver); + expect(result).toEqual([ + new QueryError( + "'nonexistent' is not a recognized built-in function name.", + QueryErrorSeverity.Error, + "SC2005", + new QueryErrorLocation({ offset: 7, lineNumber: 7, column: 7 }, { offset: 18, lineNumber: 18, column: 18 }), + ), + ]); + }); }); diff --git a/src/Common/QueryError.ts b/src/Common/QueryError.ts index 51748d1a8..c1bab1c02 100644 --- a/src/Common/QueryError.ts +++ b/src/Common/QueryError.ts @@ -214,16 +214,28 @@ export default class QueryError { return null; } - // Assign to a new variable because of a TypeScript flow typing quirk, see below. - if (message.startsWith("Message: ")) { - // Reassigning this to 'error' restores the original type of 'error', which is 'unknown'. - // So we use a separate variable to avoid this. - message = message.substring("Message: ".length); + // Some newer backends produce a message that contains a doubly-nested JSON payload. + // In this case, the message we get is a fully-complete JSON object we can parse. + // So let's try that first + if (message.startsWith("{") && message.endsWith("}")) { + let outer: unknown = undefined; + try { + outer = JSON.parse(message); + if (typeof outer === "object" && "message" in outer && typeof outer.message === "string") { + message = outer.message; + } + } catch (e) { + // Just continue if the parsing fails. We'll use the fallback logic below. + } } const lines = message.split("\n"); message = lines[0].trim(); + if (message.startsWith("Message: ")) { + message = message.substring("Message: ".length); + } + let parsed: unknown; try { parsed = JSON.parse(message);