Skip to content

feat(catalog): enforce a slug-safe code on v2 catalog objects - #6403

Open
rsempe wants to merge 1 commit into
mainfrom
bil-531
Open

rsempe wants to merge 1 commit into
mainfrom
bil-531

Conversation

@rsempe

@rsempe rsempe commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Context

No Lago model validates code format, so every code-keyed v2 route carries a greedy code: /.*/ constraint. Verified consequence: a product coded something/filters is unreachable on member routes because GET /api/v2/products/something/filters resolves to the filters index of product something; other slashed codes shadow-collide with current or future nested segments. The catalog tables are empty in production until v2 ships — the only window to add a format rule for free.

Description

  • New CatalogCodeFormat concern (validates :code, format: {with: CODE_FORMAT}, allow_blank: true) included in every code-keyed v2 catalog model: Product, ProductCategory, ProductFilter, RateCard, RateCardRate, CatalogPlan, RatePhase.
  • allow_blank leaves the empty case to each model's existing presence validation (no double error).
  • The failure is a standard code field error (value_is_invalid), so REST and GraphQL surface it on the field the caller sent.
  • No data migration — tables are empty.

@lago-claude-ai-agent

Copy link
Copy Markdown
Contributor

Automated pre-review (advisory, not a required check) — verdict: HOLD · CI green

HOLD — the code-format rule is incomplete for the v2 code-based routes.

  • Apply the same validation and coverage to CatalogPlan and RatePhase; /plans/:code and nested /rate_phases/:code are sibling member routes that can still persist slash/space codes.
  • Reject and test the special path-segment codes . and ..; the current regex accepts both even though clients and intermediaries may normalize them, so they are not reliably addressable.

## Context

No Lago model validated code format, so every code-keyed v2 route
carries a greedy `code: /.*/` constraint. A product coded `a/b` collides
with nested routes (e.g. `/products/a/filters` resolves to the filters
index), making it unreachable on member routes — and other slashed codes
shadow-collide with current or future nested segments. The catalog
tables are still empty, so this is the only window to add a format rule.

## Description

Introduce a `CatalogCodeFormat` concern enforcing a slug-safe code
(`/\A(?!\.+\z)[a-zA-Z0-9_\-.]+\z/`: letters, digits, underscore, hyphen,
dot; the negative lookahead also rejects an all-dot code like `.` or
`..`, which proxies normalize and cannot be addressed). It is included in
every code-keyed v2 catalog model — Product, ProductCategory,
ProductFilter, RateCard, RateCardRate, CatalogPlan, RatePhase.

allow_blank leaves the empty case to the existing presence validation,
and the failure is a standard `code` field error (value_is_invalid), so
REST and GraphQL surface it on the field the caller sent. No data
migration — the tables are empty.
@lago-claude-ai-agent

Copy link
Copy Markdown
Contributor

Automated pre-review (advisory, not a required check) — verdict: PASS · CI green

PASS — The shared validation covers all code-owning v2 catalog resources, rejects codes that cannot be addressed safely through member routes, and has focused coverage for every affected model.

it "rejects a code containing a slash" do
record = build(factory, code: "a/b")
record.valid?
expect(record.errors.where(:code, :invalid)).to be_present

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.

I think this could be more sensitive and validate any error in code. record.errors[:code] it does validate any error and not only the invalid one.

module CatalogCodeFormat
extend ActiveSupport::Concern

CODE_FORMAT = /\A(?!\.+\z)[a-zA-Z0-9_\-.]+\z/

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.

Image

these options with many dots and text are allowed as well?

@tiagolupepic tiagolupepic 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.

minor comments

:shipit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants