Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: handle @@ignore models properly in plugins #283

Merged
merged 3 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zenstack-monorepo",
"version": "1.0.0-alpha.79",
"version": "1.0.0-alpha.81",
"description": "",
"scripts": {
"build": "pnpm -r build",
Expand Down
2 changes: 1 addition & 1 deletion packages/language/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/language",
"version": "1.0.0-alpha.79",
"version": "1.0.0-alpha.81",
"displayName": "ZenStack modeling language compiler",
"description": "ZenStack modeling language compiler",
"homepage": "https://zenstack.dev",
Expand Down
2 changes: 1 addition & 1 deletion packages/next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/next",
"version": "1.0.0-alpha.79",
"version": "1.0.0-alpha.81",
"displayName": "ZenStack Next.js integration",
"description": "ZenStack Next.js integration",
"homepage": "https://zenstack.dev",
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/openapi/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/openapi",
"displayName": "ZenStack Plugin and Runtime for OpenAPI",
"version": "1.0.0-alpha.79",
"version": "1.0.0-alpha.81",
"description": "ZenStack plugin and runtime supporting OpenAPI",
"main": "index.js",
"repository": {
Expand Down
16 changes: 9 additions & 7 deletions packages/plugins/openapi/src/generator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Inspired by: https://github.com/omar-dulaimi/prisma-trpc-generator

import { DMMF } from '@prisma/generator-helper';
import { AUXILIARY_FIELDS, hasAttribute, PluginError, PluginOptions } from '@zenstackhq/sdk';
import { AUXILIARY_FIELDS, getDataModels, hasAttribute, PluginError, PluginOptions } from '@zenstackhq/sdk';
import { DataModel, isDataModel, type Model } from '@zenstackhq/sdk/ast';
import {
addMissingInputObjectTypesForAggregate,
Expand All @@ -28,6 +28,7 @@ export class OpenAPIGenerator {
private usedComponents: Set<string> = new Set<string>();
private aggregateOperationSupport: AggregateOperationSupport;
private includedModels: DataModel[];
private warnings: string[] = [];

constructor(private model: Model, private options: PluginOptions, private dmmf: DMMF.Document) {}

Expand All @@ -40,9 +41,7 @@ export class OpenAPIGenerator {
// input types
this.inputObjectTypes.push(...this.dmmf.schema.inputObjectTypes.prisma);
this.outputObjectTypes.push(...this.dmmf.schema.outputObjectTypes.prisma);
this.includedModels = this.model.declarations.filter(
(d): d is DataModel => isDataModel(d) && !hasAttribute(d, '@@openapi.ignore')
);
this.includedModels = getDataModels(this.model).filter((d) => !hasAttribute(d, '@@openapi.ignore'));

// add input object types that are missing from Prisma dmmf
addMissingInputObjectTypesForModelArgs(this.inputObjectTypes, this.dmmf.datamodel.models);
Expand Down Expand Up @@ -80,6 +79,8 @@ export class OpenAPIGenerator {
} else {
fs.writeFileSync(output, JSON.stringify(openapi, undefined, 2));
}

return this.warnings;
}

private pruneComponents(components: OAPI.ComponentsObject) {
Expand Down Expand Up @@ -148,7 +149,7 @@ export class OpenAPIGenerator {
...this.generatePathsForModel(model, zmodel, components),
} as OAPI.PathsObject;
} else {
console.warn(`Unable to load ZModel definition for: ${model.name}}`);
this.warnings.push(`Unable to load ZModel definition for: ${model.name}}`);
}
}
}
Expand All @@ -159,13 +160,14 @@ export class OpenAPIGenerator {
model: DMMF.Model,
zmodel: DataModel,
components: OAPI.ComponentsObject
): OAPI.PathItemObject {
): OAPI.PathItemObject | undefined {
const result: OAPI.PathItemObject & Record<string, unknown> = {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ops: (DMMF.ModelMapping & { createOne?: string | null } & Record<string, any>) | undefined =
this.dmmf.mappings.modelOperations.find((ops) => ops.model === model.name);
if (!ops) {
throw new PluginError(`No operations found for model ${model.name}`);
this.warnings.push(`Unable to find mapping for model ${model.name}`);
return undefined;
}

type OperationDefinition = {
Expand Down
130 changes: 55 additions & 75 deletions packages/plugins/openapi/tests/openapi.test.ts
Original file line number Diff line number Diff line change
@@ -1,91 +1,70 @@
/// <reference types="@types/jest" />

import { getDMMF } from '@prisma/internals';
import OpenAPIParser from '@readme/openapi-parser';
import * as fs from 'fs';
import { loadZModelAndDmmf } from '@zenstackhq/testtools';
import * as tmp from 'tmp';
import { loadDocument } from 'zenstack/cli/cli-util';
import prismaPlugin from 'zenstack/plugins/prisma';
import generate from '../src';

async function loadZModelAndDmmf(content: string) {
const prelude = `
datasource db {
provider = 'postgresql'
url = env('DATABASE_URL')
}
`;

const { name: modelFile } = tmp.fileSync({ postfix: '.zmodel' });
fs.writeFileSync(modelFile, `${prelude}\n${content}`);

const model = await loadDocument(modelFile);

const { name: prismaFile } = tmp.fileSync({ postfix: '.prisma' });
await prismaPlugin(model, { schemaPath: modelFile, output: prismaFile, generateClient: false });

const prismaContent = fs.readFileSync(prismaFile, { encoding: 'utf-8' });

const dmmf = await getDMMF({ datamodel: prismaContent });
return { model, dmmf, modelFile };
}

describe('Open API Plugin Tests', () => {
it('run plugin', async () => {
const { model, dmmf, modelFile } = await loadZModelAndDmmf(`
plugin openapi {
provider = '${process.cwd()}/dist'
}
plugin openapi {
provider = '${process.cwd()}/dist'
}

enum Role {
USER
ADMIN
}
enum Role {
USER
ADMIN
}

model User {
id String @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
email String @unique
role Role @default(USER)
posts Post[]

@@openapi.meta({
findMany: {
description: 'Find users matching the given conditions'
},
delete: {
method: 'put',
path: 'dodelete',
description: 'Delete a unique user',
summary: 'Delete a user yeah yeah',
tags: ['delete', 'user']
},
})
}

model Post {
id String @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
author User? @relation(fields: [authorId], references: [id])
authorId String?
published Boolean @default(false)
viewCount Int @default(0)

@@openapi.meta({
findMany: {
ignore: true
}
})
}
model User {
id String @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
email String @unique
role Role @default(USER)
posts Post[]

@@openapi.meta({
findMany: {
description: 'Find users matching the given conditions'
},
delete: {
method: 'put',
path: 'dodelete',
description: 'Delete a unique user',
summary: 'Delete a user yeah yeah',
tags: ['delete', 'user']
},
})
}

model Foo {
id String @id
model Post {
id String @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
author User? @relation(fields: [authorId], references: [id])
authorId String?
published Boolean @default(false)
viewCount Int @default(0)

@@openapi.meta({
findMany: {
ignore: true
}
})
}

@@openapi.ignore
}
model Foo {
id String @id
@@openapi.ignore
}

model Bar {
id String @id
@@ignore
}
`);

const { name: output } = tmp.fileSync({ postfix: '.yaml' });
Expand All @@ -102,5 +81,6 @@ describe('Open API Plugin Tests', () => {
expect(api.paths?.['/post/findMany']).toBeUndefined();

expect(api.paths?.['/foo/findMany']).toBeUndefined();
expect(api.paths?.['/bar/findMany']).toBeUndefined();
});
});
29 changes: 29 additions & 0 deletions packages/plugins/react/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/configuration
*/

export default {
// Automatically clear mock calls, instances, contexts and results before every test
clearMocks: true,

// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,

// The directory where Jest should output its coverage files
coverageDirectory: 'tests/coverage',

// An array of regexp pattern strings used to skip coverage collection
coveragePathIgnorePatterns: ['/node_modules/', '/tests/'],

// Indicates which provider should be used to instrument code for coverage
coverageProvider: 'v8',

// A list of reporter names that Jest uses when writing coverage reports
coverageReporters: ['json', 'text', 'lcov', 'clover'],

// A map from regular expressions to paths to transformers
transform: { '^.+\\.tsx?$': 'ts-jest' },

testTimeout: 300000,
};
9 changes: 7 additions & 2 deletions packages/plugins/react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/react",
"displayName": "ZenStack plugin and runtime for ReactJS",
"version": "1.0.0-alpha.79",
"version": "1.0.0-alpha.81",
"description": "ZenStack plugin and runtime for ReactJS",
"main": "index.js",
"repository": {
Expand Down Expand Up @@ -37,9 +37,14 @@
"react-dom": "^17.0.2 || ^18"
},
"devDependencies": {
"@types/jest": "^29.5.0",
"@types/react": "^18.0.26",
"@types/tmp": "^0.2.3",
"copyfiles": "^2.4.1",
"jest": "^29.5.0",
"rimraf": "^3.0.2",
"typescript": "^4.9.4"
"ts-jest": "^29.0.5",
"typescript": "^4.9.4",
"@zenstackhq/testtools": "workspace:*"
}
}
Loading