import { Request as Request$1, Response as Response$1, NextFunction, RequestHandler } from 'express'; /** * The error model shared by every layer of the @oav validator. All errors form * a tree: every node has a {@link ValidationError.children} array (possibly * empty) regardless of whether it is a leaf or a branch. Applicator keywords * (oneOf, allOf, etc.) and HTTP-level validators produce branch nodes; simple * keywords (type, required, maxLength, etc.) produce leaves. */ /** * A segment of a data or schema path. Property names are strings; array * indices are numbers. Paths are never pre-joined; consumers choose how * to render them. * * @public */ type PathSegment = string | number; /** * A single validation error, always a node in a {@link ValidationError} tree. * * Every error has a `children` array; leaf errors have `children: []`, * branch errors produced by applicator keywords have one child per relevant * subschema. Consumers can traverse without null checks. * * @remarks * The `code` field is a stable identifier (e.g. `"type"`, `"required"`, * `"oneOf"`, `"body"`) suitable for programmatic matching. The `message` * field is free-form human-readable text and SHOULD NOT be pattern-matched. * Machine-readable details live in `params`. * * @public */ interface ValidationError { /** Stable identifier of the keyword or validation layer that produced this error. */ code: string; /** * Path segments pointing at the offending data location. Read-only: the * validator snapshots/freezes these at construction and never mutates them. */ readonly path: readonly PathSegment[]; /** Human-readable description of the failure. */ message: string; /** * Keyword-specific machine-readable details. The shape per `code` is * documented in {@link BuiltInErrorParams}; consumers can use * {@link ErrorParamsFor} to narrow. Read-only after construction. */ readonly params: Readonly>; /** Child errors; always an array, empty for leaf errors. Read-only after construction. */ readonly children: readonly ValidationError[]; } /** * Shared structural types for OpenAPI 3.1 documents, JSON Schema objects, and * HTTP request/response envelopes. These types are intentionally permissive: * they describe the shape {@link @oav/spec} and {@link @oav/validator} * produce/consume, not a fully-checked schema. */ /** * A JSON value, as accepted/emitted by JSON.parse / JSON.stringify. * * @public */ type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue; }; /** * A JSON Schema reference object (`{ "$ref": "..." }`). * * @public */ interface ReferenceObject { $ref: string; summary?: string; description?: string; } /** * A JSON Schema 2020-12 object. This is a loose structural type: fields are * all optional and the compiler validates them. * * @remarks * JSON Schema 2020-12 permits a boolean schema (`true` / `false`) in place of * a schema object. Functions that accept schemas use `SchemaOrBoolean`. * * @public */ interface SchemaObject { $id?: string; $schema?: string; $ref?: string; $anchor?: string; $dynamicRef?: string; $dynamicAnchor?: string; $defs?: Record; $comment?: string; type?: string | string[]; enum?: JsonValue[]; const?: JsonValue; multipleOf?: number; maximum?: number; /** * In JSON Schema 2020-12 (OpenAPI 3.1/3.2): a number, and stands alone. * In OpenAPI 3.0: a boolean that modifies the sibling {@link SchemaObject.maximum}. * The dialect the compiler runs under decides which semantics apply. */ exclusiveMaximum?: number | boolean; minimum?: number; /** * In JSON Schema 2020-12 (OpenAPI 3.1/3.2): a number, and stands alone. * In OpenAPI 3.0: a boolean that modifies the sibling {@link SchemaObject.minimum}. */ exclusiveMinimum?: number | boolean; /** * OpenAPI 3.0 only. Combined with `type`, means "type OR null". * In 3.1+ use `type: ["…", "null"]` instead. Ignored outside the * 3.0 dialect. */ nullable?: boolean; maxLength?: number; minLength?: number; pattern?: string; format?: string; items?: SchemaOrBoolean; prefixItems?: SchemaOrBoolean[]; contains?: SchemaOrBoolean; maxContains?: number; minContains?: number; maxItems?: number; minItems?: number; uniqueItems?: boolean; unevaluatedItems?: SchemaOrBoolean; properties?: Record; patternProperties?: Record; additionalProperties?: SchemaOrBoolean; propertyNames?: SchemaOrBoolean; required?: string[]; maxProperties?: number; minProperties?: number; dependentRequired?: Record; dependentSchemas?: Record; unevaluatedProperties?: SchemaOrBoolean; allOf?: SchemaOrBoolean[]; anyOf?: SchemaOrBoolean[]; oneOf?: SchemaOrBoolean[]; not?: SchemaOrBoolean; if?: SchemaOrBoolean; then?: SchemaOrBoolean; else?: SchemaOrBoolean; title?: string; description?: string; default?: JsonValue; examples?: JsonValue[]; readOnly?: boolean; writeOnly?: boolean; deprecated?: boolean; discriminator?: DiscriminatorObject; [extension: `x-${string}`]: JsonValue | undefined; } /** * A schema value: either a schema object or a boolean (`true` accepts all, * `false` rejects all). * * @public */ type SchemaOrBoolean = SchemaObject | boolean; /** * OpenAPI 3.1 discriminator object. * * @public */ interface DiscriminatorObject { propertyName: string; mapping?: Record; } /** * A single security requirement, shared by top-level and operation-level * `security` fields. Maps scheme name (keyed into * `components.securitySchemes`) to required scopes (empty array for * non-OAuth2 schemes). * * An operation's `security` is an array of these; the operation passes * if **any** one of them is satisfied (OR semantics). Within a single * requirement, **all** listed schemes must be satisfied (AND). * * @public */ type SecurityRequirementObject = Record; /** * OpenAPI `server` entry. * * @public */ interface ServerObject { url: string; description?: string; } /** * OpenAPI `externalDocumentationObject`. * * @public */ interface ExternalDocumentationObject { url: string; description?: string; } /** * OpenAPI `callbackObject`: a map of runtime-expression strings to the * {@link PathItem} that should be invoked when the expression evaluates. * The expression dialect is documented in the OAS spec; oav does not * evaluate it. * * @public */ type CallbackObject = Record; /** * OpenAPI `pathItem`: the collection of operations available at a path. * `query` is new in 3.2 (the HTTP QUERY method for read-side requests * with a body). Older specs just don't set it. * * @public */ interface PathItem { summary?: string; description?: string; get?: OperationObject; put?: OperationObject; post?: OperationObject; delete?: OperationObject; options?: OperationObject; head?: OperationObject; patch?: OperationObject; trace?: OperationObject; /** 3.2+: HTTP QUERY method. */ query?: OperationObject; parameters?: (ParameterObject | ReferenceObject)[]; } /** * OpenAPI `operationObject` (a single method on a path). * * @public */ interface OperationObject { operationId?: string; summary?: string; description?: string; tags?: string[]; parameters?: (ParameterObject | ReferenceObject)[]; requestBody?: RequestBodyObject | ReferenceObject; responses?: Record; /** * Per-operation security requirement. Overrides the document-level * {@link OpenAPIDocument.security}. An explicit empty array opts the * operation out of the top-level requirement. See * {@link SecurityRequirementObject}. */ security?: SecurityRequirementObject[]; /** Per-operation server overrides. Overrides the document-level servers. */ servers?: ServerObject[]; /** Per-operation callbacks, keyed by callback name. */ callbacks?: Record; /** Additional external documentation. */ externalDocs?: ExternalDocumentationObject; deprecated?: boolean; } /** * OpenAPI parameter location. * * @public */ type ParameterLocation = "path" | "query" | "header" | "cookie"; /** * OpenAPI parameter serialization style. * * @public */ type ParameterStyle = "matrix" | "label" | "simple" | "form" | "spaceDelimited" | "pipeDelimited" | "deepObject"; /** * OpenAPI `parameterObject`. * * @public */ interface ParameterObject { name: string; in: ParameterLocation; description?: string; required?: boolean; deprecated?: boolean; /** * Query-only. When `true`, an empty value (`?flag=`) is legitimate and * exempted from schema validation. OpenAPI 3.1 §4.8.12.1. */ allowEmptyValue?: boolean; style?: ParameterStyle; explode?: boolean; allowReserved?: boolean; schema?: SchemaOrBoolean; content?: Record; example?: JsonValue; examples?: Record; } /** * OpenAPI `requestBodyObject`. * * @public */ interface RequestBodyObject { description?: string; content: Record; required?: boolean; } /** * OpenAPI `responseObject`. * * @public */ interface ResponseObject { description?: string; headers?: Record; content?: Record; } /** * OpenAPI `mediaTypeObject`. * * @public */ interface MediaTypeObject { schema?: SchemaOrBoolean; example?: JsonValue; examples?: Record; } /** * OpenAPI `headerObject` (like a parameter, but with `in` fixed to `header`). * * @public */ interface HeaderObject { description?: string; required?: boolean; deprecated?: boolean; style?: ParameterStyle; explode?: boolean; schema?: SchemaOrBoolean; content?: Record; } /** * An abstract HTTP request used by the validator. Values are pre-parsed * where convenient (e.g. `query` is a record, `headers` is a record); raw * strings are still accepted for parameter deserialization. * * @public */ interface HttpRequest { method: string; path: string; query?: Record; headers?: Record; cookies?: Record; contentType?: string; /** * Already-parsed request body. Typed as `unknown` because the shape * depends on the `Content-Type` and the spec: JSON gives a plain * object / array / primitive; multipart bodies arrive as * `{ [fieldname]: string | Uint8Array }`; `application/octet-stream` * as raw bytes. The validator's `format: "binary"` body-schema * bypass accepts `Buffer` / `Uint8Array` for fields declared that way. */ body?: unknown; rawBody?: string | undefined; } /** * An abstract HTTP response used by the validator. * * @public */ interface HttpResponse { status: number; headers?: Record; contentType?: string; /** See {@link HttpRequest.body}. */ body?: unknown; rawBody?: string | undefined; } /** * OpenAPI version detection and dialect identity. * * The OpenAPI Specification has two major variants that affect * validator behavior: * * - **3.0.x**: uses a draft-Wright-00-based JSON Schema dialect with * its own flavours of `type` (single string only), `nullable: true`, * boolean `exclusiveMaximum`/`Minimum`, no sibling keys for `$ref`, * and no `const` / `if`/`then`/`else` / `contains` / * `unevaluatedProperties` / `patternProperties`. * - **3.1.x** and **3.2.x**: use JSON Schema 2020-12. 3.2 is largely * additive over 3.1 (new methods like `QUERY`, tightened rules) and * shares 3.1's dialect. * * This module is intentionally tiny: it classifies a spec into one of * these buckets. Consumers (notably `@oav/validator`) use the bucket * to pick the right vocabulary set at validator construction. * * @packageDocumentation */ /** * The three supported OpenAPI major.minor lines. * * @public */ type OpenAPIVersion = "3.0" | "3.1" | "3.2"; /** * Convert an Express 5 `Request` to oav's framework-agnostic * {@link HttpRequest} shape. Read what's already on `req`; do not * touch the body parser or any async source; bodies are assumed * already-parsed by `express.json()` (or equivalent) upstream of * the validator middleware. * * Header keys are lowercased to match oav's convention. The path is * `req.path` (no query string). `Content-Type` is parsed off the * `content-type` header (charset and boundary preserved verbatim; * the validator strips them itself). * * Cookies are read from `req.cookies` if `cookie-parser` populated * them, otherwise omitted. * * Pairs with sibling `httpRequestFromExpress` in `oav-express4`, * `httpRequestFromFastify` in `oav-fastify`, etc.: same name pattern * as oav's existing {@link httpRequestFromFetch}. The Fetch variant * alone is async and returns `{ httpRequest, body }` (it has to read * the body stream); the framework variants, this one included, are * sync and return a bare `HttpRequest`. * * @public */ declare function httpRequestFromExpress(req: Request$1): HttpRequest; /** * The trio every Express 5 middleware receives. Passed to user-supplied * `onError` callbacks so they can render their own response, call * `next(err)`, or whatever the host app's error contract requires. * * Identical in shape to what an inline middleware would close over; * the type is exported only so users can annotate their callbacks. * * @public */ interface ExpressContext { req: Request$1; res: Response$1; next: NextFunction; } /** * Signature shared by `onError` on every adapter in the family * (`oav-express4`, `oav-express5`, `oav-fastify`). The `Ctx` * parameter is the only thing that varies; same name and shape * everywhere. * * Returning a Promise is supported on every adapter. `oav-express5` * awaits the return; rejected promises propagate through Express 5's * native promise handling to the host's error middleware. * * `errors` is the flat list of failing leaves, regardless of the * validator's `output` mode (a tree validator's result is flattened * before the handler is called). * * @public */ type ErrorHandler = (errors: ValidationError[], ctx: Ctx) => void | Promise; /** * The default `onError` for {@link validateRequests}. Renders the * failing leaves as an RFC 9457 `application/problem+json` response: * status from {@link httpStatusFor}, `Allow` header from * {@link allowHeaderFor} on a 405, body from {@link toProblemDetails} * (whose `detail` is the first failing leaf). * * Exported standalone for two cases: * * 1. You want oav's rendering as the fallback in your own * middleware: call this directly when you don't want to handle * the error yourself. * 2. You want a slightly different renderer: use this as the * starting point and adjust (e.g. swap the body, override the * status, add headers). * * @public */ declare function renderProblemDetails(errors: ValidationError[], ctx: ExpressContext): void; /** * A single declared operation, surfaced by {@link Router.routes}. * `method` is uppercased (`"GET"`); `pathPattern` is the template * exactly as declared in the spec (`"/pets/{id}"`). * * Lists operations actually declared on each `PathItem`; the implicit * HEAD that any GET resource also answers (RFC 9110 §9.3.2) is a * match-time fallback, not a declaration, so it is not enumerated here. * * @public */ interface RouteInfo { /** Uppercased HTTP method (e.g. `"GET"`). */ method: string; /** Path template as declared in the spec (e.g. `"/pets/{id}"`). */ pathPattern: string; } /** * A single spec-hygiene finding from {@link lintResolvedSpec}. * * Findings are reported, never fatal: the spec is structurally valid, the * shape just looks like an authoring mistake (declared but unused, * referenced but undeclared). * * @public */ interface SpecHygieneIssue { /** * - `"unused-component"`: a `components.{schemas,parameters,requestBodies,responses,headers,securitySchemes}` * entry that no operation reaches. * - `"unused-tag"`: a `tags[]` entry whose name doesn't appear in any * operation's `tags` array. * - `"unreachable-defs"`: a `$defs/` entry inside a schema that no * `$ref` in the same schema points to. * - `"path-param-undeclared"`: a `{name}` placeholder in a path template * with no matching `parameters: [{ in: "path", name }]` declaration on * the operation or its path-item. * - `"path-param-unused"`: a `parameters: [{ in: "path", name }]` * declaration whose name doesn't appear as a placeholder in the path * template. */ code: "unused-component" | "unused-tag" | "unreachable-defs" | "path-param-undeclared" | "path-param-unused"; /** RFC 6901 JSON Pointer to the offending node in the resolved document. */ pointer: string; /** Human-readable explanation. */ message: string; } /** * Result of a default-mode `validate()` call: a flat list of leaf * errors under `errors`. This is the v3 default (`output: "flat"`), * shaped to match ajv's zero-config result. Every failing leaf keyword * (`type`, `required`, `minimum`, …) is its own record, plus a childless * marker leaf for each failed composition keyword (`anyOf` / `oneOf`); * no `"schema"` branch wrappers. Each record is a {@link ValidationError} * with an empty `children`, so the `@oav/core` renderers consume it * unchanged. For the nested error tree, compile with `output: "tree"` * and see {@link TreeValidationResult}. * * A discriminated union on `valid`: a successful result carries no error * fields; a failing result always carries both `errors` (the flat leaf * list, non-empty) and `truncated`. Narrow on `result.valid` to reach * the error fields. The narrowing also makes a mistaken `result.error` * access (the tree-mode field) a compile error rather than a silent * `undefined`. * * @public */ type ValidationResult = { valid: true; } | { valid: false; /** The flat list of leaf errors. Always non-empty when `!valid`. */ errors: ValidationError[]; /** * `true` when the configured `maxErrors` cap was reached, meaning * the list may be incomplete: validation returns as soon as the * budget drains, without checking the remaining keywords. Under * the v3 default (`maxErrors: 1`) every failing result therefore * reports `truncated: true`. `false` means the cap was never hit * and the list is complete. */ truncated: boolean; }; /** * Result of a tree-mode (`output: "tree"`) `validate()` call: a single * nested {@link ValidationError} tree under `error`, with `"schema"` * branch nodes mirroring the schema's composition structure. The opt-in * counterpart to the flat {@link ValidationResult} default. The HTTP * validator in `@oav/validator` compiles in this mode so it can nest * per-location subtrees (`body`, `query`, …) under one root. * * A discriminated union on `valid`: a successful result carries no error * fields; a failing result always carries both `error` (the tree root) * and `truncated`. * * @public */ type TreeValidationResult = { valid: true; } | { valid: false; /** The root of the nested error tree. Always present when `!valid`. */ error: ValidationError; /** * `true` when at least one error was dropped because the configured * `maxErrors` cap was hit; `false` when the tree is complete. */ truncated: boolean; }; /** * A single finding from strict-mode schema linting (see * {@link CompileOptions.strict}). * * @public */ interface StrictIssue { /** * - `"partial-feature"`: the schema uses a keyword flagged as * partially-implemented (e.g. `$dynamicRef` without runtime * dynamic-scope rebinding). Compile still succeeds; the emitted * validator's semantics for this keyword may not match the spec. * - `"unknown-keyword"`: the schema declares a key that's not in the * active dialect, not an `x-*` extension, and not a standard * `$`-prefixed metadata key. Likely a typo. * - `"silent-rewrite/ref-siblings-oas30"`: under OAS 3.0 * (`refSuppressesSiblings: true`), a schema with `$ref` plus * sibling keywords other than `description` / `summary`. The * siblings are silently dropped; the validator runs the `$ref` * target only. * - `"silent-rewrite/required-not-in-properties"`: a `required` * array names a key that doesn't appear in the same schema's * `properties`. Almost always a typo. Conservative: skipped on * schemas that mix `required` with `$ref` / `allOf` / `oneOf` / * `anyOf` (the named key could be contributed by a composed * branch). * - `"silent-rewrite/redundant-composition-branches"`: an `oneOf` / * `anyOf` array where two or more branches are structurally * identical after compile-time rewrites (notably the validator's * `format: binary` opaque-body bypass). The compiled validator's * semantics differ from the source spec: identical branches * collapse, changing the match-count behavior. */ code: "partial-feature" | "unknown-keyword" | "silent-rewrite/ref-siblings-oas30" | "silent-rewrite/required-not-in-properties" | "silent-rewrite/redundant-composition-branches"; /** The offending keyword / key name as written in the schema. */ keyword: string; /** Dotted path from the root schema to the subschema holding the key. */ path: string; /** Human-readable explanation. */ message: string; } /** * Bridge between Web Standards {@link Request} / {@link Response} * objects and the validator's framework-agnostic * {@link HttpRequest} / {@link HttpResponse} shapes. Used by * `validateFetchRequest` / `validateFetchResponse` to support * route-level handlers in Next.js App Router, Hono, Bun, Deno, and * any other runtime whose HTTP primitives are `Request` / `Response`. * * The content-type dispatcher recognizes JSON (`application/json` and * `*+json`), URL-encoded forms, multipart/form-data, and text/*. For * anything else, the raw bytes come through as a `Uint8Array`; the * validator's `format: "binary"` opaque-body bypass accepts any value * when the body schema declares it that way. * * @packageDocumentation */ /** * Options shared by the `validateFetchRequest` family and * {@link httpRequestFromFetch}. Currently just the `readBody` * override. * * @public */ interface FetchRequestOptions { /** * Replace the default body reader with a user-supplied function. * Useful for streaming large uploads to disk without buffering, for * plugging in a streaming multipart parser (busboy, formidable, * `@mjackson/multipart-parser`), or for handling a content type the * default dispatcher doesn't know about. * * The callback receives the original `Request` with its body stream * intact. Return whatever shape the spec's `requestBody` schema * expects; `format: "binary"` fields pass through the validator * unchanged, so opaque placeholders (a temp-file path, a Buffer * handle, etc.) are valid. * * If you want default behavior for most content types and custom * behavior for one or two, import {@link readBodyFromFetch} and * delegate to it from inside your callback. * * @example * ```ts * await validator.validateFetchRequest(request, { * readBody: async (req) => { * if (req.headers.get("content-type")?.startsWith("multipart/")) { * const fields = await streamMultipartToDisk(req); // your parser * return { file: fields.file.path, caption: fields.caption }; * } * return readBodyFromFetch(req); * }, * }); * ``` */ readBody?: (request: Request) => Promise; } /** * The HTTP validator (flat output, the default). `validateRequest` / * `validateResponse` return a {@link @aahoughton/oav/schema!ValidationResult}: * `{ valid: true }` or `{ valid: false, errors, truncated }` with a flat * list of leaf errors. Compile with `output: "tree"` for a * {@link TreeValidator} (nested {@link ValidationError} tree) or * `output: "predicate"` for a {@link PredicateValidator} (bare boolean). * * - **Per-call HTTP validation**: {@link Validator.validateRequest}, * {@link Validator.validateResponse}. * - **Web Standards convenience**: {@link Validator.validateFetchRequest}, * {@link Validator.validateFetchResponse}. Wrap the per-call methods * with body-parsing for `Request` / `Response` consumers (Next.js, * Hono, Bun, Deno). * - **Spec introspection**: {@link Validator.getOperation}, * {@link Validator.detectedVersion}. * - **Construction-time output**: {@link Validator.warnings}, * {@link Validator.specHygieneIssues}. * - **Live observability**: {@link Validator.stats}. * * @public */ /** * The routing verdict for a method + path, with nothing compiled or * validated. Pairs with {@link Validator.getOperation}: `getOperation` * hands back the resolved operation on a clean match, while `matchRoute` * reports the verdict and keeps the 404-vs-405 distinction that * `getOperation` collapses to `null`. * * - `"match"`: the path matched and the method is declared (counting the * implicit HEAD a GET resource answers, RFC 9110 §9.3.2). * - `"method-not-allowed"`: the path matched but the method isn't * declared on it; `allowed` is the union of declared methods, uppercased, * suitable for an RFC 9110 `Allow` header. * - `"no-match"`: no path template matched at all. * * @public */ type RouteMatchResult = { readonly kind: "match"; readonly pathPattern: string; } | { readonly kind: "method-not-allowed"; readonly pathPattern: string; readonly allowed: readonly string[]; } | { readonly kind: "no-match"; }; interface Validator { /** * Validate one HTTP request against the spec. Returns `{ valid: true }` * when the request matches the operation declared at its method + path * (parameters, headers, cookies, body, and content type); otherwise * `{ valid: false, errors, truncated }` with a flat list of leaf * errors. * * Each error's `path` is prefixed with its HTTP location: `["body", * …]`, `["query", name]`, `["header", name]`, `["cookie", name]`, * `["path", name]`, or `["security"]`. (The matching error `code` is * `query-param` / `header-param` / `cookie-param` / `path-param`; the * path segment drops the `-param` suffix.) Route and method * mismatches surface as `route` / `method` leaves; see * {@link httpStatusFor} for the canonical status mapping. * * `truncated` is `true` when the `maxErrors` cap (default 1) was * reached, so more problems may exist; raise `maxErrors` to collect * them. * * Does not mutate `req`. Synchronous: parameter deserialization, * content-type matching, and schema validation all run inline. * * Paths the spec doesn't declare are treated according to * {@link ValidatorOptions.ignoreUndocumented} and * {@link ValidatorOptions.ignorePaths}: by default an undeclared * path returns a `route` error; configure to bypass the validator * entirely. * * @see {@link Validator.validateResponse} for the response-side pair. * @see {@link Validator.validateFetchRequest} for the Web Standards convenience wrapper. * @see {@link Validator.getOperation} to look up the matched operation without validating. */ validateRequest(req: HttpRequest): ValidationResult; /** * Validate one HTTP response against the spec, given the request it * answers. Returns `{ valid: true }` when the response status, content * type, headers, and body all match the responses declared on the * operation `req` resolves to; otherwise `{ valid: false, errors, * truncated }`. * * Each error's `path` is prefixed with `["body", …]`, `["header", * name]`, or `["status"]`. The `req` argument is used only to locate * the operation; its body isn't read. * * Response-body schemas compile lazily on first use per `(status, * mediaType)` pairing; {@link ValidatorStats.responseBodiesCompiled} * counts how many have been compiled since construction. * * Does not mutate `req` or `res`. Synchronous, like * {@link Validator.validateRequest}. * * @see {@link Validator.validateRequest} for the request-side pair. * @see {@link Validator.validateFetchResponse} for the Web Standards convenience wrapper. */ validateResponse(req: HttpRequest, res: HttpResponse): ValidationResult; /** * Parse a Web Standards {@link Request} and validate it in one call. * Convenient for route handlers in frameworks that expose `Request` * directly (Next.js App Router, Hono, Bun, Deno) so callers don't * repeat ~10 lines of URL / header / body extraction per route. * * Returns a discriminated union. On success, `body` is the parsed * request body, narrowed to the generic type the caller supplies * (validation has already confirmed the shape, so the cast is safe * in practice). On failure, `errors` / `truncated` are the same * fields `validateRequest` would return. * * Body parsing recognizes `application/json` (and `*+json`), * `application/x-www-form-urlencoded`, `multipart/form-data` * (file fields come through as `Uint8Array`), and `text/*`. Any * other content type is read as raw bytes; the spec's * `format: "binary"` opaque-body bypass accepts it. Override the * default reader per-call via {@link FetchRequestOptions.readBody} * for streaming, multer-style parsing, or other bespoke handling. * * @param request - The incoming Web Standards request. * @param options - Optional body-reader override. * @typeParam T - Declared shape of the parsed body on success. * * @example * ```ts * export async function POST(request: Request) { * const r = await validator.validateFetchRequest(request); * if (!r.ok) return problemResponse(r.errors); * // r.body is typed as CreatePet * } * ``` */ validateFetchRequest(request: Request, options?: FetchRequestOptions): Promise<{ ok: true; body: T; } | { ok: false; errors: ValidationError[]; truncated: boolean; }>; /** * Validate a Web Standards {@link Response} against the operation * the {@link Request} resolves to. Mirrors * {@link validateFetchRequest} for the response side; useful when * you're calling an upstream API and want to confirm its response * matches the spec, or when you're testing your own handler's * output against its OpenAPI contract. * * Both messages are consumed by this call. The `request` is used * only to match the route, method, and path; its body isn't * read (and `request.clone()` will give you back a fresh one if * you need it after the fact). * * @param request - The Web Standards request that triggered `response`. * @param response - The Web Standards response to validate. * @typeParam T - Declared shape of the parsed response body on success. * * @example * ```ts * const response = await fetch(upstreamUrl, init); * const r = await validator.validateFetchResponse(req, response); * if (!r.ok) log.warn("upstream returned malformed response", r.errors); * ``` */ validateFetchResponse(request: Request, response: Response): Promise<{ ok: true; body: T; } | { ok: false; errors: ValidationError[]; truncated: boolean; }>; /** * Look up the effective operation declaration for a method + path. * Returns the resolved (`$ref`s followed) and overlay-applied * {@link OperationObject}, the matched path pattern, and the * enclosing {@link PathItem}. Returns `null` when no operation * matches (either the path doesn't match any template or the * method isn't declared on it). * * Startup-time introspection, not a validation step: the spec is * frozen at `createValidator` time, so this is safe to call once * during application init and cache the result. Callers typically * use it to derive middleware configuration (multer limits, * accepted content types, required headers) from the same source * of truth the validator uses. * * Uses the same per-operation cache the validation path uses; * repeated calls are O(route-match) with no extra compilation. * * @example * ```ts * const info = validator.getOperation({ method: "POST", path: "/uploads" }); * const mediaTypes = Object.keys(info?.operation.requestBody?.content ?? {}); * ``` */ getOperation(req: { method: string; path: string; }): { pathPattern: string; pathItem: PathItem; operation: OperationObject; } | null; /** * Resolve a method + path to its routing verdict without compiling or * validating. Returns a {@link RouteMatchResult}: `"match"`, * `"method-not-allowed"` (with the `allowed` method set), or * `"no-match"`. * * Pairs with {@link Validator.getOperation}, which returns `null` for * both the 405 and 404 cases; `matchRoute` keeps them distinct, so a * caller can map a wrong-method hit to HTTP 405 rather than 404. * {@link combineValidators} uses it to dispatch across members while * preserving method-not-allowed semantics that `getOperation` alone * can't express. * * Startup-cheap: a single route-table scan, no schema compilation. * * @see {@link Validator.getOperation} for the operation object on a match. */ matchRoute(req: { method: string; path: string; }): RouteMatchResult; /** * Every operation the spec declares, as `{ method, pathPattern }` * pairs in route-specificity order (more literal segments first). * `method` is uppercased (`"GET"`); `pathPattern` is the template as * declared (`"/pets/{id}"`). The implicit HEAD that a GET resource * also answers (RFC 9110 §9.3.2) is a match-time fallback, not a * declaration, so it is not listed. * * Startup-time introspection over the same route table the validator * matches against, frozen at `createValidator` time. Useful for * mounting per-route middleware, generating coverage reports, or * asserting two specs are route-disjoint before * {@link combineValidators} stacks them. */ readonly routes: readonly RouteInfo[]; /** * The OpenAPI version detected from the spec's `openapi` field, or * `undefined` when the field was missing/malformed and the validator * fell back to its default dialect (see * {@link ValidatorOptions.onUnknownVersion}). */ readonly detectedVersion: OpenAPIVersion | undefined; /** * The output shape this validator was built with (see * {@link ValidatorOptions.output}): `"flat"` (default), `"tree"`, or * `"predicate"`. Lets consumers (e.g. the framework adapters) branch * on the result shape without a trial call. */ readonly output: "flat" | "tree" | "predicate"; /** * Warnings collected during `createValidator`. Populated when * `onUnknownVersion: "warn"` fires, or when the `dialect` escape * hatch suppresses a category error that would otherwise throw * (missing `openapi` field, wrong major). Empty when neither applies. * * The library never writes to `process.stderr` or `console`; this * array is the library's only record of such events. Callers that * want live output pass {@link ValidatorOptions.warn}; the CLI * wrapper does this. * * Frozen after `createValidator` returns; no post-construction * writes happen. */ readonly warnings: readonly string[]; /** * Spec-hygiene findings from {@link lintResolvedSpec}, populated when * {@link ValidatorOptions.lint} is `true`. Empty otherwise. Frozen * after `createValidator` returns. * * Different from {@link ValidatorStats.strictIssues}, which lints * compiled schemas; this one lints the OpenAPI document itself * (unused components, dead path parameters, unreachable `$defs`). */ readonly specHygieneIssues: readonly SpecHygieneIssue[]; /** * Runtime observability for compile-time-specialization optimizations. * The counters live on the validator, not inside a ValidationError * tree, so tests can assert on the optimization directly rather than * through indirect signals (throwing test schemas, source grepping). */ readonly stats: ValidatorStats; } /** The four validation methods whose return type tracks `output`. */ type OutputDependentMethods = "validateRequest" | "validateResponse" | "validateFetchRequest" | "validateFetchResponse"; /** * The HTTP validator built with `output: "tree"`. Identical to * {@link Validator} except `validateRequest` / `validateResponse` return * a {@link @aahoughton/oav/schema!TreeValidationResult} (a nested * {@link ValidationError} tree under `error`) instead of the flat default. * * @public */ interface TreeValidator extends Omit { validateRequest(req: HttpRequest): TreeValidationResult; validateResponse(req: HttpRequest, res: HttpResponse): TreeValidationResult; validateFetchRequest(request: Request, options?: FetchRequestOptions): Promise<{ ok: true; body: T; } | { ok: false; error: ValidationError; truncated: boolean; }>; validateFetchResponse(request: Request, response: Response): Promise<{ ok: true; body: T; } | { ok: false; error: ValidationError; truncated: boolean; }>; } /** * Live counters attached to an {@link Validator}. * * @public */ interface ValidatorStats { /** * Number of response-body schemas that have been lazily compiled since * the validator was constructed. Starts at `0`; bumps by one each time * a `(status, mediaType)` pairing is seen by `validateResponse` for * the first time. A spec's response bodies are NOT compiled at * `createValidator` time, so on a fresh validator this is always `0`. */ responseBodiesCompiled: number; /** * Live array of strict-mode issues surfaced by * {@link ValidatorOptions.strict}. Grows as schemas compile (request * / path / header / query schemas at construction; response-body * schemas lazily on first use). An empty array when `strict: "off"` * or when the linter found nothing to flag. * * Schema paths are the full path inside each compiled schema, not * HTTP-frame-prefixed; the linter runs over raw JSON Schema, not * OpenAPI. */ strictIssues: readonly StrictIssue[]; } /** * Options for {@link validateRequests}. The same option shape is * used by every adapter in the family (`oav-express4`, * `oav-fastify`); only the framework-typed argument differs. * * @public */ interface ValidateRequestsOptions { /** * Custom extractor from the Express request to oav's * {@link HttpRequest} shape. Default: {@link httpRequestFromExpress}. * Override when your stack populates non-standard fields the * validator needs (e.g. a proxy that puts the verified body on * `req.verifiedBody`). */ toHttpRequest?: (req: Request$1) => HttpRequest; /** * Called when {@link Validator.validateRequest} returns an error. * Default: {@link renderProblemDetails}, which writes an RFC 9457 * `application/problem+json` response with status from * {@link httpStatusFor}. * * Pass your own to render a custom envelope, map to a different * status, or call `ctx.next(err)` to delegate to the host's error * middleware. May be async; the middleware awaits it. Thrown errors * and rejected promises propagate to Express 5's error middleware * automatically (no try/catch wrapper needed). * * The middleware does not call `next()` after `onError` returns; * the callback owns the response. */ onError?: ErrorHandler; } /** * Build an Express 5 request-handler that runs every request through * a {@link Validator} and either calls `next()` (valid) or invokes * the configured `onError` (invalid). The default `onError` writes * an RFC 9457 problem-details response. * * Plural (`validateRequests`, not `validateRequest`) because the * middleware intercepts every request; the singular form is the * Validator's own per-call method. * * Express 5 is promise-native: this middleware is `async`, and * thrown exceptions / rejected promises propagate to the host's * error middleware automatically. No try/catch wrapper needed. * * Pairs with sibling `validateRequests` in `oav-express4` / * `oav-fastify`. Same factory shape across the family. * * @example * ```ts * import express from "express"; * import { validateRequests } from "@aahoughton/oav-express5"; * * const app = express(); * app.use(express.json()); * app.use(validateRequests(validator)); * ``` * * @public */ declare function validateRequests(validator: Validator | TreeValidator, options?: ValidateRequestsOptions): RequestHandler; /** * Options for {@link validateResponses}. The same option shape is used * by every adapter in the family (`oav-express4`, `oav-fastify`); only * the framework-typed argument differs. * * @public */ interface ValidateResponsesOptions { /** * Custom extractor from the Express request to oav's * {@link HttpRequest} shape, used only to match the operation the * response answers (method + path). Default: * {@link httpRequestFromExpress}. */ toHttpRequest?: (req: Request$1) => HttpRequest; /** * Predicate gating which response statuses are validated. Return * `true` to validate, `false` to pass the response through untouched. * Default: validate every status. Use it to scope validation (e.g. * `(s) => s < 500` to skip server-error pages, or `(s) => s < 300` for * success-only). A response whose status the spec doesn't declare is a * finding (the validator emits a `status` leaf); narrow this predicate * to ignore statuses you don't want checked. */ statuses?: (status: number) => boolean; /** * Called when {@link Validator.validateResponse} returns an error. * Default: throw a {@link ResponseValidationError}, which the adapter * forwards to the host's error middleware via `next(err)` (a failing * response is a server bug, so it surfaces as a 500 rather than being * rendered here). * * The callback decides what happens next: * - **Throw** (the default) to forward to the host error handler. * - **Return normally** to let the original (invalid) response body go * out anyway. This is the log-and-continue path: log the finding and * return; the body is sent unchanged. * - **Render your own response** (`ctx.res.status(...).json(...)`) and * return; once the response is sent the adapter does not also send * the original. * * May be async; the original body is sent (or not) after it settles. */ onError?: ErrorHandler; } /** * Build an Express 5 middleware that validates outgoing responses against * the spec. It wraps `res.send`, the point most responses pass through * (`res.json` stringifies and re-dispatches through it; `res.sendStatus` * sends its status text through it too). Response status and declared * headers are checked for every wrapped send, regardless of media type; * the body is parsed and validated only when it is a parseable JSON string * (the exact wire body, with `toJSON` methods, the app's `json replacer` / * `json spaces` settings, and `Date` serialization applied first). A pure * `res.end()` (and `res.redirect`, which uses it) bypasses `res.send` and * is not covered. On failure the configured `onError` runs (default: * throw, forwarded to the host error handler as a 500). * * Opt-in and explicit: mount it only where you want response checking * (typically on in development, off in production), and after * `validateRequests`. What is and isn't validated, ordering caveats, * cost, and failure-mode recipes are in the package README; the core * `validateResponse` stays a pure function that reads its arguments. * * @example * ```ts * import { validateRequests, validateResponses } from "@aahoughton/oav-express5"; * * app.use(validateRequests(validator)); * if (process.env.NODE_ENV !== "production") { * app.use(validateResponses(validator)); * } * ``` * * @public */ declare function validateResponses(validator: Validator | TreeValidator, options?: ValidateResponsesOptions): RequestHandler; /** * Thrown by the default {@link validateResponses} `onError` when an * outgoing response fails validation. A response-validation failure is * a server bug, so the default routes it through the host's error * pipeline (Express error middleware) rather than rendering a body * here. `statusCode` is `500` so the default error handler answers 500; * `errors` carries the failing leaves for a custom handler to inspect. * * @public */ declare class ResponseValidationError extends Error { /** The failing leaves from {@link Validator.validateResponse}. */ readonly errors: ValidationError[]; /** Surfaced to the host error handler so the client sees a 500. */ readonly statusCode = 500; constructor(errors: ValidationError[]); } export { type ErrorHandler, type ExpressContext, type HttpRequest, ResponseValidationError, type ValidateRequestsOptions, type ValidateResponsesOptions, type ValidationError, type Validator, httpRequestFromExpress, renderProblemDetails, validateRequests, validateResponses };