Skip to content

Commit

Permalink
fix: undefined vs null (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
timsuchanek committed May 12, 2020
1 parent 6bfe515 commit 160b597
Show file tree
Hide file tree
Showing 14 changed files with 293 additions and 55 deletions.
41 changes: 27 additions & 14 deletions src/packages/client/fixtures/blog/main.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
import { PrismaClient } from '@prisma/client'
import { PrismaClient } from './@prisma/client'

const prisma = new PrismaClient()
const prisma = new PrismaClient({
errorFormat: 'pretty',
})

async function main() {
const users = await prisma.user.findMany({
include: {
posts: {
include: {
author: true,
},
orderBy: {
title: 'aasc',
},
},
// const users = await prisma.user.findMany({
// where: {
// id: null,
// },
// include: {
// posts: {
// include: {
// author: true,
// },
// orderBy: {
// title: 'asc',
// },
// },
// },
// })

// console.log(users)

const result = await prisma.user.updateMany({
data: {
id: null,
},
} as any)
})

console.log(users)
console.log(result)

// // prisma.disconnect()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`select validation null when undefined is allowed 1`] = `
"mutation {
updateOnePost(
data: {
id: null
}
where: {
id: \\"abc\\"
}
) {
id
createdAt
updatedAt
published
title
content
authorId
}
}"
`;

exports[`select validation null when undefined is not allowed 1`] = `
"mutation {
createOnePost(data: {
published: true
title: null
}) {
id
createdAt
updatedAt
published
title
content
authorId
}
}"
`;
5 changes: 5 additions & 0 deletions src/packages/client/src/__tests__/dmmf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe('dmmf', () => {
"inputType": Array [
Object {
"isList": false,
"isNullable": false,
"isRequired": false,
"kind": "enum",
"type": "PostKind",
Expand All @@ -38,12 +39,14 @@ describe('dmmf', () => {
"inputType": Array [
Object {
"isList": false,
"isNullable": false,
"isRequired": false,
"kind": "enum",
"type": "PostKind",
},
Object {
"isList": false,
"isNullable": false,
"isRequired": false,
"kind": "enum",
"type": "PostKindFilter",
Expand All @@ -56,6 +59,7 @@ describe('dmmf', () => {
"inputType": Array [
Object {
"isList": true,
"isNullable": false,
"isRequired": false,
"kind": "enum",
"type": "PostKind",
Expand All @@ -68,6 +72,7 @@ describe('dmmf', () => {
"inputType": Array [
Object {
"isList": true,
"isNullable": false,
"isRequired": false,
"kind": "enum",
"type": "PostKind",
Expand Down
2 changes: 2 additions & 0 deletions src/packages/client/src/__tests__/document.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ test('document stringify', () => {
{
isList: false,
isRequired: false,
isNullable: true,
kind: 'scalar',
type: 'number',
},
],
bestFittingType: {
isList: false,
isRequired: false,
isNullable: true,
kind: 'scalar',
type: 'number',
},
Expand Down
92 changes: 92 additions & 0 deletions src/packages/client/src/__tests__/undefined-vs-null.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import stripAnsi from 'strip-ansi'
import { blog } from '../fixtures/blog'
import { DMMFClass } from '../runtime/dmmf'
import { makeDocument } from '../runtime/query'
import { getDMMF } from '../generation/getDMMF'

let dmmf
beforeAll(async () => {
const dmmfDocument = await getDMMF({ datamodel: blog })
dmmf = new DMMFClass(dmmfDocument)
})

describe('select validation', () => {
test('null when undefined is allowed', () => {
const select = {
data: {
id: null,
},
where: {
id: 'abc',
},
}

const document = makeDocument({
dmmf,
select,
rootTypeName: 'mutation',
rootField: 'updateOnePost',
})

expect(String(document)).toMatchSnapshot()
try {
document.validate(select)
} catch (e) {
expect(stripAnsi(e.message)).toMatchInlineSnapshot(`
"
Invalid \`prisma.updateOnePost()\` invocation:
{
data: {
id: null
~~~~
},
where: {
id: 'abc'
}
}
Argument id for data.id must not be null. Please use undefined instead.
"
`)
}
})
test('null when undefined is not allowed', () => {
const select = {
data: {
published: true,
title: null,
},
}

const document = makeDocument({
dmmf,
select,
rootTypeName: 'mutation',
rootField: 'createOnePost',
})

expect(String(document)).toMatchSnapshot()
try {
document.validate(select)
} catch (e) {
expect(stripAnsi(e.message)).toMatchInlineSnapshot(`
"
Invalid \`prisma.createOnePost()\` invocation:
{
data: {
published: true,
title: null
~~~~
}
}
Argument title: Got invalid value null on prisma.createOnePost. Provided null, expected String.
"
`)
}
})
})
2 changes: 2 additions & 0 deletions src/packages/client/src/generation/TSClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,7 @@ export class ArgsType implements Generatable {
kind: 'object',
isList: false,
isRequired: false,
isNullable: true,
},
],
comment: `Select specific fields to fetch from the ${name}`,
Expand All @@ -1165,6 +1166,7 @@ export class ArgsType implements Generatable {
kind: 'object',
isList: false,
isRequired: false,
isNullable: true,
},
],
comment: `Choose, which related nodes to fetch as well.`,
Expand Down
7 changes: 6 additions & 1 deletion src/packages/client/src/generation/generateClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,18 @@ export async function generateClient({
clientVersion,
engineVersion,
}: GenerateClientOptions): Promise<BuildClientResult | undefined> {
const useDotPrisma = !generator?.isCustomOutput || testMode
const useDotPrisma = testMode ? !runtimePath : !generator?.isCustomOutput

runtimePath =
runtimePath || (useDotPrisma ? '@prisma/client/runtime' : './runtime')

const finalOutputDir = useDotPrisma ? getDotPrismaDir(outputDir) : outputDir

if (testMode) {
Debug.enable('generateClient')
debug({ finalOutputDir })
}

const { prismaClientDmmf, fileMap } = await buildClient({
datamodel,
datamodelPath,
Expand Down
1 change: 1 addition & 0 deletions src/packages/client/src/runtime/dmmf-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export namespace DMMF {

export interface SchemaArgInputType {
isRequired: boolean
isNullable: boolean
isList: boolean
type: ArgType
kind: FieldKind
Expand Down
12 changes: 12 additions & 0 deletions src/packages/client/src/runtime/error-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export type InvalidArgError =
| InvalidArgTypeError
| AtLeastOneError
| AtMostOneError
| InvalidNullArgError

/**
* This error occurs if the user provides an arg name that doens't exist
Expand Down Expand Up @@ -90,6 +91,17 @@ export interface MissingArgError {
atMostOne: boolean
}

/**
* If a user incorrectly provided null where she shouldn't have
*/
export interface InvalidNullArgError {
type: 'invalidNullArg'
name: string
invalidType: DMMF.SchemaArgInputType[] // note that this could be an object or scalar type. in the object case, we print the whole object type
atLeastOne: boolean
atMostOne: boolean
}

export interface AtMostOneError {
type: 'atMostOne'
key: string
Expand Down
21 changes: 11 additions & 10 deletions src/packages/client/src/runtime/externalToInternalDmmf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { uniqueBy } from './utils/uniqueBy'
function transformFieldKind(model: ExternalDMMF.Model): DMMF.Model {
return {
...model,
fields: model.fields.map(field => ({
fields: model.fields.map((field) => ({
...field,
kind: field.kind === 'relation' ? ('object' as any) : field.kind,
})),
Expand All @@ -16,9 +16,9 @@ function transformFieldKind(model: ExternalDMMF.Model): DMMF.Model {

function transformDatamodel(datamodel: ExternalDMMF.Datamodel): DMMF.Datamodel {
return {
enums: datamodel.enums.map(enumValue => ({
enums: datamodel.enums.map((enumValue) => ({
...enumValue,
values: enumValue.values.map(v => v.name),
values: enumValue.values.map((v) => v.name),
})),
models: datamodel.models.map(transformFieldKind),
}
Expand All @@ -42,13 +42,13 @@ export function externalToInternalDmmf(
function transformSchema(schema: ExternalDMMF.Schema): DMMF.Schema {
return {
enums: schema.enums,
inputTypes: schema.inputTypes.map(t => ({
inputTypes: schema.inputTypes.map((t) => ({
...t,
fields: uniqueBy(transformArgs(t.fields), f => f.name),
fields: uniqueBy(transformArgs(t.fields), (f) => f.name),
})),
outputTypes: schema.outputTypes.map(o => ({
outputTypes: schema.outputTypes.map((o) => ({
...o,
fields: o.fields.map(f => ({ ...f, args: transformArgs(f.args) })),
fields: o.fields.map((f) => ({ ...f, args: transformArgs(f.args) })),
})),
}
}
Expand All @@ -64,6 +64,7 @@ function fixOrderByEnum(arg: ExternalDMMF.SchemaArg): ExternalDMMF.SchemaArg {
inputType: {
isList: arg.inputType.isList,
isRequired: arg.inputType.isRequired,
isNullable: arg.inputType.isNullable,
type: arg.inputType.type,
kind: 'object',
},
Expand All @@ -85,12 +86,12 @@ function getMappings(
datamodel: DMMF.Datamodel,
): DMMF.Mapping[] {
return mappings
.filter(mapping => {
const model = datamodel.models.find(m => m.name === mapping.model)
.filter((mapping) => {
const model = datamodel.models.find((m) => m.name === mapping.model)
if (!model) {
throw new Error(`Mapping without model ${mapping.model}`)
}
return model.fields.some(f => f.kind !== 'object')
return model.fields.some((f) => f.kind !== 'object')
})
.map((mapping: any) => ({
model: mapping.model,
Expand Down

0 comments on commit 160b597

Please sign in to comment.