From afb490bb2922286f38b5f579b030276a58c1796e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 2 Feb 2018 22:36:01 +0100 Subject: [PATCH] commands: switch object commands to CoreAPI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit License: MIT Signed-off-by: Ɓukasz Magiera --- core/commands/object/diff.go | 39 +++-- core/commands/object/object.go | 265 +++++++++------------------------ core/commands/object/patch.go | 148 +++--------------- core/coreapi/object.go | 14 +- 4 files changed, 120 insertions(+), 346 deletions(-) diff --git a/core/commands/object/diff.go b/core/commands/object/diff.go index db3303d3294..0158391aeeb 100644 --- a/core/commands/object/diff.go +++ b/core/commands/object/diff.go @@ -6,10 +6,9 @@ import ( "io" cmds "github.com/ipfs/go-ipfs/commands" - core "github.com/ipfs/go-ipfs/core" e "github.com/ipfs/go-ipfs/core/commands/e" dagutils "github.com/ipfs/go-ipfs/merkledag/utils" - path "github.com/ipfs/go-ipfs/path" + cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit" ) @@ -52,7 +51,7 @@ Example: cmdkit.BoolOption("verbose", "v", "Print extra information."), }, Run: func(req cmds.Request, res cmds.Response) { - node, err := req.InvocContext().GetNode() + api, err := req.InvocContext().GetApi() if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -61,39 +60,37 @@ Example: a := req.Arguments()[0] b := req.Arguments()[1] - pa, err := path.ParsePath(a) + pa, err := api.ParsePath(req.Context(), a, api.WithResolve(true)) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - pb, err := path.ParsePath(b) + pb, err := api.ParsePath(req.Context(), b, api.WithResolve(true)) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - ctx := req.Context() + changes, err := api.Object().Diff(req.Context(), pa, pb) - obj_a, err := core.Resolve(ctx, node.Namesys, node.Resolver, pa) - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return - } + out := make([]*dagutils.Change, len(changes)) + for i, change := range changes { + out[i] = &dagutils.Change{ + Type: change.Type, + Path: change.Path, + } - obj_b, err := core.Resolve(ctx, node.Namesys, node.Resolver, pb) - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return - } + if change.Before != nil { + out[i].Before = change.Before.Cid() + } - changes, err := dagutils.Diff(ctx, node.DAG, obj_a, obj_b) - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return + if change.After != nil { + out[i].After = change.After.Cid() + } } - res.SetOutput(&Changes{changes}) + res.SetOutput(&Changes{out}) }, Type: Changes{}, Marshalers: cmds.MarshalerMap{ diff --git a/core/commands/object/object.go b/core/commands/object/object.go index b05648a8588..5e3928e70f3 100644 --- a/core/commands/object/object.go +++ b/core/commands/object/object.go @@ -2,10 +2,7 @@ package objectcmd import ( "bytes" - "context" "encoding/base64" - "encoding/json" - "encoding/xml" "errors" "fmt" "io" @@ -14,12 +11,8 @@ import ( "text/tabwriter" cmds "github.com/ipfs/go-ipfs/commands" - core "github.com/ipfs/go-ipfs/core" e "github.com/ipfs/go-ipfs/core/commands/e" dag "github.com/ipfs/go-ipfs/merkledag" - path "github.com/ipfs/go-ipfs/path" - pin "github.com/ipfs/go-ipfs/pin" - ft "github.com/ipfs/go-ipfs/unixfs" cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid" cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit" @@ -86,31 +79,25 @@ is the raw data of the object. cmdkit.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format.").EnableStdin(), }, Run: func(req cmds.Request, res cmds.Response) { - n, err := req.InvocContext().GetNode() + api, err := req.InvocContext().GetApi() if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - fpath, err := path.ParsePath(req.Arguments()[0]) + path, err := api.ParsePath(req.Context(), req.Arguments()[0], api.WithResolve(true)) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - node, err := core.Resolve(req.Context(), n.Namesys, n.Resolver, fpath) + data, err := api.Object().Data(req.Context(), path) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - pbnode, ok := node.(*dag.ProtoNode) - if !ok { - res.SetError(dag.ErrNotProtobuf, cmdkit.ErrNormal) - return - } - - res.SetOutput(bytes.NewReader(pbnode.Data())) + res.SetOutput(data) }, } @@ -131,7 +118,7 @@ multihash. cmdkit.BoolOption("headers", "v", "Print table headers (Hash, Size, Name)."), }, Run: func(req cmds.Request, res cmds.Response) { - n, err := req.InvocContext().GetNode() + api, err := req.InvocContext().GetApi() if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -143,19 +130,33 @@ multihash. return } - fpath := path.Path(req.Arguments()[0]) - node, err := core.Resolve(req.Context(), n.Namesys, n.Resolver, fpath) + path, err := api.ParsePath(req.Context(), req.Arguments()[0], api.WithResolve(true)) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - output, err := getOutput(node) + links, err := api.Object().Links(req.Context(), path) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - res.SetOutput(output) + + outLinks := make([]Link, len(links)) + for i, link := range links { + outLinks[i] = Link{ + Hash: link.Cid.String(), + Name: link.Name, + Size: link.Size, + } + } + + out := Object{ + Hash: path.Cid().String(), + Links: outLinks, + } + + res.SetOutput(out) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { @@ -209,32 +210,42 @@ This command outputs data in the following encodings: cmdkit.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format.").EnableStdin(), }, Run: func(req cmds.Request, res cmds.Response) { - n, err := req.InvocContext().GetNode() + api, err := req.InvocContext().GetApi() if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - fpath := path.Path(req.Arguments()[0]) + path, err := api.ParsePath(req.Context(), req.Arguments()[0], api.WithResolve(true)) + if err != nil { + res.SetError(err, cmdkit.ErrNormal) + return + } + + nd, err := api.Object().Get(req.Context(), path) + if err != nil { + res.SetError(err, cmdkit.ErrNormal) + return + } - object, err := core.Resolve(req.Context(), n.Namesys, n.Resolver, fpath) + r, err := api.Object().Data(req.Context(), path) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - pbo, ok := object.(*dag.ProtoNode) - if !ok { - res.SetError(dag.ErrNotProtobuf, cmdkit.ErrNormal) + data, err := ioutil.ReadAll(r) + if err != nil { + res.SetError(err, cmdkit.ErrNormal) return } node := &Node{ - Links: make([]Link, len(object.Links())), - Data: string(pbo.Data()), + Links: make([]Link, len(nd.Links())), + Data: string(data), } - for i, link := range object.Links() { + for i, link := range nd.Links() { node.Links[i] = Link{ Hash: link.Cid.String(), Name: link.Name, @@ -291,27 +302,34 @@ var ObjectStatCmd = &cmds.Command{ cmdkit.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format.").EnableStdin(), }, Run: func(req cmds.Request, res cmds.Response) { - n, err := req.InvocContext().GetNode() + api, err := req.InvocContext().GetApi() if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - fpath := path.Path(req.Arguments()[0]) - - object, err := core.Resolve(req.Context(), n.Namesys, n.Resolver, fpath) + path, err := api.ParsePath(req.Context(), req.Arguments()[0], api.WithResolve(true)) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - ns, err := object.Stat() + ns, err := api.Object().Stat(req.Context(), path) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - res.SetOutput(ns) + oldStat := &ipld.NodeStat{ + Hash: ns.Cid.String(), + NumLinks: ns.NumLinks, + BlockSize: ns.BlockSize, + LinksSize: ns.LinksSize, + DataSize: ns.DataSize, + CumulativeSize: ns.CumulativeSize, + } + + res.SetOutput(oldStat) }, Type: ipld.NodeStat{}, Marshalers: cmds.MarshalerMap{ @@ -389,7 +407,7 @@ And then run: cmdkit.BoolOption("quiet", "q", "Write minimal output."), }, Run: func(req cmds.Request, res cmds.Response) { - n, err := req.InvocContext().GetNode() + api, err := req.InvocContext().GetApi() if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -419,30 +437,16 @@ And then run: return } - if dopin { - defer n.Blockstore.PinLock().Unlock() - } - - objectCid, err := objectPut(req.Context(), n, input, inputenc, datafieldenc) + p, err := api.Object().Put(req.Context(), input, + api.Object().WithDataType(datafieldenc), + api.Object().WithInputEnc(inputenc), + api.Object().WithPin(dopin)) if err != nil { - errType := cmdkit.ErrNormal - if err == ErrUnknownObjectEnc { - errType = cmdkit.ErrClient - } - res.SetError(err, errType) + res.SetError(err, cmdkit.ErrNormal) return } - if dopin { - n.Pinning.PinWithMode(objectCid, pin.Recursive) - err = n.Pinning.Flush() - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return - } - } - - res.SetOutput(&Object{Hash: objectCid.String()}) + res.SetOutput(&Object{Hash: p.Cid().String()}) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { @@ -488,29 +492,24 @@ Available templates: cmdkit.StringArg("template", false, false, "Template to use. Optional."), }, Run: func(req cmds.Request, res cmds.Response) { - n, err := req.InvocContext().GetNode() + api, err := req.InvocContext().GetApi() if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - node := new(dag.ProtoNode) + template := "empty" if len(req.Arguments()) == 1 { - template := req.Arguments()[0] - var err error - node, err = nodeFromTemplate(template) - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return - } + template = req.Arguments()[0] } - err = n.DAG.Add(req.Context(), node) - if err != nil { + nd, err := api.Object().New(req.Context(), api.Object().WithType(template)) + if err != nil && err != io.EOF { res.SetError(err, cmdkit.ErrNormal) return } - res.SetOutput(&Object{Hash: node.Cid().String()}) + + res.SetOutput(&Object{Hash: nd.Cid().String()}) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { @@ -530,126 +529,6 @@ Available templates: Type: Object{}, } -func nodeFromTemplate(template string) (*dag.ProtoNode, error) { - switch template { - case "unixfs-dir": - return ft.EmptyDirNode(), nil - default: - return nil, fmt.Errorf("template '%s' not found", template) - } -} - -// ErrEmptyNode is returned when the input to 'ipfs object put' contains no data -var ErrEmptyNode = errors.New("no data or links in this node") - -// objectPut takes a format option, serializes bytes from stdin and updates the dag with that data -func objectPut(ctx context.Context, n *core.IpfsNode, input io.Reader, encoding string, dataFieldEncoding string) (*cid.Cid, error) { - - data, err := ioutil.ReadAll(io.LimitReader(input, inputLimit+10)) - if err != nil { - return nil, err - } - - if len(data) >= inputLimit { - return nil, ErrObjectTooLarge - } - - var dagnode *dag.ProtoNode - switch getObjectEnc(encoding) { - case objectEncodingJSON: - node := new(Node) - err = json.Unmarshal(data, node) - if err != nil { - return nil, err - } - - // check that we have data in the Node to add - // otherwise we will add the empty object without raising an error - if NodeEmpty(node) { - return nil, ErrEmptyNode - } - - dagnode, err = deserializeNode(node, dataFieldEncoding) - if err != nil { - return nil, err - } - - case objectEncodingProtobuf: - dagnode, err = dag.DecodeProtobuf(data) - - case objectEncodingXML: - node := new(Node) - err = xml.Unmarshal(data, node) - if err != nil { - return nil, err - } - - // check that we have data in the Node to add - // otherwise we will add the empty object without raising an error - if NodeEmpty(node) { - return nil, ErrEmptyNode - } - - dagnode, err = deserializeNode(node, dataFieldEncoding) - if err != nil { - return nil, err - } - - default: - return nil, ErrUnknownObjectEnc - } - - if err != nil { - return nil, err - } - - err = n.DAG.Add(ctx, dagnode) - if err != nil { - return nil, err - } - - return dagnode.Cid(), nil -} - -// ErrUnknownObjectEnc is returned if a invalid encoding is supplied -var ErrUnknownObjectEnc = errors.New("unknown object encoding") - -type objectEncoding string - -const ( - objectEncodingJSON objectEncoding = "json" - objectEncodingProtobuf = "protobuf" - objectEncodingXML = "xml" -) - -func getObjectEnc(o interface{}) objectEncoding { - v, ok := o.(string) - if !ok { - // chosen as default because it's human readable - return objectEncodingJSON - } - - return objectEncoding(v) -} - -func getOutput(dagnode ipld.Node) (*Object, error) { - c := dagnode.Cid() - output := &Object{ - Hash: c.String(), - Links: make([]Link, len(dagnode.Links())), - } - - for i, link := range dagnode.Links() { - output.Links[i] = Link{ - Name: link.Name, - Hash: link.Cid.String(), - Size: link.Size, - } - } - - return output, nil -} - // converts the Node object into a real dag.ProtoNode func deserializeNode(nd *Node, dataFieldEncoding string) (*dag.ProtoNode, error) { dagnode := new(dag.ProtoNode) @@ -663,7 +542,7 @@ func deserializeNode(nd *Node, dataFieldEncoding string) (*dag.ProtoNode, error) } dagnode.SetData(data) default: - return nil, fmt.Errorf("Unkown data field encoding") + return nil, fmt.Errorf("unkown data field encoding") } links := make([]*ipld.Link, len(nd.Links)) @@ -683,10 +562,6 @@ func deserializeNode(nd *Node, dataFieldEncoding string) (*dag.ProtoNode, error) return dagnode, nil } -func NodeEmpty(node *Node) bool { - return (node.Data == "" && len(node.Links) == 0) -} - // copy+pasted from ../commands.go func unwrapOutput(i interface{}) (interface{}, error) { var ( diff --git a/core/commands/object/patch.go b/core/commands/object/patch.go index ab781464423..412a896920f 100644 --- a/core/commands/object/patch.go +++ b/core/commands/object/patch.go @@ -2,23 +2,14 @@ package objectcmd import ( "io" - "io/ioutil" "strings" cmds "github.com/ipfs/go-ipfs/commands" - core "github.com/ipfs/go-ipfs/core" e "github.com/ipfs/go-ipfs/core/commands/e" - dag "github.com/ipfs/go-ipfs/merkledag" - dagutils "github.com/ipfs/go-ipfs/merkledag/utils" - path "github.com/ipfs/go-ipfs/path" - ft "github.com/ipfs/go-ipfs/unixfs" - logging "gx/ipfs/QmRb5jh8z2E8hMGN2tkvs1yHynUanqnZ3UeKwgN1i9P1F8/go-log" cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit" ) -var log = logging.Logger("core/commands/object") - var ObjectPatchCmd = &cmds.Command{ Helptext: cmdkit.HelpText{ Tagline: "Create a new merkledag object based on an existing one.", @@ -71,51 +62,31 @@ the limit will not be respected by the network. cmdkit.FileArg("data", true, false, "Data to append.").EnableStdin(), }, Run: func(req cmds.Request, res cmds.Response) { - nd, err := req.InvocContext().GetNode() + api, err := req.InvocContext().GetApi() if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - root, err := path.ParsePath(req.StringArguments()[0]) + root, err := api.ParsePath(req.Context(), req.StringArguments()[0], api.WithResolve(true)) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - rootnd, err := core.Resolve(req.Context(), nd.Namesys, nd.Resolver, root) - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return - } - - rtpb, ok := rootnd.(*dag.ProtoNode) - if !ok { - res.SetError(dag.ErrNotProtobuf, cmdkit.ErrNormal) - return - } - - fi, err := req.Files().NextFile() + data, err := req.Files().NextFile() if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - data, err := ioutil.ReadAll(fi) + p, err := api.Object().AppendData(req.Context(), root, data) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - rtpb.SetData(append(rtpb.Data(), data...)) - - err = nd.DAG.Add(req.Context(), rtpb) - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return - } - - res.SetOutput(&Object{Hash: rtpb.Cid().String()}) + res.SetOutput(&Object{Hash: p.Cid().String()}) }, Type: Object{}, Marshalers: cmds.MarshalerMap{ @@ -139,51 +110,30 @@ Example: cmdkit.FileArg("data", true, false, "The data to set the object to.").EnableStdin(), }, Run: func(req cmds.Request, res cmds.Response) { - nd, err := req.InvocContext().GetNode() - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return - } - - rp, err := path.ParsePath(req.StringArguments()[0]) + api, err := req.InvocContext().GetApi() if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - - root, err := core.Resolve(req.Context(), nd.Namesys, nd.Resolver, rp) + root, err := api.ParsePath(req.Context(), req.StringArguments()[0], api.WithResolve(true)) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - rtpb, ok := root.(*dag.ProtoNode) - if !ok { - res.SetError(dag.ErrNotProtobuf, cmdkit.ErrNormal) - return - } - - fi, err := req.Files().NextFile() + data, err := req.Files().NextFile() if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - data, err := ioutil.ReadAll(fi) + p, err := api.Object().SetData(req.Context(), root, data) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - rtpb.SetData(data) - - err = nd.DAG.Add(req.Context(), rtpb) - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return - } - - res.SetOutput(&Object{Hash: rtpb.Cid().String()}) + res.SetOutput(&Object{Hash: p.Cid().String()}) }, Type: Object{}, Marshalers: cmds.MarshalerMap{ @@ -203,49 +153,26 @@ Removes a link by the given name from root. cmdkit.StringArg("link", true, false, "Name of the link to remove."), }, Run: func(req cmds.Request, res cmds.Response) { - nd, err := req.InvocContext().GetNode() + api, err := req.InvocContext().GetApi() if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - rootp, err := path.ParsePath(req.Arguments()[0]) + root, err := api.ParsePath(req.Context(), req.Arguments()[0], api.WithResolve(true)) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - root, err := core.Resolve(req.Context(), nd.Namesys, nd.Resolver, rootp) + link := req.Arguments()[1] + p, err := api.Object().RmLink(req.Context(), root, link) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - rtpb, ok := root.(*dag.ProtoNode) - if !ok { - res.SetError(dag.ErrNotProtobuf, cmdkit.ErrNormal) - return - } - - path := req.Arguments()[1] - - e := dagutils.NewDagEditor(rtpb, nd.DAG) - - err = e.RmLink(req.Context(), path) - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return - } - - nnode, err := e.Finalize(req.Context(), nd.DAG) - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return - } - - nc := nnode.Cid() - - res.SetOutput(&Object{Hash: nc.String()}) + res.SetOutput(&Object{Hash: p.Cid().String()}) }, Type: Object{}, Marshalers: cmds.MarshalerMap{ @@ -278,32 +205,21 @@ to a file containing 'bar', and returns the hash of the new object. cmdkit.BoolOption("create", "p", "Create intermediary nodes."), }, Run: func(req cmds.Request, res cmds.Response) { - nd, err := req.InvocContext().GetNode() + api, err := req.InvocContext().GetApi() if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - rootp, err := path.ParsePath(req.Arguments()[0]) + root, err := api.ParsePath(req.Context(), req.Arguments()[0], api.WithResolve(true)) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - root, err := core.Resolve(req.Context(), nd.Namesys, nd.Resolver, rootp) - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return - } + name := req.Arguments()[1] - rtpb, ok := root.(*dag.ProtoNode) - if !ok { - res.SetError(dag.ErrNotProtobuf, cmdkit.ErrNormal) - return - } - - npath := req.Arguments()[1] - childp, err := path.ParsePath(req.Arguments()[2]) + child, err := api.ParsePath(req.Context(), req.Arguments()[2], api.WithResolve(true)) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -315,34 +231,14 @@ to a file containing 'bar', and returns the hash of the new object. return } - var createfunc func() *dag.ProtoNode - if create { - createfunc = ft.EmptyDirNode - } - - e := dagutils.NewDagEditor(rtpb, nd.DAG) - - childnd, err := core.Resolve(req.Context(), nd.Namesys, nd.Resolver, childp) - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return - } - - err = e.InsertNodeAtPath(req.Context(), npath, childnd, createfunc) - if err != nil { - res.SetError(err, cmdkit.ErrNormal) - return - } - - nnode, err := e.Finalize(req.Context(), nd.DAG) + p, err := api.Object().AddLink(req.Context(), root, name, child, + api.Object().WithCreate(create)) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - nc := nnode.Cid() - - res.SetOutput(&Object{Hash: nc.String()}) + res.SetOutput(&Object{Hash: p.Cid().String()}) }, Type: Object{}, Marshalers: cmds.MarshalerMap{ diff --git a/core/coreapi/object.go b/core/coreapi/object.go index 1a232875d8f..4c7a5af06d8 100644 --- a/core/coreapi/object.go +++ b/core/coreapi/object.go @@ -319,10 +319,16 @@ func (api *ObjectAPI) Diff(ctx context.Context, before coreiface.Path, after cor out := make([]coreiface.ObjectChange, len(changes)) for i, change := range changes { out[i] = coreiface.ObjectChange{ - Type: change.Type, - Path: change.Path, - Before: api.ParseCid(change.Before), - After: api.ParseCid(change.After), + Type: change.Type, + Path: change.Path, + } + + if change.Before != nil { + out[i].Before = api.ParseCid(change.Before) + } + + if change.After != nil { + out[i].After = api.ParseCid(change.After) } }