diff --git a/packages/casl-prisma/schema.prisma b/packages/casl-prisma/schema.prisma index f4b41347a..d6354f56a 100644 --- a/packages/casl-prisma/schema.prisma +++ b/packages/casl-prisma/schema.prisma @@ -12,6 +12,7 @@ model User { firstName String lastName String age Int + verified Boolean? posts Post[] } diff --git a/packages/casl-prisma/spec/prismaQuery.spec.ts b/packages/casl-prisma/spec/prismaQuery.spec.ts index dd13bd52b..ff39de61c 100644 --- a/packages/casl-prisma/spec/prismaQuery.spec.ts +++ b/packages/casl-prisma/spec/prismaQuery.spec.ts @@ -75,6 +75,22 @@ describe('PrismaQuery evaluation', () => { expect(test({ name: 'Jane Doe' })).toBe(false) }) }) + describe('isSet', () => { + it('throws if value is not a boolean', () => { + expect(() => prismaQuery({ items: { isSet: 1 } })).toThrow(/expects to receive a boolean/) + expect(() => prismaQuery({ items: { isSet: {} } })).toThrow(/expects to receive a boolean/) + expect(() => prismaQuery({ items: { isSet: true } })).not.toThrow() + }) + + it('checks that object value is not defined when using "isSet"', () => { + const test = prismaQuery({ verified: { isSet: true } }) + expect(test({ verified: true })).toBe(true) + expect(test({ verified: false })).toBe(true) + expect(test({ verified: null })).toBe(true) + expect(test({ verified: undefined })).toBe(false) + expect(test({})).toBe(false) + }) + }) describe('in', () => { it('throws if passed value is not an array', () => { diff --git a/packages/casl-prisma/src/prisma/PrismaQueryParser.ts b/packages/casl-prisma/src/prisma/PrismaQueryParser.ts index f0d1c764b..4078bc59f 100644 --- a/packages/casl-prisma/src/prisma/PrismaQueryParser.ts +++ b/packages/casl-prisma/src/prisma/PrismaQueryParser.ts @@ -41,6 +41,15 @@ const not: FieldInstruction = { } }; +const isSet: FieldInstruction = { + type: 'field', + validate(instruction, value) { + if (typeof value !== 'boolean') { + throw ParsingQueryError.invalidArgument(instruction.name, value, 'a boolean'); + } + } +} + const within: FieldInstruction = { type: 'field', validate(instruction, value) { @@ -195,6 +204,7 @@ const instructions = { none: inverted('some', relation), is: relation, isNot: inverted('is', relation), + isSet }; export interface ParseOptions { diff --git a/packages/casl-prisma/src/prisma/interpretPrismaQuery.ts b/packages/casl-prisma/src/prisma/interpretPrismaQuery.ts index 930dda21f..0919acc1d 100644 --- a/packages/casl-prisma/src/prisma/interpretPrismaQuery.ts +++ b/packages/casl-prisma/src/prisma/interpretPrismaQuery.ts @@ -79,6 +79,11 @@ const not: JsInterpreter = (condition, object, { interpret }) return condition.value.every(subCondition => !interpret(subCondition, object)); }; +const isSet: JsInterpreter> = (condition, object, { get }) => { + const item = get(object, condition.field); + return item !== undefined; +} + function toComparable(value: unknown) { return value && typeof value === 'object' ? value.valueOf() : value; } @@ -112,6 +117,7 @@ export const interpretPrismaQuery = createJsInterpreter({ every, some, is, + isSet, }, { get: (object, field) => object[field], compare: compareValues,