Switch all API validation to Zod schemas, change tests and validation#384
Open
rcantin-w wants to merge 9 commits into
Open
Switch all API validation to Zod schemas, change tests and validation#384rcantin-w wants to merge 9 commits into
rcantin-w wants to merge 9 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR migrates Content API query validation to Zod schemas and introduces an OpenAPI generator that imports those schemas directly, aligning runtime validation with automated spec generation.
Changes:
- Replaces bespoke query-param validators with shared Zod schemas/helpers and per-controller
*QuerySchemaexports. - Updates list controllers (articles/events/all) and validation unit tests to use Zod parsing and errors.
- Adds an OpenAPI 3.1 generator script using
@asteasolutions/zod-to-openapi, plus dependency updates.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
yarn.lock |
Locks new dependencies (Zod, zod-to-openapi, yaml) and transitive updates. |
common/services/init-apm.ts |
Updates Zod import style for v4 ({ z }). |
api/test/validation.test.ts |
Reworks validation tests around Zod parsing and ZodError assertions. |
api/src/controllers/validation.ts |
Introduces shared Zod validation helpers/schemas (enums, IDs, dates, pagination, work IDs). |
api/src/controllers/events.ts |
Adds EventsQuerySchema and migrates query validation to schema-based parsing. |
api/src/controllers/error.ts |
Adds centralized ZodError → 400 ErrorResponse handling. |
api/src/controllers/articles.ts |
Adds ArticlesQuerySchema and migrates validation to schema-based parsing. |
api/src/controllers/addressables.ts |
Adds AddressablesQuerySchema and migrates validation to schema-based parsing. |
api/scripts/generate-openapi.ts |
New script to generate OpenAPI 3.1 YAML from Zod schemas. |
api/package.json |
Adds zod dependency and dev deps for OpenAPI generation (zod-to-openapi, yaml). |
AGENTS.md |
Documents the new Zod-based validation + OpenAPI generation workflow and repo conventions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
rcantin-w
commented
Jun 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This migration automates OpenAPI spec generation so
reference/content.yamlinwellcomecollection/developers.wellcomecollection.orgstays in sync with the code. Three main changes:api/**changes onmainWhat Changed
1. Validation Layer (
api/src/controllers/validation.ts)Before: Custom
QueryValidatorConfig+ imperative validatorsAfter: Zod schemas replacing the custom validators
2. Events Controller (
api/src/controllers/events.ts)Before: Two-pass validation
After: Single-pass + focused helper
Key point:
transformFormat()handles the complex logic (alias mapping + negation):format=workshop→ Prismic IDformat=!exhibitions→ excludeFormat clauseThis consolidation removes
paramsValidator,EventsSortSchema, three dedicated validators, and the unusedQueryParamstype.3. OpenAPI Generator (
api/scripts/generate-openapi.ts)Before: Hand-written parameter schemas duplicating the controller logic
After: Schema-driven — controller schemas imported directly
Impact: Adding a new filter auto-appears in the spec without touching the generator. Descriptions live in
.meta()on the schema field, co-located with validation.4. GitHub Action (
.github/workflows/sync-openapi-spec.yml)Before: Nothing. Manual process to sync the spec.
After: Automated on every push to
mainwithapi/**changesUses a GitHub App token (configured via
vars.SYNC_APP_ID+secrets.SYNC_APP_PRIVATE_KEY) for cross-repo access.5. Repository Guidelines (
AGENTS.md)Created documentation for AI assistants (and human contributors):
@weco/aliases required for cross-directory imports, includingcommon/HttpError,ZodErrorreturns 400New Developer Workflow: Adding a Filter
Example: Add
?series=to/articles1. Zod Schema (
api/src/controllers/articles.ts)2. ES Query (
api/src/queries/articles.ts)Wire it into the Elasticsearch filter.
3. Test (
api/test/articles.test.ts)4. Add a description — add
.meta({ description: '...' })to the field in the schema. The generator picks it up automatically:5. GitHub Action handles syncing — Push to
main, the Action auto-generates the spec and opens a PR to the docs repo.I did a test by adding this branch in the action and it created: wellcomecollection/developers.wellcomecollection.org#75
The change looks big now but it'll be smaller and specific moving forward. We're just moving from a manual, different way of documenting. If you run it locally, it looks almost the same as prod does right now.
Improvements
For Users
pageSizemust be 1–100, not 999)For Developers
For Teams
Architecture: OpenAPI Automation
Before
flowchart TD subgraph code["This repo (content-api)"] A["Custom validators\nqueryValidator\nprismicIdValidator\nworkIdValidator"] end subgraph runtime["API runtime"] D["Express request"] E["Two-pass validation\nEventsSortSchema.parse()\n+ paramsValidator()"] F["Elasticsearch query"] G["JSON response"] end subgraph docs["developers.wellcomecollection.org"] H["reference/content.yaml\n(hand-maintained)"] I["Rendered API docs"] end A -->|"used by"| E A ~~~ H H -.->|"manually kept in sync"| I D --> E E --> F F --> GAfter
flowchart TD subgraph code["This repo (content-api)"] A["Zod schemas\nArticlesQuerySchema\nEventsQuerySchema\nAddressablesQuerySchema"] B["generate-openapi.ts\n(run by GitHub Action on push to main;\nimports schemas directly,\nschemas carry their own\ndescriptions via .meta())"] C["GitHub Action\nsync-openapi-spec.yml\n(triggers on push to main/api/**)"] end subgraph runtime["API runtime"] D["Express request"] E["Schema.parse(req.query)\nvalidates + transforms"] F["Elasticsearch query"] G["JSON response"] end subgraph docs["developers.wellcomecollection.org"] H["reference/content.yaml"] I["Rendered API docs"] end A -->|"used by"| B A -->|"used by"| E B -->|"generates YAML"| C C -->|"opens PR with updated"| H H -->|"rendered as"| I D --> E E --> F F --> G