diff --git a/README.md b/README.md index 2146b6c..d11c22f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This is an IPLD ADL that provides string based pathing for protobuf nodes. The top level node behaves like a map where LookupByString returns the Hash property on the Link in the protobufs list of Links whos Name property matches the key. This should enable selector traversals that work based of paths. -Note that while it works internally with go-codec-dagpb, the Reify method (used to get a UnixFSNode from a DagPB node should actually work successfully with go-ipld-prime-proto nodes) +Note that while it works internally with go-codec-dagpb, the `Reify()` method (used to get a UnixFSNode from a DagPB node) should actually work successfully with any schema-compliant go-ipld-prime `Node`. Using `ReifyDagPB()` will require that the incoming `Node` be of type `PBNode` from go-codec-dagpb. ## License diff --git a/file/file_test.go b/file/file_test.go index ee01a71..af40343 100644 --- a/file/file_test.go +++ b/file/file_test.go @@ -13,7 +13,9 @@ import ( "github.com/ipld/go-car/v2/blockstore" dagpb "github.com/ipld/go-codec-dagpb" "github.com/ipld/go-ipld-prime" + "github.com/ipld/go-ipld-prime/datamodel" cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/ipld/go-ipld-prime/node/basicnode" ) func TestRootV0File(t *testing.T) { @@ -32,6 +34,74 @@ func TestRootV0File(t *testing.T) { } } +func TestBasicnodeReify(t *testing.T) { + baseFile := "./fixtures/QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o.car" + root, ls := open(baseFile, t) + nb := basicnode.Prototype.Any.NewBuilder() + err := datamodel.Copy(root, nb) + if err != nil { + t.Fatal(err) + } + basicroot := nb.Build() + file, err := unixfsnode.Reify(ipld.LinkContext{}, basicroot, ls) + if err != nil { + t.Fatal(err) + } + if file == basicroot { + t.Fatal("node pass-through with Reify()") + } + fc, err := file.AsBytes() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(fc, []byte("hello world\n")) { + t.Errorf("file content does not match: %s", string(fc)) + } +} + +func TestReifyDagPB(t *testing.T) { + baseFile := "./fixtures/QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o.car" + root, ls := open(baseFile, t) + file, err := unixfsnode.Reify(ipld.LinkContext{}, root, ls) + if err != nil { + t.Fatal(err) + } + if file == root { + t.Fatal("node pass-through with Reify()") + } + fc, err := file.AsBytes() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(fc, []byte("hello world\n")) { + t.Errorf("file content does not match: %s", string(fc)) + } +} + +func TestBasicnodeReifyDagPBFails(t *testing.T) { + baseFile := "./fixtures/QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o.car" + root, ls := open(baseFile, t) + nb := basicnode.Prototype.Any.NewBuilder() + err := datamodel.Copy(root, nb) + if err != nil { + t.Fatal(err) + } + basicroot := nb.Build() + // we expect this to pass through without being interpreted as UnixFS since + // ReifyDagPB is strict + file, err := unixfsnode.ReifyDagPB(ipld.LinkContext{}, basicroot, ls) + if err != nil { + t.Fatal(err) + } + if file != basicroot { + t.Fatal("node did not pass-through Reify") + } + _, err = file.AsBytes() + if err == nil { + t.Fatal("should not be able to AsBytes() unreified node") + } +} + func TestNamedV0File(t *testing.T) { baseFile := "./fixtures/QmT8EC9sJq63SkDZ1mWLbWWyVV66PuqyHWpKkH4pcVyY4H.car" root, ls := open(baseFile, t) diff --git a/reification.go b/reification.go index fc79291..7f07e8b 100644 --- a/reification.go +++ b/reification.go @@ -12,12 +12,33 @@ import ( "github.com/ipld/go-ipld-prime" ) +// ReifyDagPB looks at an ipld Node and tries to interpret it as a UnixFSNode +// if successful, it returns the UnixFSNode. +// +// ReifyDagPB strictly requires that an incoming node be a +// github.com/ipld/go-codec-dagpb#PBNode type in order to reify it. Use this +// reification method if you want to apply the strict form of the UnixFS +// specification by type checking (note that a type check of PBNode does not +// guarantee that the data was DAG-PB encoded, nor does the reverse hold as +// there is currently no way to determine original codec by inspecting a Node). +func ReifyDagPB(lnkCtx ipld.LinkContext, maybePBNodeRoot ipld.Node, lsys *ipld.LinkSystem) (ipld.Node, error) { + if _, ok := maybePBNodeRoot.(dagpb.PBNode); !ok { + return maybePBNodeRoot, nil + } + return Reify(lnkCtx, maybePBNodeRoot, lsys) +} + // Reify looks at an ipld Node and tries to interpret it as a UnixFSNode // if successful, it returns the UnixFSNode func Reify(lnkCtx ipld.LinkContext, maybePBNodeRoot ipld.Node, lsys *ipld.LinkSystem) (ipld.Node, error) { pbNode, ok := maybePBNodeRoot.(dagpb.PBNode) if !ok { - return maybePBNodeRoot, nil + // see if the node has the right structure anyway + pbb := dagpb.Type.PBNode.NewBuilder() + if err := pbb.AssignNode(maybePBNodeRoot); err != nil { + return maybePBNodeRoot, nil + } + pbNode = pbb.Build().(dagpb.PBNode) } if !pbNode.FieldData().Exists() { // no data field, therefore, not UnixFS