diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4cf54e29..8f5d7161 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -22,6 +22,7 @@ model User { successor User? @relation("BlogOwnerHistory", fields: [successorId], references: [id]) predecessor User? @relation("BlogOwnerHistory") role Role @default(USER) + secondRole Role? posts Post[] keywords String[] biography Json diff --git a/src/generator/properties.ts b/src/generator/properties.ts index 29e9eb55..1ae77870 100644 --- a/src/generator/properties.ts +++ b/src/generator/properties.ts @@ -145,13 +145,17 @@ function isSingleReference(field: DMMF.Field) { } function getEnumListByDMMFType(modelMetaData: ModelMetaData) { - return (field: DMMF.Field): string[] | undefined => { + return (field: DMMF.Field): (string | null)[] | undefined => { const enumItem = modelMetaData.enums.find( ({ name }) => name === field.type, ) if (!enumItem) return undefined - return enumItem.values.map((item) => item.name) + const items: (string | null)[] = enumItem.values.map((item) => item.name) + if (!field.isRequired) { + items.push(null) + } + return items } } diff --git a/src/tests/generator.test.ts b/src/tests/generator.test.ts index 0005875c..301340ea 100644 --- a/src/tests/generator.test.ts +++ b/src/tests/generator.test.ts @@ -25,6 +25,7 @@ const datamodelPostGresQL = /* Prisma */ ` successor User? @relation("BlogOwnerHistory", fields: [successorId], references: [id]) predecessor User? @relation("BlogOwnerHistory") role Role @default(USER) + secondRole Role? posts Post[] keywords String[] biography Json @@ -147,6 +148,10 @@ describe('JSON Schema Generator', () => { enum: ['USER', 'ADMIN'], type: 'string', }, + secondRole: { + type: ['string', 'null'], + enum: ['USER', 'ADMIN', null], + }, successor: { anyOf: [ { $ref: '#/definitions/User' }, @@ -247,6 +252,10 @@ describe('JSON Schema Generator', () => { enum: ['USER', 'ADMIN'], type: 'string', }, + secondRole: { + type: ['string', 'null'], + enum: ['USER', 'ADMIN', null], + }, successor: { anyOf: [ { $ref: '#/definitions/User' }, @@ -346,6 +355,10 @@ describe('JSON Schema Generator', () => { enum: ['USER', 'ADMIN'], type: 'string', }, + secondRole: { + type: ['string', 'null'], + enum: ['USER', 'ADMIN', null], + }, successor: { anyOf: [ { $ref: '#/definitions/User' }, @@ -427,6 +440,10 @@ describe('JSON Schema Generator', () => { enum: ['USER', 'ADMIN'], type: 'string', }, + secondRole: { + type: ['string', 'null'], + enum: ['USER', 'ADMIN', null], + }, weight: { default: 333.33, type: ['number', 'null'], @@ -508,6 +525,10 @@ describe('JSON Schema Generator', () => { enum: ['USER', 'ADMIN'], type: 'string', }, + secondRole: { + type: ['string', 'null'], + enum: ['USER', 'ADMIN', null], + }, successorId: { default: 123, type: ['integer', 'null'], @@ -604,6 +625,10 @@ describe('JSON Schema Generator', () => { enum: ['USER', 'ADMIN'], type: 'string', }, + secondRole: { + type: ['string', 'null'], + enum: ['USER', 'ADMIN', null], + }, successor: { anyOf: [ { $ref: '#/definitions/User' }, @@ -721,6 +746,11 @@ describe('JSON Schema Generator', () => { enum: ['USER', 'ADMIN'], type: 'string', }, + secondRole: { + type: ['string', 'null'], + enum: ['USER', 'ADMIN', null], + originalType: 'Role', + }, successor: { anyOf: [ { $ref: '#/definitions/User' }, @@ -824,6 +854,10 @@ describe('JSON Schema Generator', () => { enum: ['USER', 'ADMIN'], type: 'string', }, + secondRole: { + anyOf: [{ type: 'string' }, { type: 'null' }], + enum: ['USER', 'ADMIN', null], + }, successor: { anyOf: [ { $ref: '#/definitions/User' }, @@ -919,6 +953,10 @@ describe('JSON Schema Generator', () => { enum: ['USER', 'ADMIN'], type: 'string', }, + secondRole: { + type: ['string', 'null'], + enum: ['USER', 'ADMIN', null], + }, successor: { anyOf: [ { $ref: '#/definitions/User' }, @@ -1023,6 +1061,10 @@ describe('JSON Schema Generator', () => { enum: ['USER', 'ADMIN'], type: 'string', }, + secondRole: { + type: ['string', 'null'], + enum: ['USER', 'ADMIN', null], + }, successor: { anyOf: [ { $ref: 'schemaId#/definitions/User' }, @@ -1050,7 +1092,63 @@ describe('JSON Schema Generator', () => { }) // eslint-disable-next-line jest/expect-expect - it('generated schema validates against input', async () => { + it('generated schema validates against input 1', async () => { + const dmmf = await getDMMF({ datamodel: datamodelPostGresQL }) + const jsonSchema = transformDMMF(dmmf, { + persistOriginalType: 'true', + }) + const ajv = new Ajv({ + allowUnionTypes: true, + }).addKeyword('originalType') + + addFormats(ajv) + + const validate = ajv.compile(jsonSchema) + const valid = validate({ + post: { + id: 0, + user: { + id: 100, + }, + }, + user: { + id: 10, + createdAt: '1997-07-16T19:20:30.45+01:00', + email: 'jan@scharnow.city', + biography: { + bornIn: 'Scharnow', + }, + number: 34534535435353, + bytes: 'SGVsbG8gd29ybGQ=', + favouriteDecimal: 22.32, + is18: true, + keywords: ['prisma2', 'json-schema', 'generator'], + name: null, + posts: [ + { + id: 4, + }, + { + id: 20, + }, + ], + predecessor: { + id: 10, + email: 'horst@wassermann.de', + }, + successor: null, + role: 'USER', + weight: 10.12, + }, + }) + + if (!valid) { + throw new Error(ajv.errorsText(validate.errors)) + } + }) + + // eslint-disable-next-line jest/expect-expect + it('generated schema validates against input 2', async () => { const dmmf = await getDMMF({ datamodel: datamodelPostGresQL }) const jsonSchema = transformDMMF(dmmf, { persistOriginalType: 'true', @@ -1096,6 +1194,7 @@ describe('JSON Schema Generator', () => { }, successor: null, role: 'USER', + secondRole: null, weight: 10.12, }, }) @@ -1105,6 +1204,63 @@ describe('JSON Schema Generator', () => { } }) + // eslint-disable-next-line jest/expect-expect + it('generated schema validates against input 3', async () => { + const dmmf = await getDMMF({ datamodel: datamodelPostGresQL }) + const jsonSchema = transformDMMF(dmmf, { + persistOriginalType: 'true', + }) + const ajv = new Ajv({ + allowUnionTypes: true, + }).addKeyword('originalType') + + addFormats(ajv) + + const validate = ajv.compile(jsonSchema) + const valid = validate({ + post: { + id: 0, + user: { + id: 100, + }, + }, + user: { + id: 10, + createdAt: '1997-07-16T19:20:30.45+01:00', + email: 'jan@scharnow.city', + biography: { + bornIn: 'Scharnow', + }, + number: 34534535435353, + bytes: 'SGVsbG8gd29ybGQ=', + favouriteDecimal: 22.32, + is18: true, + keywords: ['prisma2', 'json-schema', 'generator'], + name: null, + posts: [ + { + id: 4, + }, + { + id: 20, + }, + ], + predecessor: { + id: 10, + email: 'horst@wassermann.de', + }, + successor: null, + role: 'USER', + secondRole: 'ADMIN', + weight: 10.12, + }, + }) + + if (!valid) { + throw new Error(ajv.errorsText(validate.errors)) + } + }); + it('nullable anyOf field', async () => { const dmmf = await getDMMF({ datamodel: datamodelPostGresQL_anyOfCheck,