Skip to content

Added route settings parser, serializer and validation#28990

Open
vershwal wants to merge 1 commit into
mainfrom
princi-hkg-1826-route-settings-parser-serializer-validation
Open

Added route settings parser, serializer and validation#28990
vershwal wants to merge 1 commit into
mainfrom
princi-hkg-1826-route-settings-parser-serializer-validation

Conversation

@vershwal

@vershwal vershwal commented Jun 30, 2026

Copy link
Copy Markdown
Member

ref https://linear.app/ghost/issue/HKG-1826

Why

The existing validate.js tangles 5 concerns into a single pass — validation, {slug} to :slug conversion, data expansion to {query, router}, template normalization, and defaults. It also imports RESOURCE_CONFIG from the frontend, creating a cross-boundary dependency that blocks moving route settings to the store layer.

This PR introduces a Zod-based parser/serializer that handles parsing, validation, and type transformation in a single pass — so the new store layer (PR #28821) can persist a clean domain model without pulling in frontend routing code.

What

route-settings-parser.ts — Zod schemas, parser, serializer & validation

Builds on the RouteSettings type model from PR #28821 (already on main). Uses Zod schemas for runtime validation + parsing + transformation, eliminating unsafe as casts on the raw YAML input boundary.

Parser — parseRouteSettings(raw: unknown):

  • Validates and transforms raw YAML object to RouteSettings domain model in one pass
  • Routes become an array with explicit path property (YAML uses object keys)
  • template (string or string[]) normalizes to templates: string[]
  • content_type maps to contentType (camelCase in domain model)
  • Channel vs template routes resolved via a single RouteObjectSchema that branches on controller — no union fallthrough that could silently reclassify malformed channel routes
  • Preserves user-written form: {slug} notation stays as-is, short-form data (tag.recipes) is not expanded — that is the activation bridge's job
  • Null route values (/about/: in YAML) produce friendly "Please define a template." errors
  • Missing permalink produces "Please define a permalink route."

Serializer — serializeRouteSettings(settings):

  • Converts back to YAML string
  • Bare string routes stay bare (e.g. /about/: about)
  • rss: true omitted (it is the default)
  • contentType maps back to content_type (snake_case in YAML)

Validation (via Zod schemas):

  • Path slashes (leading/trailing) and :slug colon-notation rejection on routes, collections, and taxonomies
  • Permalink required on collections
  • Taxonomy keys restricted to tag/author
  • Data: anchored shortform regex, resource name validation (tag/page/post/author for shortform, tags/posts/pages/authors for longform)
  • slug required on read data entries via z.discriminatedUnion
  • Reserved data key names and deprecated author key detected via record superRefine

Tests

  • 34 parser/serializer tests: null/empty input, bare strings, template normalization, channel detection, content_type mapping, data passthrough, collections, taxonomies, serialization roundtrips (model→yaml→model and yaml→model→yaml)
  • 41 validation tests: path slashes, null route values, malformed channel routes, template requirements, permalink checks, :slug rejection on routes/collections/taxonomies, taxonomy keys, data format, resource validation, reserved key names — with message assertions on key user-facing errors
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d75657f6-3b28-41ad-90be-55f2273176ae

📥 Commits

Reviewing files that changed from the base of the PR and between 461aed4 and 435c828.

📒 Files selected for processing (3)
  • ghost/core/core/server/services/route-settings/route-settings-parser.ts
  • ghost/core/test/unit/server/services/route-settings/route-settings-parser.test.ts
  • ghost/core/test/unit/server/services/route-settings/validate-route-settings.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • ghost/core/test/unit/server/services/route-settings/validate-route-settings.test.ts
  • ghost/core/test/unit/server/services/route-settings/route-settings-parser.test.ts
  • ghost/core/core/server/services/route-settings/route-settings-parser.ts

Walkthrough

This PR adds a new route settings parser module using zod for validation and js-yaml for serialization. It defines TypeScript types for routes (channel/template), collections, taxonomies, and data entries. parseRouteSettings validates and normalizes raw input into typed structures, enforcing path formatting, template requirements, and rejecting reserved or deprecated data keys. serializeRouteSettings converts typed settings back into YAML, with conditional field emission. Unit tests cover parsing, serialization roundtrips, and validation edge cases.

Possibly related PRs

  • TryGhost/Ghost#28821: Introduces the RouteSettingsStore contract that consumes the same RouteSettings type used here.

Suggested reviewers: allouis

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding route settings parsing, serialization, and validation.
Description check ✅ Passed The description is directly related to the changeset and explains the parser, serializer, validation, and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch princi-hkg-1826-route-settings-parser-serializer-validation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nx-cloud

nx-cloud Bot commented Jun 30, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit 435c828

Command Status Duration Result
nx run @tryghost/admin:build ✅ Succeeded 6s View ↗
nx run ghost:build:assets ✅ Succeeded 2s View ↗
nx run ghost:build:tsc ✅ Succeeded 6s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-07-01 12:02:53 UTC

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/core/server/services/route-settings/validate-route-settings.ts`:
- Around line 145-146: `validateRoute()` currently enforces leading/trailing
slashes but still allows deprecated `:slug` segments on route paths. Update the
route path validation flow in `validate-route-settings` to call
`rejectColonNotation()` for `route.path`, alongside the existing
`requireLeadingSlash()` and `requireTrailingSlash()` checks, so routes are
rejected consistently with collections and taxonomies.
- Around line 49-59: The shortform validation in validateRouteSettings currently
only checks a prefix via the entry match, which allows malformed route values to
slip through. Update the validation in validateRouteSettings so the pattern is
anchored to the full string while still allowing valid slug separators, and keep
the existing ValidationError path and message shape for invalid entries. Make
sure the check around entry and the subsequent resource extraction still work
correctly with the stricter full-string validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6a7a803f-57ae-4e68-98fe-cb20e36343ec

📥 Commits

Reviewing files that changed from the base of the PR and between f824e12 and 375a138.

📒 Files selected for processing (4)
  • ghost/core/core/server/services/route-settings/route-settings-parser.ts
  • ghost/core/core/server/services/route-settings/validate-route-settings.ts
  • ghost/core/test/unit/server/services/route-settings/route-settings-parser.test.ts
  • ghost/core/test/unit/server/services/route-settings/validate-route-settings.test.ts
Comment thread ghost/core/core/server/services/route-settings/validate-route-settings.ts Outdated
Comment thread ghost/core/core/server/services/route-settings/validate-route-settings.ts Outdated
@vershwal vershwal force-pushed the princi-hkg-1826-route-settings-parser-serializer-validation branch from 375a138 to d3ccf79 Compare June 30, 2026 10:59
@vershwal vershwal requested a review from allouis June 30, 2026 11:19
Comment thread ghost/core/core/server/services/route-settings/route-settings-parser.ts Outdated
Comment thread ghost/core/core/server/services/route-settings/validate-route-settings.ts Outdated
@vershwal vershwal force-pushed the princi-hkg-1826-route-settings-parser-serializer-validation branch from d3ccf79 to 461aed4 Compare July 1, 2026 11:15

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/core/server/services/route-settings/route-settings-parser.ts`:
- Line 119: The RESERVED_DATA_KEYS list in route-settings-parser.ts is missing
browse-entry wrapper fields, so data: {fields: ...} and data: {page: ...} are
not rejected consistently. Update RESERVED_DATA_KEYS to include both fields and
page, and make the same change wherever that constant or equivalent reserved-key
check is duplicated so the parsing logic in the route-settings parser treats
them like filter, limit, and order.
- Around line 419-420: The RSS serialization logic in route-settings-parser
should only write the rss field when it is explicitly disabled, since rss
defaults to enabled. Update the channel/collection mapping code around the
existing rss checks in the relevant parser methods so that entry.rss is emitted
only when the source value is false, and omit it for undefined or true. Apply
the same change in both affected blocks in route-settings-parser to keep channel
routes and collections consistent.
- Line 319: The route settings parser is coercing any falsy root value into an
empty object, which hides invalid non-object YAML roots. Update the logic in
route-settings-parser’s parsing flow around the raw-to-obj assignment so only
null or undefined become an empty settings object, and preserve false, 0, and
empty string so the schema validation can reject them. Refer to the parsing code
that sets obj from raw in the route-settings-parser implementation.
- Around line 185-229: RouteObjectSchema currently accepts fields that
RouteObjectSchema.transform later drops for the wrong route type, so tighten
validation to reject unsupported fields instead of silently discarding them.
Update the schema/transform logic around RouteObjectSchema to make filter,
order, limit, and rss valid only for channel routes, and content_type valid only
for template routes, so invalid combinations fail validation before persistence.
Keep the existing type-specific handling in the channel/template branches, but
ensure the schema enforces those constraints up front.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f51ef84e-c33d-401a-aad0-600d077615a6

📥 Commits

Reviewing files that changed from the base of the PR and between d3ccf79 and 461aed4.

📒 Files selected for processing (3)
  • ghost/core/core/server/services/route-settings/route-settings-parser.ts
  • ghost/core/test/unit/server/services/route-settings/route-settings-parser.test.ts
  • ghost/core/test/unit/server/services/route-settings/validate-route-settings.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • ghost/core/test/unit/server/services/route-settings/validate-route-settings.test.ts
  • ghost/core/test/unit/server/services/route-settings/route-settings-parser.test.ts
Comment thread ghost/core/core/server/services/route-settings/route-settings-parser.ts Outdated
ref https://linear.app/ghost/issue/HKG-1826

- replaced hand-rolled parsing and validation with Zod schemas so raw YAML input is validated andtransformed in a single pass, eliminating unsafe `as` casts on the unknown input boundary
- deleted validate-route-settings.ts; all validation rules now live in the Zod schemas inside route-settings-parser.ts
- rewrote validate-route-settings tests to exercise parseRouteSettings directly with raw YAML-shaped objects
- added roundtrip tests (model→yaml→model and yaml→model→yaml) to lock serializer fidelity
@vershwal vershwal force-pushed the princi-hkg-1826-route-settings-parser-serializer-validation branch from 461aed4 to 435c828 Compare July 1, 2026 11:30
@vershwal vershwal requested a review from allouis July 1, 2026 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants