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  }