diff --git a/CLAUDE.md b/CLAUDE.md
index e2f34a7..a90c770 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -64,13 +64,99 @@ generated/
└── index.ts
```
+### Configuration Options
+
The generator is configured via Prisma schema:
```prisma
+generator class_validator {
+ provider = "prisma-class-validator-generator"
+ output = "./generated" // optional, defaults to ./generated
+ swagger = "true" // optional, adds @ApiProperty decorators
+ separateRelationFields = "true" // optional, creates separate base/relation classes
+}
+```
+
+#### Configuration Flags
+
+**`swagger`** (optional, default: `false`)
+- Adds NestJS Swagger `@ApiProperty` decorators alongside class-validator decorators
+- Includes type information, examples, array handling, and enum values
+- Useful for automatic API documentation generation in NestJS applications
+
+**`separateRelationFields`** (optional, default: `false`)
+- Splits models into separate base and relation classes for better NestJS integration
+- Creates `ModelBase` (scalar fields only), `ModelRelations` (relations only), and combined `Model` class
+- Enables use of NestJS mapped types like `PickType`, `PartialType`, etc.
+- Perfect for DTOs that need to exclude relations or work with specific field subsets
+
+#### Example Usage
+
+**Basic Usage (class-validator only):**
+```prisma
+generator class_validator {
+ provider = "prisma-class-validator-generator"
+ output = "./generated"
+}
+```
+Generates:
+```typescript
+export class User {
+ @IsDefined()
+ @IsInt()
+ id!: number;
+
+ @IsDefined()
+ @IsString()
+ email!: string;
+}
+```
+
+**With Swagger Support:**
+```prisma
generator class_validator {
provider = "prisma-class-validator-generator"
- output = "./generated" // optional, defaults to ./generated
+ output = "./generated"
+ swagger = "true"
+}
+```
+Generates:
+```typescript
+export class User {
+ @IsDefined()
+ @ApiProperty({ example: 'Generated by autoincrement', type: "integer" })
+ @IsInt()
+ id!: number;
+
+ @IsDefined()
+ @ApiProperty({ type: "string" })
+ @IsString()
+ email!: string;
+}
+```
+
+**With Relation Splitting:**
+```prisma
+generator class_validator {
+ provider = "prisma-class-validator-generator"
+ output = "./generated"
+ separateRelationFields = "true"
+}
+```
+Generates:
+- `UserBase.model.ts` - Only scalar fields with decorators
+- `UserRelations.model.ts` - Only relation fields with decorators
+- `User.model.ts` - Combined class extending UserBase with relations
+
+**Full NestJS Integration:**
+```prisma
+generator class_validator {
+ provider = "prisma-class-validator-generator"
+ output = "./generated"
+ swagger = "true"
+ separateRelationFields = "true"
}
```
+Perfect for NestJS APIs with automatic Swagger docs and flexible DTOs.
## Modern Development Setup (Prisma 6+)
diff --git a/README.md b/README.md
index dcda515..ddcb95d 100644
--- a/README.md
+++ b/README.md
@@ -235,8 +235,10 @@ Customize the generator behavior:
```prisma
generator class_validator {
- provider = "prisma-class-validator-generator"
- output = "./src/models" // Output directory
+ provider = "prisma-class-validator-generator"
+ output = "./src/models" // Output directory
+ swagger = "true" // Add Swagger decorators
+ separateRelationFields = "true" // Split base/relation classes
}
```
@@ -245,6 +247,53 @@ generator class_validator {
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `output` | `string` | `"./generated"` | Output directory for generated models |
+| `swagger` | `string` | `"false"` | Add NestJS `@ApiProperty` decorators for Swagger docs |
+| `separateRelationFields` | `string` | `"false"` | Generate separate base and relation classes for flexible DTOs |
+
+### 🌟 New in v6.0.0-beta.1: NestJS & Swagger Integration
+
+#### Swagger Support (`swagger = "true"`)
+
+Automatically generates NestJS Swagger decorators alongside class-validator decorators:
+
+```typescript
+export class User {
+ @IsDefined()
+ @ApiProperty({ example: 'Generated by autoincrement', type: "integer" })
+ @IsInt()
+ id!: number;
+
+ @IsDefined()
+ @ApiProperty({ type: "string" })
+ @IsString()
+ email!: string;
+
+ @IsOptional()
+ @ApiProperty({ type: "string", required: false })
+ @IsString()
+ name?: string | null;
+}
+```
+
+#### Relation Field Splitting (`separateRelationFields = "true"`)
+
+Perfect for NestJS DTOs - generates separate classes for maximum flexibility:
+
+- **`UserBase.model.ts`** - Only scalar fields with validation decorators
+- **`UserRelations.model.ts`** - Only relation fields
+- **`User.model.ts`** - Combined class extending UserBase
+
+This enables powerful NestJS patterns:
+```typescript
+// Create DTO without relations using PickType
+export class CreateUserDto extends PickType(UserBase, ['email', 'name']) {}
+
+// Update DTO with partial fields
+export class UpdateUserDto extends PartialType(UserBase) {}
+
+// Full model with relations for responses
+export class UserResponseDto extends User {}
+```
## 📚 Advanced Usage
@@ -457,7 +506,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
---
-
Made with ❤️ by the Prisma Class Validator Generator team
+
Made with ❤️ by Omar Dulaimi
⭐ Star us on GitHub •
🐛 Report Issues •
diff --git a/src/generate-class.ts b/src/generate-class.ts
index ef3311c..b66aaf9 100644
--- a/src/generate-class.ts
+++ b/src/generate-class.ts
@@ -1,25 +1,41 @@
import type { DMMF as PrismaDMMF } from '@prisma/generator-helper';
import path from 'path';
import { OptionalKind, Project, PropertyDeclarationStructure } from 'ts-morph';
+import type { GeneratorConfig } from './prisma-generator';
import {
generateClassValidatorImport,
generateEnumImports,
generateHelpersImports,
generatePrismaImport,
generateRelationImportsImport,
+ generateSwaggerImport,
getDecoratorsByFieldType,
getDecoratorsImportsByType,
+ getSwaggerImportsByType,
getTSDataTypeFromFieldType,
shouldImportHelpers,
shouldImportPrisma,
+ shouldImportSwagger,
} from './helpers';
export default async function generateClass(
project: Project,
- outputDir: string,
+ config: GeneratorConfig,
model: PrismaDMMF.Model,
) {
- const dirPath = path.resolve(outputDir, 'models');
+ if (config.separateRelationFields) {
+ generateSeparateRelationClasses(project, config, model);
+ } else {
+ generateSingleClass(project, config, model);
+ }
+}
+
+function generateSingleClass(
+ project: Project,
+ config: GeneratorConfig,
+ model: PrismaDMMF.Model,
+) {
+ const dirPath = path.resolve(config.outputDir, 'models');
const filePath = path.resolve(dirPath, `${model.name}.model.ts`);
const sourceFile = project.createSourceFile(filePath, undefined, {
overwrite: true,
@@ -38,6 +54,17 @@ export default async function generateClass(
}
generateClassValidatorImport(sourceFile, validatorImports as Array);
+
+ // Add Swagger imports if enabled
+ if (
+ config.swagger &&
+ shouldImportSwagger(model.fields as PrismaDMMF.Field[])
+ ) {
+ const swaggerImports = getSwaggerImportsByType(
+ model.fields as PrismaDMMF.Field[],
+ );
+ generateSwaggerImport(sourceFile, swaggerImports);
+ }
const relationImports = new Set();
model.fields.forEach((field) => {
if (field.relationName && model.name !== field.type) {
@@ -67,10 +94,216 @@ export default async function generateClass(
hasExclamationToken: field.isRequired,
hasQuestionToken: !field.isRequired,
trailingTrivia: '\r\n',
- decorators: getDecoratorsByFieldType(field),
+ decorators: getDecoratorsByFieldType(field, config.swagger),
};
},
),
],
});
}
+
+function generateSeparateRelationClasses(
+ project: Project,
+ config: GeneratorConfig,
+ model: PrismaDMMF.Model,
+) {
+ // Separate base fields from relation fields
+ const baseFields = model.fields.filter((field) => !field.relationName);
+ const relationFields = model.fields.filter((field) => field.relationName);
+
+ // Generate base class (without relations)
+ generateBaseClass(project, config, model, baseFields);
+
+ // Generate relation class (only relations)
+ if (relationFields.length > 0) {
+ generateRelationClass(project, config, model, relationFields);
+ }
+
+ // Generate combined class that extends base and includes relations
+ generateCombinedClass(project, config, model, baseFields, relationFields);
+}
+
+function generateBaseClass(
+ project: Project,
+ config: GeneratorConfig,
+ model: PrismaDMMF.Model,
+ fields: PrismaDMMF.Field[],
+) {
+ const dirPath = path.resolve(config.outputDir, 'models');
+ const filePath = path.resolve(dirPath, `${model.name}Base.model.ts`);
+ const sourceFile = project.createSourceFile(filePath, undefined, {
+ overwrite: true,
+ });
+
+ const validatorImports = [
+ ...new Set(
+ fields
+ .map((field) => getDecoratorsImportsByType(field))
+ .flatMap((item) => item),
+ ),
+ ];
+
+ if (shouldImportPrisma(fields as PrismaDMMF.Field[])) {
+ generatePrismaImport(sourceFile);
+ }
+
+ generateClassValidatorImport(sourceFile, validatorImports as Array);
+
+ // Add Swagger imports if enabled
+ if (config.swagger && shouldImportSwagger(fields as PrismaDMMF.Field[])) {
+ const swaggerImports = getSwaggerImportsByType(
+ fields as PrismaDMMF.Field[],
+ );
+ generateSwaggerImport(sourceFile, swaggerImports);
+ }
+
+ if (shouldImportHelpers(fields as PrismaDMMF.Field[])) {
+ generateHelpersImports(sourceFile, ['getEnumValues']);
+ }
+
+ generateEnumImports(sourceFile, fields as PrismaDMMF.Field[]);
+
+ sourceFile.addClass({
+ name: `${model.name}Base`,
+ isExported: true,
+ properties: [
+ ...fields.map>((field) => {
+ return {
+ name: field.name,
+ type: getTSDataTypeFromFieldType(field),
+ hasExclamationToken: field.isRequired,
+ hasQuestionToken: !field.isRequired,
+ trailingTrivia: '\r\n',
+ decorators: getDecoratorsByFieldType(field, config.swagger),
+ };
+ }),
+ ],
+ });
+}
+
+function generateRelationClass(
+ project: Project,
+ config: GeneratorConfig,
+ model: PrismaDMMF.Model,
+ relationFields: PrismaDMMF.Field[],
+) {
+ const dirPath = path.resolve(config.outputDir, 'models');
+ const filePath = path.resolve(dirPath, `${model.name}Relations.model.ts`);
+ const sourceFile = project.createSourceFile(filePath, undefined, {
+ overwrite: true,
+ });
+
+ const validatorImports = [
+ ...new Set(
+ relationFields
+ .map((field) => getDecoratorsImportsByType(field))
+ .flatMap((item) => item),
+ ),
+ ];
+
+ generateClassValidatorImport(sourceFile, validatorImports as Array);
+
+ // Add Swagger imports if enabled
+ if (
+ config.swagger &&
+ shouldImportSwagger(relationFields as PrismaDMMF.Field[])
+ ) {
+ const swaggerImports = getSwaggerImportsByType(
+ relationFields as PrismaDMMF.Field[],
+ );
+ generateSwaggerImport(sourceFile, swaggerImports);
+ }
+
+ const relationImports = new Set();
+ relationFields.forEach((field) => {
+ if (field.relationName && model.name !== field.type) {
+ relationImports.add(field.type);
+ }
+ });
+
+ generateRelationImportsImport(sourceFile, [
+ ...relationImports,
+ ] as Array);
+
+ sourceFile.addClass({
+ name: `${model.name}Relations`,
+ isExported: true,
+ properties: [
+ ...relationFields.map>(
+ (field) => {
+ return {
+ name: field.name,
+ type: getTSDataTypeFromFieldType(field),
+ hasExclamationToken: field.isRequired,
+ hasQuestionToken: !field.isRequired,
+ trailingTrivia: '\r\n',
+ decorators: getDecoratorsByFieldType(field, config.swagger),
+ };
+ },
+ ),
+ ],
+ });
+}
+
+function generateCombinedClass(
+ project: Project,
+ config: GeneratorConfig,
+ model: PrismaDMMF.Model,
+ baseFields: PrismaDMMF.Field[],
+ relationFields: PrismaDMMF.Field[],
+) {
+ const dirPath = path.resolve(config.outputDir, 'models');
+ const filePath = path.resolve(dirPath, `${model.name}.model.ts`);
+ const sourceFile = project.createSourceFile(filePath, undefined, {
+ overwrite: true,
+ });
+
+ // Import base class
+ sourceFile.addImportDeclaration({
+ moduleSpecifier: `./${model.name}Base.model`,
+ namedImports: [`${model.name}Base`],
+ });
+
+ // Import relation types for the combined class
+ const relationImports = new Set();
+ relationFields.forEach((field) => {
+ if (field.relationName && model.name !== field.type) {
+ relationImports.add(field.type);
+ }
+ });
+
+ if (relationImports.size > 0) {
+ generateRelationImportsImport(sourceFile, [
+ ...relationImports,
+ ] as Array);
+ }
+
+ // Combined class extends base and includes relations
+ if (relationFields.length > 0) {
+ sourceFile.addClass({
+ name: model.name,
+ isExported: true,
+ extends: `${model.name}Base`,
+ properties: [
+ ...relationFields.map>(
+ (field) => {
+ return {
+ name: field.name,
+ type: getTSDataTypeFromFieldType(field),
+ hasExclamationToken: field.isRequired,
+ hasQuestionToken: !field.isRequired,
+ trailingTrivia: '\r\n',
+ };
+ },
+ ),
+ ],
+ });
+ } else {
+ // If no relations, just extend base
+ sourceFile.addClass({
+ name: model.name,
+ isExported: true,
+ extends: `${model.name}Base`,
+ });
+ }
+}
diff --git a/src/helpers.ts b/src/helpers.ts
index e238bd2..f6ce8b3 100644
--- a/src/helpers.ts
+++ b/src/helpers.ts
@@ -68,17 +68,30 @@ export const getTSDataTypeFromFieldType = (field: PrismaDMMF.Field) => {
if (field.isList) {
type = `${type}[]`;
}
-
+
// Add null union for optional fields to match Prisma client behavior
if (!field.isRequired) {
type = `${type} | null`;
}
-
+
return type;
};
-export const getDecoratorsByFieldType = (field: PrismaDMMF.Field) => {
+export const getDecoratorsByFieldType = (
+ field: PrismaDMMF.Field,
+ includeSwagger: boolean = false,
+) => {
const decorators: OptionalKind[] = [];
+
+ // Add Swagger decorators first if enabled
+ if (includeSwagger) {
+ const swaggerDecorator = getSwaggerDecoratorByFieldType(field);
+ if (swaggerDecorator) {
+ decorators.push(swaggerDecorator);
+ }
+ }
+
+ // Add class-validator decorators
switch (field.type) {
case 'Int':
decorators.push({
@@ -86,6 +99,12 @@ export const getDecoratorsByFieldType = (field: PrismaDMMF.Field) => {
arguments: [],
});
break;
+ case 'Float':
+ decorators.push({
+ name: 'IsNumber',
+ arguments: [],
+ });
+ break;
case 'DateTime':
decorators.push({
name: 'IsDate',
@@ -125,12 +144,77 @@ export const getDecoratorsByFieldType = (field: PrismaDMMF.Field) => {
return decorators;
};
+export const getSwaggerDecoratorByFieldType = (field: PrismaDMMF.Field) => {
+ const args: string[] = [];
+
+ // Base properties
+ if (field.hasDefaultValue && field.default !== null) {
+ if (typeof field.default === 'object' && 'name' in field.default) {
+ // Handle function defaults like autoincrement(), now()
+ args.push(`example: 'Generated by ${field.default.name}'`);
+ } else {
+ args.push(`example: ${JSON.stringify(field.default)}`);
+ }
+ }
+
+ // Type-specific properties
+ switch (field.type) {
+ case 'Int':
+ args.push('type: "integer"');
+ break;
+ case 'Float':
+ args.push('type: "number"');
+ break;
+ case 'String':
+ args.push('type: "string"');
+ break;
+ case 'Boolean':
+ args.push('type: "boolean"');
+ break;
+ case 'DateTime':
+ args.push('type: "string"', 'format: "date-time"');
+ break;
+ case 'Decimal':
+ args.push('type: "string"', 'description: "Decimal value as string"');
+ break;
+ case 'Json':
+ args.push('type: "object"');
+ break;
+ case 'Bytes':
+ args.push('type: "string"', 'format: "byte"');
+ break;
+ }
+
+ // Array handling
+ if (field.isList) {
+ args.push('isArray: true');
+ }
+
+ // Required/optional
+ if (!field.isRequired) {
+ args.push('required: false');
+ }
+
+ // Enum handling
+ if (field.kind === 'enum') {
+ args.push(`enum: Object.values(${field.type})`);
+ }
+
+ return {
+ name: 'ApiProperty',
+ arguments: args.length > 0 ? [`{ ${args.join(', ')} }`] : [],
+ };
+};
+
export const getDecoratorsImportsByType = (field: PrismaDMMF.Field) => {
const validatorImports = new Set();
switch (field.type) {
case 'Int':
validatorImports.add('IsInt');
break;
+ case 'Float':
+ validatorImports.add('IsNumber');
+ break;
case 'DateTime':
validatorImports.add('IsDate');
break;
@@ -204,6 +288,26 @@ export const generateEnumImports = (
}
};
+export const shouldImportSwagger = (fields: PrismaDMMF.Field[]) => {
+ return fields.length > 0; // Always import if we have fields and swagger is enabled
+};
+
+export const getSwaggerImportsByType = (fields: PrismaDMMF.Field[]) => {
+ const swaggerImports = new Set(['ApiProperty']);
+ // Add more swagger imports as needed
+ return [...swaggerImports];
+};
+
+export const generateSwaggerImport = (
+ sourceFile: SourceFile,
+ swaggerImports: Array,
+) => {
+ sourceFile.addImportDeclaration({
+ moduleSpecifier: '@nestjs/swagger',
+ namedImports: swaggerImports,
+ });
+};
+
export function generateEnumsIndexFile(
sourceFile: SourceFile,
enumNames: string[],
diff --git a/src/prisma-generator.ts b/src/prisma-generator.ts
index 232fced..7286784 100644
--- a/src/prisma-generator.ts
+++ b/src/prisma-generator.ts
@@ -9,8 +9,21 @@ import { generateEnumsIndexFile, generateModelsIndexFile } from './helpers';
import { project } from './project';
import removeDir from './utils/removeDir';
+export interface GeneratorConfig {
+ outputDir: string;
+ swagger: boolean;
+ separateRelationFields: boolean;
+}
+
export async function generate(options: GeneratorOptions) {
const outputDir = parseEnvValue(options.generator.output as EnvValue);
+
+ const config: GeneratorConfig = {
+ outputDir,
+ swagger: options.generator.config?.swagger === 'true',
+ separateRelationFields:
+ options.generator.config?.separateRelationFields === 'true',
+ };
await fs.mkdir(outputDir, { recursive: true });
await removeDir(outputDir, true);
@@ -39,7 +52,7 @@ export async function generate(options: GeneratorOptions) {
}
prismaClientDmmf.datamodel.models.forEach((model) =>
- generateClass(project, outputDir, model),
+ generateClass(project, config, model),
);
const helpersIndexSourceFile = project.createSourceFile(
diff --git a/tests/relation-splitting.test.ts b/tests/relation-splitting.test.ts
new file mode 100644
index 0000000..8594afd
--- /dev/null
+++ b/tests/relation-splitting.test.ts
@@ -0,0 +1,129 @@
+import { exec } from 'child_process';
+import { promisify } from 'util';
+import { existsSync, readFileSync } from 'fs';
+import { describe, it, expect, beforeAll } from 'vitest';
+import path from 'path';
+
+const execAsync = promisify(exec);
+
+describe('Relation Splitting Generation', () => {
+ beforeAll(async () => {
+ // Build the generator first
+ await execAsync('npm run build');
+
+ // Generate models for full-features schema
+ const schemaPath = path.resolve(__dirname, 'schemas/full-features.prisma');
+ await execAsync(`npx prisma generate --schema="${schemaPath}"`);
+ }, 60000);
+
+ it('should generate separate base and relation classes', () => {
+ const outputPath = path.resolve(__dirname, 'generated/full-features');
+ const modelsDir = path.join(outputPath, 'models');
+
+ // Check that all expected files are generated
+ expect(() =>
+ readFileSync(path.join(modelsDir, 'UserBase.model.ts')),
+ ).not.toThrow();
+ expect(() =>
+ readFileSync(path.join(modelsDir, 'UserRelations.model.ts')),
+ ).not.toThrow();
+ expect(() =>
+ readFileSync(path.join(modelsDir, 'User.model.ts')),
+ ).not.toThrow();
+
+ expect(() =>
+ readFileSync(path.join(modelsDir, 'PostBase.model.ts')),
+ ).not.toThrow();
+ expect(() =>
+ readFileSync(path.join(modelsDir, 'PostRelations.model.ts')),
+ ).not.toThrow();
+ expect(() =>
+ readFileSync(path.join(modelsDir, 'Post.model.ts')),
+ ).not.toThrow();
+ });
+
+ it('should generate UserBase with only non-relation fields', () => {
+ const outputPath = path.resolve(__dirname, 'generated/full-features');
+ const userBasePath = path.join(outputPath, 'models', 'UserBase.model.ts');
+ const userBase = readFileSync(userBasePath, 'utf-8');
+
+ // Should contain scalar fields
+ expect(userBase).toContain('id!: number');
+ expect(userBase).toContain('email!: string');
+ expect(userBase).toContain('name?: string | null');
+
+ // Should NOT contain relation fields
+ expect(userBase).not.toContain('posts');
+
+ // Should have both class-validator and Swagger decorators
+ expect(userBase).toContain('@IsInt()');
+ expect(userBase).toContain('@ApiProperty({');
+ });
+
+ it('should generate UserRelations with only relation fields', () => {
+ const outputPath = path.resolve(__dirname, 'generated/full-features');
+ const userRelationsPath = path.join(
+ outputPath,
+ 'models',
+ 'UserRelations.model.ts',
+ );
+ const userRelations = readFileSync(userRelationsPath, 'utf-8');
+
+ // Should contain relation fields
+ expect(userRelations).toContain('posts!: Post[]');
+
+ // Should NOT contain scalar fields
+ expect(userRelations).not.toContain('id!: number');
+ expect(userRelations).not.toContain('email!: string');
+
+ // Should import related models
+ expect(userRelations).toContain('import { Post } from "./"');
+
+ // Should have decorators for relations
+ expect(userRelations).toContain('@ApiProperty({ isArray: true })');
+ });
+
+ it('should generate combined User class extending base', () => {
+ const outputPath = path.resolve(__dirname, 'generated/full-features');
+ const userPath = path.join(outputPath, 'models', 'User.model.ts');
+ const user = readFileSync(userPath, 'utf-8');
+
+ // Should extend base class
+ expect(user).toContain('extends UserBase');
+
+ // Should import base class
+ expect(user).toContain('import { UserBase } from "./UserBase.model"');
+
+ // Should import relation types
+ expect(user).toContain('import { Post } from "./"');
+
+ // Should include relation properties
+ expect(user).toContain('posts!: Post[]');
+ });
+
+ it('should generate PostBase without relation fields', () => {
+ const outputPath = path.resolve(__dirname, 'generated/full-features');
+ const postBasePath = path.join(outputPath, 'models', 'PostBase.model.ts');
+ const postBase = readFileSync(postBasePath, 'utf-8');
+
+ // Should contain all scalar fields including foreign key
+ expect(postBase).toContain('id!: number');
+ expect(postBase).toContain('title!: string');
+ expect(postBase).toContain('authorId?: number | null');
+ expect(postBase).toContain('rating!: number');
+
+ // Should NOT contain relation fields (but should contain foreign key)
+ expect(postBase).not.toContain('author?: User');
+ expect(postBase).not.toContain('import { User } from "./"');
+ });
+
+ it('should handle models with no relations correctly', () => {
+ // If we had a model with no relations, it should still work
+ const outputPath = path.resolve(__dirname, 'generated/full-features');
+ const userPath = path.join(outputPath, 'models', 'User.model.ts');
+ const user = readFileSync(userPath, 'utf-8');
+
+ // Should be valid TypeScript
+ expect(user).toContain('export class User extends UserBase');
+ });
+});
diff --git a/tests/schemas/full-features.prisma b/tests/schemas/full-features.prisma
new file mode 100644
index 0000000..883ba6b
--- /dev/null
+++ b/tests/schemas/full-features.prisma
@@ -0,0 +1,35 @@
+generator client {
+ provider = "prisma-client-js"
+}
+
+generator class_validator {
+ provider = "node ./lib/generator.js"
+ output = "../generated/full-features"
+ swagger = "true"
+ separateRelationFields = "true"
+}
+
+datasource db {
+ provider = "sqlite"
+ url = "file:./test.db"
+}
+
+model User {
+ id Int @id @default(autoincrement())
+ email String @unique
+ name String?
+ posts Post[]
+}
+
+model Post {
+ id Int @id @default(autoincrement())
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ title String
+ content String?
+ published Boolean @default(false)
+ viewCount Int @default(0)
+ author User? @relation(fields: [authorId], references: [id])
+ authorId Int?
+ rating Float
+}
\ No newline at end of file
diff --git a/tests/schemas/swagger.prisma b/tests/schemas/swagger.prisma
new file mode 100644
index 0000000..28d16d8
--- /dev/null
+++ b/tests/schemas/swagger.prisma
@@ -0,0 +1,34 @@
+generator client {
+ provider = "prisma-client-js"
+}
+
+generator class_validator {
+ provider = "node ./lib/generator.js"
+ output = "../generated/swagger"
+ swagger = "true"
+}
+
+datasource db {
+ provider = "sqlite"
+ url = "file:./test.db"
+}
+
+model User {
+ id Int @id @default(autoincrement())
+ email String @unique
+ name String?
+ posts Post[]
+}
+
+model Post {
+ id Int @id @default(autoincrement())
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ title String
+ content String?
+ published Boolean @default(false)
+ viewCount Int @default(0)
+ author User? @relation(fields: [authorId], references: [id])
+ authorId Int?
+ rating Float
+}
\ No newline at end of file
diff --git a/tests/swagger-generation.test.ts b/tests/swagger-generation.test.ts
new file mode 100644
index 0000000..0d08d8b
--- /dev/null
+++ b/tests/swagger-generation.test.ts
@@ -0,0 +1,71 @@
+import { exec } from 'child_process';
+import { promisify } from 'util';
+import { existsSync, readFileSync } from 'fs';
+import { describe, it, expect, beforeAll } from 'vitest';
+import path from 'path';
+
+const execAsync = promisify(exec);
+
+describe('Swagger Generation', () => {
+ beforeAll(async () => {
+ // Build the generator first
+ await execAsync('npm run build');
+
+ // Generate models for swagger schema
+ const schemaPath = path.resolve(__dirname, 'schemas/swagger.prisma');
+ await execAsync(`npx prisma generate --schema="${schemaPath}"`);
+ }, 60000);
+
+ it('should generate models with Swagger decorators when enabled', () => {
+ const outputPath = path.resolve(__dirname, 'generated/swagger');
+ const userModelPath = path.join(outputPath, 'models', 'User.model.ts');
+ const userModel = readFileSync(userModelPath, 'utf-8');
+
+ // Check for Swagger imports
+ expect(userModel).toContain(
+ 'import { ApiProperty } from "@nestjs/swagger"',
+ );
+
+ // Check for ApiProperty decorators
+ expect(userModel).toContain('@ApiProperty({');
+ expect(userModel).toContain('type: "integer"');
+ expect(userModel).toContain('type: "string"');
+ expect(userModel).toContain('required: false');
+ expect(userModel).toContain('isArray: true');
+ });
+
+ it('should generate Post model with correct Swagger decorators', () => {
+ const outputPath = path.resolve(__dirname, 'generated/swagger');
+ const postModelPath = path.join(outputPath, 'models', 'Post.model.ts');
+ const postModel = readFileSync(postModelPath, 'utf-8');
+
+ // Check for DateTime format
+ expect(postModel).toContain('format: "date-time"');
+
+ // Check for boolean type
+ expect(postModel).toContain('type: "boolean"');
+
+ // Check for Float handling
+ expect(postModel).toContain('type: "number"');
+ expect(postModel).toContain('@IsNumber()');
+
+ // Check for default value examples
+ expect(postModel).toContain('example: false');
+ expect(postModel).toContain('example: 0');
+ });
+
+ it('should include both class-validator and Swagger decorators', () => {
+ const outputPath = path.resolve(__dirname, 'generated/swagger');
+ const userModelPath = path.join(outputPath, 'models', 'User.model.ts');
+ const userModel = readFileSync(userModelPath, 'utf-8');
+
+ // Check for class-validator decorators
+ expect(userModel).toContain('@IsInt()');
+ expect(userModel).toContain('@IsString()');
+ expect(userModel).toContain('@IsDefined()');
+ expect(userModel).toContain('@IsOptional()');
+
+ // Check for Swagger decorators
+ expect(userModel).toContain('@ApiProperty({');
+ });
+});