mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
fix(oc-path): quote positional JSONC keys (#130499)
This commit is contained in:
@@ -11,6 +11,7 @@ import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
||||
import { isMap, isScalar, isSeq, type Node, type Pair } from "yaml";
|
||||
import type { MdAst } from "./ast.js";
|
||||
import type { JsoncValue } from "./jsonc/ast.js";
|
||||
import { resolveJsoncPositionalSegment } from "./jsonc/resolve-value.js";
|
||||
import type { JsonlAst, JsonlLine } from "./jsonl/ast.js";
|
||||
import { pickJsonlLine } from "./jsonl/line.js";
|
||||
import type { OcPath, PredicateSpec } from "./oc-path.js";
|
||||
@@ -303,11 +304,15 @@ const jsoncOps: WalkOps<JsoncValue> = {
|
||||
return null;
|
||||
},
|
||||
positional(node, seg) {
|
||||
const concrete = positionalForJsoncNode(node, seg);
|
||||
const concrete = resolveJsoncPositionalSegment(node, seg);
|
||||
if (concrete === null) {
|
||||
return null;
|
||||
}
|
||||
return jsoncOps.lookup(node, concrete);
|
||||
const match = jsoncOps.lookup(node, concrete);
|
||||
if (match === null || node.kind !== "object") {
|
||||
return match;
|
||||
}
|
||||
return { keySub: quoteSeg(concrete), child: match.child };
|
||||
},
|
||||
*predicate(node, pred) {
|
||||
if (node.kind === "object") {
|
||||
@@ -327,17 +332,6 @@ const jsoncOps: WalkOps<JsoncValue> = {
|
||||
walk: walkJsonc,
|
||||
};
|
||||
|
||||
function positionalForJsoncNode(node: JsoncValue, seg: string): string | null {
|
||||
if (node.kind === "object") {
|
||||
const keys = node.entries.map((e) => e.key);
|
||||
return resolvePositionalSeg(seg, { indexable: false, size: keys.length, keys });
|
||||
}
|
||||
if (node.kind === "array") {
|
||||
return resolvePositionalSeg(seg, { indexable: true, size: node.items.length });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ---------- JSONL walker ---------------------------------------------------
|
||||
|
||||
// First slot is a line address; subsequent slots descend into the
|
||||
|
||||
@@ -5,13 +5,13 @@ import {
|
||||
isPositionalSeg,
|
||||
isQuotedSeg,
|
||||
parseArrayIndexSegment,
|
||||
resolvePositionalSeg,
|
||||
splitRespectingBrackets,
|
||||
unquoteSeg,
|
||||
} from "../oc-path.js";
|
||||
import { OcEmitSentinelError, REDACTED_SENTINEL } from "../sentinel.js";
|
||||
import type { JsoncAst, JsoncValue } from "./ast.js";
|
||||
import { parseJsonc } from "./parse.js";
|
||||
import { resolveJsoncPositionalSegment } from "./resolve-value.js";
|
||||
|
||||
type JsoncEditPath = Array<string | number>;
|
||||
type JsoncEditTarget = { readonly path: JsoncEditPath; readonly value: JsoncValue };
|
||||
@@ -126,7 +126,7 @@ function resolveEditTarget(root: JsoncValue, segments: readonly string[]): Jsonc
|
||||
return null;
|
||||
}
|
||||
if (isPositionalSeg(segment)) {
|
||||
const concrete = positionalForJsonc(current, segment);
|
||||
const concrete = resolveJsoncPositionalSegment(current, segment);
|
||||
if (concrete !== null) {
|
||||
segment = concrete;
|
||||
}
|
||||
@@ -154,17 +154,6 @@ function resolveEditTarget(root: JsoncValue, segments: readonly string[]): Jsonc
|
||||
return { path: out, value: current };
|
||||
}
|
||||
|
||||
function positionalForJsonc(node: JsoncValue, segment: string): string | null {
|
||||
if (node.kind === "object") {
|
||||
const keys = node.entries.map((entry) => entry.key);
|
||||
return resolvePositionalSeg(segment, { indexable: false, size: keys.length, keys });
|
||||
}
|
||||
if (node.kind === "array") {
|
||||
return resolvePositionalSeg(segment, { indexable: true, size: node.items.length });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function jsoncValueToJson(value: JsoncValue): unknown {
|
||||
switch (value.kind) {
|
||||
case "object":
|
||||
|
||||
@@ -23,7 +23,7 @@ export function resolveJsoncValueOcPath(
|
||||
return null;
|
||||
}
|
||||
if (isPositionalSeg(seg)) {
|
||||
const concrete = positionalForJsonc(current, seg);
|
||||
const concrete = resolveJsoncPositionalSegment(current, seg);
|
||||
if (concrete !== null) {
|
||||
seg = concrete;
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export function resolveJsoncValueOcPath(
|
||||
return { kind: "value", node: current, path: walked };
|
||||
}
|
||||
|
||||
function positionalForJsonc(node: JsoncValue, seg: string): string | null {
|
||||
export function resolveJsoncPositionalSegment(node: JsoncValue, seg: string): string | null {
|
||||
if (node.kind === "object") {
|
||||
const keys = node.entries.map((e) => e.key);
|
||||
return resolvePositionalSeg(seg, { indexable: false, size: keys.length, keys });
|
||||
|
||||
@@ -117,6 +117,31 @@ describe("findOcPaths — JSONC kind", () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "quotes positional object keys",
|
||||
raw: '{"items":{"zeta.key":10,"alpha":20}}',
|
||||
pattern: "oc://config/items/$first",
|
||||
expectedPath: 'oc://config/items/"zeta.key"',
|
||||
expectedValue: "10",
|
||||
},
|
||||
{
|
||||
name: "emits positional array indexes",
|
||||
raw: '{"items":[10,20,30]}',
|
||||
pattern: "oc://config/items/$last",
|
||||
expectedPath: "oc://config/items/2",
|
||||
expectedValue: "30",
|
||||
},
|
||||
])("$name", ({ raw, pattern, expectedPath, expectedValue }) => {
|
||||
const ast = parseJsonc(raw).ast;
|
||||
const out = findOcPaths(ast, parseOcPath(pattern));
|
||||
|
||||
expect(out).toHaveLength(1);
|
||||
const result = requireFirstResult(out);
|
||||
expect(formatOcPath(result.path)).toBe(expectedPath);
|
||||
expect(result.match.kind === "leaf" && result.match.valueText).toBe(expectedValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe("findOcPaths — slash-deep JSONC paths", () => {
|
||||
|
||||
Reference in New Issue
Block a user