Skip to content

Commit ca90a57

Browse files
committed
feat: add TemporarilyUnavailableError for 503 responses
1 parent 130e5bc commit ca90a57

5 files changed

Lines changed: 70 additions & 0 deletions

File tree

src/client.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
SubscriptionRequiredError,
66
NotFoundError,
77
RateLimitError,
8+
TemporarilyUnavailableError,
89
UpstreamError,
910
} from "./errors.js";
1011
import type {
@@ -101,6 +102,11 @@ export class RdapClient {
101102
throw new RateLimitError(message, error, retryAfter ? parseInt(retryAfter, 10) : null);
102103
}
103104

105+
if (response.status === 503) {
106+
const retryAfter = response.headers.get("Retry-After");
107+
throw new TemporarilyUnavailableError(message, error, retryAfter ? parseInt(retryAfter, 10) : null);
108+
}
109+
104110
const ErrorClass = ERROR_MAP[response.status];
105111
if (ErrorClass) {
106112
throw new ErrorClass(message, error);

src/errors.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ export class RateLimitError extends RdapApiError {
5555
}
5656
}
5757

58+
/** Raised when the domain data is temporarily unavailable (HTTP 503). */
59+
export class TemporarilyUnavailableError extends RdapApiError {
60+
readonly retryAfter: number | null;
61+
62+
constructor(message: string, error: string, retryAfter: number | null) {
63+
super(message, 503, error);
64+
this.name = "TemporarilyUnavailableError";
65+
this.retryAfter = retryAfter;
66+
}
67+
}
68+
5869
/** Raised when the upstream RDAP server fails (HTTP 502). */
5970
export class UpstreamError extends RdapApiError {
6071
constructor(message: string, error: string) {

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export {
66
NotFoundError,
77
RateLimitError,
88
SubscriptionRequiredError,
9+
TemporarilyUnavailableError,
910
UpstreamError,
1011
ValidationError,
1112
} from "./errors.js";

tests/client.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
AuthenticationError,
55
NotFoundError,
66
RateLimitError,
7+
TemporarilyUnavailableError,
78
RdapApiError,
89
SubscriptionRequiredError,
910
UpstreamError,
@@ -334,6 +335,40 @@ describe("error handling", () => {
334335
}
335336
});
336337

338+
it("throws TemporarilyUnavailableError on 503 with retryAfter", async () => {
339+
const client = new RdapClient("test-key", { baseUrl: BASE_URL });
340+
globalThis.fetch = mockFetch(
341+
{ error: "temporarily_unavailable", message: "Data for this domain is temporarily unavailable." },
342+
503,
343+
{ "Retry-After": "300" },
344+
);
345+
346+
try {
347+
await client.domain("test.com");
348+
expect.fail("Should have thrown");
349+
} catch (err) {
350+
expect(err).toBeInstanceOf(TemporarilyUnavailableError);
351+
expect((err as TemporarilyUnavailableError).retryAfter).toBe(300);
352+
expect((err as TemporarilyUnavailableError).statusCode).toBe(503);
353+
}
354+
});
355+
356+
it("throws TemporarilyUnavailableError with null retryAfter when header missing", async () => {
357+
const client = new RdapClient("test-key", { baseUrl: BASE_URL });
358+
globalThis.fetch = mockFetch(
359+
{ error: "temporarily_unavailable", message: "Data for this domain is temporarily unavailable." },
360+
503,
361+
);
362+
363+
try {
364+
await client.domain("test.com");
365+
expect.fail("Should have thrown");
366+
} catch (err) {
367+
expect(err).toBeInstanceOf(TemporarilyUnavailableError);
368+
expect((err as TemporarilyUnavailableError).retryAfter).toBeNull();
369+
}
370+
});
371+
337372
it("throws UpstreamError on 502", async () => {
338373
const client = new RdapClient("test-key", { baseUrl: BASE_URL });
339374
globalThis.fetch = mockFetch(

tests/errors.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
SubscriptionRequiredError,
77
NotFoundError,
88
RateLimitError,
9+
TemporarilyUnavailableError,
910
UpstreamError,
1011
} from "../src/errors.js";
1112

@@ -82,6 +83,22 @@ describe("RateLimitError", () => {
8283
});
8384
});
8485

86+
describe("TemporarilyUnavailableError", () => {
87+
it("has status 503, retryAfter, and correct prototype chain", () => {
88+
const err = new TemporarilyUnavailableError("Temporarily unavailable", "temporarily_unavailable", 300);
89+
expect(err.statusCode).toBe(503);
90+
expect(err.retryAfter).toBe(300);
91+
expect(err.name).toBe("TemporarilyUnavailableError");
92+
expect(err).toBeInstanceOf(TemporarilyUnavailableError);
93+
expect(err).toBeInstanceOf(RdapApiError);
94+
});
95+
96+
it("handles null retryAfter", () => {
97+
const err = new TemporarilyUnavailableError("Temporarily unavailable", "temporarily_unavailable", null);
98+
expect(err.retryAfter).toBeNull();
99+
});
100+
});
101+
85102
describe("UpstreamError", () => {
86103
it("has status 502 and correct prototype chain", () => {
87104
const err = new UpstreamError("Upstream failed", "upstream_error");

0 commit comments

Comments
 (0)