Closed nwidynski closed 7 months ago
Hi @nwidynski , thanks for filing this! I'll look into it and make a fix soon if it's confirmed a bug.
Hi @nwidynski , I've pushed a new v1.10.1 release which should handle non-exising fields properly during validation. Could you give it a try? Thanks!
@ymc9 it really got better, thanks!
Only one problem is remaining when using other operators like in
for example:
model ValidationTest extends BaseEntity, PublicBase {
id Int @id @default(autoincrement())
type String
name String
@@validate(type in [
'fee',
'tax'
],
'type must be one of the following values: fee, tax'
)
}
describe('Update validation test', () => {
it('should resolve', async () => {
const enhancedPrisma = enhance(new PrismaClient());
const validationTest = await enhancedPrisma.validationTest.create({
data: {
type: 'application_fee',
name: 'validation name',
},
});
await enhancedPrisma.validationTest.update({
where: {
id: validationTest.id,
},
data: {
name: 'validation name updated',
},
});
});
});
Here the validationTest.update
is failing.
Error calling enhanced Prisma method
update: denied by policy: validationTest entities failed 'update' check, input failed validation: Validation error: type must be one of the following values: fee, tax
Oh, sorry I missed testing the in
operator. There's some intricacy about it. I've made one more fix with a bunch more test cases. Also the CLI checking has been tightened up for cases that are not supposed to support. Please see if the new 1.10.3 build works. Thanks!
Thanks for the update @ymc9 it really looks even better than before!
But now we are experiencing one very confusing error with @@validate
and I don't know how it is connected to any change.
Basically the usage of deleted_at
does not work anymore:
model ValidationTest {
id Int @id @default(autoincrement())
type String
name String?
deleted_at DateTime? @db.Timestamp(6)
deleted_at2 DateTime? @db.Timestamp(6)
@@validate(type in [
'fee',
'tax'
],
'type must be one of the following values: fee, tax'
)
@@allow('all', true)
@@map('validation_test')
@@schema("public")
}
When using this model and using this test suite:
describe('Update validation test', () => {
it.only('should resolve', async () => {
const enhancedPrisma = enhance(testContext.prismaClient);
const validationTest = await enhancedPrisma.validationTest.create({
data: {
type: 'fee',
name: 'validation name',
},
});
await enhancedPrisma.validationTest.updateMany({
where: {
id: validationTest.id,
},
data: {
deleted_at: new Date(),
},
});
});
});
We are receiving this error:
"Error calling enhanced Prisma method updateMany
: denied by policy: validationTest entities failed 'postUpdate' check, entity { id: 26 } failed policy check"
Strange thing is, when we are executing this test:
describe('Update validation test', () => {
it.only('should resolve', async () => {
const enhancedPrisma = enhance(testContext.prismaClient);
const validationTest = await enhancedPrisma.validationTest.create({
data: {
type: 'fee',
name: 'validation name',
},
});
await enhancedPrisma.validationTest.updateMany({
where: {
id: validationTest.id,
},
data: {
deleted_at2: new Date(),
},
});
});
});
it works. It looks more like a general issue, but I investigated that when I remove the validation part of the model:
model ValidationTest {
id Int @id @default(autoincrement())
type String
name String?
deleted_at DateTime? @db.Timestamp(6)
deleted_at2 DateTime? @db.Timestamp(6)
@@allow('all', true)
@@map('validation_test')
@@schema("public")
}
Then this test will also work:
describe('Update validation test', () => {
it.only('should resolve', async () => {
const enhancedPrisma = enhance(testContext.prismaClient);
const validationTest = await enhancedPrisma.validationTest.create({
data: {
type: 'fee',
name: 'validation name',
},
});
await enhancedPrisma.validationTest.updateMany({
where: {
id: validationTest.id,
},
data: {
deleted_at: new Date(),
},
});
});
});
It does not make any sense as it seems deleted_at
is neither a reserved keyword inside prisma nor zenstack. Also its strange that it only fails when using it combined with @@validate
.
I hope this is enough information to work with it. Otherwise just tell me and I try to give you a better example.
Okay sorry it seems like it could be connected to our prisma extensions will investigate this further
So we checked this further and the problem seems to be some kind of findFirst
call which will be added through the addition of @@validation
.
We are doing some manual kind of deletion checks with prisa, basically we override the findFirst
(and other) methods to not find deleted properties anymore.
But after the softDeletion
call it can't be found as it is already deleted. We could change it to the "zenstack way" by doing @@deny('read', deleted_at != null)
. But we are also using the non enhanced prisma client at some places and there we would loose the ability of doing a normal find (many other methods) and get/ update values which are not deleted without adding it to the query every time + not having too many custom queries.
Therefore our questions:
@@validate
to only check the given input params instead of doing a findFirst after applying updates? I guess this will have good reasoning why it is done, but still we wanted to ask this question. As it seems the errors could also be thrown way earlier which could save some time if we would throw errors if the params can not be validated and we don't have to execute the queries.deleted_at
and maybe also on other places.
For example, is it possible to override the prisma transaction id manually and give it some prefix? When looking into the arguments of findOne
we can see __internalParams
and inside of this attribute we have the transaction
which has this form:
{
"kind": "itx",
"id": "cltn0zdrd00013r0ekdelheoz",
}
Just for a better understanding, this is what our current impl. of findOne looks like:
query: {
$allModels: {
async findFirst({ model, args, query }) {
const isDeleteable = doesModelIncludeField(model, 'deleted_at');
if (isDeleteable) {
if (containsAttribute('deleted_at', args.where || {})) {
return query(args);
}
args.where = { ...args.where, deleted_at: null };
return query(args);
}
return query(args);
},
},
},
I think I've understood the issue. This is where ZenStack enhancement and client extensions can have some unexpected interactions ...
Just to explain what the findFirst
is for. ZenStack may do a read-back after a mutation for several reasons, including:
future()
calls), the result is read back to check it.I did some debugging and also skimmed through some of Prisma's code but couldn't find a way to "tag" the operations initiated by ZenStack. Transaction ID looks like a nice way to do it, but I couldn't find a way to influence it ... I also tried to inject some bogus fields into args, but Prisma does pretty strict checking against the input.
In your case, is it possible to enhance the original PrismaClient without the client extension installed?
To further proceed we just switched to the recommended way of doing soft deletes from zenstack. So with this change everything is working fine, I guess this issue can be closed. Thank you so much @ymc9! :)
To further proceed we just switched to the recommended way of doing soft deletes from zenstack. So with this change everything is working fine, I guess this issue can be closed. Thank you so much @ymc9! :)
Thank you for helping us find these issues. Really appreciate it!
Description and expected behavior When implementing model-level validation and running
UPDATE
actions, Zenstack doesn't pass non-existent properties. Since a property isn't mutated when its not passed, it should not invalidate the action.Screenshots
Environment (please complete the following information):