Skip to content

Commit e5f6120

Browse files
committed
fix(spec): the drivers-name refusal names only the offending name
Build the sentence from the input (zod 4 error function) instead of once for the whole list, pin that it names only itself, and correct the NAME_RE docblock in the admin service, which describes the first of the two patterns. Claude-Session: https://claude.ai/code/session_01ADHkmS1mEJA9L4pTrajNhU Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4df813f commit e5f6120

3 files changed

Lines changed: 22 additions & 9 deletions

File tree

‎packages/services/service-datasource/src/datasource-admin-service.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ import type {
5454
} from './contracts/index.js';
5555
import type { Logger } from './logger.js';
5656

57-
/** Datasource name rule (mirrors `DatasourceSchema.name`). */
57+
/**
58+
* Datasource name rule: the identifier pattern, the first of the two patterns on
59+
* `DatasourceSchema.name`. The second, the route-shadowed names, is refused by
60+
* name in `assertValidName` from the spec's `ROUTE_SHADOWED_DATASOURCE_NAMES`.
61+
*/
5862
const NAME_RE = /^[a-z_][a-z0-9_]*$/;
5963

6064
/**

‎packages/spec/src/data/datasource-route-shadowed-name.test.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ describe('DatasourceSchema.name refuses a route-shadowed name', () => {
8383
expect(message).toContain('could never be read by name');
8484
expect(message).not.toMatch(/#\d{3,}/);
8585
});
86+
87+
it(`the refusal for '${name}' names only that name: built from the input, never from the whole list`, () => {
88+
const message = DatasourceSchema.safeParse(sqlite(name)).error!.issues[0]!.message;
89+
// Every quoted name and every literal route the sentence carries is this one.
90+
expect([...message.matchAll(/'([a-z0-9_]+)'/g)].map((m) => m[1])).toEqual([name]);
91+
expect([...message.matchAll(/\/api\/v1\/datasources\/([a-z0-9_]+)/g)].map((m) => m[1])).toEqual([name]);
92+
});
8693
}
8794

8895
it('the code door: defineStack refuses it with STACK_SCHEMA_INVALID / 422 at datasources.0.name', () => {

‎packages/spec/src/data/datasource.zod.ts‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -616,14 +616,16 @@ export const ROUTE_SHADOWED_DATASOURCE_NAMES = Object.freeze(['drivers'] as cons
616616
const quotedRouteShadowedNames = ROUTE_SHADOWED_DATASOURCE_NAMES.map((name) => `'${name}'`).join(', ');
617617

618618
/**
619-
* The refusal `DatasourceSchema.name` answers for a route-shadowed name: it
620-
* names the name and why, in the admin create door's sentence family.
619+
* The refusal `DatasourceSchema.name` answers for a route-shadowed name: built
620+
* from the offending input, so it names that one name and its route (never the
621+
* whole list), in the admin create door's sentence family.
621622
*/
622-
const ROUTE_SHADOWED_NAME_REFUSAL =
623-
`Datasource name ${quotedRouteShadowedNames} is reserved: the datasource admin API serves its own route `
624-
+ `at /api/v1/datasources/${ROUTE_SHADOWED_DATASOURCE_NAMES.join(' and /api/v1/datasources/')}, ahead of `
625-
+ 'the by-name read /api/v1/datasources/:name, so a datasource of that name could never be read by '
626-
+ 'name. Pick another name.';
623+
function routeShadowedNameRefusal(issue: { input?: unknown }): string {
624+
const name = String(issue.input);
625+
return `Datasource name '${name}' is reserved: the datasource admin API serves its own route at `
626+
+ `/api/v1/datasources/${name}, ahead of the by-name read /api/v1/datasources/:name, so a datasource `
627+
+ 'of that name could never be read by name. Pick another name.';
628+
}
627629

628630
/**
629631
* A second pattern on `name`, not a refinement: zod emits both patterns into
@@ -699,7 +701,7 @@ export const DatasourceSchema = lazySchema(() => strictObject(
699701
*/
700702
name: z.string()
701703
.regex(/^[a-z_][a-z0-9_]*$/)
702-
.regex(NOT_ROUTE_SHADOWED_NAME, ROUTE_SHADOWED_NAME_REFUSAL)
704+
.regex(NOT_ROUTE_SHADOWED_NAME, { error: routeShadowedNameRefusal })
703705
.describe(
704706
`Unique datasource identifier (snake_case). Reserved: ${quotedRouteShadowedNames}, a path the `
705707
+ 'datasource admin API serves itself, so a datasource of that name could never be read by name.',

0 commit comments

Comments
 (0)