diff --git a/packages/runtime/src/enhancements/node/policy/policy-utils.ts b/packages/runtime/src/enhancements/node/policy/policy-utils.ts index 2ee389476..7c340b07b 100644 --- a/packages/runtime/src/enhancements/node/policy/policy-utils.ts +++ b/packages/runtime/src/enhancements/node/policy/policy-utils.ts @@ -939,7 +939,7 @@ export class PolicyUtil extends QueryUtils { } } - if (schema) { + if (schema && !this.options.validation?.inputOnlyValidationForUpdate) { // TODO: push down schema check to the database this.validateZodSchema(model, undefined, result, true, (err) => { throw this.deniedByPolicy( diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 5027fb5c6..fe584bb1b 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -140,6 +140,11 @@ export type EnhancementOptions = { * The encryption options for using the `encrypted` enhancement. */ encryption?: SimpleEncryption | CustomEncryption; + + /** + * Options for data validation. + */ + validation?: ValidationOptions; }; /** @@ -209,3 +214,20 @@ export type CustomEncryption = { */ decrypt: (model: string, field: FieldInfo, cipher: string) => Promise; }; + +/** + * Options for data validation. + */ +export type ValidationOptions = { + /** + * Whether to validate "update" operations based only on the input data. By default, ZenStack + * validates the entity after a update operation completes (inside a transaction), and rejects + * the operation if validation fails. This implies the entire entity needs to satisfy the + * validation rules, even for fields that are not part of the update input data. + * + * You can use this option to toggle the behavior to only validate the input data. + * + * Default is `false`. + */ + inputOnlyValidationForUpdate?: boolean; +}; diff --git a/tests/regression/tests/issue-2025.test.ts b/tests/regression/tests/issue-2025.test.ts new file mode 100644 index 000000000..92a8b6388 --- /dev/null +++ b/tests/regression/tests/issue-2025.test.ts @@ -0,0 +1,42 @@ +import { loadSchema } from '@zenstackhq/testtools'; + +describe('issue 2025', () => { + it('regression', async () => { + const { enhanceRaw, prisma } = await loadSchema( + ` + model User { + id String @id @default(cuid()) + email String @unique @email + termsAndConditions Int? + @@allow('all', true) + } + ` + ); + + const user = await prisma.user.create({ + data: { + email: 'xyz', // invalid email + }, + }); + + const db = enhanceRaw(prisma, undefined, { validation: { inputOnlyValidationForUpdate: true } }); + await expect( + db.user.update({ + where: { id: user.id }, + data: { + termsAndConditions: 1, + }, + }) + ).toResolveTruthy(); + + const db1 = enhanceRaw(prisma); + await expect( + db1.user.update({ + where: { id: user.id }, + data: { + termsAndConditions: 1, + }, + }) + ).toBeRejectedByPolicy(); + }); +});