Skip to content

Commit 48fcdd9

Browse files
committed
test: require 100% coverage on the query catalog
Gate src/spec/query-capabilities.ts and src/runtime/query-capabilities.ts at 100% statements/branches/functions/lines. JSONPath harvest now keeps [?(@.key==value)] intact instead of splitting on the inner dot.
1 parent b748dfc commit 48fcdd9

3 files changed

Lines changed: 856 additions & 63 deletions

File tree

‎src/spec/query-capabilities.ts‎

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -460,11 +460,11 @@ function coalesceSearchable(
460460
global: string[] | null | undefined,
461461
): string[] | null | undefined {
462462
if (entity !== undefined) {
463-
if (entity === null) return []
463+
if (entity === null) return null
464464
if (global === null || global === undefined) return entity
465465
return unionStrings(global, entity)
466466
}
467-
if (global === null) return []
467+
if (global === null) return null
468468
return global
469469
}
470470

@@ -615,8 +615,15 @@ export function opsAreClosed(field: EffectiveFilterField, caps: EffectiveQueryCa
615615
export function fieldAllows(field: EffectiveFilterField, op: FilterOp, caps: EffectiveQueryCapabilities): boolean {
616616
const ops = opsForField(field, caps)
617617
if (ops.includes(op)) return true
618-
for (const [alias, target] of Object.entries(caps.aliases)) {
619-
if (target === op && isFilterOp(alias) && ops.includes(alias)) return true
618+
const names = Object.keys(caps.aliases)
619+
let i = 0
620+
while (i < names.length) {
621+
const alias = names[i] as string
622+
i += 1
623+
const target = caps.aliases[alias as FilterOp]
624+
if (target !== op) continue
625+
if (!isFilterOp(alias)) continue
626+
if (ops.includes(alias)) return true
620627
}
621628
return false
622629
}
@@ -676,7 +683,6 @@ export function applyHarvest(
676683
extra.map((field) => {
677684
const row: EffectiveFilterField = { field: field.field }
678685
if (field.type !== undefined) row.type = field.type
679-
if (field.ops !== undefined) row.ops = field.ops
680686
return row
681687
}),
682688
),
@@ -759,10 +765,9 @@ export function readJsonPathList(body: unknown, path: string): unknown[] {
759765

760766
function applyJsonPathFilter(node: unknown, token: string): unknown[] | undefined {
761767
const match = /^\?\(@\.([A-Za-z_][\w]*)==(true|false|null|"[^"]*"|'[^']*'|[^\s)]+)\)$/.exec(token)
762-
if (match === null || !Array.isArray(node)) return undefined
768+
if (match === null || match[1] === undefined || match[2] === undefined || !Array.isArray(node)) return undefined
763769
const key = match[1]
764-
const expected = parseJsonPathLiteral(match[2] ?? "")
765-
if (key === undefined) return undefined
770+
const expected = parseJsonPathLiteral(match[2])
766771
return node.filter((item) => {
767772
if (item === null || typeof item !== "object") return false
768773
return (item as Record<string, unknown>)[key] === expected
@@ -782,14 +787,28 @@ function parseJsonPathLiteral(raw: string): unknown {
782787

783788
function tokenizeJsonPath(path: string): string[] {
784789
const out: string[] = []
785-
for (const part of path.split(".")) {
786-
const match = /^([^[]*)(?:\[(\*|\d+|\?\([^)]+\))\])?$/.exec(part)
787-
if (match === null) {
788-
out.push(part)
790+
let buf = ""
791+
for (let i = 0; i < path.length; i++) {
792+
const ch = path[i]
793+
if (ch === ".") {
794+
if (buf !== "") out.push(buf)
795+
buf = ""
789796
continue
790797
}
791-
if (match[1] !== undefined && match[1] !== "") out.push(match[1])
792-
if (match[2] !== undefined) out.push(match[2])
798+
if (ch === "[") {
799+
if (buf !== "") out.push(buf)
800+
buf = ""
801+
const end = path.indexOf("]", i)
802+
if (end === -1) {
803+
out.push(path.slice(i))
804+
return out
805+
}
806+
out.push(path.slice(i + 1, end))
807+
i = end
808+
continue
809+
}
810+
buf += ch
793811
}
812+
if (buf !== "") out.push(buf)
794813
return out
795814
}

0 commit comments

Comments
 (0)