first commit
This commit is contained in:
129
docs/foundation/flow/rules/src/validators/schema/schema.md
Normal file
129
docs/foundation/flow/rules/src/validators/schema/schema.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# SchemaValidator — `@suaveplan/rules/validators/schema`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`SchemaValidator` validates a `Record<string, unknown>` against a JSON-schema-like `Schema` definition. It covers the 8 primitive `FieldType`s (`string`, `number`, `integer`, `boolean`, `object`, `array`, `date`, `datetime`, `any`), 7 string formats (`email`, `url`, `uuid`, `date`, `datetime`, `ipv4`, `ipv6`, `regex`), 5 transforms (`trim`, `lowercase`, `uppercase`, `parseNumber`, `parseDate`), `enum` checks, `min`/`max`/`minLength`/`maxLength`/`pattern` constraints, recursive nested objects, recursive array items, and caller-supplied custom validators keyed by field path.
|
||||
|
||||
Every input schema is Zod-validated at construction — malformed schemas raise `RulesError(RULES.SCHEMA_INVALID)`. The `validate` method returns a `ValidationResult` rather than throwing for any normal failure mode (missing required, wrong type, format miss, custom-refinement failure). OTel histogram and counter fire per invocation.
|
||||
|
||||
## Features
|
||||
|
||||
- 8 field types with strict matching (e.g. `integer` rejects non-integer numbers).
|
||||
- 7 format checks via shared regex map.
|
||||
- 5 transforms applied before validation; non-applicable transforms (e.g. `lowercase` on a number) are no-ops.
|
||||
- Nested objects + arrays validated recursively with structured paths (`user.profile.name`, `items[3].sku`).
|
||||
- `enum` membership checks.
|
||||
- `min` / `max` / `minLength` / `maxLength` / `pattern` constraints.
|
||||
- Custom validators registered by field path; return `true`, `false`, or a string error message.
|
||||
- Zod-validated `Schema` at construction → `RULES.SCHEMA_INVALID` on rejection.
|
||||
- Pre-aborted `AbortSignal` short-circuits the recursion with `RULES.SCHEMA_INVALID`.
|
||||
- Returns `{ valid, errors, value }` where `value` carries the transformed data tree.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { SchemaValidator } from "@suaveplan/rules/validators/schema";
|
||||
|
||||
const validator = new SchemaValidator({
|
||||
email: { type: "string", required: true, format: "email" },
|
||||
age: { type: "integer", min: 18 },
|
||||
});
|
||||
|
||||
const result = await validator.validate({ email: "ada@example.com", age: 30 });
|
||||
result.valid; // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { SchemaValidator } from "@suaveplan/rules/validators/schema";
|
||||
|
||||
const validator = new SchemaValidator(
|
||||
{
|
||||
username: {
|
||||
type: "string",
|
||||
required: true,
|
||||
minLength: 3,
|
||||
maxLength: 20,
|
||||
transforms: ["trim", "lowercase"],
|
||||
},
|
||||
profile: {
|
||||
type: "object",
|
||||
properties: {
|
||||
bio: { type: "string", maxLength: 240 },
|
||||
tags: {
|
||||
type: "array",
|
||||
items: { type: "string", minLength: 2 },
|
||||
},
|
||||
},
|
||||
},
|
||||
registeredAt: { type: "date", transforms: ["parseDate"], required: true },
|
||||
},
|
||||
{
|
||||
customValidators: {
|
||||
username: async (value) =>
|
||||
typeof value === "string" && !value.startsWith("admin")
|
||||
? true
|
||||
: "reserved username prefix",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const result = await validator.validate({
|
||||
username: " ADA ",
|
||||
profile: { bio: "Mathematician", tags: ["math", "logic"] },
|
||||
registeredAt: "1815-12-10",
|
||||
});
|
||||
|
||||
if (!result.valid) {
|
||||
for (const err of result.errors) console.warn(err.path, err.code, err.message);
|
||||
}
|
||||
console.log(result.value); // { username: "ada", profile: {...}, registeredAt: Date }
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `class SchemaValidator`
|
||||
|
||||
- `new SchemaValidator(schema, options?)` — Zod-validates the schema; raises `RULES.SCHEMA_INVALID` on rejection.
|
||||
- `options.customValidators: Record<string, CustomValidatorFn>` — keyed by field path. Each validator receives `(value, ValidatorContext)` and returns `true`, `false`, or a `string` error.
|
||||
- `validate(data, options?)` → `Promise<ValidationResult>`. `options.signal` short-circuits.
|
||||
|
||||
### `interface Schema`
|
||||
|
||||
`Record<string, FieldSchema>` where `FieldSchema` carries `type`, `required`, `format`, `min`, `max`, `minLength`, `maxLength`, `pattern`, `enum`, `items` (recursive), `properties` (recursive), `transforms`, `default`.
|
||||
|
||||
### Format dispatch
|
||||
|
||||
| Format | Regex |
|
||||
|---|---|
|
||||
| `email` | `^[^\s@]+@[^\s@]+\.[^\s@]+$` |
|
||||
| `url` | `^https?://.+` (case-insensitive) |
|
||||
| `uuid` | RFC-4122 8-4-4-4-12 hex |
|
||||
| `date` | `^\d{4}-\d{2}-\d{2}$` |
|
||||
| `datetime` | ISO 8601 prefix `YYYY-MM-DDTHH:MM:SS` |
|
||||
| `ipv4` | dotted-quad shape |
|
||||
| `ipv6` | hex-and-colons (not strictly compliant; pre-filter only) |
|
||||
| `regex` | accepts anything (`.*`) — placeholder slot for future tightening |
|
||||
|
||||
### Transform dispatch
|
||||
|
||||
| Transform | Applies when | Effect |
|
||||
|---|---|---|
|
||||
| `trim` | string | `value.trim()` |
|
||||
| `lowercase` | string | `value.toLowerCase()` |
|
||||
| `uppercase` | string | `value.toUpperCase()` |
|
||||
| `parseNumber` | string | `Number(value)` if not `NaN`; else leave |
|
||||
| `parseDate` | string | `new Date(value)` if valid; else leave |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `validateField` is recursive. Pre-aborted signals are checked at each top-level field boundary; nested recursion inherits the signal via the `ValidatorContext`.
|
||||
- The transformed value is accumulated into a parallel `transformed` tree mirroring the schema shape. Callers consume `result.value` to get the post-transform view of the input.
|
||||
- `format: "regex"` is a placeholder that always matches. The field-level `pattern` option is the way to assert a specific regex; future hardening can replace the format slot with a per-pattern compile-cache.
|
||||
- The validator deliberately does NOT short-circuit on first error — it accumulates every issue across the tree so callers can present them all at once. Tests assert specific codes against the first error for clarity; production callers should iterate `result.errors`.
|
||||
- The CustomValidatorFn return type `boolean | string | Promise<boolean | string>` matches the convention used by zxcvbn and Joi: a string return is treated as a custom error message, otherwise a falsy return generates the default message.
|
||||
- Type matching is strict: `number` rejects `NaN`; `date` / `datetime` accept only `Date` instances with valid `getTime()`. The `parseDate` transform converts ISO strings into `Date` before the type check fires.
|
||||
Reference in New Issue
Block a user