first commit
This commit is contained in:
78
docs/security-stack/security/x509/src/errors/error-codes.md
Normal file
78
docs/security-stack/security/x509/src/errors/error-codes.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# `errors/error-codes` — typed error registry
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Defines the frozen registry of typed error codes raised by `@suaveplan/x509`. Every public surface in the package throws `X509Error` (a subclass of `@suaveplan/error`'s `SuaveplanError<X509ErrorCode>`); consumers switch on `error.code` to map RFC 5280 §6.1.4 abort conditions, ASN.1/PEM parser failures, and internal-boundary refusals into actionable handling. The literal union is pinned by `expectTypeOf` in the test suite so accidental additions or removals fail typecheck before they ship.
|
||||
|
||||
## Features
|
||||
|
||||
- 20-code frozen registry constructed via `@suaveplan/error`'s `defineCodes("X509", [...])`.
|
||||
- `X509ErrorCode` literal-union derived directly from the registry — no manual duplication.
|
||||
- `X509Error` concrete subclass of `SuaveplanError<X509ErrorCode>` carrying optional `cause`, `context`, and `messageKey`.
|
||||
- Every code is thrown by at least one production code path (verified by `error-codes.test.ts::"every code in the registry is thrown by at least one production path"`).
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { X509_CODES, X509Error } from "@suaveplan/x509/errors";
|
||||
|
||||
try {
|
||||
// ... call into @suaveplan/x509 ...
|
||||
} catch (err) {
|
||||
if (err instanceof X509Error) {
|
||||
if (err.code === X509_CODES.CERT_EXPIRED) {
|
||||
// present user-facing renewal prompt
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { X509_CODES, X509Error, type X509ErrorCode } from "@suaveplan/x509/errors";
|
||||
|
||||
const HUMAN_READABLE: Record<X509ErrorCode, string> = {
|
||||
[X509_CODES.PEM_MALFORMED]: "The PEM block is malformed.",
|
||||
[X509_CODES.ASN1_MALFORMED]: "The ASN.1 encoding is malformed.",
|
||||
[X509_CODES.CERTIFICATE_MALFORMED]: "The certificate body is malformed.",
|
||||
[X509_CODES.UNSUPPORTED_VERSION]: "Certificate version is unsupported.",
|
||||
[X509_CODES.EXTENSION_MALFORMED]: "An extension value is malformed.",
|
||||
[X509_CODES.CRITICAL_EXTENSION_UNRECOGNIZED]: "Critical extension is unrecognised.",
|
||||
[X509_CODES.SIGNATURE_ALG_UNSUPPORTED]: "Signature algorithm is unsupported.",
|
||||
[X509_CODES.SIGNATURE_INVALID]: "Signature did not verify.",
|
||||
[X509_CODES.CERT_EXPIRED]: "Certificate has expired.",
|
||||
[X509_CODES.CERT_NOT_YET_VALID]: "Certificate is not yet valid.",
|
||||
[X509_CODES.NAME_CHAIN_BROKEN]: "Issuer/subject name chaining broken.",
|
||||
[X509_CODES.NAME_CONSTRAINT_VIOLATION]: "Name constraint violated.",
|
||||
[X509_CODES.ISSUER_NOT_CA]: "Issuer is not a CA.",
|
||||
[X509_CODES.KEY_USAGE_INVALID]: "Issuer lacks keyCertSign.",
|
||||
[X509_CODES.EXTENDED_KEY_USAGE_INVALID]: "Required EKU absent.",
|
||||
[X509_CODES.CHAIN_TOO_DEEP]: "Chain depth exceeds bound.",
|
||||
[X509_CODES.SELF_SIGNED_LEAF]: "Self-signed leaf without explicit trust.",
|
||||
[X509_CODES.TRUST_ANCHOR_NOT_FOUND]: "No trust anchor matched.",
|
||||
[X509_CODES.OCSP_REVOKED]: "Certificate is OCSP-revoked.",
|
||||
[X509_CODES.ABORTED]: "Validation was aborted.",
|
||||
};
|
||||
|
||||
throw new X509Error(X509_CODES.CERT_EXPIRED, "error.x509.cert-expired", {
|
||||
context: { notAfter: 1700000000000, now: 1800000000000 },
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `X509_CODES` — frozen object mapping each code identifier to its `"X509.<NAME>"` literal string.
|
||||
- `type X509ErrorCode` — discriminated literal union, exactly 20 members.
|
||||
- `class X509Error extends SuaveplanError<X509ErrorCode>` — concrete error class.
|
||||
- `constructor(code: X509ErrorCode, messageKey: string, options?: SuaveplanErrorOptions)` — `options` may carry `cause`, `context`, and i18n params per `@suaveplan/error`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Codes were chosen to map 1:1 onto RFC 5280 §6.1.4 abort conditions plus parser-boundary failures. There is no aggregate "validation failed" code — every failure path names the precise condition so callers can route programmatically.
|
||||
- The registry MUST stay frozen; adding a code is a breaking change because consumers may switch exhaustively on `X509ErrorCode`.
|
||||
- `defineCodes` namespaces codes under `"X509"`; the raw string form is `"X509.<NAME>"` (e.g. `"X509.CERT_EXPIRED"`).
|
||||
- `messageKey` is an i18n key; the human-readable mapping above is illustrative — production consumers wire it through `@suaveplan/i18n`.
|
||||
Reference in New Issue
Block a user