diff --git a/packages/cli/test/ts-schema-gen.test.ts b/packages/cli/test/ts-schema-gen.test.ts index 06a1cab57..8d1ed07d0 100644 --- a/packages/cli/test/ts-schema-gen.test.ts +++ b/packages/cli/test/ts-schema-gen.test.ts @@ -852,6 +852,65 @@ model Post { }); }); + it('supports implicit conversions from enums to arrays', async () => { + const { schema } = await generateTsSchema(` +enum PostStatus { + DRAFT + ACTIVE + CANCELLED +} + +model User { + id Int @id @default(autoincrement()) +} + +model Post { + id String @id + status String + + @@validate(status in PostStatus) +} + `); + + expect(schema.models['Post']?.attributes).toMatchObject([ + { + name: '@@validate', + args: [ + { + name: 'value', + value: { + kind: 'binary', + op: 'in', + left: { + kind: 'field', + field: 'status', + }, + right: { + kind: 'array', + type: 'PostStatus', + items: [ + { + kind: 'literal', + value: 'DRAFT', + }, + { + kind: 'literal', + value: 'ACTIVE', + }, + { + kind: 'literal', + value: 'CANCELLED', + }, + ], + }, + binding: undefined, + }, + }, + ], + }, + ]); + }); + it('supports @@strict for type defs', async () => { const { schema } = await generateTsSchema(` model User { diff --git a/packages/language/src/generated/ast.ts b/packages/language/src/generated/ast.ts index 545fdb944..c5ecbe0fd 100644 --- a/packages/language/src/generated/ast.ts +++ b/packages/language/src/generated/ast.ts @@ -902,7 +902,7 @@ export function isReferenceExpr(item: unknown): item is ReferenceExpr { return reflection.isInstance(item, ReferenceExpr.$type); } -export type ReferenceTarget = CollectionPredicateBinding | DataField | EnumField | FunctionParam; +export type ReferenceTarget = CollectionPredicateBinding | DataField | Enum | EnumField | FunctionParam; export const ReferenceTarget = { $type: 'ReferenceTarget' @@ -1427,7 +1427,7 @@ export class ZModelAstReflection extends langium.AbstractAstReflection { name: Enum.name } }, - superTypes: [AbstractDeclaration.$type, TypeDeclaration.$type] + superTypes: [AbstractDeclaration.$type, ReferenceTarget.$type, TypeDeclaration.$type] }, EnumField: { name: EnumField.$type, diff --git a/packages/language/src/generated/grammar.ts b/packages/language/src/generated/grammar.ts index 0f7f341a4..ac3ae2b27 100644 --- a/packages/language/src/generated/grammar.ts +++ b/packages/language/src/generated/grammar.ts @@ -4008,6 +4008,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "typeRef": { "$ref": "#/rules@30" } + }, + { + "$type": "SimpleType", + "typeRef": { + "$ref": "#/rules@46" + } } ] } diff --git a/packages/language/src/zmodel-linker.ts b/packages/language/src/zmodel-linker.ts index 6766a6afc..0908f7c20 100644 --- a/packages/language/src/zmodel-linker.ts +++ b/packages/language/src/zmodel-linker.ts @@ -265,6 +265,12 @@ export class ZModelLinker extends DefaultLinker { } else if (isDataField(target) || isFunctionParam(target)) { // other references are resolved to their declared type this.resolveToDeclaredType(node, target.type); + } else if (isEnum(target)) { + node.$resolvedType = { + decl: target, + array: true, + nullable: false, + }; } } } diff --git a/packages/language/src/zmodel.langium b/packages/language/src/zmodel.langium index 4f39ea28e..6856e6183 100644 --- a/packages/language/src/zmodel.langium +++ b/packages/language/src/zmodel.langium @@ -66,7 +66,7 @@ ConfigArrayExpr: ConfigExpr: LiteralExpr | InvocationExpr | ConfigArrayExpr; -type ReferenceTarget = FunctionParam | DataField | EnumField | CollectionPredicateBinding; +type ReferenceTarget = FunctionParam | DataField | EnumField | CollectionPredicateBinding | Enum; ThisExpr: value='this'; diff --git a/packages/language/test/attribute-application.test.ts b/packages/language/test/attribute-application.test.ts index aaec9f987..be5bdfe74 100644 --- a/packages/language/test/attribute-application.test.ts +++ b/packages/language/test/attribute-application.test.ts @@ -817,6 +817,32 @@ describe('Attribute application validation tests', () => { ); }); + it('@@validate accepts implicit array conversions from enum references', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + enum PostStatus { + DRAFT + ACTIVE + CANCELLED + } + + model User { + id Int @id @default(autoincrement()) + } + + model Post { + id String @id + status String + + @@validate(status in PostStatus) + } + `); + }); + describe('@@strict attribute', () => { it('accepts type defs', async () => { await loadSchema(` diff --git a/packages/sdk/src/ts-schema-generator.ts b/packages/sdk/src/ts-schema-generator.ts index 1ab6befbc..9ed9c464c 100644 --- a/packages/sdk/src/ts-schema-generator.ts +++ b/packages/sdk/src/ts-schema-generator.ts @@ -1489,6 +1489,16 @@ export class TsSchemaGenerator { .when(isCollectionPredicateBinding, () => this.createExpressionUtilsCall('binding', [this.createLiteralNode(expr.target.$refText)]), ) + .when(isEnum, () => + this.createExpressionUtilsCall('array', [ + this.createLiteralNode(expr.target.$refText), + ts.factory.createArrayLiteralExpression( + (target as Enum).fields.map((field) => + this.createLiteralExpression('StringLiteral', field.name), + ), + ), + ]), + ) .otherwise(() => { throw Error(`Unsupported reference type: ${expr.target.$refText}`); }); diff --git a/packages/zod/test/factory.test.ts b/packages/zod/test/factory.test.ts index 19c5e9bce..312d2bb51 100644 --- a/packages/zod/test/factory.test.ts +++ b/packages/zod/test/factory.test.ts @@ -91,6 +91,7 @@ describe.each([ expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf
(); // relation fields are NOT present by default — use include/select to opt in @@ -444,7 +445,13 @@ describe.each([ const userSchema = factory.makeModelSchema('User'); const result = userSchema.safeParse({ ...validUser, - address: { residents: [], street: '123 Main St', city: 'Springfield', zip: null }, + address: { + residents: [], + street: '123 Main St', + city: 'Springfield', + zip: null, + type: 'RESIDENTIAL', + }, }); expect(result.success).toBe(true); }); @@ -453,7 +460,13 @@ describe.each([ const userSchema = factory.makeModelSchema('User'); const result = userSchema.safeParse({ ...validUser, - address: { residents: [], street: '123 Main St', city: 'Springfield', zip: '12345' }, + address: { + residents: [], + street: '123 Main St', + city: 'Springfield', + zip: '12345', + type: 'RESIDENTIAL', + }, }); expect(result.success).toBe(true); }); @@ -462,7 +475,14 @@ describe.each([ const userSchema = factory.makeModelSchema('User'); const result = userSchema.safeParse({ ...validUser, - address: { residents: [], street: '123 Main St', city: 'Springfield', zip: null, extra: 'field' }, + address: { + residents: [], + street: '123 Main St', + city: 'Springfield', + zip: null, + type: 'RESIDENTIAL', + extra: 'field', + }, }); expect(result.success).toBe(false); }); @@ -471,7 +491,7 @@ describe.each([ const userSchema = factory.makeModelSchema('User'); const result = userSchema.safeParse({ ...validUser, - address: { residents: [], street: '123 Main St' }, + address: { residents: [], street: '123 Main St', type: 'RESIDENTIAL' }, }); expect(result.success).toBe(false); }); @@ -523,7 +543,13 @@ describe.each([ it('generates schema for Address typedef', () => { const addressSchema = factory.makeTypeSchema('Address'); expect( - addressSchema.safeParse({ residents: [], street: '123 Main', city: 'Springfield', zip: null }).success, + addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: null, + type: 'RESIDENTIAL', + }).success, ).toBe(true); }); @@ -541,6 +567,7 @@ describe.each([ city: 'Springfield', zip: null, extra: 'field', + type: 'RESIDENTIAL', }); expect(result.success).toBe(false); }); @@ -548,15 +575,26 @@ describe.each([ it('accepts Address with optional zip as null', () => { const addressSchema = factory.makeTypeSchema('Address'); expect( - addressSchema.safeParse({ residents: [], street: '123 Main', city: 'Springfield', zip: null }).success, + addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: null, + type: 'RESIDENTIAL', + }).success, ).toBe(true); }); it('accepts Address with optional zip as a string', () => { const addressSchema = factory.makeTypeSchema('Address'); expect( - addressSchema.safeParse({ residents: [], street: '123 Main', city: 'Springfield', zip: '12345' }) - .success, + addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: '12345', + type: 'RESIDENTIAL', + }).success, ).toBe(true); }); @@ -564,23 +602,38 @@ describe.each([ it('passes when zip is null', () => { const addressSchema = factory.makeTypeSchema('Address'); expect( - addressSchema.safeParse({ residents: [], street: '123 Main', city: 'Springfield', zip: null }) - .success, + addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: null, + type: 'RESIDENTIAL', + }).success, ).toBe(true); }); it('passes when zip is omitted', () => { const addressSchema = factory.makeTypeSchema('Address'); expect( - addressSchema.safeParse({ residents: [], street: '123 Main', city: 'Springfield' }).success, + addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + type: 'RESIDENTIAL', + }).success, ).toBe(true); }); it('passes when zip is exactly 5 characters', () => { const addressSchema = factory.makeTypeSchema('Address'); expect( - addressSchema.safeParse({ residents: [], street: '123 Main', city: 'Springfield', zip: '90210' }) - .success, + addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: '90210', + type: 'RESIDENTIAL', + }).success, ).toBe(true); }); @@ -591,6 +644,7 @@ describe.each([ street: '123 Main', city: 'Springfield', zip: '123', + type: 'RESIDENTIAL', }); expect(result.success).toBe(false); }); @@ -602,6 +656,7 @@ describe.each([ street: '123 Main', city: 'Springfield', zip: '123456', + type: 'RESIDENTIAL', }); expect(result.success).toBe(false); }); @@ -613,6 +668,7 @@ describe.each([ street: '123 Main', city: 'Springfield', zip: '123', + type: 'RESIDENTIAL', }); expect(result.success).toBe(false); if (!result.success) { @@ -629,6 +685,7 @@ describe.each([ street: '123 Main', city: 'Springfield', zip: '123', + type: 'RESIDENTIAL', }); expect(result.success).toBe(false); if (!result.success) { @@ -638,7 +695,13 @@ describe.each([ it('fails when city is too short', () => { const addressSchema = factory.makeTypeSchema('Address'); - const result = addressSchema.safeParse({ residents: [], street: '123 Main', city: '', zip: '12345' }); + const result = addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: '', + zip: '12345', + type: 'RESIDENTIAL', + }); expect(result.success).toBe(false); }); @@ -662,16 +725,61 @@ describe.each([ avatar: null, metadata: null, status: 'ACTIVE', - address: { residents: [], street: '123 Main', city: 'Springfield', zip: '90210' }, + address: { + residents: [], + street: '123 Main', + city: 'Springfield', + zip: '90210', + type: 'RESIDENTIAL', + }, }; expect(userSchema.safeParse(validUser).success).toBe(true); expect( userSchema.safeParse({ ...validUser, - address: { residents: ['Alice'], street: '123 Main', city: 'Springfield', zip: '123' }, + address: { + residents: ['Alice'], + street: '123 Main', + city: 'Springfield', + zip: '123', + type: 'RESIDENTIAL', + }, }).success, ).toBe(false); }); + + it('passes when field value matches implicitly converted enum', () => { + const addressSchema = factory.makeTypeSchema('Address'); + let result = addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: '12345', + type: 'RESIDENTIAL', + }); + expect(result.success).toBe(true); + + result = addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: '12345', + type: 'COMMERCIAL', + }); + expect(result.success).toBe(true); + }); + + it('fails when field value does not match implicitly converted enum', () => { + const addressSchema = factory.makeTypeSchema('Address'); + const result = addressSchema.safeParse({ + residents: [], + street: '123 Main', + city: 'Springfield', + zip: '12345', + type: 'UNKNOWN', + }); + expect(result.success).toBe(false); + }); }); }); diff --git a/packages/zod/test/schema/schema-lite.ts b/packages/zod/test/schema/schema-lite.ts index c1f44d019..0faec6126 100644 --- a/packages/zod/test/schema/schema-lite.ts +++ b/packages/zod/test/schema/schema-lite.ts @@ -18,6 +18,7 @@ export class SchemaType implements SchemaDef { name: "id", type: "String", id: true, + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }] as readonly AttributeApplication[], default: ExpressionUtils.call("cuid") as FieldDefault }, email: { @@ -129,6 +130,7 @@ export class SchemaType implements SchemaDef { name: "id", type: "String", id: true, + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }] as readonly AttributeApplication[], default: ExpressionUtils.call("cuid") as FieldDefault }, title: { @@ -171,6 +173,7 @@ export class SchemaType implements SchemaDef { name: "id", type: "String", id: true, + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }] as readonly AttributeApplication[], default: ExpressionUtils.call("cuid") as FieldDefault }, name: { @@ -184,6 +187,7 @@ export class SchemaType implements SchemaDef { discount: { name: "discount", type: "Float", + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }] as readonly AttributeApplication[], default: 0 as FieldDefault }, finalPrice: { @@ -211,11 +215,13 @@ export class SchemaType implements SchemaDef { name: "id", type: "Int", id: true, + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }] as readonly AttributeApplication[], default: ExpressionUtils.call("autoincrement") as FieldDefault }, createdAt: { name: "createdAt", type: "DateTime", + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("now") }] }] as readonly AttributeApplication[], default: ExpressionUtils.call("now") as FieldDefault }, assetType: { @@ -239,12 +245,14 @@ export class SchemaType implements SchemaDef { name: "id", type: "Int", id: true, + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }] as readonly AttributeApplication[], default: ExpressionUtils.call("autoincrement") as FieldDefault }, createdAt: { name: "createdAt", type: "DateTime", originModel: "Asset", + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("now") }] }] as readonly AttributeApplication[], default: ExpressionUtils.call("now") as FieldDefault }, assetType: { @@ -275,12 +283,14 @@ export class SchemaType implements SchemaDef { name: "id", type: "Int", id: true, + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }] as readonly AttributeApplication[], default: ExpressionUtils.call("autoincrement") as FieldDefault }, createdAt: { name: "createdAt", type: "DateTime", originModel: "Asset", + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("now") }] }] as readonly AttributeApplication[], default: ExpressionUtils.call("now") as FieldDefault }, assetType: { @@ -327,9 +337,14 @@ export class SchemaType implements SchemaDef { name: "zip", type: "String", optional: true + }, + type: { + name: "type", + type: "String" } }, attributes: [ + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.field("type"), "in", ExpressionUtils.array("AddressType", [ExpressionUtils.literal("RESIDENTIAL"), ExpressionUtils.literal("COMMERCIAL")])) }] }, { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.binary(ExpressionUtils.field("zip"), "==", ExpressionUtils._null()), "||", ExpressionUtils.binary(ExpressionUtils.call("length", [ExpressionUtils.field("zip")]), "==", ExpressionUtils.literal(5))) }, { name: "message", value: ExpressionUtils.literal("Zip code must be exactly 5 characters") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("zip")]) }] }, { name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("A mailing address") }] } ] as readonly AttributeApplication[] @@ -346,6 +361,13 @@ export class SchemaType implements SchemaDef { attributes: [ { name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("User account status") }] } ] as readonly AttributeApplication[] + }, + AddressType: { + name: "AddressType", + values: { + RESIDENTIAL: "RESIDENTIAL", + COMMERCIAL: "COMMERCIAL" + } } } as const; authType = "User" as const; diff --git a/packages/zod/test/schema/schema.ts b/packages/zod/test/schema/schema.ts index fa7bc045c..e377af1b8 100644 --- a/packages/zod/test/schema/schema.ts +++ b/packages/zod/test/schema/schema.ts @@ -343,9 +343,14 @@ export class SchemaType implements SchemaDef { name: "zip", type: "String", optional: true + }, + type: { + name: "type", + type: "String" } }, attributes: [ + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.field("type"), "in", ExpressionUtils.array("AddressType", [ExpressionUtils.literal("RESIDENTIAL"), ExpressionUtils.literal("COMMERCIAL")])) }] }, { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.binary(ExpressionUtils.field("zip"), "==", ExpressionUtils._null()), "||", ExpressionUtils.binary(ExpressionUtils.call("length", [ExpressionUtils.field("zip")]), "==", ExpressionUtils.literal(5))) }, { name: "message", value: ExpressionUtils.literal("Zip code must be exactly 5 characters") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("zip")]) }] }, { name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("A mailing address") }] } ] as readonly AttributeApplication[] @@ -362,6 +367,13 @@ export class SchemaType implements SchemaDef { attributes: [ { name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("User account status") }] } ] as readonly AttributeApplication[] + }, + AddressType: { + name: "AddressType", + values: { + RESIDENTIAL: "RESIDENTIAL", + COMMERCIAL: "COMMERCIAL" + } } } as const; authType = "User" as const; diff --git a/packages/zod/test/schema/schema.zmodel b/packages/zod/test/schema/schema.zmodel index e7deb27aa..ba1475d80 100644 --- a/packages/zod/test/schema/schema.zmodel +++ b/packages/zod/test/schema/schema.zmodel @@ -10,12 +10,19 @@ enum Status { @@meta("description", "User account status") } +enum AddressType { + RESIDENTIAL + COMMERCIAL +} + type Address { residents String[] street String @meta("description", "Street address line") city String @length(2) zip String? + type String + @@validate(type in AddressType) @@validate(zip == null || length(zip) == 5, "Zip code must be exactly 5 characters", ["zip"]) @@meta("description", "A mailing address") } diff --git a/tests/e2e/orm/client-api/enum.test.ts b/tests/e2e/orm/client-api/enum.test.ts new file mode 100644 index 000000000..72fb4ce1f --- /dev/null +++ b/tests/e2e/orm/client-api/enum.test.ts @@ -0,0 +1,63 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import type { ClientContract } from '@zenstackhq/orm'; +import { schema } from '../schemas/enum/schema'; +import { createTestClient } from '@zenstackhq/testtools'; + +describe('Enum tests', () => { + let client: ClientContract; + + beforeEach(async () => { + client = await createTestClient(schema); + }); + + afterEach(async () => { + await client?.$disconnect(); + }); + + it('works when implicitly converted to arrays', async () => { + await expect( + client.post.create({ + data: { + id: '1', + status: 'ACTIVE', + }, + }), + ).resolves.toMatchObject({ + id: '1', + status: 'ACTIVE', + }); + + await expect( + client.post.create({ + data: { + id: '2', + status: 'DRAFT', + }, + }), + ).resolves.toMatchObject({ + id: '2', + status: 'DRAFT', + }); + + await expect( + client.post.create({ + data: { + id: '3', + status: 'CANCELLED', + }, + }), + ).resolves.toMatchObject({ + id: '3', + status: 'CANCELLED', + }); + + await expect( + client.post.create({ + data: { + id: '3', + status: 'UNKNOWN', + }, + }), + ).rejects.toThrow(/Validation error/); + }); +}); diff --git a/tests/e2e/orm/schemas/enum/schema.ts b/tests/e2e/orm/schemas/enum/schema.ts new file mode 100644 index 000000000..99cb511be --- /dev/null +++ b/tests/e2e/orm/schemas/enum/schema.ts @@ -0,0 +1,66 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { type SchemaDef, type AttributeApplication, type FieldDefault, ExpressionUtils } from "@zenstackhq/schema"; +export class SchemaType implements SchemaDef { + provider = { + type: "sqlite" + } as const; + models = { + User: { + name: "User", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }] as readonly AttributeApplication[], + default: ExpressionUtils.call("autoincrement") as FieldDefault + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + } + }, + Post: { + name: "Post", + fields: { + id: { + name: "id", + type: "String", + id: true, + attributes: [{ name: "@id" }] as readonly AttributeApplication[] + }, + status: { + name: "status", + type: "String" + } + }, + attributes: [ + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.field("status"), "in", ExpressionUtils.array("PostStatus", [ExpressionUtils.literal("DRAFT"), ExpressionUtils.literal("ACTIVE"), ExpressionUtils.literal("CANCELLED")])) }] } + ] as readonly AttributeApplication[], + idFields: ["id"], + uniqueFields: { + id: { type: "String" } + } + } + } as const; + enums = { + PostStatus: { + name: "PostStatus", + values: { + DRAFT: "DRAFT", + ACTIVE: "ACTIVE", + CANCELLED: "CANCELLED" + } + } + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/enum/schema.zmodel b/tests/e2e/orm/schemas/enum/schema.zmodel new file mode 100644 index 000000000..d95f40bdd --- /dev/null +++ b/tests/e2e/orm/schemas/enum/schema.zmodel @@ -0,0 +1,21 @@ +datasource db { + provider = 'sqlite' + url = 'file:./dev.db' +} + +enum PostStatus { + DRAFT + ACTIVE + CANCELLED +} + +model User { + id Int @id @default(autoincrement()) +} + +model Post { + id String @id + status String + + @@validate(status in PostStatus) +} \ No newline at end of file