Initial Commit
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# dmarc-error
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Defines the lightweight error class and stable code registry for `@suaveplan/dmarc`. Because this package carries no `@suaveplan/error` runtime dependency (it is a leaf package with no `@suaveplan/*` runtime deps), it provides its own self-contained error substrate that gives callers the same discriminated-error contract: a strongly-typed `code` field for `switch`/`instanceof` discrimination, plus an idiomatic `cause` chain via the built-in `ErrorOptions`.
|
||||
|
||||
## Features
|
||||
|
||||
- `DMARC_CODES` — three stable string-literal constants (`DMARC.PARSE_ERROR`, `DMARC.INVALID_REPORT`, `DMARC.UNSUPPORTED_ENCODING`)
|
||||
- `DmarcCode` — union type of all valid code values
|
||||
- `DmarcError` — `Error` subclass with a `readonly code: DmarcCode` field and preserved `name`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { DmarcError, DMARC_CODES } from "@suaveplan/dmarc/errors";
|
||||
|
||||
try {
|
||||
// ... parse logic
|
||||
} catch (cause) {
|
||||
throw new DmarcError(DMARC_CODES.PARSE_ERROR, "Failed to parse XML", { cause });
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { DmarcError, DMARC_CODES, type DmarcCode } from "@suaveplan/dmarc/errors";
|
||||
|
||||
function handleDmarcError(err: unknown): never {
|
||||
if (err instanceof DmarcError) {
|
||||
switch (err.code) {
|
||||
case DMARC_CODES.PARSE_ERROR:
|
||||
console.error("XML parse failure:", err.message, err.cause);
|
||||
break;
|
||||
case DMARC_CODES.INVALID_REPORT:
|
||||
console.error("Schema validation failed:", err.message);
|
||||
break;
|
||||
case DMARC_CODES.UNSUPPORTED_ENCODING:
|
||||
console.error("Cannot decode input:", err.message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `DMARC_CODES`
|
||||
|
||||
`{ PARSE_ERROR: "DMARC.PARSE_ERROR"; INVALID_REPORT: "DMARC.INVALID_REPORT"; UNSUPPORTED_ENCODING: "DMARC.UNSUPPORTED_ENCODING" }`
|
||||
|
||||
Immutable code registry. Use these constants instead of raw strings so that typos produce type errors.
|
||||
|
||||
### `DmarcCode`
|
||||
|
||||
`"DMARC.PARSE_ERROR" | "DMARC.INVALID_REPORT" | "DMARC.UNSUPPORTED_ENCODING"`
|
||||
|
||||
Union of all valid code values for use in signatures and `switch` exhaustiveness checks.
|
||||
|
||||
### `DmarcError`
|
||||
|
||||
`class DmarcError extends Error`
|
||||
|
||||
**Constructor:** `new DmarcError(code: DmarcCode, message: string, options?: ErrorOptions)`
|
||||
|
||||
- `code` — one of the `DMARC_CODES` values
|
||||
- `message` — human-readable description
|
||||
- `options.cause` — original error for cause-chain logging
|
||||
|
||||
**Properties:**
|
||||
|
||||
- `code: DmarcCode` — readonly; set during construction
|
||||
- `name: string` — always `"DmarcError"`
|
||||
|
||||
**Throws:** nothing
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The absence of `@suaveplan/error` as a runtime dependency is intentional: this package is a leaf node in the dependency graph and must remain importable in environments where the broader Suaveplan stack is not installed. The same discriminated-error pattern (stable `code` field, `cause` chain, subclassed `Error`) is preserved so that tooling and callers that understand `@suaveplan/error` conventions can handle these errors uniformly.
|
||||
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { DMARC_CODES, DmarcError } from "./dmarc-error.js";
|
||||
|
||||
describe("DMARC_CODES", () => {
|
||||
it("has the expected code values", { timeout: 5000 }, () => {
|
||||
expect(DMARC_CODES.PARSE_ERROR).toBe("DMARC.PARSE_ERROR");
|
||||
expect(DMARC_CODES.INVALID_REPORT).toBe("DMARC.INVALID_REPORT");
|
||||
expect(DMARC_CODES.UNSUPPORTED_ENCODING).toBe(
|
||||
"DMARC.UNSUPPORTED_ENCODING"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DmarcError", () => {
|
||||
it("is an instance of Error", { timeout: 5000 }, () => {
|
||||
const err = new DmarcError(DMARC_CODES.PARSE_ERROR, "test");
|
||||
expect(err).toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
it("has the correct name", { timeout: 5000 }, () => {
|
||||
const err = new DmarcError(DMARC_CODES.PARSE_ERROR, "test");
|
||||
expect(err.name).toBe("DmarcError");
|
||||
});
|
||||
|
||||
it("sets the code field", { timeout: 5000 }, () => {
|
||||
const err = new DmarcError(DMARC_CODES.INVALID_REPORT, "bad report");
|
||||
expect(err.code).toBe(DMARC_CODES.INVALID_REPORT);
|
||||
});
|
||||
|
||||
it("sets the message field", { timeout: 5000 }, () => {
|
||||
const err = new DmarcError(DMARC_CODES.PARSE_ERROR, "my message");
|
||||
expect(err.message).toBe("my message");
|
||||
});
|
||||
|
||||
it("stores cause when provided", { timeout: 5000 }, () => {
|
||||
const cause = new Error("original");
|
||||
const err = new DmarcError(DMARC_CODES.UNSUPPORTED_ENCODING, "wrap", {
|
||||
cause,
|
||||
});
|
||||
expect(err.cause).toBe(cause);
|
||||
});
|
||||
|
||||
it("works with instanceof discrimination", { timeout: 5000 }, () => {
|
||||
const err = new DmarcError(DMARC_CODES.PARSE_ERROR, "x");
|
||||
expect(err instanceof DmarcError).toBe(true);
|
||||
expect(err instanceof Error).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* DMARC error codes as string literal constants.
|
||||
*
|
||||
* All codes are prefixed with `DMARC.` and fall into three categories:
|
||||
* - `DMARC.PARSE_ERROR` — XML parsing or structural failure
|
||||
* - `DMARC.INVALID_REPORT` — the parsed structure does not conform to the DMARC schema
|
||||
* - `DMARC.UNSUPPORTED_ENCODING` — input encoding cannot be handled (not gzip, not UTF-8)
|
||||
*/
|
||||
export const DMARC_CODES = {
|
||||
PARSE_ERROR: "DMARC.PARSE_ERROR",
|
||||
INVALID_REPORT: "DMARC.INVALID_REPORT",
|
||||
UNSUPPORTED_ENCODING: "DMARC.UNSUPPORTED_ENCODING",
|
||||
} as const;
|
||||
|
||||
export type DmarcCode = (typeof DMARC_CODES)[keyof typeof DMARC_CODES];
|
||||
|
||||
/**
|
||||
* Lightweight error class for all `@suaveplan/dmarc` failures.
|
||||
*
|
||||
* Extends the built-in `Error` with a strongly-typed `code` field drawn from
|
||||
* `DMARC_CODES`. Since this package carries no `@suaveplan/error` runtime
|
||||
* dependency, this is a self-contained substitute that provides the same
|
||||
* discriminated-error contract to callers.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { DmarcError, DMARC_CODES } from "@suaveplan/dmarc/errors";
|
||||
*
|
||||
* throw new DmarcError(DMARC_CODES.PARSE_ERROR, "Unexpected root element", {
|
||||
* cause: originalError,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export class DmarcError extends Error {
|
||||
/** Stable string code identifying the failure kind. */
|
||||
public readonly code: DmarcCode;
|
||||
|
||||
public constructor(
|
||||
code: DmarcCode,
|
||||
message: string,
|
||||
options?: ErrorOptions
|
||||
) {
|
||||
super(message, options);
|
||||
this.name = "DmarcError";
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
1
packages/communication/emails/dmarc/src/errors/index.ts
Normal file
1
packages/communication/emails/dmarc/src/errors/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { DMARC_CODES, type DmarcCode, DmarcError } from "./dmarc-error";
|
||||
Reference in New Issue
Block a user