Skip to content

Commit

Permalink
Throw error if cloud apl GET fails (#296)
Browse files Browse the repository at this point in the history
  • Loading branch information
lkostrowski authored Nov 21, 2023
1 parent 5537c5a commit d1c30dc
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/sixty-geese-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@saleor/app-sdk": minor
---

Changed behavior or Saleor Cloud APL - get method. Prevoiusly ANY error was catched and method returned "undefined". Now only 404-like errors will return "undefined" and error like 5xx, timeouts, wrong body, etc. will result with thrown error. This is technically a breaking change on function level, but in the end app will fail anyway - if APL is not found, it can't proceed. Now error is thrown earlier. It should help with debugging and better custom error handling.
2 changes: 1 addition & 1 deletion src/APL/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from "./apl";
export * from "./env-apl";
export * from "./file-apl";
export * from "./saleor-cloud-apl";
export * from "./saleor-cloud";
export * from "./upstash-apl";
2 changes: 2 additions & 0 deletions src/APL/saleor-cloud/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./saleor-cloud-apl";
export * from "./saleor-cloud-apl-errors";
6 changes: 6 additions & 0 deletions src/APL/saleor-cloud/saleor-cloud-apl-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class SaleorCloudAplError extends Error {
constructor(public code: string, message: string) {
super(message);
this.name = "SaleorCloudAplError";
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { afterEach, describe, expect, it, vi } from "vitest";

import { AuthData } from "./apl";
import { AuthData } from "../apl";
import { GetAllAplResponseShape, SaleorCloudAPL, SaleorCloudAPLConfig } from "./saleor-cloud-apl";
import { SaleorCloudAplError } from "./saleor-cloud-apl-errors";

const fetchMock = vi.fn();

Expand Down Expand Up @@ -73,6 +74,52 @@ describe("APL", () => {
});

describe("get", () => {
describe("Handles failures on API level", () => {
it("Throws error if status is 500", async () => {
fetchMock.mockResolvedValueOnce({
status: 500,
ok: false,
async json() {
throw new Error();
},
});

const apl = new SaleorCloudAPL(aplConfig);

try {
await apl.get(stubAuthData.saleorApiUrl);
} catch (e) {
const err = e as SaleorCloudAplError;

expect(err.code).toEqual("FAILED_TO_REACH_API");
expect(err).toMatchInlineSnapshot("[SaleorCloudAplError: Api responded with 500]");
}
});

it("Throws error if status is 200 but JSON is malformed", async () => {
fetchMock.mockResolvedValueOnce({
status: 200,
ok: true,
async json() {
throw new Error("json error");
},
});

const apl = new SaleorCloudAPL(aplConfig);

try {
await apl.get(stubAuthData.saleorApiUrl);
} catch (e) {
const err = e as SaleorCloudAplError;

expect(err.code).toEqual("RESPONSE_BODY_INVALID");
expect(err).toMatchInlineSnapshot(
"[SaleorCloudAplError: Cant parse response body: json error]"
);
}
});
});

describe("Read existing auth data", () => {
it("Read existing auth data", async () => {
fetchMock.mockResolvedValue({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { hasProp } from "../has-prop";
import { APL, AplConfiguredResult, AplReadyResult, AuthData } from "./apl";
import { createAPLDebug } from "./apl-debug";
import { authDataFromObject } from "./auth-data-from-object";
import { hasProp } from "../../has-prop";
import { APL, AplConfiguredResult, AplReadyResult, AuthData } from "../apl";
import { createAPLDebug } from "../apl-debug";
import { authDataFromObject } from "../auth-data-from-object";
import { SaleorCloudAplError } from "./saleor-cloud-apl-errors";

const debug = createAPLDebug("SaleorCloudAPL");

Expand All @@ -23,13 +24,12 @@ export type GetAllAplResponseShape = {
results: CloudAPLAuthDataShape[];
};

const validateResponseStatus = (response: Response) => {
if (response.status === 404) {
debug("Auth data not found");
debug("%O", response);
export const CloudAplError = {
FAILED_TO_REACH_API: "FAILED_TO_REACH_API",
RESPONSE_BODY_INVALID: "RESPONSE_BODY_INVALID",
};

throw new Error("Auth data not found");
}
const validateResponseStatus = (response: Response) => {
if (!response.ok) {
debug("Response failed with status %s", response.status);
debug("%O", response);
Expand Down Expand Up @@ -102,25 +102,41 @@ export class SaleorCloudAPL implements APL {
debug("Failed to reach API call: %s", extractErrorMessage(error));
debug("%O", error);

return undefined;
throw new SaleorCloudAplError(
CloudAplError.FAILED_TO_REACH_API,
`${extractErrorMessage(error)}`
);
});

if (!response) {
debug("No response from the API");
return undefined;

throw new SaleorCloudAplError(
CloudAplError.FAILED_TO_REACH_API,
"Response couldnt be resolved"
);
}

try {
validateResponseStatus(response);
debug("Response status valid");
} catch {
debug("Response status not valid");
if (response.status >= 500) {
throw new SaleorCloudAplError(
CloudAplError.FAILED_TO_REACH_API,
`Api responded with ${response.status}`
);
}

if (response.status === 404) {
debug("No auth data for given saleorApiUrl");
return undefined;
}

const parsedResponse = (await response.json().catch((e) => {
debug("Failed to parse response: %s", extractErrorMessage(e));
debug("%O", e);

throw new SaleorCloudAplError(
CloudAplError.RESPONSE_BODY_INVALID,
`Cant parse response body: ${extractErrorMessage(e)}`
);
})) as CloudAPLAuthDataShape;

const authData = authDataFromObject(mapAPIResponseToAuthData(parsedResponse));
Expand Down

0 comments on commit d1c30dc

Please sign in to comment.