github.com/ipld/go-ipld-prime@v0.21.0/traversal/patch/eval.go (about) 1 // Package patch provides an implementation of the IPLD Patch specification. 2 // IPLD Patch is a system for declaratively specifying patches to a document, 3 // which can then be applied to produce a new, modified document. 4 // 5 // This package is EXPERIMENTAL; its behavior and API might change as it's still 6 // in development. 7 package patch 8 9 import ( 10 "fmt" 11 12 "github.com/ipld/go-ipld-prime/datamodel" 13 "github.com/ipld/go-ipld-prime/traversal" 14 ) 15 16 type Op string 17 18 const ( 19 Op_Add = "add" 20 Op_Remove = "remove" 21 Op_Replace = "replace" 22 Op_Move = "move" 23 Op_Copy = "copy" 24 Op_Test = "test" 25 ) 26 27 type Operation struct { 28 Op Op // Always required. 29 Path datamodel.Path // Always required. 30 Value datamodel.Node // Present on 'add', 'replace', 'test'. 31 From datamodel.Path // Present on 'move', 'copy'. 32 } 33 34 func Eval(n datamodel.Node, ops []Operation) (datamodel.Node, error) { 35 var err error 36 for _, op := range ops { 37 n, err = EvalOne(n, op) 38 if err != nil { 39 return nil, err 40 } 41 } 42 return n, nil 43 } 44 45 func EvalOne(n datamodel.Node, op Operation) (datamodel.Node, error) { 46 switch op.Op { 47 case Op_Add: 48 // The behavior of the 'add' op in jsonpatch varies based on if the parent of the target path is a list. 49 // If the parent of the target path is a list, then 'add' is really more of an 'insert': it should slide the rest of the values down. 50 // There's also a special case for "-", which means "append to the end of the list". 51 // Otherwise, if the destination path exists, it's an error. (No upserting.) 52 // Handling this requires looking at the parent of the destination node, so we split this into *two* traversal.FocusedTransform calls. 53 return traversal.FocusedTransform(n, op.Path.Pop(), func(prog traversal.Progress, parent datamodel.Node) (datamodel.Node, error) { 54 if parent.Kind() == datamodel.Kind_List { 55 seg := op.Path.Last() 56 var idx int64 57 if seg.String() == "-" { 58 idx = -1 59 } 60 var err error 61 idx, err = seg.Index() 62 if err != nil { 63 return nil, fmt.Errorf("patch-invalid-path-through-list: at %q", op.Path) // TODO error structuralization and review the code 64 } 65 66 nb := parent.Prototype().NewBuilder() 67 la, err := nb.BeginList(parent.Length() + 1) 68 if err != nil { 69 return nil, err 70 } 71 for itr := n.ListIterator(); !itr.Done(); { 72 i, v, err := itr.Next() 73 if err != nil { 74 return nil, err 75 } 76 if idx == i { 77 la.AssembleValue().AssignNode(op.Value) 78 } 79 if err := la.AssembleValue().AssignNode(v); err != nil { 80 return nil, err 81 } 82 } 83 // TODO: is one-past-the-end supposed to be supported or supposed to be ruled out? 84 if idx == -1 { 85 la.AssembleValue().AssignNode(op.Value) 86 } 87 if err := la.Finish(); err != nil { 88 return nil, err 89 } 90 return nb.Build(), nil 91 } 92 return prog.FocusedTransform(parent, datamodel.NewPath([]datamodel.PathSegment{op.Path.Last()}), func(prog traversal.Progress, point datamodel.Node) (datamodel.Node, error) { 93 if point != nil && !point.IsAbsent() { 94 return nil, fmt.Errorf("patch-target-exists: at %q", op.Path) // TODO error structuralization and review the code 95 } 96 return op.Value, nil 97 }, false) 98 }, false) 99 case "remove": 100 return traversal.FocusedTransform(n, op.Path, func(_ traversal.Progress, point datamodel.Node) (datamodel.Node, error) { 101 return nil, nil // Returning a nil value here means "remove what's here". 102 }, false) 103 case "replace": 104 // TODO i think you need a check that it's not landing under itself here 105 return traversal.FocusedTransform(n, op.Path, func(_ traversal.Progress, point datamodel.Node) (datamodel.Node, error) { 106 return op.Value, nil // is this right? what does FocusedTransform do re upsert? 107 }, false) 108 case "move": 109 // TODO i think you need a check that it's not landing under itself here 110 source, err := traversal.Get(n, op.From) 111 if err != nil { 112 return nil, err 113 } 114 n, err := traversal.FocusedTransform(n, op.Path, func(_ traversal.Progress, point datamodel.Node) (datamodel.Node, error) { 115 return source, nil // is this right? what does FocusedTransform do re upsert? 116 }, false) 117 if err != nil { 118 return nil, err 119 } 120 return traversal.FocusedTransform(n, op.From, func(_ traversal.Progress, point datamodel.Node) (datamodel.Node, error) { 121 return nil, nil // Returning a nil value here means "remove what's here". 122 }, false) 123 case "copy": 124 // TODO i think you need a check that it's not landing under itself here 125 source, err := traversal.Get(n, op.From) 126 if err != nil { 127 return nil, err 128 } 129 return traversal.FocusedTransform(n, op.Path, func(_ traversal.Progress, point datamodel.Node) (datamodel.Node, error) { 130 return source, nil // is this right? what does FocusedTransform do re upsert? 131 }, false) 132 case "test": 133 point, err := traversal.Get(n, op.Path) 134 if err != nil { 135 return nil, err 136 } 137 if datamodel.DeepEqual(point, op.Value) { 138 return n, nil 139 } 140 return n, fmt.Errorf("test failed") // TODO real error handling and a code 141 default: 142 return nil, fmt.Errorf("misuse: invalid operation: %s", op.Op) // TODO real error handling and a code 143 } 144 }