Files
skills/docs/foundation/flow/cbor/src/cddl/validate.md
2026-05-29 08:20:56 +01:00

3.3 KiB

validate

Purpose

Walk a decoded CBOR value against a parsed CDDL schema and report the first mismatch (with a JSON-pointer-like path). The validator is the run-time counterpart to the parser — given a typed AST and a value, return { valid, errors } describing whether the value conforms.

Features

  • All primitive prelude types: uint, nint, int, bstr, tstr, bool, nil, null, undefined, any, number, text, bytes, float / float16 / float32 / float64, tdate (RFC 3339 string), time (number or bigint epoch).
  • Literal-value matching: text, byte, integer (signed and unsigned), bool, null, undefined.
  • Range types: inclusive (..) and exclusive (...).
  • Choice: tries each alternative in isolation; first match wins.
  • Arrays: positional exact-occurrence entries, optional, one-or-more, zero-or-more, and M*N ranges. Extra entries past the schema are rejected.
  • Maps: text and integer keys; required (exact) and optional (?) keys; reports the missing key in error messages.
  • Tagged items via #6.<n>(T) parse syntax → typed { kind: "tagged" } AST.
  • Named references: walks name = T rules transparently; entry rule is selectable via options.rule.
  • Control operators: .size <int|range> (string or bytes length), .regexp "pattern" (RegExp match on text), .cbor T (left side is bytes). Unknown control operators pass through (treated as informational rather than fail-closed).

Usage: Basic Example

import { validateCbor } from "@suaveplan/cbor/cddl";

const r = validateCbor({ iss: "alice", exp: 1700000000 }, `
  cwt = { iss: tstr, ? exp: int }
`);
console.log(r.valid); // true

Usage: Advanced Example

import { parseCddl, validateCbor } from "@suaveplan/cbor/cddl";

const schema = parseCddl(`
  message = #6.18(cose-sign1)
  cose-sign1 = [ bstr, { * any => any }, bstr / nil, bstr ]
`);

import { decode } from "@suaveplan/cbor";
import { Tagged } from "@suaveplan/cbor/tags";

const value = decode(wireBytes, { tagHandling: "raw" });
const r = validateCbor(value, schema, { rule: "message" });
if (!r.valid) {
  for (const err of r.errors) {
    console.error(`at ${err.path.join(".")}: expected ${err.expected}, got ${err.got}`);
  }
}

API Reference

  • validateCbor(value: unknown, schema: CddlAst | string, options?: { rule?: string }): CddlValidationResult
  • interface CddlValidationResult { valid: boolean; errors: CddlValidationError[] }
  • interface CddlValidationError { path: (string | number)[]; expected: string; got: string }

Throws CborError(CDDL_RULE_NOT_FOUND) if options.rule names a rule that doesn't exist; throws CborError(CDDL_UNDEFINED_REFERENCE) if a rule body references an undefined name.

Implementation Notes

The walk is a recursive AST switch dispatching on type.kind. Choice validation runs each alternative against a fresh sub-context — the first alternative whose walk produces zero errors wins. Map validation accepts both Map instances and plain objects; the latter is wrapped via Object.entries for uniform key lookup. Array entries are matched greedily within their occurrence-range bound — no backtracking — which is correct for the schemas this package targets but a future extension could add limited backtracking for more permissive schemas.