mirror of
https://github.com/jakejarvis/rdapper.git
synced 2026-10-26 08:26:22 -04:00
feat: reject isAvailable/isRegistered failures with a coded RdapperError
The helpers threw a plain Error with only the message, so callers couldn't tell a rate limit from invalid input or their own abort. Throw an RdapperError whose code, phase, server, and retryAfterMs mirror the lookup result (an abort keeps the name "AbortError"), and export RdapperError for instanceof checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
3 files changed
+34
-4
No files matched your search
@@ -63,6 +63,7 @@ await isAvailable("likely-unregistered-thing-320485230458.com"); // => true
|
||||
- Returns the registrable domain string, or `null` for IPs/invalid input; [options](https://github.com/remusao/tldts/blob/master/packages/tldts-core/src/options.ts) are forwarded to `tldts` (e.g., `allowPrivateDomains`).
|
||||
- `isRegistered(domain, options?) => Promise<boolean>`
|
||||
- `isAvailable(domain, options?) => Promise<boolean>`
|
||||
- Reject when the lookup fails, with an `RdapperError` (exported) whose `code`, `phase`, `server`, and `retryAfterMs` mirror the result's `errorCode`, `errorPhase`, `errorServer`, and `retryAfterMs`. An abort keeps the name `"AbortError"`.
|
||||
- `finalizeContact(contact, redactedHint?) => Contact`
|
||||
- Cleans a contact: drops placeholder values (recording them in `redactedFields`), sets `privacyService`, resolves `country`/`countryCode`, and sets `redacted`. Safe to re-run on an already-cleaned contact, e.g. to upgrade contacts stored by an older version.
|
||||
- `isPrivacyContact(contact) => boolean`
|
||||
|
||||
@@ -240,3 +240,20 @@ describe("RDAP responses that can't be read", () => {
|
||||
expect(res.error).not.toContain("no RDAP server listed");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isAvailable / isRegistered errors", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("reject with an RdapperError carrying the lookup's error code", async () => {
|
||||
const { isAvailable, isRegistered, RdapperError } = await import(".");
|
||||
const err = await isAvailable("not a domain").catch((e: unknown) => e);
|
||||
expect(err).toBeInstanceOf(RdapperError);
|
||||
expect(err).toMatchObject({ code: "invalid_input", name: "RdapperError" });
|
||||
const aborted = await isRegistered("example.com", { signal: AbortSignal.abort() }).catch(
|
||||
(e: unknown) => e,
|
||||
);
|
||||
expect(aborted).toMatchObject({ code: "aborted", name: "AbortError" });
|
||||
});
|
||||
});
|
||||
+16
-4
@@ -224,23 +224,34 @@ async function runLookup(
|
||||
return { ok: true, record: mergedRecord, attempts: ctx.attempts };
|
||||
}
|
||||
|
||||
/** A failed lookup as an error, keeping its code (and the name "AbortError" for aborts). */
|
||||
function lookupError(res: LookupResult): RdapperError {
|
||||
return new RdapperError(res.errorCode ?? "unknown", res.error || "Lookup failed", {
|
||||
phase: res.errorPhase,
|
||||
server: res.errorServer,
|
||||
retryAfterMs: res.retryAfterMs,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a domain appears available (not registered).
|
||||
* Performs a lookup and resolves to a boolean. Rejects on lookup error.
|
||||
* Performs a lookup and resolves to a boolean. Rejects on lookup error with an RdapperError
|
||||
* carrying the result's errorCode (as `code`), errorPhase, errorServer, and retryAfterMs.
|
||||
*/
|
||||
export async function isAvailable(domain: string, opts?: LookupOptions): Promise<boolean> {
|
||||
const res = await lookup(domain, opts);
|
||||
if (!res.ok || !res.record) throw new Error(res.error || "Lookup failed");
|
||||
if (!res.ok || !res.record) throw lookupError(res);
|
||||
return res.record.isRegistered === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a domain appears registered.
|
||||
* Performs a lookup and resolves to a boolean. Rejects on lookup error.
|
||||
* Performs a lookup and resolves to a boolean. Rejects on lookup error with an RdapperError
|
||||
* carrying the result's errorCode (as `code`), errorPhase, errorServer, and retryAfterMs.
|
||||
*/
|
||||
export async function isRegistered(domain: string, opts?: LookupOptions): Promise<boolean> {
|
||||
const res = await lookup(domain, opts);
|
||||
if (!res.ok || !res.record) throw new Error(res.error || "Lookup failed");
|
||||
if (!res.ok || !res.record) throw lookupError(res);
|
||||
return res.record.isRegistered === true;
|
||||
}
|
||||
|
||||
@@ -254,4 +265,5 @@ export { finalizeContact, isPrivacyContact } from "./lib/contacts";
|
||||
export { resolveCountry } from "./lib/countries";
|
||||
export { isPlaceholderValue, isPrivacyName } from "./lib/privacy";
|
||||
export { normalizeEppStatus } from "./lib/status";
|
||||
export { RdapperError } from "./lib/errors";
|
||||
export type * from "./types";
|
||||
Reference in new issue
Block a user