Skip to content

Commit

Permalink
fix(client,cli): apply dynamic denylist for models with tests (#2589)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jolg42 committed May 28, 2020
1 parent 0568edf commit e704e6d
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 11 deletions.
21 changes: 21 additions & 0 deletions src/packages/cli/fixtures/project/subdir/denylist.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
datasource my_db {
provider = "sqlite"
url = env("SQLITE_URL")
}

generator client {
provider = "prisma-client-js"
output = "@prisma/client"
}

model Blog {
id Int @id
}

model public {
id Int @id
}

model var {
id Int @id
}
21 changes: 21 additions & 0 deletions src/packages/cli/fixtures/project/subdir/dynamic-denylist.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
datasource my_db {
provider = "sqlite"
url = env("SQLITE_URL")
}

generator client {
provider = "prisma-client-js"
output = "@prisma/client"
}

model Blog {
id Int @id
}

model BlogClient {
id Int @id
}

model BlogArgs {
id Int @id
}
12 changes: 12 additions & 0 deletions src/packages/cli/fixtures/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ if [[ ${GENERATE} != *"Generated "* ]]; then
exit 1
fi

GENERATE_DENYLIST=$(node ../../../build/index.js generate --schema=denylist.prisma 2>&1)
if [[ ${GENERATE_DENYLIST} != *"Error validating model \"public\""* ]]; then
echo "prisma generate denylist is broken"
exit 1
fi

GENERATE_DYNAMIC_DENYLIST=$(node ../../../build/index.js generate --schema=dynamic-denylist.prisma)
if [[ ${GENERATE_DYNAMIC_DENYLIST} != *"model BlogClient"* ]]; then
echo "prisma generate dynamic denylist is broken"
exit 1
fi

#
# Test --schema from schema dir
#
Expand Down
11 changes: 9 additions & 2 deletions src/packages/cli/src/Generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class Generate implements Command {
`)

private logText = ''
private hasGeneratorErrored = false

private runGenerate = simpleDebounce(
async ({ generators }: { generators: Generator[] }) => {
Expand Down Expand Up @@ -75,7 +76,9 @@ export class Generate implements Command {
)
generator.stop()
} catch (err) {
message.push(err.message)
this.hasGeneratorErrored = true
message.push(`${err.message}\n\n`)
generator.stop()
}
}

Expand Down Expand Up @@ -200,7 +203,11 @@ const prisma = new PrismaClient()`)}
\`\`\`
Explore the full API: ${link('http://pris.ly/d/client')}`
logUpdate('\n' + this.logText + (isJSClient ? hint : ''))
logUpdate(
'\n' +
this.logText +
(isJSClient && !this.hasGeneratorErrored ? hint : ''),
)
}

return ''
Expand Down
35 changes: 35 additions & 0 deletions src/packages/client/src/__tests__/generation/denylist.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
generator client {
provider = "prisma-client-js"
}

model public {
id Int @id
}

model dmmf {
id Int @id
}

model OnlyOne {
id Int @id
}

model delete {
id Int @id
}

model AND {
id Int @id
}

model User {
id Int @id
}

model UserClient {
id Int @id
}

