Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions packages/plugins/openapi/src/rest-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,14 +409,17 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
private generateFilterParameters(model: DataModel) {
const result: OAPI.ParameterObject[] = [];

const hasMultipleIds = model.fields.filter((f) => isIdField(f)).length > 1;

for (const field of model.fields) {
if (isForeignKeyField(field)) {
// no filtering with foreign keys because one can filter
// directly on the relationship
continue;
}

if (isIdField(field)) {
// For multiple ids, make each id field filterable like a regular field
if (isIdField(field) && !hasMultipleIds) {
// id filter
result.push(this.makeFilterParameter(field, 'id', 'Id filter'));
continue;
Expand Down Expand Up @@ -843,7 +846,9 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
}

private generateModelEntity(model: DataModel, mode: 'read' | 'create' | 'update'): OAPI.SchemaObject {
const fields = model.fields.filter((f) => !isIdField(f));
const idFields = model.fields.filter((f) => isIdField(f));
// For compound ids, each component is also exposed as a separate field
const fields = idFields.length > 1 ? model.fields : model.fields.filter((f) => !isIdField(f));

const attributes: Record<string, OAPI.SchemaObject> = {};
const relationships: Record<string, OAPI.ReferenceObject | OAPI.SchemaObject> = {};
Expand Down Expand Up @@ -886,8 +891,8 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {

if (mode === 'create') {
// 'id' is required if there's no default value
const idField = model.fields.find((f) => isIdField(f));
if (idField && !hasAttribute(idField, '@default')) {
const idFields = model.fields.filter((f) => isIdField(f));
if (idFields.length && idFields.every((f) => !hasAttribute(f, '@default'))) {
properties = { id: { type: 'string' }, ...properties };
toplevelRequired.unshift('id');
}
Expand Down
42 changes: 42 additions & 0 deletions packages/plugins/openapi/tests/baseline/rest-3.0.0.baseline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ tags:
description: Profile operations
- name: post_Item
description: Post-related operations
- name: postLike
description: PostLike operations
paths:
/user:
get:
Expand Down Expand Up @@ -193,6 +195,16 @@ paths:
explode: false
schema:
type: string
- name: filter[likes]
required: false
description: Equality filter for "likes"
in: query
style: form
explode: false
schema:
type: array
items:
type: string
responses:
'200':
description: Successful operation
Expand Down Expand Up @@ -543,6 +555,16 @@ paths:
explode: false
schema:
type: string
- name: filter[likes]
required: false
description: Equality filter for "likes"
in: query
style: form
explode: false
schema:
type: array
items:
type: string
responses:
'200':
description: Successful operation
Expand Down Expand Up @@ -745,6 +767,16 @@ paths:
explode: false
schema:
type: string
- name: filter[likes]
required: false
description: Equality filter for "likes"
in: query
style: form
explode: false
schema:
type: array
items:
type: string
responses:
'200':
description: Successful operation
Expand Down Expand Up @@ -1593,6 +1625,16 @@ paths:
explode: false
schema:
type: string
- name: filter[likes]
required: false
description: Equality filter for "likes"
in: query
style: form
explode: false
schema:
type: array
items:
type: string
responses:
'200':
description: Successful operation
Expand Down
42 changes: 42 additions & 0 deletions packages/plugins/openapi/tests/baseline/rest-3.1.0.baseline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ tags:
description: Profile operations
- name: post_Item
description: Post-related operations
- name: postLike
description: PostLike operations
paths:
/user:
get:
Expand Down Expand Up @@ -193,6 +195,16 @@ paths:
explode: false
schema:
type: string
- name: filter[likes]
required: false
description: Equality filter for "likes"
in: query
style: form
explode: false
schema:
type: array
items:
type: string
responses:
'200':
description: Successful operation
Expand Down Expand Up @@ -543,6 +555,16 @@ paths:
explode: false
schema:
type: string
- name: filter[likes]
required: false
description: Equality filter for "likes"
in: query
style: form
explode: false
schema:
type: array
items:
type: string
responses:
'200':
description: Successful operation
Expand Down Expand Up @@ -745,6 +767,16 @@ paths:
explode: false
schema:
type: string
- name: filter[likes]
required: false
description: Equality filter for "likes"
in: query
style: form
explode: false
schema:
type: array
items:
type: string
responses:
'200':
description: Successful operation
Expand Down Expand Up @@ -1593,6 +1625,16 @@ paths:
explode: false
schema:
type: string
- name: filter[likes]
required: false
description: Equality filter for "likes"
in: query
style: form
explode: false
schema:
type: array
items:
type: string
responses:
'200':
description: Successful operation
Expand Down
47 changes: 47 additions & 0 deletions packages/plugins/openapi/tests/openapi-restful.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ model User {
role role @default(USER)
posts post_Item[]
profile Profile?
likes PostLike[]
}

model Profile {
Expand All @@ -55,12 +56,21 @@ model post_Item {
published Boolean @default(false)
viewCount Int @default(0)
notes String?
likes PostLike[]

@@openapi.meta({
tagDescription: 'Post-related operations'
})
}

model PostLike {
post post_Item @relation(fields: [postId], references: [id])
postId String
user User @relation(fields: [userId], references: [id])
userId String
@@id([postId, userId])
}

model Foo {
id String @id
@@openapi.ignore
Expand Down Expand Up @@ -98,9 +108,15 @@ model Bar {
expect(api.paths?.['/user/{id}/relationships/posts']?.['get']).toBeTruthy();
expect(api.paths?.['/user/{id}/relationships/posts']?.['post']).toBeTruthy();
expect(api.paths?.['/user/{id}/relationships/posts']?.['patch']).toBeTruthy();
expect(api.paths?.['/user/{id}/relationships/likes']?.['get']).toBeTruthy();
expect(api.paths?.['/user/{id}/relationships/likes']?.['post']).toBeTruthy();
expect(api.paths?.['/user/{id}/relationships/likes']?.['patch']).toBeTruthy();
expect(api.paths?.['/post_Item/{id}/relationships/author']?.['get']).toBeTruthy();
expect(api.paths?.['/post_Item/{id}/relationships/author']?.['post']).toBeUndefined();
expect(api.paths?.['/post_Item/{id}/relationships/author']?.['patch']).toBeTruthy();
expect(api.paths?.['/post_Item/{id}/relationships/likes']?.['get']).toBeTruthy();
expect(api.paths?.['/post_Item/{id}/relationships/likes']?.['post']).toBeTruthy();
expect(api.paths?.['/post_Item/{id}/relationships/likes']?.['patch']).toBeTruthy();
expect(api.paths?.['/foo']).toBeUndefined();
expect(api.paths?.['/bar']).toBeUndefined();

Expand Down Expand Up @@ -323,6 +339,37 @@ model Foo {
expect(parsed).toMatchObject(baseline);
}
});

it('exposes individual fields from a compound id as attributes', async () => {
const { model, dmmf, modelFile } = await loadZModelAndDmmf(`
plugin openapi {
provider = '${normalizePath(path.resolve(__dirname, '../dist'))}'
}

model User {
email String
role String
company String
@@id([role, company])
}
`);

const { name: output } = tmp.fileSync({ postfix: '.yaml' });

const options = buildOptions(model, modelFile, output, '3.1.0');
await generate(model, options, dmmf);

await OpenAPIParser.validate(output);

const parsed = YAML.parse(fs.readFileSync(output, 'utf-8'));
expect(parsed.openapi).toBe('3.1.0');

expect(Object.keys(parsed.components.schemas.User.properties.attributes.properties)).toEqual(
expect.arrayContaining(['role', 'company'])
);

console.log(JSON.stringify(parsed));
});
});

function buildOptions(model: Model, modelFile: string, output: string, specVersion = '3.0.0') {
Expand Down
Loading