Skip to content

Commit 65eab67

Browse files
authored
78 feature request support omitting fields rather than entire models (#83)
* update nvm * added description * allow to exclude model fields * reduce complexity * publish version * ignore esbuild
1 parent d355e4c commit 65eab67

13 files changed

Lines changed: 313 additions & 65 deletions

.changeset/famous-coins-open.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"prisma-openapi": minor
3+
---
4+
5+
Allow to exclude fields from prisma models

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
22.15.0
1+
24.13.1

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ A Prisma generator that automatically creates OpenAPI specifications from your P
2525
- [Custom Configuration](#custom-configuration)
2626
- [JSDoc Integration](#jsdoc-integration)
2727
- [Prisma Comments as Descriptions](#prisma-comments-as-descriptions)
28+
- [Field Exclusion](#field-exclusion)
2829
- [Configuration](#configuration)
2930
- [License](#license)
3031

@@ -281,6 +282,38 @@ User:
281282
description: Optional display name
282283
```
283284

285+
### Field Exclusion
286+
287+
You can exclude specific fields from the generated OpenAPI schema using two approaches:
288+
289+
**1. Using `@openapi.ignore` in field comments:**
290+
291+
Add `@openapi.ignore` to a field's triple-slash comment to exclude it from the generated schema:
292+
293+
```prisma
294+
model User {
295+
id Int @id @default(autoincrement())
296+
email String @unique
297+
name String?
298+
/// @openapi.ignore
299+
password String
300+
}
301+
```
302+
303+
**2. Using `excludeFields` in generator config:**
304+
305+
Specify fields to exclude using `ModelName.fieldName` format:
306+
307+
```prisma
308+
generator openapi {
309+
provider = "prisma-openapi"
310+
output = "./openapi"
311+
excludeFields = "User.password, User.secretKey"
312+
}
313+
```
314+
315+
Both approaches can be used together. A field is excluded if it matches **either** condition. Excluded fields are removed from both `properties` and `required` in the generated schema.
316+
284317
## Configuration
285318

286319
| Option | Description | Default |
@@ -290,6 +323,7 @@ User:
290323
| `description` | API description in OpenAPI spec | Empty string |
291324
| `includeModels` | Comma-separated list of models to include | All models |
292325
| `excludeModels` | Comma-separated list of models to exclude | None |
326+
| `excludeFields` | Comma-separated list of fields to exclude (`ModelName.fieldName`) | None |
293327
| `generateYaml` | Generate YAML format | `true` |
294328
| `generateJson` | Generate JSON format | `false` |
295329
| `generateJsDoc` | Include JSDoc comments in the schema | `false` |

esbuild.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { build } from 'esbuild';
1+
import {build} from 'esbuild';
22

33
await build({
44
entryPoints: ['src/lib/index.ts'],

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"author": "Nitzan Ohana <16689354+nitzano@users.noreply.github.com>",
44
"repository": "git@github.com:nitzano/prisma-openapi.git",
55
"version": "1.5.6",
6-
"description": "",
6+
"description": "Generate OpenAPI documentation from prisma schema",
77
"main": "dist/index.js",
88
"bin": {
99
"prisma-openapi": "./dist/index.js"

src/on-generate/generate-js-doc-content.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {GeneratorOptions} from '@prisma/generator-helper';
2+
import {isFieldIgnored} from './is-field-ignored.js';
23

34
/**
45
* Generate JSDoc OpenAPI comments for Prisma models
@@ -7,6 +8,7 @@ export function generateJsDocumentContent(
78
models: GeneratorOptions['dmmf']['datamodel']['models'],
89
filteredModels: GeneratorOptions['dmmf']['datamodel']['models'],
910
enums: GeneratorOptions['dmmf']['datamodel']['enums'],
11+
excludeFields?: string[],
1012
): string {
1113
// Create JSDoc OpenAPI content with a single block
1214
let jsDocumentContent = `/**
@@ -20,9 +22,9 @@ export function generateJsDocumentContent(
2022
* ${model.name}:
2123
* type: object
2224
* properties:
23-
${generateModelProperties(model).trimEnd()}
25+
${generateModelProperties(model, excludeFields).trimEnd()}
2426
* required:
25-
${generateRequiredProperties(model)}`;
27+
${generateRequiredProperties(model, excludeFields)}`;
2628
}
2729

2830
// Add enum schemas
@@ -46,10 +48,15 @@ ${generateEnumValues(enumType)}`;
4648
*/
4749
function generateModelProperties(
4850
model: GeneratorOptions['dmmf']['datamodel']['models'][0],
51+
excludeFields?: string[],
4952
): string {
5053
let properties = '';
5154

5255
for (const field of model.fields) {
56+
if (isFieldIgnored(model.name, field, excludeFields)) {
57+
continue;
58+
}
59+
5360
let propertyType = '';
5461

5562
// Handle different field types
@@ -165,9 +172,13 @@ function generateModelProperties(
165172
*/
166173
function generateRequiredProperties(
167174
model: GeneratorOptions['dmmf']['datamodel']['models'][0],
175+
excludeFields?: string[],
168176
): string {
169177
const requiredFields = model.fields
170-
.filter((field) => field.isRequired)
178+
.filter(
179+
(field) =>
180+
field.isRequired && !isFieldIgnored(model.name, field, excludeFields),
181+
)
171182
.map((field) => field.name);
172183

173184
return requiredFields.map((field) => ` * - ${field}`).join('\n');

src/on-generate/generate-open-api-spec.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import type {GeneratorOptions} from '@prisma/generator-helper';
22
import {OpenApiBuilder, type SchemaObject} from 'openapi3-ts/oas31';
33
import {generatePropertiesFromModel} from './generate-properties-from-model.js';
4-
import {type PrismaOpenApiOptions} from './generator-options.js';
4+
import {
5+
type PrismaOpenApiOptions,
6+
parseCommaSeparatedList,
7+
} from './generator-options.js';
8+
import {isFieldIgnored} from './is-field-ignored.js';
59

610
/**
711
* Generate an OpenAPI specification object from Prisma models
@@ -18,14 +22,25 @@ export function generateOpenApiSpec(
1822
version: '1.0.0',
1923
});
2024

25+
const excludeFieldsList = parseCommaSeparatedList(options.excludeFields);
26+
2127
// Create schemas for all filtered models
2228
for (const model of filteredModels) {
2329
const modelSchema: SchemaObject = {
2430
type: 'object',
2531
description: model.documentation,
26-
properties: generatePropertiesFromModel(model, filteredModels, enums),
32+
properties: generatePropertiesFromModel(
33+
model,
34+
filteredModels,
35+
enums,
36+
excludeFieldsList,
37+
),
2738
required: model.fields
28-
.filter((field) => field.isRequired)
39+
.filter(
40+
(field) =>
41+
field.isRequired &&
42+
!isFieldIgnored(model.name, field, excludeFieldsList),
43+
)
2944
.map((field) => field.name),
3045
};
3146
builder.addSchema(model.name, modelSchema);

src/on-generate/generate-properties-from-model.ts

Lines changed: 51 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,50 @@
11
import type {GeneratorOptions} from '@prisma/generator-helper';
22
import {type ReferenceObject, type SchemaObject} from 'openapi3-ts/oas31';
3+
import {isFieldIgnored} from './is-field-ignored.js';
4+
5+
/**
6+
* Map a Prisma scalar type to an OpenAPI schema object
7+
*/
8+
function mapScalarType(type: string): SchemaObject {
9+
switch (type) {
10+
case 'String': {
11+
return {type: 'string'};
12+
}
13+
14+
case 'Int': {
15+
return {type: 'integer', format: 'int32'};
16+
}
17+
18+
case 'BigInt': {
19+
return {type: 'integer', format: 'int64'};
20+
}
21+
22+
case 'Float':
23+
case 'Decimal': {
24+
return {type: 'number', format: 'double'};
25+
}
26+
27+
case 'Boolean': {
28+
return {type: 'boolean'};
29+
}
30+
31+
case 'DateTime': {
32+
return {type: 'string', format: 'date-time'};
33+
}
34+
35+
case 'Json': {
36+
return {type: 'object'};
37+
}
38+
39+
case 'unsupported': {
40+
return {type: 'string', description: 'Unsupported type'};
41+
}
42+
43+
default: {
44+
return {type: 'string', description: 'Unknown type'};
45+
}
46+
}
47+
}
348

449
/**
550
* Generate OpenAPI properties from a Prisma model
@@ -8,70 +53,21 @@ export function generatePropertiesFromModel(
853
model: GeneratorOptions['dmmf']['datamodel']['models'][0],
954
allModels: GeneratorOptions['dmmf']['datamodel']['models'],
1055
enums: GeneratorOptions['dmmf']['datamodel']['enums'],
56+
excludeFields?: string[],
1157
): Record<string, SchemaObject | ReferenceObject> {
1258
const properties: Record<string, SchemaObject | ReferenceObject> = {};
1359

1460
for (const field of model.fields) {
61+
if (isFieldIgnored(model.name, field, excludeFields)) {
62+
continue;
63+
}
64+
1565
let property: SchemaObject | ReferenceObject;
1666

1767
// Handle different field types
1868
switch (field.kind) {
1969
case 'scalar': {
20-
// Map Prisma scalar types to OpenAPI types
21-
const scalarProperty: SchemaObject = {};
22-
switch (field.type) {
23-
case 'String': {
24-
scalarProperty.type = 'string';
25-
break;
26-
}
27-
28-
case 'Int': {
29-
scalarProperty.type = 'integer';
30-
scalarProperty.format = 'int32';
31-
break;
32-
}
33-
34-
case 'BigInt': {
35-
scalarProperty.type = 'integer';
36-
scalarProperty.format = 'int64';
37-
break;
38-
}
39-
40-
case 'Float':
41-
case 'Decimal': {
42-
scalarProperty.type = 'number';
43-
scalarProperty.format = 'double';
44-
break;
45-
}
46-
47-
case 'Boolean': {
48-
scalarProperty.type = 'boolean';
49-
break;
50-
}
51-
52-
case 'DateTime': {
53-
scalarProperty.type = 'string';
54-
scalarProperty.format = 'date-time';
55-
break;
56-
}
57-
58-
case 'Json': {
59-
scalarProperty.type = 'object';
60-
break;
61-
}
62-
63-
case 'unsupported': {
64-
scalarProperty.type = 'string';
65-
scalarProperty.description = 'Unsupported type';
66-
break;
67-
}
68-
69-
default: {
70-
scalarProperty.type = 'string';
71-
scalarProperty.description = 'Unknown type';
72-
break;
73-
}
74-
}
70+
const scalarProperty = mapScalarType(field.type);
7571

7672
if (field.isList) {
7773
property = {

src/on-generate/generator-options.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ export type PrismaOpenApiOptions = {
3232
*/
3333
excludeModels?: string;
3434

35+
/**
36+
* Comma-separated list of fields to exclude (format: "ModelName.fieldName")
37+
* @default undefined (none)
38+
*/
39+
excludeFields?: string;
40+
3541
/**
3642
* Generate YAML format
3743
* @default true
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const openapiIgnoreTag = '@openapi.ignore';
2+
3+
/**
4+
* Check if a field should be ignored in OpenAPI generation.
5+
* A field is ignored if:
6+
* 1. Its documentation contains @openapi.ignore
7+
* 2. It is listed in the excludeFields option (as "ModelName.fieldName")
8+
*/
9+
export function isFieldIgnored(
10+
modelName: string,
11+
field: {name: string; documentation?: string | undefined},
12+
excludeFields?: string[],
13+
): boolean {
14+
if (field.documentation?.includes(openapiIgnoreTag)) {
15+
return true;
16+
}
17+
18+
if (excludeFields?.includes(`${modelName}.${field.name}`)) {
19+
return true;
20+
}
21+
22+
return false;
23+
}
24+
25+
/**
26+
* Strip the @openapi.ignore tag from documentation so it doesn't leak into descriptions
27+
*/
28+
export function cleanDocumentation(documentation: string): string {
29+
return documentation.replace(openapiIgnoreTag, '').trim();
30+
}

0 commit comments

Comments
 (0)