diff --git a/.changeset/lemon-years-sniff.md b/.changeset/lemon-years-sniff.md new file mode 100644 index 000000000000..473a4bbe19ea --- /dev/null +++ b/.changeset/lemon-years-sniff.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +New filter validation logic supporting set and range queries in Vectorize CLI diff --git a/packages/wrangler/src/__tests__/vectorize/vectorize.test.ts b/packages/wrangler/src/__tests__/vectorize/vectorize.test.ts index 37cbdd9d94ff..1d2e132bf6aa 100644 --- a/packages/wrangler/src/__tests__/vectorize/vectorize.test.ts +++ b/packages/wrangler/src/__tests__/vectorize/vectorize.test.ts @@ -727,11 +727,11 @@ describe("vectorize commands", () => { describe("vectorize query filter", () => { it("should parse correctly", async () => { let jsonString = - '{ "p1": "abc", "p2": { "$ne": true }, "p3": 10, "p4": false, "nested.p5": "abcd" }'; // Successful parse + '{ "p1": "abc", "p2": { "$ne": true }, "p3": 10, "p4": false, "nested.p5": "abcd", "p6": { "$in": ["a", 3, 4] }, "p7": {"$gt": 4, "$lte": "aaa"} }'; // Successful parse expect( JSON.stringify(validateQueryFilter(JSON.parse(jsonString))) ).toMatchInlineSnapshot( - `"{\\"p1\\":\\"abc\\",\\"p2\\":{\\"$ne\\":true},\\"p3\\":10,\\"p4\\":false,\\"nested.p5\\":\\"abcd\\"}"` + `"{\\"p1\\":\\"abc\\",\\"p2\\":{\\"$ne\\":true},\\"p3\\":10,\\"p4\\":false,\\"nested.p5\\":\\"abcd\\",\\"p6\\":{\\"$in\\":[\\"a\\",3,4]},\\"p7\\":{\\"$gt\\":4,\\"$lte\\":\\"aaa\\"}}"` ); jsonString = diff --git a/packages/wrangler/src/vectorize/query.ts b/packages/wrangler/src/vectorize/query.ts index 675d193481da..65fe838b37fe 100644 --- a/packages/wrangler/src/vectorize/query.ts +++ b/packages/wrangler/src/vectorize/query.ts @@ -8,13 +8,11 @@ import type { } from "../yargs-types"; import type { VectorizeMatches, - VectorizeMetadataFilterInnerValue, VectorizeMetadataFilterValue, VectorizeMetadataRetrievalLevel, VectorizeQueryOptions, VectorizeVectorMetadataFilter, VectorizeVectorMetadataFilterOp, - VectorizeVectorMetadataValue, } from "./types"; export function options(yargs: CommonYargsArgv) { @@ -155,6 +153,12 @@ export async function handler( logger.log(JSON.stringify(res, null, 2)); } +function validateQueryFilterInnerValue( + innerValue: VectorizeMetadataFilterValue +) { + return ["string", "number", "boolean"].includes(typeof innerValue); +} + export function validateQueryFilter( input: object ): VectorizeVectorMetadataFilter | null { @@ -176,7 +180,7 @@ export function validateQueryFilter( for (const field in parsedObj) { if (Object.prototype.hasOwnProperty.call(parsedObj, field)) { const value = ( - parsedObj as Record + parsedObj as Record )[field]; if (Array.isArray(value)) { @@ -186,38 +190,28 @@ export function validateQueryFilter( if (typeof value === "object" && value !== null) { // Handle nested objects - const innerObj: Partial<{ - [Op in VectorizeVectorMetadataFilterOp]?: Exclude< - VectorizeVectorMetadataValue, - string[] - > | null; - }> = {}; + const innerObj: VectorizeVectorMetadataFilter = {}; let validInnerObj = true; for (const op in value) { if (Object.prototype.hasOwnProperty.call(value, op)) { - if (!["$eq", "$ne"].includes(op)) { - // Skip objects with invalid operators - validInnerObj = false; - break; - } - const innerValue = (value as VectorizeMetadataFilterInnerValue)[ - op as VectorizeVectorMetadataFilterOp - ]; - if (Array.isArray(innerValue)) { - // Skip arrays in nested objects - validInnerObj = false; - break; - } - if ( - typeof innerValue === "object" && - innerValue !== null && - Object.keys(innerValue).length === 0 - ) { - // Skip empty objects in nested objects + const innerValue = value[op]; + if (["$eq", "$ne", "$lt", "$lte", "$gt", "gte"].includes(op)) { + if (!validateQueryFilterInnerValue(innerValue)) { + validInnerObj = false; + } + } else if (["$in", "$nin"].includes(op)) { + if (!Array.isArray(innerValue)) { + validInnerObj = false; + } else { + if (!innerValue.every(validateQueryFilterInnerValue)) { + validInnerObj = false; + } + } + } else { validInnerObj = false; - break; } + innerObj[op as VectorizeVectorMetadataFilterOp] = innerValue; } } diff --git a/packages/wrangler/src/vectorize/types.ts b/packages/wrangler/src/vectorize/types.ts index 7fd7f6ffa62f..910e5c1e96e2 100644 --- a/packages/wrangler/src/vectorize/types.ts +++ b/packages/wrangler/src/vectorize/types.ts @@ -25,11 +25,19 @@ export type VectorFloatArray = Float32Array | Float64Array; * * This list is expected to grow as support for more operations are released. */ -export type VectorizeVectorMetadataFilterOp = "$eq" | "$ne"; +export type VectorizeMetadataFilterEqualityOp = "$eq" | "$ne"; +export type VectorizeMetadataFilterSetOp = "$in" | "$nin"; +export type VectorizeMetadataFilterRangeOp = "$lt" | "$lte" | "$gt" | "$gte"; + +export type VectorizeVectorMetadataFilterOp = + | VectorizeMetadataFilterEqualityOp + | VectorizeMetadataFilterSetOp + | VectorizeMetadataFilterRangeOp; export type VectorizeMetadataFilterInnerValue = Record< VectorizeVectorMetadataFilterOp, - Exclude + | Exclude + | Exclude[] >; export type VectorizeMetadataFilterValue =