Skip to content
Latest stable: v0.8.0.

Rule Validation

Every adapter validates ChaosConfig from Node before it touches the page. Malformed configs throw ChaosConfigError with structured ValidationIssue[] so failures land in the test runner, not the browser console.

Adapters call validateChaosConfig immediately on entry to injectChaos (and injectSWChaos). Page-side chaosUtils.start also re-validates after deserialization, so the engine never trusts an unvalidated config. Validated configs carry a non-enumerable brand on a global Symbol.for('chaos-maker.validated') so subsequent calls short-circuit when no validation options change.

unknownFields is 'reject' by default. Pass 'warn' or 'ignore' only when you ship configs that intentionally carry extra metadata.

ModeThrows?Returned configConsole output
'reject' (default)yes - ChaosConfigError listing every unknown pathn/an/a
'warn'noparsed + unknowns strippedone aggregated console.warn listing all unknown paths in deterministic sort order
'ignore'noparsed + unknowns strippednone

Both 'warn' and 'ignore' strip unknown fields from the returned config so they cannot leak into the engine, presets, or interceptors.

import { validateChaosConfig } from '@chaos-maker/core';
const validated = validateChaosConfig(config, { unknownFields: 'warn' });

ChaosConfigError.issues is ValidationIssue[]. Issues are deterministically sorted by path then code and the rendered message caps at 50 entries with a ... and N more summary line.

import { injectChaos, ChaosConfigError } from '@chaos-maker/playwright';
try {
await injectChaos(page, config);
} catch (e) {
if (e instanceof ChaosConfigError) {
for (const issue of e.issues) {
console.error(issue.path, issue.code, issue.ruleType, issue.message, issue.expected, issue.received);
}
}
throw e;
}

error.messages returns the v0.4.x string array (path: message) for callers that grep stack traces.

The advanced matchers surface contributes four issue codes alongside the existing set:

  • matcher_not_found - rule references a matcher name not in the matchers registry.
  • matcher_collision - two matchers keys collide after trim().
  • matcher_inline_conflict - rule mixes matcher with inline matcher fields.
  • matcher_cycle - registry entry carries its own matcher field. Reserved for future composition; observable today via untyped configs.

A new RuleType value matcher carries these on the issue. See Advanced matchers for the full surface.

Run additional checks per RuleType. Validators receive each rule as Readonly<unknown>; narrow with a type guard. Mutating the rule arg is undefined behavior - Chaos Maker’s engine deep-clones the canonical config at expansion time, so your mutations may not propagate.

import { validateChaosConfig } from '@chaos-maker/core';
validateChaosConfig(config, {
customValidators: {
'network.failure': (rule, ctx) => {
const r = rule as { probability: number };
if (r.probability > 0.5) {
return [{
path: ctx.path,
code: 'custom',
ruleType: ctx.ruleType,
message: 'probability over 0.5 not allowed in CI',
}];
}
return [];
},
},
});

ChaosConfig.schemaVersion?: 1 is reserved for forward-compat. Omit unless a future major release explicitly bumps it. Unknown values are rejected with code: 'unknown_schema_version' before any other Zod parsing runs, so the failure message is unambiguous.

Pass onDeprecation: (issue) => … to receive a ValidationIssue whenever a deprecated field is set. The registry is empty for this release - the rails exist so the first deprecation is a one-line addition.

@chaos-maker/core ships dist/chaos-config.schema.json plus a sidecar dist/chaos-config.schema.notes.md. Wire the JSON file as a "$schema" reference for IDE autocomplete:

{
"$schema": "./node_modules/@chaos-maker/core/dist/chaos-config.schema.json",
"network": {
"failures": [
{ "urlPattern": "/api", "statusCode": 503, "probability": 1 }
]
}
}

The artifact is a tooling approximation. Several Zod refinements do not translate:

Zod refinementJSON Schema fate
Group-name dedup (superRefine)dropped
Mutually exclusive counting (onNth / everyNth / afterN)weakly approximated
WebSocket close-code range (1000 or 3000-4999)translated as ranges; verify with the runtime validator
WebSocket reason UTF-8 byte length <= 123dropped (JSON Schema string length counts code points)
GraphQL operation RegExp /g / /y flag rejectiondropped
graphqlOperation accepting a RegExp instancerendered as string
Preset-name .trim() deduplicationdropped

Use the JSON Schema for IDE / external linters; use validateChaosConfig for actual gating in CI and at runtime. Treat divergence as expected - fix the runtime, not the artifact.

Validated configs carry a Symbol.for('chaos-maker.validated') brand carrying the validator’s version number. A subsequent validateChaosConfig call with the same input and no options returns the input unchanged (referential equality). Stamping is the final step - a config that fails any layer is never branded.

The brand is non-enumerable, non-writable, and non-configurable, so:

  • JSON.parse(JSON.stringify(cfg)) strips it (re-validation on the page-side / SW boundary).
  • serializeForTransport(cfg) strips it (re-validation in the browser).
  • User code cannot forge a brand.
  • v0.4.x configs validate unchanged - no new fields are required.
  • ChaosConfigError.message is enhanced with code and expected / received. Old test grep helpers can switch to error.messages to keep the v0.4.x string shape.
  • injectChaos(page, config, { validation: { unknownFields: 'warn' } }) is the migration path if your configs carry intentional extra metadata.