diff --git a/gateway/blocks_backend.go b/gateway/blocks_backend.go index e5ddf8762..c95f3751e 100644 --- a/gateway/blocks_backend.go +++ b/gateway/blocks_backend.go @@ -316,7 +316,7 @@ func (bb *BlocksBackend) GetCAR(ctx context.Context, p path.ImmutablePath, param } // walkGatewaySimpleSelector walks the subgraph described by the path and terminal element parameters -func walkGatewaySimpleSelector(ctx context.Context, p path.Path, params CarParams, lsys *ipld.LinkSystem, pathResolver resolver.Resolver) error { +func walkGatewaySimpleSelector(ctx context.Context, p path.ImmutablePath, params CarParams, lsys *ipld.LinkSystem, pathResolver resolver.Resolver) error { // First resolve the path since we always need to. lastCid, remainder, err := pathResolver.ResolveToLastNode(ctx, p) if err != nil { @@ -545,7 +545,7 @@ func (bb *BlocksBackend) getPathRoots(ctx context.Context, contentPath path.Immu // TODO: should we be more explicit here and is this part of the IPFSBackend contract? // The issue here was that we returned datamodel.ErrWrongKind instead of this resolver error if isErrNotFound(err) { - return nil, nil, resolver.ErrNoLink{Name: root, Node: lastPath.Cid()} + return nil, nil, &resolver.ErrNoLink{Name: root, Node: lastPath.Cid()} } return nil, nil, err } @@ -639,7 +639,12 @@ func (bb *BlocksBackend) resolvePath(ctx context.Context, p path.Path) (path.Imm return nil, fmt.Errorf("unsupported path namespace: %s", p.Namespace()) } - node, rest, err := bb.resolver.ResolveToLastNode(ctx, p) + imPath, err := path.NewImmutablePath(p) + if err != nil { + return nil, err + } + + node, rest, err := bb.resolver.ResolveToLastNode(ctx, imPath) if err != nil { return nil, err } diff --git a/gateway/errors.go b/gateway/errors.go index 17a22ca62..f846a1964 100644 --- a/gateway/errors.go +++ b/gateway/errors.go @@ -179,15 +179,14 @@ func webError(w http.ResponseWriter, r *http.Request, c *Config, err error, defa // isErrNotFound returns true for IPLD errors that should return 4xx errors (e.g. the path doesn't exist, the data is // the wrong type, etc.), rather than issues with just finding and retrieving the data. func isErrNotFound(err error) bool { + if errors.Is(err, &resolver.ErrNoLink{}) { + return true + } + // Checks if err is of a type that does not implement the .Is interface and // cannot be directly compared to. Therefore, errors.Is cannot be used. for { - _, ok := err.(resolver.ErrNoLink) - if ok { - return true - } - - _, ok = err.(datamodel.ErrWrongKind) + _, ok := err.(datamodel.ErrWrongKind) if ok { return true } diff --git a/gateway/gateway_test.go b/gateway/gateway_test.go index 785c338ca..678147502 100644 --- a/gateway/gateway_test.go +++ b/gateway/gateway_test.go @@ -771,7 +771,7 @@ func TestErrorBubblingFromBackend(t *testing.T) { } testError("500 Not Found from IPLD", &ipld.ErrNotFound{}, http.StatusInternalServerError) - testError("404 Not Found from path resolver", resolver.ErrNoLink{}, http.StatusNotFound) + testError("404 Not Found from path resolver", &resolver.ErrNoLink{}, http.StatusNotFound) testError("502 Bad Gateway", ErrBadGateway, http.StatusBadGateway) testError("504 Gateway Timeout", ErrGatewayTimeout, http.StatusGatewayTimeout) diff --git a/path/resolver/resolver.go b/path/resolver/resolver.go index 57b763785..7421b93bf 100644 --- a/path/resolver/resolver.go +++ b/path/resolver/resolver.go @@ -3,7 +3,6 @@ package resolver import ( "context" - "errors" "fmt" "time" @@ -15,7 +14,6 @@ import ( fetcherhelpers "github.com/ipfs/boxo/fetcher/helpers" "github.com/ipfs/boxo/path" cid "github.com/ipfs/go-cid" - format "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" "github.com/ipld/go-ipld-prime" cidlink "github.com/ipld/go-ipld-prime/linking/cid" @@ -24,76 +22,73 @@ import ( "github.com/ipld/go-ipld-prime/traversal/selector/builder" ) -var log = logging.Logger("pathresolv") +var log = logging.Logger("path/resolver") -// ErrNoComponents is used when Paths after a protocol -// do not contain at least one component -var ErrNoComponents = errors.New( - "path must contain at least one component") - -// ErrNoLink is returned when a link is not found in a path +// ErrNoLink is returned when a link is not found in a path. type ErrNoLink struct { Name string Node cid.Cid } -// Error implements the Error interface for ErrNoLink with a useful -// human readable message. -func (e ErrNoLink) Error() string { +// Error implements the [errors.Error] interface. +func (e *ErrNoLink) Error() string { return fmt.Sprintf("no link named %q under %s", e.Name, e.Node.String()) } +// Is implements [errors.Is] interface. +func (e *ErrNoLink) Is(err error) bool { + switch err.(type) { + case *ErrNoLink: + return true + default: + return false + } +} + // Resolver provides path resolution to IPFS. type Resolver interface { - // ResolveToLastNode walks the given path and returns the cid of the - // last block referenced by the path, and the path segments to - // traverse from the final block boundary to the final node within the - // block. - ResolveToLastNode(ctx context.Context, fpath path.Path) (cid.Cid, []string, error) - // ResolvePath fetches the node for given path. It returns the last - // item returned by ResolvePathComponents and the last link traversed - // which can be used to recover the block. - ResolvePath(ctx context.Context, fpath path.Path) (ipld.Node, ipld.Link, error) - // ResolvePathComponents fetches the nodes for each segment of the given path. - // It uses the first path component as a hash (key) of the first node, then - // resolves all other components walking the links via a selector traversal - ResolvePathComponents(ctx context.Context, fpath path.Path) ([]ipld.Node, error) + // ResolveToLastNode walks the given path and returns the CID of the last block + // referenced by the path, as well as the remainder of the path segments to traverse + // from the final block boundary to the final node within the block. + ResolveToLastNode(context.Context, path.ImmutablePath) (cid.Cid, []string, error) + + // ResolvePath fetches the node for the given path. It returns the last item returned + // by [Resolver.ResolvePathComponents] and the last link traversed which can be used + // to recover the block. + ResolvePath(context.Context, path.ImmutablePath) (ipld.Node, ipld.Link, error) + + // ResolvePathComponents fetches the nodes for each segment of the given path. It + // uses the first path component as the CID of the first node, then resolves all + // other components walking the links via a selector traversal. + ResolvePathComponents(context.Context, path.ImmutablePath) ([]ipld.Node, error) } -// basicResolver implements the Resolver interface. -// It references a FetcherFactory, which is uses to resolve nodes. -// TODO: now that this is more modular, try to unify this code with the -// -// the resolvers in namesys. +// basicResolver implements the [Resolver] interface. It requires a [fetcher.Factory], +// which is used to resolve the nodes. type basicResolver struct { FetcherFactory fetcher.Factory } -// NewBasicResolver constructs a new basic resolver. -func NewBasicResolver(fetcherFactory fetcher.Factory) Resolver { +// NewBasicResolver constructs a new basic resolver using the given [fetcher.Factory]. +func NewBasicResolver(factory fetcher.Factory) Resolver { return &basicResolver{ - FetcherFactory: fetcherFactory, + FetcherFactory: factory, } } -// ResolveToLastNode walks the given path and returns the cid of the last -// block referenced by the path, and the path segments to traverse from the -// final block boundary to the final node within the block. -func (r *basicResolver) ResolveToLastNode(ctx context.Context, fpath path.Path) (cid.Cid, []string, error) { +// ResolveToLastNode implements [Resolver.ResolveToLastNode]. +func (r *basicResolver) ResolveToLastNode(ctx context.Context, fpath path.ImmutablePath) (cid.Cid, []string, error) { ctx, span := startSpan(ctx, "basicResolver.ResolveToLastNode", trace.WithAttributes(attribute.Stringer("Path", fpath))) defer span.End() - c, p, err := splitImmutablePath(fpath) - if err != nil { - return cid.Cid{}, nil, err - } + c, remainder := fpath.Cid(), fpath.Segments()[2:] - if len(p) == 0 { + if len(remainder) == 0 { return c, nil, nil } // create a selector to traverse and match all path segments - pathSelector := pathAllSelector(p[:len(p)-1]) + pathSelector := pathAllSelector(remainder[:len(remainder)-1]) // create a new cancellable session ctx, cancel := context.WithTimeout(ctx, time.Minute) @@ -107,19 +102,19 @@ func (r *basicResolver) ResolveToLastNode(ctx context.Context, fpath path.Path) if len(nodes) < 1 { return cid.Cid{}, nil, fmt.Errorf("path %v did not resolve to a node", fpath) - } else if len(nodes) < len(p) { - return cid.Undef, nil, ErrNoLink{Name: p[len(nodes)-1], Node: lastCid} + } else if len(nodes) < len(remainder) { + return cid.Undef, nil, &ErrNoLink{Name: remainder[len(nodes)-1], Node: lastCid} } parent := nodes[len(nodes)-1] - lastSegment := p[len(p)-1] + lastSegment := remainder[len(remainder)-1] // find final path segment within node nd, err := parent.LookupBySegment(ipld.ParsePathSegment(lastSegment)) switch err.(type) { case nil: case schema.ErrNoSuchField: - return cid.Undef, nil, ErrNoLink{Name: lastSegment, Node: lastCid} + return cid.Undef, nil, &ErrNoLink{Name: lastSegment, Node: lastCid} default: return cid.Cid{}, nil, err } @@ -127,7 +122,7 @@ func (r *basicResolver) ResolveToLastNode(ctx context.Context, fpath path.Path) // if last node is not a link, just return it's cid, add path to remainder and return if nd.Kind() != ipld.Kind_Link { // return the cid and the remainder of the path - return lastCid, p[len(p)-depth-1:], nil + return lastCid, remainder[len(remainder)-depth-1:], nil } lnk, err := nd.AsLink() @@ -143,22 +138,18 @@ func (r *basicResolver) ResolveToLastNode(ctx context.Context, fpath path.Path) return clnk.Cid, []string{}, nil } -// ResolvePath fetches the node for given path. It returns the last item -// returned by ResolvePathComponents and the last link traversed which can be used to recover the block. +// ResolvePath implements [Resolver.ResolvePath]. // -// Note: if/when the context is cancelled or expires then if a multi-block ADL node is returned then it may not be -// possible to load certain values. -func (r *basicResolver) ResolvePath(ctx context.Context, fpath path.Path) (ipld.Node, ipld.Link, error) { +// Note: if/when the context is cancelled or expires then if a multi-block ADL +// node is returned then it may not be possible to load certain values. +func (r *basicResolver) ResolvePath(ctx context.Context, fpath path.ImmutablePath) (ipld.Node, ipld.Link, error) { ctx, span := startSpan(ctx, "basicResolver.ResolvePath", trace.WithAttributes(attribute.Stringer("Path", fpath))) defer span.End() - c, p, err := splitImmutablePath(fpath) - if err != nil { - return nil, nil, err - } + c, remainder := fpath.Cid(), fpath.Segments()[2:] // create a selector to traverse all path segments but only match the last - pathSelector := pathLeafSelector(p) + pathSelector := pathLeafSelector(remainder) nodes, c, _, err := r.resolveNodes(ctx, c, pathSelector) if err != nil { @@ -170,72 +161,25 @@ func (r *basicResolver) ResolvePath(ctx context.Context, fpath path.Path) (ipld. return nodes[len(nodes)-1], cidlink.Link{Cid: c}, nil } -// ResolveSingle simply resolves one hop of a path through a graph with no -// extra context (does not opaquely resolve through sharded nodes) -// Deprecated: fetch node as ipld-prime or convert it and then use a selector to traverse through it. -func ResolveSingle(ctx context.Context, ds format.NodeGetter, nd format.Node, names []string) (*format.Link, []string, error) { - _, span := startSpan(ctx, "ResolveSingle", trace.WithAttributes(attribute.Stringer("CID", nd.Cid()))) - defer span.End() - return nd.ResolveLink(names) -} - -// ResolvePathComponents fetches the nodes for each segment of the given path. -// It uses the first path component as a hash (key) of the first node, then -// resolves all other components walking the links via a selector traversal +// ResolvePathComponents implements [Resolver.ResolvePathComponents]. // -// Note: if/when the context is cancelled or expires then if a multi-block ADL node is returned then it may not be -// possible to load certain values. -func (r *basicResolver) ResolvePathComponents(ctx context.Context, fpath path.Path) (nodes []ipld.Node, err error) { +// Note: if/when the context is cancelled or expires then if a multi-block ADL +// node is returned then it may not be possible to load certain values. +func (r *basicResolver) ResolvePathComponents(ctx context.Context, fpath path.ImmutablePath) (nodes []ipld.Node, err error) { ctx, span := startSpan(ctx, "basicResolver.ResolvePathComponents", trace.WithAttributes(attribute.Stringer("Path", fpath))) defer span.End() defer log.Debugw("resolvePathComponents", "fpath", fpath, "error", err) - c, p, err := splitImmutablePath(fpath) - if err != nil { - return nil, err - } + c, remainder := fpath.Cid(), fpath.Segments()[2:] // create a selector to traverse and match all path segments - pathSelector := pathAllSelector(p) + pathSelector := pathAllSelector(remainder) nodes, _, _, err = r.resolveNodes(ctx, c, pathSelector) return nodes, err } -// ResolveLinks iteratively resolves names by walking the link hierarchy. -// Every node is fetched from the Fetcher, resolving the next name. -// Returns the list of nodes forming the path, starting with ndd. This list is -// guaranteed never to be empty. -// -// ResolveLinks(nd, []string{"foo", "bar", "baz"}) -// would retrieve "baz" in ("bar" in ("foo" in nd.Links).Links).Links -// -// Note: if/when the context is cancelled or expires then if a multi-block ADL node is returned then it may not be -// possible to load certain values. -func (r *basicResolver) ResolveLinks(ctx context.Context, ndd ipld.Node, names []string) (nodes []ipld.Node, err error) { - ctx, span := startSpan(ctx, "basicResolver.ResolveLinks") - defer span.End() - - defer log.Debugw("resolvePathComponents", "names", names, "error", err) - // create a selector to traverse and match all path segments - pathSelector := pathAllSelector(names) - - session := r.FetcherFactory.NewSession(ctx) - - // traverse selector - nodes = []ipld.Node{ndd} - err = session.NodeMatching(ctx, ndd, pathSelector, func(res fetcher.FetchResult) error { - nodes = append(nodes, res.Node) - return nil - }) - if err != nil { - return nil, err - } - - return nodes, err -} - // Finds nodes matching the selector starting with a cid. Returns the matched nodes, the cid of the block containing // the last node, and the depth of the last node within its block (root is depth 0). func (r *basicResolver) resolveNodes(ctx context.Context, c cid.Cid, sel ipld.Node) ([]ipld.Node, cid.Cid, int, error) { @@ -302,13 +246,3 @@ func pathSelector(path []string, ssb builder.SelectorSpecBuilder, reduce func(st func startSpan(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { return otel.Tracer("boxo/path/resolver").Start(ctx, fmt.Sprintf("Path.%s", name), opts...) } - -// splitImmutablePath cleans up and splits the given path. -func splitImmutablePath(p path.Path) (cid.Cid, []string, error) { - imPath, err := path.NewImmutablePath(p) - if err != nil { - return cid.Undef, nil, err - } - - return imPath.Cid(), imPath.Segments()[2:], nil -} diff --git a/path/resolver/resolver_test.go b/path/resolver/resolver_test.go index 41f214ed6..e86e59ae1 100644 --- a/path/resolver/resolver_test.go +++ b/path/resolver/resolver_test.go @@ -3,7 +3,6 @@ package resolver_test import ( "bytes" "context" - "fmt" "math/rand" "strings" "testing" @@ -26,7 +25,6 @@ import ( "github.com/ipfs/go-unixfsnode" dagcbor "github.com/ipld/go-ipld-prime/codec/dagcbor" dagjson "github.com/ipld/go-ipld-prime/codec/dagjson" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -38,7 +36,7 @@ func randNode() *merkledag.ProtoNode { return node } -func TestRecurivePathResolution(t *testing.T) { +func TestRecursivePathResolution(t *testing.T) { ctx := context.Background() bsrv := dagmock.Bserv() @@ -47,28 +45,21 @@ func TestRecurivePathResolution(t *testing.T) { c := randNode() err := b.AddNodeLink("grandchild", c) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) err = a.AddNodeLink("child", b) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) for _, n := range []*merkledag.ProtoNode{a, b, c} { err = bsrv.AddBlock(ctx, n) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } - aKey := a.Cid() + p, err := path.Join(path.NewIPFSPath(a.Cid()), "child", "grandchild") + require.NoError(t, err) - p, err := path.Join(path.NewIPFSPath(aKey), "child", "grandchild") - if err != nil { - t.Fatal(err) - } + imPath, err := path.NewImmutablePath(p) + require.NoError(t, err) fetcherFactory := bsfetcher.NewFetcherConfig(bsrv) fetcherFactory.NodeReifier = unixfsnode.Reify @@ -80,53 +71,26 @@ func TestRecurivePathResolution(t *testing.T) { }) resolver := resolver.NewBasicResolver(fetcherFactory) - node, lnk, err := resolver.ResolvePath(ctx, p) - if err != nil { - t.Fatal(err) - } + node, lnk, err := resolver.ResolvePath(ctx, imPath) + require.NoError(t, err) uNode, ok := node.(unixfsnode.PathedPBNode) require.True(t, ok) fd := uNode.FieldData() byts, err := fd.Must().AsBytes() require.NoError(t, err) + require.Equal(t, cidlink.Link{Cid: c.Cid()}, lnk) + require.Equal(t, c.Data(), byts) - assert.Equal(t, cidlink.Link{Cid: c.Cid()}, lnk) - - assert.Equal(t, c.Data(), byts) - cKey := c.Cid() - - rCid, rest, err := resolver.ResolveToLastNode(ctx, p) - if err != nil { - t.Fatal(err) - } - - if len(rest) != 0 { - t.Error("expected rest to be empty") - } - - if rCid.String() != cKey.String() { - t.Fatal(fmt.Errorf( - "ResolveToLastNode failed for %s: %s != %s", - p.String(), rCid.String(), cKey.String())) - } - - p2 := path.NewIPFSPath(aKey) - - rCid, rest, err = resolver.ResolveToLastNode(ctx, p2) - if err != nil { - t.Fatal(err) - } - - if len(rest) != 0 { - t.Error("expected rest to be empty") - } + rCid, remainder, err := resolver.ResolveToLastNode(ctx, imPath) + require.NoError(t, err) + require.Empty(t, remainder) + require.Equal(t, c.Cid().String(), rCid.String()) - if rCid.String() != aKey.String() { - t.Fatal(fmt.Errorf( - "ResolveToLastNode failed for %s: %s != %s", - p.String(), rCid.String(), cKey.String())) - } + rCid, remainder, err = resolver.ResolveToLastNode(ctx, path.NewIPFSPath(a.Cid())) + require.NoError(t, err) + require.Empty(t, remainder) + require.Equal(t, a.Cid().String(), rCid.String()) } func TestResolveToLastNode_ErrNoLink(t *testing.T) { @@ -138,24 +102,16 @@ func TestResolveToLastNode_ErrNoLink(t *testing.T) { c := randNode() err := b.AddNodeLink("grandchild", c) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) err = a.AddNodeLink("child", b) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) for _, n := range []*merkledag.ProtoNode{a, b, c} { err = bsrv.AddBlock(ctx, n) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } - aKey := a.Cid() - fetcherFactory := bsfetcher.NewFetcherConfig(bsrv) fetcherFactory.PrototypeChooser = dagpb.AddSupportToChooser(func(lnk ipld.Link, lnkCtx ipld.LinkContext) (ipld.NodePrototype, error) { if tlnkNd, ok := lnkCtx.LinkNode.(schema.TypedLinkNode); ok { @@ -167,19 +123,27 @@ func TestResolveToLastNode_ErrNoLink(t *testing.T) { r := resolver.NewBasicResolver(fetcherFactory) // test missing link intermediate segment - p, err := path.Join(path.NewIPFSPath(aKey), "cheese", "time") + p, err := path.Join(path.NewIPFSPath(a.Cid()), "cheese", "time") require.NoError(t, err) - _, _, err = r.ResolveToLastNode(ctx, p) - require.EqualError(t, err, resolver.ErrNoLink{Name: "cheese", Node: aKey}.Error()) + imPath, err := path.NewImmutablePath(p) + require.NoError(t, err) + + _, _, err = r.ResolveToLastNode(ctx, imPath) + require.ErrorIs(t, err, &resolver.ErrNoLink{}) + require.Equal(t, "cheese", err.(*resolver.ErrNoLink).Name) + require.Equal(t, a.Cid(), err.(*resolver.ErrNoLink).Node) // test missing link at end - bKey := b.Cid() - p, err = path.Join(path.NewIPFSPath(aKey), "child", "apples") + p, err = path.Join(path.NewIPFSPath(a.Cid()), "child", "apples") + require.NoError(t, err) + + imPath, err = path.NewImmutablePath(p) require.NoError(t, err) - _, _, err = r.ResolveToLastNode(ctx, p) - require.EqualError(t, err, resolver.ErrNoLink{Name: "apples", Node: bKey}.Error()) + _, _, err = r.ResolveToLastNode(ctx, imPath) + require.Equal(t, "apples", err.(*resolver.ErrNoLink).Name) + require.Equal(t, b.Cid(), err.(*resolver.ErrNoLink).Node) } func TestResolveToLastNode_NoUnnecessaryFetching(t *testing.T) { @@ -195,9 +159,10 @@ func TestResolveToLastNode_NoUnnecessaryFetching(t *testing.T) { err = bsrv.AddBlock(ctx, a) require.NoError(t, err) - aKey := a.Cid() + p, err := path.Join(path.NewIPFSPath(a.Cid()), "child") + require.NoError(t, err) - p, err := path.Join(path.NewIPFSPath(aKey), "child") + imPath, err := path.NewImmutablePath(p) require.NoError(t, err) fetcherFactory := bsfetcher.NewFetcherConfig(bsrv) @@ -210,7 +175,7 @@ func TestResolveToLastNode_NoUnnecessaryFetching(t *testing.T) { fetcherFactory.NodeReifier = unixfsnode.Reify resolver := resolver.NewBasicResolver(fetcherFactory) - resolvedCID, remainingPath, err := resolver.ResolveToLastNode(ctx, p) + resolvedCID, remainingPath, err := resolver.ResolveToLastNode(ctx, imPath) require.NoError(t, err) require.Equal(t, len(remainingPath), 0, "cannot have remaining path") @@ -225,9 +190,11 @@ func TestPathRemainder(t *testing.T) { nb := basicnode.Prototype.Any.NewBuilder() err := dagjson.Decode(nb, strings.NewReader(`{"foo": {"bar": "baz"}}`)) require.NoError(t, err) + out := new(bytes.Buffer) err = dagcbor.Encode(nb.Build(), out) require.NoError(t, err) + lnk, err := cid.Prefix{ Version: 1, Codec: cid.DagCBOR, @@ -235,40 +202,46 @@ func TestPathRemainder(t *testing.T) { MhLength: 32, }.Sum(out.Bytes()) require.NoError(t, err) + blk, err := blocks.NewBlockWithCid(out.Bytes(), lnk) require.NoError(t, err) + bsrv.AddBlock(ctx, blk) fetcherFactory := bsfetcher.NewFetcherConfig(bsrv) resolver := resolver.NewBasicResolver(fetcherFactory) - newPath, err := path.Join(path.NewIPFSPath(lnk), "foo", "bar") + p, err := path.Join(path.NewIPFSPath(lnk), "foo", "bar") + require.NoError(t, err) + + imPath, err := path.NewImmutablePath(p) require.NoError(t, err) - rp1, remainder, err := resolver.ResolveToLastNode(ctx, newPath) + rp, remainder, err := resolver.ResolveToLastNode(ctx, imPath) require.NoError(t, err) - assert.Equal(t, lnk, rp1) + require.Equal(t, lnk, rp) require.Equal(t, "foo/bar", strings.Join(remainder, "/")) } func TestResolveToLastNode_MixedSegmentTypes(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + bsrv := dagmock.Bserv() a := randNode() err := bsrv.AddBlock(ctx, a) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) nb := basicnode.Prototype.Any.NewBuilder() json := `{"foo":{"bar":[0,{"boom":["baz",1,2,{"/":"CID"},"blop"]}]}}` json = strings.ReplaceAll(json, "CID", a.Cid().String()) err = dagjson.Decode(nb, strings.NewReader(json)) require.NoError(t, err) + out := new(bytes.Buffer) err = dagcbor.Encode(nb.Build(), out) require.NoError(t, err) + lnk, err := cid.Prefix{ Version: 1, Codec: cid.DagCBOR, @@ -276,8 +249,10 @@ func TestResolveToLastNode_MixedSegmentTypes(t *testing.T) { MhLength: 32, }.Sum(out.Bytes()) require.NoError(t, err) + blk, err := blocks.NewBlockWithCid(out.Bytes(), lnk) require.NoError(t, err) + bsrv.AddBlock(ctx, blk) fetcherFactory := bsfetcher.NewFetcherConfig(bsrv) resolver := resolver.NewBasicResolver(fetcherFactory) @@ -285,9 +260,11 @@ func TestResolveToLastNode_MixedSegmentTypes(t *testing.T) { newPath, err := path.Join(path.NewIPFSPath(lnk), "foo", "bar", "1", "boom", "3") require.NoError(t, err) - cid, remainder, err := resolver.ResolveToLastNode(ctx, newPath) + imPath, err := path.NewImmutablePath(newPath) require.NoError(t, err) - assert.Equal(t, 0, len(remainder)) - assert.True(t, cid.Equals(a.Cid())) + cid, remainder, err := resolver.ResolveToLastNode(ctx, imPath) + require.NoError(t, err) + require.Equal(t, 0, len(remainder)) + require.True(t, cid.Equals(a.Cid())) }