model UserArgs {
id Int @id
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
generator client {
provider = "prisma-client-js"
}

model User {
id Int @id
}

model UserClient {
id Int @id
text String
}

model UserArgs {
id Int @id
}

model Prisma {
id Int @id
}

model Earth {
id Int @id
}
145 changes: 144 additions & 1 deletion src/packages/client/src/__tests__/generation/generator.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { getGenerator, getPackedPackage } from '@prisma/sdk'
import { getGenerator, getPackedPackage, Generator } from '@prisma/sdk'
import fs from 'fs'
import path from 'path'
import { omit } from '../../omit'
import { promisify } from 'util'
import stripAnsi from 'strip-ansi'
import rimraf from 'rimraf'
const del = promisify(rimraf)

Expand Down Expand Up @@ -68,6 +69,148 @@ describe('generator', () => {
generator.stop()
})

test('denylist from engine validation', async () => {
const prismaClientTarget = path.join(
__dirname,
'./node_modules/@prisma/client',
)
// Make sure, that nothing is cached.
try {
await del(prismaClientTarget)
} catch (e) {
//
}
await getPackedPackage('@prisma/client', prismaClientTarget)

if (!fs.existsSync(prismaClientTarget)) {
throw new Error(`Prisma Client didn't get packed properly 🤔`)
}

try {
await getGenerator({
schemaPath: path.join(__dirname, 'denylist.prisma'),
baseDir: __dirname,
printDownloadProgress: false,
skipDownload: true,
})
} catch (e) {
expect(stripAnsi(e.message)).toMatchInlineSnapshot(`
"Schema parsing
error: Error validating model \\"public\\": The model name \`public\` is invalid. It is a reserved name. Please change it.
--> schema.prisma:5
|
4 |
5 | model public {
6 | id Int @id
7 | }
|
error: Error validating model \\"dmmf\\": The model name \`dmmf\` is invalid. It is a reserved name. Please change it.
--> schema.prisma:9
|
8 |
9 | model dmmf {
10 | id Int @id
11 | }
|
error: Error validating model \\"OnlyOne\\": The model name \`OnlyOne\` is invalid. It is a reserved name. Please change it.
--> schema.prisma:13
|
12 |
13 | model OnlyOne {
14 | id Int @id
15 | }
|
error: Error validating model \\"delete\\": The model name \`delete\` is invalid. It is a reserved name. Please change it.
--> schema.prisma:17
|
16 |
17 | model delete {
18 | id Int @id
19 | }
|
Validation Error Count: 4"
`)
}
})

test('denylist dynamic from client', async () => {
const prismaClientTarget = path.join(
__dirname,
'./node_modules/@prisma/client',
)
// Make sure, that nothing is cached.
try {
await del(prismaClientTarget)
} catch (e) {
//
}
await getPackedPackage('@prisma/client', prismaClientTarget)

if (!fs.existsSync(prismaClientTarget)) {
throw new Error(`Prisma Client didn't get packed properly 🤔`)
}

const generator = await getGenerator({
schemaPath: path.join(__dirname, 'dynamic-denylist.prisma'),
baseDir: __dirname,
printDownloadProgress: false,
skipDownload: true,
})

// Test dynamic denylist errors
let dynamicReservedWordError
try {
await generator.generate()
} catch (e) {
dynamicReservedWordError = e
} finally {
expect(
stripAnsi(dynamicReservedWordError.message).split('generation/')[1],
).toMatchInlineSnapshot(`
"dynamic-denylist.prisma\\" contains reserved keywords.
Rename the following items:
- \\"model UserClient\\"
- \\"model UserArgs\\""
`)
}
generator.stop()
})

test('schema path does not exist', async () => {
const prismaClientTarget = path.join(
__dirname,
'./node_modules/@prisma/client',
)
// Make sure, that nothing is cached.
try {
await del(prismaClientTarget)
} catch (e) {
//
}
await getPackedPackage('@prisma/client', prismaClientTarget)

if (!fs.existsSync(prismaClientTarget)) {
throw new Error(`Prisma Client didn't get packed properly 🤔`)
}

let doesnNotExistError
try {
await getGenerator({
schemaPath: path.join(__dirname, 'doesnotexist.prisma'),
baseDir: __dirname,
printDownloadProgress: false,
skipDownload: true,
})
} catch (e) {
doesnNotExistError = e
} finally {
expect(
stripAnsi(doesnNotExistError.message).split('generation/')[1],
).toMatchInlineSnapshot(`"doesnotexist.prisma does not exist"`)
}
})

test.skip('inMemory', async () => {
const generator = await getGenerator({
schemaPath: path.join(__dirname, 'schema.prisma'),
Expand Down
27 changes: 19 additions & 8 deletions src/packages/client/src/generation/generateClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ const exists = promisify(fs.exists)
const copyFile = promisify(fs.copyFile)
const stat = promisify(fs.stat)

export class DenylistError extends Error {
constructor(message: string) {
super(message)
this.name = 'DenylistError'
this.stack = undefined
}
}

export interface GenerateClientOptions {
datamodel: string
datamodelPath: string
Expand Down Expand Up @@ -155,15 +163,15 @@ export async function generateClient({

const denylistsErrors = validateDmmfAgainstDenylists(prismaClientDmmf)
if (denylistsErrors) {
console.error(
`${chalk.redBright.bold(
'Error: ',
)}The schema at "${datamodelPath}" contains reserved keywords.\n Rename the following items:`,
)
let message = `${chalk.redBright.bold(
'Error: ',
)}The schema at "${datamodelPath}" contains reserved keywords.\n Rename the following items:`

for (const error of denylistsErrors) {
console.error(' - ' + error.message)
message += '\n - ' + error.message
}
process.exit(1)

throw new DenylistError(message)
}

await makeDir(finalOutputDir)
Expand Down Expand Up @@ -440,6 +448,8 @@ function validateDmmfAgainstDenylists(prismaClientDmmf): Error[] | null {
const errorArray = [] as Error[]

const denylists = {
// A copy of this list is also in prisma-engines. Any edit should be done in both places.
// https://github.com/prisma/prisma-engines/blob/master/libs/datamodel/core/src/validator/invalid_model_names.rs
models: [
'dmmf',
'PromiseType',
Expand Down Expand Up @@ -609,7 +619,8 @@ function validateDmmfAgainstDenylists(prismaClientDmmf): Error[] | null {
for (const it of prismaClientDmmf.datamodel.models) {
if (
denylists.models.includes(it.name) ||
denylists.fields.includes(it.name)
denylists.fields.includes(it.name) ||
denylists.dynamic.includes(it.name)
) {
errorArray.push(Error(`"model ${it.name}"`))
}
Expand Down

0 comments on commit e704e6d

Please sign in to comment.