mirror of https://github.com/openclaw/openclaw.git
209 lines
6.6 KiB
TypeScript
209 lines
6.6 KiB
TypeScript
import { isDeepStrictEqual } from "node:util";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
import { isRecord } from "./shared.js";
|
|
|
|
function isArrayIndexSegment(segment: string): boolean {
|
|
return /^\d+$/.test(segment);
|
|
}
|
|
|
|
function expectedContainer(nextSegment: string): "array" | "object" {
|
|
return isArrayIndexSegment(nextSegment) ? "array" : "object";
|
|
}
|
|
|
|
function parseArrayLeafTarget(
|
|
cursor: unknown,
|
|
leaf: string,
|
|
segments: string[],
|
|
): { array: unknown[]; index: number } | null {
|
|
if (!Array.isArray(cursor)) {
|
|
return null;
|
|
}
|
|
if (!isArrayIndexSegment(leaf)) {
|
|
throw new Error(`Invalid array index segment "${leaf}" at ${segments.join(".")}.`);
|
|
}
|
|
return { array: cursor, index: Number.parseInt(leaf, 10) };
|
|
}
|
|
|
|
function traverseToLeafParent(params: {
|
|
root: unknown;
|
|
segments: string[];
|
|
requireExistingSegment: boolean;
|
|
}): unknown {
|
|
if (params.segments.length === 0) {
|
|
throw new Error("Target path is empty.");
|
|
}
|
|
|
|
let cursor: unknown = params.root;
|
|
for (let index = 0; index < params.segments.length - 1; index += 1) {
|
|
const segment = params.segments[index] ?? "";
|
|
if (Array.isArray(cursor)) {
|
|
if (!isArrayIndexSegment(segment)) {
|
|
throw new Error(
|
|
`Invalid array index segment "${segment}" at ${params.segments.join(".")}.`,
|
|
);
|
|
}
|
|
const arrayIndex = Number.parseInt(segment, 10);
|
|
if (params.requireExistingSegment && (arrayIndex < 0 || arrayIndex >= cursor.length)) {
|
|
throw new Error(
|
|
`Path segment does not exist at ${params.segments.slice(0, index + 1).join(".")}.`,
|
|
);
|
|
}
|
|
cursor = cursor[arrayIndex];
|
|
continue;
|
|
}
|
|
|
|
if (!isRecord(cursor)) {
|
|
throw new Error(
|
|
`Invalid path shape at ${params.segments.slice(0, index).join(".") || "<root>"}.`,
|
|
);
|
|
}
|
|
if (params.requireExistingSegment && !Object.prototype.hasOwnProperty.call(cursor, segment)) {
|
|
throw new Error(
|
|
`Path segment does not exist at ${params.segments.slice(0, index + 1).join(".")}.`,
|
|
);
|
|
}
|
|
cursor = cursor[segment];
|
|
}
|
|
return cursor;
|
|
}
|
|
|
|
export function getPath(root: unknown, segments: string[]): unknown {
|
|
if (segments.length === 0) {
|
|
return undefined;
|
|
}
|
|
let cursor: unknown = root;
|
|
for (const segment of segments) {
|
|
if (Array.isArray(cursor)) {
|
|
if (!isArrayIndexSegment(segment)) {
|
|
return undefined;
|
|
}
|
|
cursor = cursor[Number.parseInt(segment, 10)];
|
|
continue;
|
|
}
|
|
if (!isRecord(cursor)) {
|
|
return undefined;
|
|
}
|
|
cursor = cursor[segment];
|
|
}
|
|
return cursor;
|
|
}
|
|
|
|
export function setPathCreateStrict(
|
|
root: OpenClawConfig,
|
|
segments: string[],
|
|
value: unknown,
|
|
): boolean {
|
|
if (segments.length === 0) {
|
|
throw new Error("Target path is empty.");
|
|
}
|
|
let cursor: unknown = root;
|
|
let changed = false;
|
|
|
|
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
const segment = segments[index] ?? "";
|
|
const nextSegment = segments[index + 1] ?? "";
|
|
const needs = expectedContainer(nextSegment);
|
|
|
|
if (Array.isArray(cursor)) {
|
|
if (!isArrayIndexSegment(segment)) {
|
|
throw new Error(`Invalid array index segment "${segment}" at ${segments.join(".")}.`);
|
|
}
|
|
const arrayIndex = Number.parseInt(segment, 10);
|
|
const existing = cursor[arrayIndex];
|
|
if (existing === undefined || existing === null) {
|
|
cursor[arrayIndex] = needs === "array" ? [] : {};
|
|
changed = true;
|
|
} else if (needs === "array" ? !Array.isArray(existing) : !isRecord(existing)) {
|
|
throw new Error(`Invalid path shape at ${segments.slice(0, index + 1).join(".")}.`);
|
|
}
|
|
cursor = cursor[arrayIndex];
|
|
continue;
|
|
}
|
|
|
|
if (!isRecord(cursor)) {
|
|
throw new Error(`Invalid path shape at ${segments.slice(0, index).join(".") || "<root>"}.`);
|
|
}
|
|
const existing = cursor[segment];
|
|
if (existing === undefined || existing === null) {
|
|
cursor[segment] = needs === "array" ? [] : {};
|
|
changed = true;
|
|
} else if (needs === "array" ? !Array.isArray(existing) : !isRecord(existing)) {
|
|
throw new Error(`Invalid path shape at ${segments.slice(0, index + 1).join(".")}.`);
|
|
}
|
|
cursor = cursor[segment];
|
|
}
|
|
|
|
const leaf = segments[segments.length - 1] ?? "";
|
|
const arrayTarget = parseArrayLeafTarget(cursor, leaf, segments);
|
|
if (arrayTarget) {
|
|
if (!isDeepStrictEqual(arrayTarget.array[arrayTarget.index], value)) {
|
|
arrayTarget.array[arrayTarget.index] = value;
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
if (!isRecord(cursor)) {
|
|
throw new Error(`Invalid path shape at ${segments.slice(0, -1).join(".") || "<root>"}.`);
|
|
}
|
|
if (!isDeepStrictEqual(cursor[leaf], value)) {
|
|
cursor[leaf] = value;
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
export function setPathExistingStrict(
|
|
root: OpenClawConfig,
|
|
segments: string[],
|
|
value: unknown,
|
|
): boolean {
|
|
const cursor = traverseToLeafParent({ root, segments, requireExistingSegment: true });
|
|
|
|
const leaf = segments[segments.length - 1] ?? "";
|
|
const arrayTarget = parseArrayLeafTarget(cursor, leaf, segments);
|
|
if (arrayTarget) {
|
|
if (arrayTarget.index < 0 || arrayTarget.index >= arrayTarget.array.length) {
|
|
throw new Error(`Path segment does not exist at ${segments.join(".")}.`);
|
|
}
|
|
if (!isDeepStrictEqual(arrayTarget.array[arrayTarget.index], value)) {
|
|
arrayTarget.array[arrayTarget.index] = value;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (!isRecord(cursor)) {
|
|
throw new Error(`Invalid path shape at ${segments.slice(0, -1).join(".") || "<root>"}.`);
|
|
}
|
|
if (!Object.prototype.hasOwnProperty.call(cursor, leaf)) {
|
|
throw new Error(`Path segment does not exist at ${segments.join(".")}.`);
|
|
}
|
|
if (!isDeepStrictEqual(cursor[leaf], value)) {
|
|
cursor[leaf] = value;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function deletePathStrict(root: OpenClawConfig, segments: string[]): boolean {
|
|
const cursor = traverseToLeafParent({ root, segments, requireExistingSegment: false });
|
|
|
|
const leaf = segments[segments.length - 1] ?? "";
|
|
const arrayTarget = parseArrayLeafTarget(cursor, leaf, segments);
|
|
if (arrayTarget) {
|
|
if (arrayTarget.index < 0 || arrayTarget.index >= arrayTarget.array.length) {
|
|
return false;
|
|
}
|
|
// Arrays are compacted to preserve predictable index semantics.
|
|
arrayTarget.array.splice(arrayTarget.index, 1);
|
|
return true;
|
|
}
|
|
if (!isRecord(cursor)) {
|
|
throw new Error(`Invalid path shape at ${segments.slice(0, -1).join(".") || "<root>"}.`);
|
|
}
|
|
if (!Object.prototype.hasOwnProperty.call(cursor, leaf)) {
|
|
return false;
|
|
}
|
|
delete cursor[leaf];
|
|
return true;
|
|
}
|