Skip to content

Replace vendored Gavel with an in-house TypeScript validator #59

Description

@dalberola

Summary

Replace the vendored, unmaintained Gavel.js bundle (packages/dredd/lib/vendor/gavel.js, a ~10.3k-line minified build artifact of gavel@9.1.5) with a small in-house TypeScript validator, then delete the bundle.

This completes the direction already documented in packages/dredd/lib/vendor/README.md ("incrementally replace Gavel with in-house ajv-based validation and eventually drop this bundle") and fits the TS migration tracked in #28.

Why

  • Upstream Gavel is unmaintained (last release 2021-12-09) and pulls in abandoned @apiaryio deps; it is vendored as a frozen, minified artifact that cannot be refactored in place.
  • Dredd has already replaced most of Gavel's job: response bodies that carry a JSON Schema (both OpenAPI 3.1 and 3.0, the latter now stamped with the OAS-3.1 dialect in dredd-transactions/compile/openapi30Schema.js:160) are validated by the in-house ajv path validateBodySchemaWithAjv (lib/TransactionRunner.js:154), not Gavel.
  • A native TS module removes the /** @type {any} */ (gavel) casts at the call site (lib/TransactionRunner.js:954) and collapses today's split validation (ajv for schema'd bodies + Gavel for everything else) into one coherent module.

Current consumption contract (narrow)

Gavel is imported in exactly one place — import gavel from './vendor/gavel' (lib/TransactionRunner.js:7) — and called once:

gavel.validate(expected, real) // -> { valid: boolean, fields: { statusCode?, headers?, body? } }
  • Dredd itself reads only result.valid and result.fields[name].errors[].message (lib/TransactionRunner.js:994-1035).
  • The full structured result is forwarded opaquely (JSON-serialized) to the Apiary remote reporter (lib/reporters/ApiaryReporter.js:411); nothing else in-repo destructures the rich shape.
  • The target field shape already exists in-house: createJSONSchemaValidationResult{ valid, kind, values, errors: [{ message, location: { pointer, property } }] } (lib/TransactionRunner.js:122).

Residual surface to reimplement

With schema'd bodies already on ajv, Gavel is only still invoked (the else branch at lib/TransactionRunner.js:976) for:

  1. statusCode — exact equality.
  2. headers — expected ⊆ real, case-insensitive, content-type aware.
  3. body — only when there is no ajv-dialect schema (API Blueprint inputs, example-only / text bodies, schemaless JSON).

Main risk: behavioral parity on exact strings

Integration/unit tests assert Gavel's exact error messages and field order. A drop-in replacement must reproduce these (or the tests are updated deliberately):

  • Expected status code '%s', but got '%s'.test/integration/response-test.js:261
  • Actual and expected data do not match.test/integration/response-test.js:300
  • Value of the ‘content-type’ must be application/json. / No validator found for real data media type 'text/plain' and expected data media type 'application/json'. / Real and expected data does not match.test/unit/reporters/ApiaryReporter-test.js:51
  • Logged field order is asserted exactly as ['headers', 'body', 'statusCode']lib/TransactionRunner.js:1027.

Proposed plan (prioritized, incremental — each step ships green)

  • P0 — Lock the contract. Enumerate every Gavel message string / kind / field-shape the test suite pins (grep + run suite against a stub). Capture as a parity checklist. No behavior change.
  • P1 — Scaffold lib/validation/ (TS) with the public surface validate(expected, real) returning the existing field-result shape; wire it behind the existing call site but keep delegating to Gavel (no-op switch).
  • P2 — statusCode validator (smallest, fully covered by tests). Swap it in; keep Gavel for headers+body.
  • P3 — headers validator (subset match, case-insensitive, content-type/charset handling + the content-type message).
  • P4 — schemaless/text/example body validator (the only non-trivial piece; replicate gavel@9.1.5 body behavior — example→generated-schema and the "no validator found" media-type path).
  • P5 — Cut over validateTransaction to the in-house module for all paths; unify with validateBodySchemaWithAjv.
  • P6 — Delete lib/vendor/gavel.js + gavel-license.js, update lib/vendor/README.md, drop coverage excludes.

Acceptance criteria

  • import gavel from './vendor/gavel' removed; bundle files deleted.
  • Full yarn test (unit + integration + smoke) green with no test weakened solely to pass.
  • No new runtime dependency on any @apiaryio package.
  • Apiary reporter payload (validationResult) shape unchanged for consumers.

Open questions to resolve during P0/P4

  • Exact semantics of Gavel's schemaless-JSON body validation (example → generated schema, comparison strictness) — to be read from gavel@9.1.5 source before reimplementing.
  • Whether any non-JSON (text) body comparison paths are exercised by real fixtures, or only by the Apiary unit test.

Relates to #28.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions