mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
move ijson frontend/backend to nextwave
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// ijson values are regular JSON values: string, number, boolean, null, object, array
|
||||
// path is an array of strings and numbers
|
||||
|
||||
type PathType = (string | number)[];
|
||||
|
||||
var simplePathStrRe = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
||||
|
||||
function formatPath(path: PathType): string {
|
||||
if (path.length == 0) {
|
||||
return "$";
|
||||
}
|
||||
let pathStr = "$";
|
||||
for (let pathPart of path) {
|
||||
if (typeof pathPart === "string") {
|
||||
if (simplePathStrRe.test(pathPart)) {
|
||||
pathStr += "." + pathPart;
|
||||
} else {
|
||||
pathStr += "[" + JSON.stringify(pathPart) + "]";
|
||||
}
|
||||
} else if (typeof pathPart === "number") {
|
||||
pathStr += "[" + pathPart + "]";
|
||||
} else {
|
||||
pathStr += ".*";
|
||||
}
|
||||
}
|
||||
return pathStr;
|
||||
}
|
||||
|
||||
function isArray(obj: any): boolean {
|
||||
return obj != null && Array.isArray(obj);
|
||||
}
|
||||
|
||||
function isObject(obj: any): boolean {
|
||||
return obj != null && obj instanceof Object && !isArray(obj);
|
||||
}
|
||||
|
||||
function getPath(obj: any, path: PathType): any {
|
||||
let cur = obj;
|
||||
for (let pathPart of path) {
|
||||
if (cur == null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof pathPart === "string") {
|
||||
if (isObject(cur)) {
|
||||
cur = cur[pathPart];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if (typeof pathPart === "number") {
|
||||
if (isArray(cur)) {
|
||||
cur = cur[pathPart];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
throw new Error("Invalid path part: " + pathPart);
|
||||
}
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
type SetPathOpts = {
|
||||
force?: boolean;
|
||||
remove?: boolean;
|
||||
combinefn?: (oldVal: any, newVal: any, opts: SetPathOpts) => any;
|
||||
};
|
||||
|
||||
function combineFn_arrayAppend(oldVal: any, newVal: any, opts: SetPathOpts): any {
|
||||
if (oldVal == null) {
|
||||
return [newVal];
|
||||
}
|
||||
if (!isArray(oldVal) && !opts.force) {
|
||||
throw new Error("Cannot append to non-array: " + oldVal);
|
||||
}
|
||||
if (!isArray(oldVal)) {
|
||||
return [newVal];
|
||||
}
|
||||
oldVal.push(newVal);
|
||||
return oldVal;
|
||||
}
|
||||
|
||||
function checkPath(path: PathType): boolean {
|
||||
if (!isArray(path)) {
|
||||
return false;
|
||||
}
|
||||
for (let pathPart of path) {
|
||||
if (typeof pathPart !== "string" && typeof pathPart !== "number") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function setPath(obj: any, path: PathType, value: any, opts: SetPathOpts) {
|
||||
if (opts == null) {
|
||||
opts = {};
|
||||
}
|
||||
if (opts.remove && value != null) {
|
||||
throw new Error("Cannot set value and remove at the same time");
|
||||
}
|
||||
if (path == null) {
|
||||
path = [];
|
||||
}
|
||||
if (!checkPath(path)) {
|
||||
throw new Error("Invalid path: " + formatPath(path));
|
||||
}
|
||||
return setPathInternal(obj, path, value, opts);
|
||||
}
|
||||
|
||||
function isEmpty(obj: any): boolean {
|
||||
if (obj == null) {
|
||||
return true;
|
||||
}
|
||||
if (isArray(obj)) {
|
||||
return obj.length == 0;
|
||||
}
|
||||
if (isObject(obj)) {
|
||||
for (let key in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function removeFromArr(arr: any[], idx: number): any[] {
|
||||
console.log("removefromarray", arr, idx);
|
||||
if (idx >= arr.length) {
|
||||
return arr;
|
||||
}
|
||||
if (idx == arr.length - 1) {
|
||||
arr.pop();
|
||||
if (arr.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
arr[idx] = null;
|
||||
return arr;
|
||||
}
|
||||
|
||||
function setPathInternal(obj: any, path: PathType, value: any, opts: SetPathOpts): any {
|
||||
if (path.length == 0) {
|
||||
if (opts.combinefn != null) {
|
||||
return opts.combinefn(obj, value, opts);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
const pathPart = path[0];
|
||||
if (typeof pathPart === "string") {
|
||||
if (obj == null) {
|
||||
if (opts.remove) {
|
||||
return null;
|
||||
}
|
||||
obj = {};
|
||||
}
|
||||
if (!isObject(obj)) {
|
||||
if (opts.force) {
|
||||
obj = {};
|
||||
} else {
|
||||
throw new Error("Cannot set path on non-object: " + obj);
|
||||
}
|
||||
}
|
||||
if (opts.remove && path.length == 1) {
|
||||
delete obj[pathPart];
|
||||
if (isEmpty(obj)) {
|
||||
return null;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
const newVal = setPathInternal(obj[pathPart], path.slice(1), value, opts);
|
||||
if (opts.remove && newVal == null) {
|
||||
delete obj[pathPart];
|
||||
if (isEmpty(obj)) {
|
||||
return null;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
obj[pathPart] = newVal;
|
||||
return obj;
|
||||
} else if (typeof pathPart === "number") {
|
||||
if (pathPart < 0 || !Number.isInteger(pathPart)) {
|
||||
throw new Error("Invalid path part: " + pathPart);
|
||||
}
|
||||
if (obj == null) {
|
||||
if (opts.remove) {
|
||||
return null;
|
||||
}
|
||||
obj = [];
|
||||
}
|
||||
if (!isArray(obj)) {
|
||||
if (opts.force) {
|
||||
obj = [];
|
||||
} else {
|
||||
throw new Error("Cannot set path on non-array: " + obj);
|
||||
}
|
||||
}
|
||||
if (opts.remove && path.length == 1) {
|
||||
return removeFromArr(obj, pathPart);
|
||||
}
|
||||
const newVal = setPathInternal(obj[pathPart], path.slice(1), value, opts);
|
||||
if (opts.remove && newVal == null) {
|
||||
return removeFromArr(obj, pathPart);
|
||||
}
|
||||
obj[pathPart] = newVal;
|
||||
return obj;
|
||||
} else {
|
||||
throw new Error("Invalid path part: " + pathPart);
|
||||
}
|
||||
}
|
||||
|
||||
function getCommandPath(command: object): PathType {
|
||||
if (command["path"] == null) {
|
||||
return [];
|
||||
}
|
||||
return command["path"];
|
||||
}
|
||||
|
||||
function applyCommand(data: any, command: any): any {
|
||||
if (command == null) {
|
||||
throw new Error("Invalid command (null)");
|
||||
}
|
||||
if (!isObject(command)) {
|
||||
throw new Error("Invalid command (not an object): " + command);
|
||||
}
|
||||
const commandType = command.type;
|
||||
if (commandType == null) {
|
||||
throw new Error("Invalid command (no type): " + command);
|
||||
}
|
||||
const path = getCommandPath(command);
|
||||
if (!checkPath(path)) {
|
||||
throw new Error("Invalid command path: " + formatPath(path));
|
||||
}
|
||||
switch (commandType) {
|
||||
case "set":
|
||||
return setPath(data, path, command.value, null);
|
||||
|
||||
case "del":
|
||||
return setPath(data, path, null, { remove: true });
|
||||
|
||||
case "append":
|
||||
return setPath(data, path, command.value, { combinefn: combineFn_arrayAppend });
|
||||
|
||||
default:
|
||||
throw new Error("Invalid command type: " + commandType);
|
||||
}
|
||||
}
|
||||
|
||||
export { applyCommand, combineFn_arrayAppend, getPath, setPath };
|
||||
export type { PathType, SetPathOpts };
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,164 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package ijson
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDeepEqual(t *testing.T) {
|
||||
if !DeepEqual(float64(1), float64(1)) {
|
||||
t.Errorf("DeepEqual(1, 1) should be true")
|
||||
}
|
||||
if DeepEqual(float64(1), float64(2)) {
|
||||
t.Errorf("DeepEqual(1, 2) should be false")
|
||||
}
|
||||
if !DeepEqual([]any{"a", 2.8, true, map[string]any{"c": 1.1}}, []any{"a", 2.8, true, map[string]any{"c": 1.1}}) {
|
||||
t.Errorf("DeepEqual complex should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPath(t *testing.T) {
|
||||
data := []any{"a", 2.8, true, map[string]any{"c": 1.1}}
|
||||
|
||||
rtn, err := GetPath(data, []any{0})
|
||||
if err != nil {
|
||||
t.Errorf("GetPath failed: %v", err)
|
||||
}
|
||||
if rtn != "a" {
|
||||
t.Errorf("GetPath failed: %v", rtn)
|
||||
}
|
||||
|
||||
rtn, err = GetPath(data, []any{50})
|
||||
if err != nil {
|
||||
t.Errorf("GetPath failed: %v", err)
|
||||
}
|
||||
if rtn != nil {
|
||||
t.Errorf("GetPath failed: %v", rtn)
|
||||
}
|
||||
|
||||
rtn, err = GetPath(data, []any{3, "c"})
|
||||
if err != nil {
|
||||
t.Errorf("GetPath failed: %v", err)
|
||||
}
|
||||
if rtn != 1.1 {
|
||||
t.Errorf("GetPath failed: %v", rtn)
|
||||
}
|
||||
}
|
||||
|
||||
func makeValue() any {
|
||||
return []any{"a", 2.8, true, map[string]any{"c": 1.1}}
|
||||
}
|
||||
|
||||
func TestSetPath(t *testing.T) {
|
||||
rtn, err := SetPath(makeValue(), []any{0}, "b", nil)
|
||||
if err != nil {
|
||||
t.Errorf("SetPath failed: %v", err)
|
||||
}
|
||||
if rtn.([]any)[0] != "b" {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
rtn, err = SetPath(makeValue(), []any{10}, "b", nil)
|
||||
if err != nil {
|
||||
t.Errorf("SetPath failed: %v", err)
|
||||
}
|
||||
if len(rtn.([]any)) != 11 {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
rtn, _ = GetPath(rtn, []any{10})
|
||||
if rtn != "b" {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
_, err = SetPath(makeValue(), []any{"a"}, "b", nil)
|
||||
if err == nil {
|
||||
t.Errorf("SetPath should have failed")
|
||||
}
|
||||
rtn, err = SetPath(makeValue(), []any{"a"}, "b", &SetPathOpts{Force: true})
|
||||
if err != nil {
|
||||
t.Errorf("SetPath failed: %v", err)
|
||||
}
|
||||
if !DeepEqual(rtn, map[string]any{"a": "b"}) {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
rtn, err = SetPath(makeValue(), nil, "c", &SetPathOpts{CombineFn: CombineFn_ArrayAppend})
|
||||
if err != nil {
|
||||
t.Errorf("SetPath failed: %v", err)
|
||||
}
|
||||
if !DeepEqual(rtn, []any{"a", 2.8, true, map[string]any{"c": 1.1}, "c"}) {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
_, err = SetPath(makeValue(), nil, "c", &SetPathOpts{CombineFn: CombineFn_ArrayAppend, Budget: -1})
|
||||
if err == nil {
|
||||
t.Errorf("SetPath should have failed")
|
||||
}
|
||||
rtn, err = SetPath(makeValue(), []any{5000}, "c", nil)
|
||||
if err != nil {
|
||||
t.Errorf("SetPath failed: %v", err)
|
||||
}
|
||||
if len(rtn.([]any)) != 5001 {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
_, err = SetPath(makeValue(), []any{5000}, "c", &SetPathOpts{Budget: 1000})
|
||||
if err == nil {
|
||||
t.Errorf("SetPath should have failed")
|
||||
}
|
||||
rtn, err = SetPath(makeValue(), []any{3, "c"}, nil, &SetPathOpts{Remove: true})
|
||||
if err != nil {
|
||||
t.Errorf("SetPath failed: %v", err)
|
||||
}
|
||||
if !DeepEqual(rtn, []any{"a", 2.8, true}) {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
rtn, _ = SetPath(makeValue(), []any{3}, nil, &SetPathOpts{Remove: true})
|
||||
rtn, _ = SetPath(rtn, []any{2}, nil, &SetPathOpts{Remove: true})
|
||||
rtn, _ = SetPath(rtn, []any{1}, nil, &SetPathOpts{Remove: true})
|
||||
rtn, _ = SetPath(rtn, []any{0}, nil, &SetPathOpts{Remove: true})
|
||||
if rtn != nil {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
rtn, err = SetPath(makeValue(), []any{3, "d"}, 2.2, nil)
|
||||
if err != nil {
|
||||
t.Errorf("SetPath failed: %v", err)
|
||||
}
|
||||
if !DeepEqual(rtn, []any{"a", 2.8, true, map[string]any{"c": 1.1, "d": 2.2}}) {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
|
||||
rtn, err = SetPath(makeValue(), []any{1}, 2.2, &SetPathOpts{CombineFn: CombineFn_Inc})
|
||||
if err != nil {
|
||||
t.Errorf("SetPath failed: %v", err)
|
||||
}
|
||||
if !DeepEqual(rtn, []any{"a", 5.0, true, map[string]any{"c": 1.1}}) {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
|
||||
rtn, err = SetPath(makeValue(), []any{1}, 500.0, &SetPathOpts{CombineFn: CombineFn_Min})
|
||||
if err != nil {
|
||||
t.Errorf("SetPath failed: %v", err)
|
||||
}
|
||||
if rtn.([]any)[1] != 2.8 {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
|
||||
rtn, err = SetPath(makeValue(), []any{1}, 500.0, &SetPathOpts{CombineFn: CombineFn_Max})
|
||||
if err != nil {
|
||||
t.Errorf("SetPath failed: %v", err)
|
||||
}
|
||||
if rtn.([]any)[1] != 500.0 {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
|
||||
rtn, err = SetPath(makeValue(), []any{1}, 500.0, &SetPathOpts{CombineFn: CombineFn_SetUnless})
|
||||
if err != nil {
|
||||
t.Errorf("SetPath failed: %v", err)
|
||||
}
|
||||
if rtn.([]any)[1] != 2.8 {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
rtn, err = SetPath(makeValue(), []any{8}, 500.0, &SetPathOpts{CombineFn: CombineFn_SetUnless})
|
||||
if err != nil {
|
||||
t.Errorf("SetPath failed: %v", err)
|
||||
}
|
||||
if rtn.([]any)[8] != 500.0 {
|
||||
t.Errorf("SetPath failed: %v", rtn)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user