diff --git a/.changeset/tiny-tools-share.md b/.changeset/tiny-tools-share.md new file mode 100644 index 00000000..7834844d --- /dev/null +++ b/.changeset/tiny-tools-share.md @@ -0,0 +1,25 @@ +--- +"@styra/ucast-prisma": patch +--- + +Introduce laxer handling of empty compound conditions + +This aligns better with common Rego patterns, like using multi-value +rules for generating conditions: + +```rego +conditions.or contains {"tickets.resolved": false} if { ... } +``` + +If the RHS is not satisfied, the conditions would yield + +```json +{ + "conditions": { + "or": [] + } +} +``` + +and with this change, this will be valid. The condition itself is +going to be dropped. diff --git a/packages/ucast-prisma/src/instructions.ts b/packages/ucast-prisma/src/instructions.ts index d1439124..8cb0d56d 100644 --- a/packages/ucast-prisma/src/instructions.ts +++ b/packages/ucast-prisma/src/instructions.ts @@ -14,19 +14,9 @@ function ensureIsArray(instruction: { name: string }, value: any) { } } -function ensureIsNonEmptyArray(instruction: { name: string }, value: any[]) { - ensureIsArray(instruction, value); - - if (!value.length) { - throw new Error( - `"${instruction.name}" expects to have at least one element in array` - ); - } -} - export const and = { type: "compound", - validate: ensureIsNonEmptyArray, + validate: ensureIsArray, parse(instruction: { name: string }, queries: any[], { parse }: any) { const conditions = queries.map((query) => parse(query)); return new CompoundCondition(instruction.name, conditions); diff --git a/packages/ucast-prisma/tests/adapter.test.ts b/packages/ucast-prisma/tests/adapter.test.ts index 56ba8bd0..6afeca2e 100644 --- a/packages/ucast-prisma/tests/adapter.test.ts +++ b/packages/ucast-prisma/tests/adapter.test.ts @@ -4,24 +4,43 @@ import { describe, it, expect } from "vitest"; describe("ucastToPrisma", () => { describe("field operators", () => { it("converts (implicit) 'eq' to 'equals'", () => { - const p = ucastToPrisma({ "table.name": "test" }); - expect(p).toStrictEqual({ table: { name: { equals: "test" } } }); + const p = ucastToPrisma({ "table.name": "test" }, "table"); + expect(p).toStrictEqual({ name: { equals: "test" } }); }); it("converts 'eq' to 'equals'", () => { - const p = ucastToPrisma({ "table.name": { eq: "test" } }); - expect(p).toStrictEqual({ table: { name: { equals: "test" } } }); + const p = ucastToPrisma({ "table.name": { eq: "test" } }, "table"); + expect(p).toStrictEqual({ name: { equals: "test" } }); }); // NOTE(sr): interesting because the interpreter for `in` is `in_` (keyword clash) it("converts 'in' to 'in'", () => { - const p = ucastToPrisma({ "table.name": { in: ["a", "b"] } }); - expect(p).toStrictEqual({ table: { name: { in: ["a", "b"] } } }); + const p = ucastToPrisma({ "table.name": { in: ["a", "b"] } }, "table"); + expect(p).toStrictEqual({ name: { in: ["a", "b"] } }); }); it("converts 'notIn' to 'notIn'", () => { - const p = ucastToPrisma({ "table.name": { notIn: ["a", "b"] } }); - expect(p).toStrictEqual({ table: { name: { notIn: ["a", "b"] } } }); + const p = ucastToPrisma({ "table.name": { notIn: ["a", "b"] } }, "table"); + expect(p).toStrictEqual({ name: { notIn: ["a", "b"] } }); + }); + }); + describe("compound operators", () => { + it("allows empty 'or' (drops it)", () => { + const p = ucastToPrisma({ or: [] }, "table"); + expect(p).toStrictEqual({}); + }); + + it("projects 'or' by primary table", () => { + const p = ucastToPrisma( + { or: [{ "tickets.resolved": false }, { "users.name": "ceasar" }] }, + "tickets" + ); + expect(p).toStrictEqual({ + OR: [ + { resolved: { equals: false } }, + { users: { name: { equals: "ceasar" } } }, + ], + }); }); }); }); diff --git a/packages/ucast-prisma/tests/interpreters.test.ts b/packages/ucast-prisma/tests/interpreters.test.ts index cfb9906f..c47315de 100644 --- a/packages/ucast-prisma/tests/interpreters.test.ts +++ b/packages/ucast-prisma/tests/interpreters.test.ts @@ -79,6 +79,13 @@ describe("Condition interpreter", () => { }); }); + it("drops empty OR conditions", () => { + const condition = new CompoundCondition("or", []); + const f = interpret(condition); + + expect(f).toStrictEqual({}); + }); + it('generates query with OR for "or" per table', () => { const condition = new CompoundCondition("or", [ new FieldCondition("eq", "user.id", 12),