Files
2026-05-27 23:29:53 +01:00

79 lines
2.7 KiB
Markdown

# `validate` — tenant-label validation
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides the default RFC-1035-style hostname-label validator
(`defaultLabelValidator`) plus two pure string helpers (`normalizeHost`,
`stripBaseDomain`) that the resolver uses to take an inbound `Host`
header all the way down to a candidate tenant slug. Splitting these
helpers out of the resolver body keeps the resolver readable and the
host-parsing edge cases unit-testable in isolation.
Tenant labels are persisted as `tenantId` and compared against by
downstream RBAC / row-level filters, so the canonical form is enforced
here: lower-case ASCII, 1..63 characters, `[a-z0-9-]`, no leading or
trailing hyphen.
## Features
- `defaultLabelValidator` — RFC 1035 §2.3.1 LDH-style validator.
- `MAX_LABEL_LENGTH` constant exported for downstream schemas.
- `normalizeHost(raw)` — strip `:port` suffix, lower-case, refuse the
empty / starts-with-`:` cases.
- `stripBaseDomain(host, baseDomain)` — tri-state return:
`string` (labels), `null` (exact match — no tenant), `undefined`
(mismatch — caller decides whether to raise).
## Usage: Basic
```ts
import {
defaultLabelValidator,
normalizeHost,
stripBaseDomain,
} from "@suaveplan/api-tenant-resolver-subdomain/validate";
const host = normalizeHost("Acme.API.Example.com:443");
// → "acme.api.example.com"
const labels = stripBaseDomain(host!, "api.example.com");
// → "acme"
defaultLabelValidator(labels!); // → true
```
## Usage: Advanced
```ts
import { defaultLabelValidator } from "@suaveplan/api-tenant-resolver-subdomain/validate";
const RESERVED = new Set(["www", "admin", "api"]);
const customValidator = (label: string): boolean => {
if (!defaultLabelValidator(label)) return false;
return !RESERVED.has(label);
};
// → pass `customValidator` as `validate` in SubdomainTenantResolverOptions
```
## API Reference
- `LabelValidator` — `(label: string) => boolean`.
- `defaultLabelValidator: LabelValidator`.
- `MAX_LABEL_LENGTH: 63`.
- `normalizeHost(raw: string | null | undefined): string | null`.
- `stripBaseDomain(host, baseDomain): string | null | undefined`.
## Implementation Notes
The label validator is hand-coded against the character-code range
(`0x30..0x39`, `0x61..0x7a`, `0x2d`) rather than a regex so the
hot-path has no allocator overhead. The tri-state `stripBaseDomain`
return is the standard "missing vs absent vs malformed" pattern from
the rest of the codebase: callers MUST distinguish "host is exactly the
apex" (advance the chain — common for marketing pages) from "host has
the wrong apex" (strict-mode error). `normalizeHost` does not honour
IPv6 bracketed literals (`[::1]`) — tenant resolution by IPv6 host is
not a supported deployment shape.