implement ijson commands

This commit is contained in:
sawka 2024-04-12 13:16:26 -07:00
parent e044487489
commit 41fe49a54c
2 changed files with 107 additions and 49 deletions

View File

@ -22,8 +22,10 @@ function formatPath(path: PathType): string {
} else {
pathStr += "[" + JSON.stringify(pathPart) + "]";
}
} else {
} else if (typeof pathPart === "number") {
pathStr += "[" + pathPart + "]";
} else {
pathStr += ".*";
}
}
return pathStr;
@ -82,6 +84,18 @@ function combineFn_arrayAppend(oldVal: any, newVal: any, opts: SetPathOpts): any
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 = {};
@ -89,6 +103,12 @@ function setPath(obj: any, path: PathType, value: any, opts: SetPathOpts) {
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);
}
@ -194,5 +214,42 @@ function setPathInternal(obj: any, path: PathType, value: any, opts: SetPathOpts
}
}
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 type { PathType, SetPathOpts };
export { getPath, setPath, combineFn_arrayAppend };
export { getPath, setPath, applyCommand, combineFn_arrayAppend };

View File

@ -22,44 +22,10 @@ const (
AppendCommandStr = "append"
)
var _ = CommandType(SetCommand{})
var _ = CommandType(DelCommand{})
var _ = CommandType(AppendCommand{})
type CommandType interface {
GetCommandType() string
}
type SetCommand struct {
Type string `json:"type"` // "set"
Path []any `json:"path"` // each path entry must be either a string or int
Data any `json:"data"` // data must be a valid JSON structure
}
func (c SetCommand) GetCommandType() string {
return SetCommandStr
}
// removes values from the state
type DelCommand struct {
Type string `json:"type"` // "del"
Path []any `json:"path"`
}
func (c DelCommand) GetCommandType() string {
return DelCommandStr
}
// appends to an array
type AppendCommand struct {
Type string `json:"type"` // "append"
Path []any `json:"path"`
Data any `json:"data"`
}
func (c AppendCommand) GetCommandType() string {
return AppendCommandStr
}
// instead of defining structs for commands, we just define a command shape
// set: type, path, value
// del: type, path
// arrayappend: type, path, value
type PathError struct {
Err string
@ -498,15 +464,50 @@ func DeepEqual(v1 any, v2 any) bool {
}
}
func ApplyCommand(data any, command CommandType, budget int) (any, error) {
switch command := command.(type) {
case SetCommand:
return SetPath(data, command.Path, command.Data, &SetPathOpts{Budget: budget})
case DelCommand:
return SetPath(data, command.Path, nil, &SetPathOpts{Remove: true, Budget: budget})
case AppendCommand:
return SetPath(data, command.Path, command.Data, &SetPathOpts{CombineFn: CombineFn_ArrayAppend, Budget: budget})
func getCommandType(command map[string]any) string {
typeVal, ok := command["type"]
if !ok {
return ""
}
typeStr, ok := typeVal.(string)
if !ok {
return ""
}
return typeStr
}
func getCommandPath(command map[string]any) []any {
pathVal, ok := command["path"]
if !ok {
return nil
}
path, ok := pathVal.([]any)
if !ok {
return nil
}
return path
}
func ApplyCommand(data any, command any, budget int) (any, error) {
mapVal, ok := command.(map[string]any)
if !ok {
return nil, fmt.Errorf("ApplyCommand: expected map, but got %T", command)
}
commandType := getCommandType(mapVal)
if commandType == "" {
return nil, fmt.Errorf("ApplyCommand: missing type field")
}
switch commandType {
case SetCommandStr:
path := getCommandPath(mapVal)
return SetPath(data, path, mapVal["data"], &SetPathOpts{Budget: budget})
case DelCommandStr:
path := getCommandPath(mapVal)
return SetPath(data, path, nil, &SetPathOpts{Remove: true, Budget: budget})
case AppendCommandStr:
path := getCommandPath(mapVal)
return SetPath(data, path, mapVal["data"], &SetPathOpts{CombineFn: CombineFn_ArrayAppend, Budget: budget})
default:
return nil, fmt.Errorf("unknown command type %T", command)
return nil, fmt.Errorf("ApplyCommand: unknown command type %q", commandType)
}
}