Skip to content

GraphError has an empty message when the error response is not Graph JSON (text/plain, HTML or empty body) #2011

Open

Description

Describe the bug

When Graph (or a gateway in front of it) answers a request with an error status and a body that is not the usual { "error": { ... } } JSON, the thrown GraphError has message === "" and code === null. Logging error.message or showing it to a user then yields nothing; only statusCode says what happened.

From reading the code (unchanged between 3.0.7 and dev @ e6a6221):

  • On a non-2xx response, GraphResponseHandler.getResponse throws the parsed body itself. For text/plain that is a string; for text/html and application/xml it is the raw ReadableStream when DOMParser is undefined, as in Node (L125, parseDocumentResponse); without a Content-Type it is the stream as well (L147).
  • GraphErrorHandler.getError only reads a message from a Graph JSON body or from an Error. Anything else falls through to new GraphError(statusCode) with no message (L108), and the constructor passes undefined to super (GraphError.ts L63).

A plain-text body such as Service Unavailable ends up in error.body, but not in error.message.

Expected behavior

error.message is never empty for an HTTP error. For example, the status line when there is nothing better ("HTTP 503" or "503 Service Unavailable"), or the text body when the response is text/plain.

How to reproduce

A local server stands in for Graph and answers four paths with an error status. Run with Node 22 or bun:

import http from "node:http";
import { Client, GraphError } from "@microsoft/microsoft-graph-client";
import pkg from "@microsoft/microsoft-graph-client/package.json" with { type: "json" };

const cases = {
  "/json-error": [403, "application/json", JSON.stringify({ error: { code: "Authorization_RequestDenied", message: "Insufficient privileges to complete the operation." } })],
  "/html-502": [502, "text/html", "<html><body>Bad Gateway</body></html>"],
  "/text-503": [503, "text/plain", "Service Unavailable"],
  "/empty-500": [500, undefined, ""],
};
const server = await new Promise((r) => { const s = http.createServer((req, res) => {
  const [status, type, body] = cases[new URL(req.url, "http://x").pathname];
  res.writeHead(status, type ? { "content-type": type } : {}).end(body);
}).listen(0, "127.0.0.1", () => r(s)); });

const client = Client.initWithMiddleware({
  baseUrl: `http://127.0.0.1:${server.address().port}`,
  authProvider: { getAccessToken: async () => "example-token" },
});
console.log("@microsoft/microsoft-graph-client", pkg.version, "| runtime", typeof Bun !== "undefined" ? `bun ${Bun.version}` : `node ${process.version}`);
for (const path of Object.keys(cases)) {
  try { await client.api(path).version("").get(); }
  catch (e) {
    console.log(`${path.padEnd(12)} GraphError: ${e instanceof GraphError} statusCode: ${e.statusCode} code: ${JSON.stringify(e.code)} message: ${JSON.stringify(e.message)} body: ${JSON.stringify(e.body)?.slice(0, 50)}`);
  }
}
server.close();

Output:

@microsoft/microsoft-graph-client 3.0.7 | runtime node v22.23.3
/json-error  GraphError: true statusCode: 403 code: "Authorization_RequestDenied" message: "Insufficient privileges to complete the operation." body: "{\"code\":\"Authorization_RequestDenied\",\"messa
/html-502    GraphError: true statusCode: 502 code: null message: "" body: {}
/text-503    GraphError: true statusCode: 503 code: null message: "" body: "Service Unavailable"
/empty-500   GraphError: true statusCode: 500 code: null message: "" body: {}

The same output with bun 1.4.4.

SDK Version

3.0.7 (latest on npm). Code read on dev @ e6a6221 (package.json says 3.0.8, not published yet).

Latest version known to work for scenario above?

None known.

Known Workarounds

Fall back on the status code when the message is empty, e.g. error.message || `HTTP ${error.statusCode}` .

Debug output

Not applicable. The output above is the full repro output.

Configuration

  • OS: Linux (WSL2), x64
  • Node.js v22.23.3 and bun 1.4.4
  • Not specific to this configuration, as far as I can tell from the code.

Other information

A possible fix is a fallback message in getError's last branch: the body when it is a non-empty string, otherwise a status line such as HTTP ${statusCode}.

Related: #353 fixed an empty error coming from the auth provider callback, which is a different path. #1534 (closed) logs a 401 GraphError whose first line has no message text, which looks like the same case.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions