Skip to content

Commit

Permalink
feat: support expired and not-found ref API errors (#327)
Browse files Browse the repository at this point in the history
* feat: support expired and not-found ref API errors

* fix: extend `RefNotFoundError` and `RefExpiredError` from `ForbiddenError` for backwards compatibility
  • Loading branch information
angeloashmore authored Oct 6, 2023
1 parent fb555fd commit 26d5b0f
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 10 deletions.
21 changes: 17 additions & 4 deletions src/createClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { ForbiddenError } from "./errors/ForbiddenError";
import { NotFoundError } from "./errors/NotFoundError";
import { ParsingError } from "./errors/ParsingError";
import { PrismicError } from "./errors/PrismicError";
import { RefExpiredError } from "./errors/RefExpiredError";
import { RefNotFoundError } from "./errors/RefNotFoundError";

import { LinkResolverFunction, asLink } from "./helpers/asLink";

Expand Down Expand Up @@ -1900,22 +1902,33 @@ export class Client<TDocuments extends PrismicDocument = PrismicDocument> {
// - Incorrect access token for query endpoint
case 403: {
throw new ForbiddenError(
"error" in res.json ? res.json.error : res.json.message,
res.json.error || res.json.message,
url,
res.json,
);
}

// Not Found (this response has an empty body)
// - Incorrect repository name
// Not Found
// - Incorrect repository name (this response has an empty body)
// - Ref does not exist
case 404: {
if (res.json && res.json.type === "api_notfound_error") {
throw new RefNotFoundError(res.json.message, url, res.json);
}

throw new NotFoundError(
`Prismic repository not found. Check that "${this.endpoint}" is pointing to the correct repository.`,
url,
undefined,
undefined, // res.json is empty
);
}

// Gone
// - Ref is expired
case 410: {
throw new RefExpiredError(res.json.message, url, res.json);
}

// Too Many Requests
// - Exceeded the maximum number of requests per second
case 429: {
Expand Down
8 changes: 5 additions & 3 deletions src/errors/ForbiddenError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ type ForbiddenErrorQueryAPIResponse = {
error: string;
};

export class ForbiddenError extends PrismicError<
ForbiddenErrorRepositoryAPIResponse | ForbiddenErrorQueryAPIResponse
> {}
export class ForbiddenError<
TResponse =
| ForbiddenErrorRepositoryAPIResponse
| ForbiddenErrorQueryAPIResponse,
> extends PrismicError<TResponse> {}
4 changes: 3 additions & 1 deletion src/errors/NotFoundError.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { PrismicError } from "./PrismicError";

export class NotFoundError extends PrismicError<undefined> {}
export class NotFoundError<
TResponse = undefined,
> extends PrismicError<TResponse> {}
4 changes: 3 additions & 1 deletion src/errors/ParsingError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ type ParsingErrorAPIResponse = {
location: string;
};

export class ParsingError extends PrismicError<ParsingErrorAPIResponse> {}
export class ParsingError<
TResponse = ParsingErrorAPIResponse,
> extends PrismicError<TResponse> {}
14 changes: 14 additions & 0 deletions src/errors/RefExpiredError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ForbiddenError } from "./ForbiddenError";

type RefExpiredErrorAPIResponse = {
type: "api_validation_error";
message: string;
};

// This error extends `ForbiddenError` for backwards compatibility. Before the
// API started returning 410 for expired refs, it returnd 403, which threw a
// `ForbiddenError`.
// TODO: Extend this error from `PrismicError` in v8.
export class RefExpiredError<
TResponse = RefExpiredErrorAPIResponse,
> extends ForbiddenError<TResponse> {}
14 changes: 14 additions & 0 deletions src/errors/RefNotFoundError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ForbiddenError } from "./ForbiddenError";

type RefNotFoundErrorAPIResponse = {
type: "api_notfound_error";
message: string;
};

// This error extends `ForbiddenError` for backwards compatibility. Before the
// API started returning 404 for not found refs, it returnd 403, which threw a
// `ForbiddenError`.
// TODO: Extend this error from `PrismicError` in v8.
export class RefNotFoundError<
TResponse = RefNotFoundErrorAPIResponse,
> extends ForbiddenError<TResponse> {}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ export type { HTMLRichTextSerializer } from "./helpers/asHTML";
export { PrismicError } from "./errors/PrismicError";
export { ForbiddenError } from "./errors/ForbiddenError";
export { NotFoundError } from "./errors/NotFoundError";
export { RefNotFoundError } from "./errors/RefNotFoundError";
export { RefExpiredError } from "./errors/RefExpiredError";
export { ParsingError } from "./errors/ParsingError";

//=============================================================================
Expand Down
56 changes: 55 additions & 1 deletion test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ it("throws PrismicError if response is not 200, 400, 401, 403, or 404", async (c
const client = createTestClient();

const queryEndpoint = new URL(
"documents/search",
"./documents/search",
`${client.endpoint}/`,
).toString();

Expand Down Expand Up @@ -684,6 +684,60 @@ it("throws NotFoundError if repository does not exist", async (ctx) => {
await expect(() => client.get()).rejects.toThrowError(prismic.NotFoundError);
});

it("throws RefNotFoundError if ref does not exist", async (ctx) => {
const queryResponse = {
type: "api_notfound_error",
message: "message",
};

mockPrismicRestAPIV2({ ctx });

const client = createTestClient();

const queryEndpoint = new URL(
"./documents/search",
`${client.endpoint}/`,
).toString();

ctx.server.use(
msw.rest.get(queryEndpoint, (_req, res, ctx) => {
return res(ctx.status(404), ctx.json(queryResponse));
}),
);

await expect(() => client.get()).rejects.toThrowError(queryResponse.message);
await expect(() => client.get()).rejects.toThrowError(
prismic.RefNotFoundError,
);
});

it("throws RefExpiredError if ref is expired", async (ctx) => {
const queryResponse = {
type: "api_validation_error",
message: "message",
};

mockPrismicRestAPIV2({ ctx });

const client = createTestClient();

const queryEndpoint = new URL(
"./documents/search",
`${client.endpoint}/`,
).toString();

ctx.server.use(
msw.rest.get(queryEndpoint, (_req, res, ctx) => {
return res(ctx.status(410), ctx.json(queryResponse));
}),
);

await expect(() => client.get()).rejects.toThrowError(queryResponse.message);
await expect(() => client.get()).rejects.toThrowError(
prismic.RefExpiredError,
);
});

it("retries after `retry-after` milliseconds if response code is 429", async (ctx) => {
const retryAfter = 200; // ms
/**
Expand Down

0 comments on commit 26d5b0f

Please sign in to comment.