package processor import ( "encoding/json" "fmt" "modify/logger" "modify/processor/jsonpath" lua "github.com/yuin/gopher-lua" ) // JSONProcessor implements the Processor interface for JSON documents type JSONProcessor struct{} // ProcessContent implements the Processor interface for JSONProcessor func (p *JSONProcessor) ProcessContent(content string, pattern string, luaExpr string) (string, int, int, error) { logger.Debug("Processing JSON content with JSONPath: %s", pattern) // Parse JSON document logger.Trace("Parsing JSON document") var jsonData interface{} err := json.Unmarshal([]byte(content), &jsonData) if err != nil { logger.Error("Failed to parse JSON: %v", err) return content, 0, 0, fmt.Errorf("error parsing JSON: %v", err) } // Find nodes matching the JSONPath pattern logger.Debug("Executing JSONPath query: %s", pattern) nodes, err := jsonpath.Get(jsonData, pattern) if err != nil { logger.Error("Failed to execute JSONPath: %v", err) return content, 0, 0, fmt.Errorf("error getting nodes: %v", err) } matchCount := len(nodes) logger.Debug("Found %d nodes matching JSONPath", matchCount) if matchCount == 0 { logger.Warning("No nodes matched the JSONPath pattern: %s", pattern) return content, 0, 0, nil } modCount := 0 for i, node := range nodes { logger.Trace("Processing node #%d at path: %s with value: %v", i+1, node.Path, node.Value) // Initialize Lua L, err := NewLuaState() if err != nil { logger.Error("Failed to create Lua state: %v", err) return content, len(nodes), 0, fmt.Errorf("error creating Lua state: %v", err) } defer L.Close() logger.Trace("Lua state initialized successfully") err = p.ToLua(L, node.Value) if err != nil { logger.Error("Failed to convert value to Lua: %v", err) return content, len(nodes), 0, fmt.Errorf("error converting to Lua: %v", err) } logger.Trace("Converted node value to Lua: %v", node.Value) originalScript := luaExpr fullScript := BuildLuaScript(luaExpr) logger.Debug("Original script: %q, Full script: %q", originalScript, fullScript) // Execute Lua script logger.Trace("Executing Lua script: %q", fullScript) if err := L.DoString(fullScript); err != nil { logger.Error("Failed to execute Lua script: %v", err) return content, len(nodes), 0, fmt.Errorf("error executing Lua %q: %v", fullScript, err) } logger.Trace("Lua script executed successfully") // Get modified value result, err := p.FromLua(L) if err != nil { logger.Error("Failed to get result from Lua: %v", err) return content, len(nodes), 0, fmt.Errorf("error getting result from Lua: %v", err) } logger.Trace("Retrieved modified value from Lua: %v", result) modified := false modified = L.GetGlobal("modified").String() == "true" if !modified { logger.Debug("No changes made to node at path: %s", node.Path) continue } // Apply the modification to the JSON data logger.Debug("Updating JSON at path: %s with new value: %v", node.Path, result) err = p.updateJSONValue(jsonData, node.Path, result) if err != nil { logger.Error("Failed to update JSON at path %s: %v", node.Path, err) return content, len(nodes), 0, fmt.Errorf("error updating JSON: %v", err) } logger.Debug("Updated JSON at path: %s successfully", node.Path) modCount++ } logger.Info("JSON processing complete: %d modifications from %d matches", modCount, matchCount) // Convert the modified JSON back to a string with same formatting logger.Trace("Marshalling JSON data back to string") var jsonBytes []byte jsonBytes, err = json.MarshalIndent(jsonData, "", " ") if err != nil { logger.Error("Failed to marshal JSON: %v", err) return content, modCount, matchCount, fmt.Errorf("error marshalling JSON: %v", err) } return string(jsonBytes), modCount, matchCount, nil } // updateJSONValue updates a value in the JSON structure based on its JSONPath func (p *JSONProcessor) updateJSONValue(jsonData interface{}, path string, newValue interface{}) error { logger.Trace("Updating JSON value at path: %s", path) // Special handling for root node if path == "$" { logger.Debug("Handling special case for root node update") // For the root node, we'll copy the value to the jsonData reference // This is a special case since we can't directly replace the interface{} variable // We need to handle different types of root elements switch rootValue := newValue.(type) { case map[string]interface{}: // For objects, we need to copy over all keys rootMap, ok := jsonData.(map[string]interface{}) if !ok { // If the original wasn't a map, completely replace it with the new map // This is handled by the jsonpath.Set function logger.Debug("Root was not a map, replacing entire root") return jsonpath.Set(jsonData, path, newValue) } // Clear the original map logger.Trace("Clearing original root map") for k := range rootMap { delete(rootMap, k) } // Copy all keys from the new map logger.Trace("Copying keys to root map") for k, v := range rootValue { rootMap[k] = v } return nil case []interface{}: // For arrays, we need to handle similarly rootArray, ok := jsonData.([]interface{}) if !ok { // If the original wasn't an array, use jsonpath.Set logger.Debug("Root was not an array, replacing entire root") return jsonpath.Set(jsonData, path, newValue) } // Clear and recreate the array logger.Trace("Replacing root array") *&rootArray = rootValue return nil default: // For other types, use jsonpath.Set logger.Debug("Replacing root with primitive value") return jsonpath.Set(jsonData, path, newValue) } } // For non-root paths, use the regular Set method logger.Trace("Using regular Set method for non-root path") err := jsonpath.Set(jsonData, path, newValue) if err != nil { logger.Error("Failed to set JSON value at path %s: %v", path, err) return fmt.Errorf("failed to update JSON value at path '%s': %w", path, err) } return nil } // ToLua converts JSON values to Lua variables func (p *JSONProcessor) ToLua(L *lua.LState, data interface{}) error { table, err := ToLua(L, data) if err != nil { return err } L.SetGlobal("v", table) return nil } // FromLua retrieves values from Lua func (p *JSONProcessor) FromLua(L *lua.LState) (interface{}, error) { luaValue := L.GetGlobal("v") return FromLua(L, luaValue) }