Skip to content

Commit 0d74a02

Browse files
ymc9CopilotCopilot
authored
fix(zmodel): require implicitly used "public" schema to be declared in "schemas" config (#492)
* fix(zmodel): require implicitly used "public" schema to be declared in "schemas" config * Update packages/language/src/validators/datasource-validator.ts Co-authored-by: Copilot <[email protected]> * test: verify "public" schema not required when all models have explicit @@Schema (#496) * Initial plan * Add test for explicit schema usage without public Co-authored-by: ymc9 <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: ymc9 <[email protected]> --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 53d1a75 commit 0d74a02

File tree

2 files changed

+92
-9
lines changed

2 files changed

+92
-9
lines changed

packages/language/src/validators/datasource-validator.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { ValidationAcceptor } from 'langium';
22
import { SUPPORTED_PROVIDERS } from '../constants';
3-
import { DataSource, isConfigArrayExpr, isInvocationExpr, isLiteralExpr } from '../generated/ast';
3+
import { DataSource, isConfigArrayExpr, isDataModel, isEnum, isInvocationExpr, isLiteralExpr } from '../generated/ast';
44
import { getStringLiteral } from '../utils';
55
import { validateDuplicatedDeclarations, type AstValidator } from './common';
66

@@ -70,14 +70,28 @@ export default class DataSourceValidator implements AstValidator<DataSource> {
7070
accept('error', '"schemas" must be an array of string literals', {
7171
node: schemasField,
7272
});
73-
} else if (
74-
// validate `defaultSchema` is included in `schemas`
75-
defaultSchemaValue &&
76-
!schemasValue.items.some((e) => getStringLiteral(e) === defaultSchemaValue)
77-
) {
78-
accept('error', `"${defaultSchemaValue}" must be included in the "schemas" array`, {
79-
node: schemasField,
80-
});
73+
} else {
74+
const schemasArray = schemasValue.items.map((e) => getStringLiteral(e)!);
75+
76+
if (defaultSchemaValue) {
77+
// validate `defaultSchema` is included in `schemas`
78+
if (!schemasArray.includes(defaultSchemaValue)) {
79+
accept('error', `"${defaultSchemaValue}" must be included in the "schemas" array`, {
80+
node: schemasField,
81+
});
82+
}
83+
} else {
84+
// if no explicit default schema is specified, and there are models or enums without '@@schema',
85+
// "public" is implicitly used, so it must be included in the "schemas" array
86+
const hasImplicitPublicSchema = ds.$container.declarations.some(
87+
(d) => (isDataModel(d) || isEnum(d)) && !d.attributes.some((a) => a.decl.$refText === '@@schema'),
88+
);
89+
if (hasImplicitPublicSchema && !schemasArray.includes('public')) {
90+
accept('error', `"public" must be included in the "schemas" array`, {
91+
node: schemasField,
92+
});
93+
}
94+
}
8195
}
8296
}
8397
}

tests/e2e/orm/client-api/pg-custom-schema.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,75 @@ model Foo {
247247
).rejects.toThrow('"mySchema" must be included in the "schemas" array');
248248
});
249249

250+
it('requires implicit public schema to be included in schemas', async () => {
251+
await expect(
252+
createTestClient(
253+
`
254+
datasource db {
255+
provider = 'postgresql'
256+
schemas = ['mySchema']
257+
url = '$DB_URL'
258+
}
259+
260+
enum Role {
261+
ADMIN
262+
USER
263+
}
264+
265+
model Foo {
266+
id Int @id
267+
name String
268+
role Role
269+
@@schema('mySchema')
270+
}
271+
272+
model Bar {
273+
id Int @id
274+
name String
275+
}
276+
`,
277+
),
278+
).rejects.toThrow('"public" must be included in the "schemas" array');
279+
});
280+
281+
it('does not require public schema when all models and enums have explicit schema', async () => {
282+
const db = await createTestClient(
283+
`
284+
datasource db {
285+
provider = 'postgresql'
286+
schemas = ['mySchema']
287+
url = '$DB_URL'
288+
}
289+
290+
enum Role {
291+
ADMIN
292+
USER
293+
@@schema('mySchema')
294+
}
295+
296+
model Foo {
297+
id Int @id
298+
name String
299+
role Role
300+
@@schema('mySchema')
301+
}
302+
303+
model Bar {
304+
id Int @id
305+
name String
306+
@@schema('mySchema')
307+
}
308+
`,
309+
{
310+
provider: 'postgresql',
311+
usePrismaPush: true,
312+
},
313+
);
314+
315+
await expect(db.foo.create({ data: { id: 1, name: 'test', role: 'ADMIN' } })).toResolveTruthy();
316+
await expect(db.bar.create({ data: { id: 1, name: 'test' } })).toResolveTruthy();
317+
});
318+
250319
it('allows specifying schema only on a few models', async () => {
251320
let fooQueriesVerified = false;
252321
let barQueriesVerified = false;

0 commit comments

Comments
 (0)