diff --git a/core/coreapi/interface/path.go b/core/coreapi/interface/path.go index bb87a6b3b..b4a9f0dbd 100644 --- a/core/coreapi/interface/path.go +++ b/core/coreapi/interface/path.go @@ -29,13 +29,14 @@ type Path interface { // ResolvedPath is a resolved Path type ResolvedPath interface { - // Cid returns cid referred to by path + // Cid returns the CID referred to by path Cid() *cid.Cid - // Root returns cid of root path + // Root returns the CID of root path Root() *cid.Cid - //TODO: Path remainder + // Remainder returns unresolved part of the path + Remainder() string Path } diff --git a/core/coreapi/path.go b/core/coreapi/path.go index 85407211d..bb9d59b9c 100644 --- a/core/coreapi/path.go +++ b/core/coreapi/path.go @@ -1,7 +1,9 @@ package coreapi import ( - "context" + context "context" + gopath "path" + strings "strings" core "github.com/ipfs/go-ipfs/core" coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" @@ -22,18 +24,29 @@ type path struct { // resolvedPath implements coreiface.resolvedPath type resolvedPath struct { path - cid *cid.Cid - root *cid.Cid + cid *cid.Cid + root *cid.Cid + remainder string } // IpfsPath parses the path from `c`, reruns the parsed path. func (api *CoreAPI) IpfsPath(c *cid.Cid) coreiface.ResolvedPath { - return &resolvedPath{path: path{ipfspath.Path("/ipfs/" + c.String())}, cid: c, root: c} + return &resolvedPath{ + path: path{ipfspath.Path("/ipfs/" + c.String())}, + cid: c, + root: c, + remainder: "", + } } // IpldPath parses the path from `c`, reruns the parsed path. func (api *CoreAPI) IpldPath(c *cid.Cid) coreiface.ResolvedPath { - return &resolvedPath{path: path{ipfspath.Path("/ipld/" + c.String())}, cid: c, root: c} + return &resolvedPath{ + path: path{ipfspath.Path("/ipld/" + c.String())}, + cid: c, + root: c, + remainder: "", + } } // ResolveNode resolves the path `p` using Unixfs resolver, gets and returns the @@ -66,25 +79,40 @@ func resolvePath(ctx context.Context, ng ipld.NodeGetter, nsys namesys.NameSyste return p.(coreiface.ResolvedPath), nil } - r := &resolver.Resolver{ - DAG: ng, - ResolveOnce: uio.ResolveUnixfsOnce, - } - - p2 := ipfspath.FromString(p.String()) - node, err := core.Resolve(ctx, nsys, r, p2) + ipath := p.(*path).path + ipath, err := core.ResolveIPNS(ctx, nsys, ipath) if err == core.ErrNoNamesys { return nil, coreiface.ErrOffline } else if err != nil { return nil, err } + resolveOnce := uio.ResolveUnixfsOnce + if strings.HasPrefix(ipath.String(), "/ipld") { + resolveOnce = resolver.ResolveSingle + } + + r := &resolver.Resolver{ + DAG: ng, + ResolveOnce: resolveOnce, + } + + node, rest, err := r.ResolveToLastNode(ctx, ipath) + if err != nil { + return nil, err + } + var root *cid.Cid - if p2.IsJustAKey() { + if ipath.IsJustAKey() { root = node.Cid() } - return &resolvedPath{path: path{p2}, cid: node.Cid(), root: root}, nil + return &resolvedPath{ + path: path{ipath}, + cid: node.Cid(), + root: root, + remainder: gopath.Join(rest...), + }, nil } // ParsePath parses path `p` using ipfspath parser, returns the parsed path. @@ -120,3 +148,7 @@ func (p *resolvedPath) Cid() *cid.Cid { func (p *resolvedPath) Root() *cid.Cid { return p.root } + +func (p *resolvedPath) Remainder() string { + return p.remainder +} diff --git a/core/coreapi/path_test.go b/core/coreapi/path_test.go index 7fc5ad5af..d35e57ba9 100644 --- a/core/coreapi/path_test.go +++ b/core/coreapi/path_test.go @@ -32,3 +32,84 @@ func TestMutablePath(t *testing.T) { t.Error("expected /ipld path to be immutable") } } + +func TestPathRemainder(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Fatal(err) + } + + obj, err := api.Dag().Put(ctx, strings.NewReader(`{"foo": {"bar": "baz"}}`)) + if err != nil { + t.Fatal(err) + } + + p1, err := api.ParsePath(obj.String() + "/foo/bar") + if err != nil { + t.Error(err) + } + + rp1, err := api.ResolvePath(ctx, p1) + if err != nil { + t.Fatal(err) + } + + if rp1.Remainder() != "foo/bar" { + t.Error("expected to get path remainder") + } +} + +func TestEmptyPathRemainder(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Fatal(err) + } + + obj, err := api.Dag().Put(ctx, strings.NewReader(`{"foo": {"bar": "baz"}}`)) + if err != nil { + t.Fatal(err) + } + + if obj.Remainder() != "" { + t.Error("expected the resolved path to not have a remainder") + } + + p1, err := api.ParsePath(obj.String()) + if err != nil { + t.Error(err) + } + + rp1, err := api.ResolvePath(ctx, p1) + if err != nil { + t.Fatal(err) + } + + if rp1.Remainder() != "" { + t.Error("expected the resolved path to not have a remainder") + } +} + +func TestInvalidPathRemainder(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Fatal(err) + } + + obj, err := api.Dag().Put(ctx, strings.NewReader(`{"foo": {"bar": "baz"}}`)) + if err != nil { + t.Fatal(err) + } + + p1, err := api.ParsePath(obj.String() + "/bar/baz") + if err != nil { + t.Error(err) + } + + _, err = api.ResolvePath(ctx, p1) + if err == nil || err.Error() != "no such link found" { + t.Fatalf("unexpected error: %s", err) + } +} diff --git a/core/pathresolver.go b/core/pathresolver.go index ea5ea7d9c..7fbb47d6a 100644 --- a/core/pathresolver.go +++ b/core/pathresolver.go @@ -19,10 +19,8 @@ import ( var ErrNoNamesys = errors.New( "core/resolve: no Namesys on IpfsNode - can't resolve ipns entry") -// Resolve resolves the given path by parsing out protocol-specific -// entries (e.g. /ipns/) and then going through the /ipfs/ -// entries and returning the final node. -func Resolve(ctx context.Context, nsys namesys.NameSystem, r *resolver.Resolver, p path.Path) (ipld.Node, error) { +// ResolveIPNS resolves /ipns paths +func ResolveIPNS(ctx context.Context, nsys namesys.NameSystem, p path.Path) (path.Path, error) { if strings.HasPrefix(p.String(), "/ipns/") { evt := log.EventBegin(ctx, "resolveIpnsPath") defer evt.Done() @@ -31,36 +29,47 @@ func Resolve(ctx context.Context, nsys namesys.NameSystem, r *resolver.Resolver, // TODO(cryptix): we should be able to query the local cache for the path if nsys == nil { evt.Append(logging.LoggableMap{"error": ErrNoNamesys.Error()}) - return nil, ErrNoNamesys + return "", ErrNoNamesys } seg := p.Segments() if len(seg) < 2 || seg[1] == "" { // just "/" without further segments evt.Append(logging.LoggableMap{"error": path.ErrNoComponents.Error()}) - return nil, path.ErrNoComponents + return "", path.ErrNoComponents } extensions := seg[2:] resolvable, err := path.FromSegments("/", seg[0], seg[1]) if err != nil { evt.Append(logging.LoggableMap{"error": err.Error()}) - return nil, err + return "", err } respath, err := nsys.Resolve(ctx, resolvable.String()) if err != nil { evt.Append(logging.LoggableMap{"error": err.Error()}) - return nil, err + return "", err } segments := append(respath.Segments(), extensions...) p, err = path.FromSegments("/", segments...) if err != nil { evt.Append(logging.LoggableMap{"error": err.Error()}) - return nil, err + return "", err } } + return p, nil +} + +// Resolve resolves the given path by parsing out protocol-specific +// entries (e.g. /ipns/) and then going through the /ipfs/ +// entries and returning the final node. +func Resolve(ctx context.Context, nsys namesys.NameSystem, r *resolver.Resolver, p path.Path) (ipld.Node, error) { + p, err := ResolveIPNS(ctx, nsys, p) + if err != nil { + return nil, err + } // ok, we have an IPFS path now (or what we'll treat as one) return r.ResolvePath(ctx, p) diff --git a/path/path.go b/path/path.go index cc0ec31b2..1c2bd2fe6 100644 --- a/path/path.go +++ b/path/path.go @@ -161,7 +161,7 @@ func SplitList(pth string) []string { // must be a Multihash) and return it separately. func SplitAbsPath(fpath Path) (*cid.Cid, []string, error) { parts := fpath.Segments() - if parts[0] == "ipfs" { + if parts[0] == "ipfs" || parts[0] == "ipld" { parts = parts[1:] }