195 lines
6.3 KiB
Go
195 lines
6.3 KiB
Go
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)
|
|
}
|