diff --git a/src/app.test.ts b/src/app.test.ts index bac286d98..a0658e08c 100644 --- a/src/app.test.ts +++ b/src/app.test.ts @@ -169,6 +169,20 @@ await test('createApp', async (t) => { assert.deepEqual(data, { error: 'Body must be a JSON object' }) }) + await t.test('GET /businesses?categories.id=... filters on array fields', async () => { + const business1 = { id: '1', categories: [{ id: 1 }, { id: 2 }] } + const business2 = { id: '2', categories: [{ id: 3 }] } + db.data = { businesses: [business1, business2] } + + const matching = await fetch(`http://localhost:${port}/businesses?categories.id=2`) + assert.equal(matching.status, 200) + assert.deepEqual(await matching.json(), [business1]) + + const empty = await fetch(`http://localhost:${port}/businesses?categories.id=10`) + assert.equal(empty.status, 200) + assert.deepEqual(await empty.json(), []) + }) + await t.test('PUT /posts/1 with null body returns 400', async () => { const response = await fetch(`http://localhost:${port}/posts/1`, { method: 'PUT', diff --git a/src/matches-where.test.ts b/src/matches-where.test.ts index 26a2be4a9..85744f866 100644 --- a/src/matches-where.test.ts +++ b/src/matches-where.test.ts @@ -6,7 +6,17 @@ import type { JsonObject } from 'type-fest' import { matchesWhere } from './matches-where.ts' await test('matchesWhere', async (t) => { - const obj: JsonObject = { a: 10, b: 20, c: 'x', nested: { a: 10, b: 20 } } + const obj: JsonObject = { + a: 10, + b: 20, + c: 'x', + nested: { a: 10, b: 20 }, + items: [ + { id: 1, name: 'foo' }, + { id: 2, name: 'bar' }, + ], + tags: ['red', 'blue'], + } const cases: [JsonObject, boolean][] = [ [{ a: { eq: 10 } }, true], [{ a: { eq: 11 } }, false], @@ -53,6 +63,21 @@ await test('matchesWhere', async (t) => { [{ c: { endsWith: 'z' } }, false], [{ a: { endsWith: '1' } }, false], [{ c: { endsWith: 1 } }, false], + // Array fields match when at least one item matches + [{ items: { id: { eq: 1 } } }, true], + [{ items: { id: { eq: 2 } } }, true], + [{ items: { id: { eq: 10 } } }, false], + [{ items: { id: { gt: 1 } } }, true], + [{ items: { id: { gt: 2 } } }, false], + [{ items: { name: { contains: 'BA' } } }, true], + [{ items: { name: { contains: 'baz' } } }, false], + // All conditions must be met by the same item + [{ items: { id: { eq: 1 }, name: { eq: 'foo' } } }, true], + [{ items: { id: { eq: 1 }, name: { eq: 'bar' } } }, false], + // Arrays of primitives have no properties to match + [{ tags: { id: { eq: 1 } } }, false], + [{ or: [{ items: { id: { eq: 10 } } }, { a: { eq: 10 } }] }, true], + [{ or: [{ items: { id: { eq: 10 } } }, { a: { eq: 11 } }] }, false], ] for (const [query, expected] of cases) { diff --git a/src/matches-where.ts b/src/matches-where.ts index 036b00c69..3340c90e8 100644 --- a/src/matches-where.ts +++ b/src/matches-where.ts @@ -72,6 +72,13 @@ export function matchesWhere(obj: JsonObject, where: JsonObject): boolean { continue } + // An array matches if at least one of its items matches + if (Array.isArray(field)) { + const matched = field.some((item) => isJSONObject(item) && matchesWhere(item, value)) + if (!matched) return false + continue + } + if (isJSONObject(field)) { if (!matchesWhere(field, value)) return false }