Skip to content

Commit

Permalink
Preserve response body on api requests with XML response types. (#4180)
Browse files Browse the repository at this point in the history
Today, apiv2 will try to parse XML responses as JSON with poor results, e.g.

```
⚠ functions: Upload Error: Unable to parse JSON: SyntaxError: Unexpected token < in JSON at position 0

Error: Unable to parse JSON: SyntaxError: Unexpected token < in JSON at position 0
```

Here, we explicitly add support for `xml` as a responseType and deal with it as a string. E.g.

```
⚠  functions: Upload Error: HTTP Error: 400, <?xml version='1.0' encoding='UTF-8'?><Error><Code>EntityTooLarge</Code><Message>Your proposed upload is larger than the maximum object size specified in your Policy Document.</Message><Details>Content-length exceeds upper bound on range</Details></Error>

Error: HTTP Error: 400, <?xml version='1.0' encoding='UTF-8'?><Error><Code>EntityTooLarge</Code><Message>Your proposed upload is larger than the maximum object size specified in your Policy Document.</Message><Details>Content-length exceeds upper bound on range</Details></Error>
```

Fixes #4146
  • Loading branch information
taeold committed Feb 17, 2022
1 parent 520ae1f commit ebd3323
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -4,4 +4,5 @@
- Fixes broken functions:config:clone command (#4173).
- Fixes issue where `auth:import` would fail when reading a JSON file. (#4157)
- Fixes issue where custom claims added in Auth Emulator UI was not properly shown.
- Improves handling of API requests with XML responses (#4180).
- Updates the underlying request library in Hosting deploys and uses project-scoped URLs. (#2558)
4 changes: 3 additions & 1 deletion src/apiv2.ts
Expand Up @@ -23,7 +23,7 @@ interface BaseRequestOptions<T> extends VerbOptions {
method: HttpMethod;
path: string;
body?: T | string | NodeJS.ReadableStream;
responseType?: "json" | "stream";
responseType?: "json" | "stream" | "xml";
redirect?: "error" | "follow" | "manual";
compress?: boolean;
}
Expand Down Expand Up @@ -415,6 +415,8 @@ export class Client {
throw new FirebaseError(`Unable to parse JSON: ${err}`);
}
}
} else if (options.responseType === "xml") {
body = (await res.text()) as unknown as ResT;
} else if (options.responseType === "stream") {
body = res.body as unknown as ResT;
} else {
Expand Down
1 change: 1 addition & 0 deletions src/gcp/storage.ts
Expand Up @@ -158,6 +158,7 @@ export async function upload(
method: "PUT",
path: url.pathname,
queryParams: url.searchParams,
responseType: "xml",
headers: {
"content-type": "application/zip",
...extraHeaders,
Expand Down
24 changes: 16 additions & 8 deletions src/responseToError.js
Expand Up @@ -4,18 +4,26 @@ const _ = require("lodash");
const { FirebaseError } = require("./error");

module.exports = function (response, body) {
if (typeof body === "string" && response.statusCode === 404) {
body = {
error: {
message: "Not Found",
},
};
}

if (response.statusCode < 400) {
return null;
}

if (typeof body === "string") {
if (response.statusCode === 404) {
body = {
error: {
message: "Not Found",
},
};
} else {
body = {
error: {
message: body,
},
};
}
}

if (typeof body !== "object") {
try {
body = JSON.parse(body);
Expand Down
30 changes: 30 additions & 0 deletions src/test/apiv2.spec.ts
Expand Up @@ -394,6 +394,36 @@ describe("apiv2", () => {
expect(nock.isDone()).to.be.true;
});

it("should preserve XML messages", async () => {
const xml = "<?xml version='1.0' encoding='UTF-8'?><Message>Hello!</Message>";
nock("https://example.com").get("/path/to/foo").reply(200, xml);

const c = new Client({ urlPrefix: "https://example.com" });
const r = await c.request({
method: "GET",
path: "/path/to/foo",
responseType: "xml",
});
expect(r.body).to.deep.equal(xml);
expect(nock.isDone()).to.be.true;
});

it("should preserve XML messages on error", async () => {
const xml =
"<?xml version='1.0' encoding='UTF-8'?><Error><Code>EntityTooLarge</Code></Error>";
nock("https://example.com").get("/path/to/foo").reply(400, xml);

const c = new Client({ urlPrefix: "https://example.com" });
await expect(
c.request({
method: "GET",
path: "/path/to/foo",
responseType: "xml",
})
).to.eventually.be.rejectedWith(FirebaseError, /EntityTooLarge/);
expect(nock.isDone()).to.be.true;
});

describe("with a proxy", () => {
let proxyServer: Server;
let targetServer: Server;
Expand Down

0 comments on commit ebd3323

Please sign in to comment.