Skip to content

Commit

Permalink
refactor!: remove .Remainder, coreiface.ResolvePath returns remainder
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias committed Sep 7, 2023
1 parent 3214084 commit c4cf7d4
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 90 deletions.
6 changes: 4 additions & 2 deletions coreiface/coreapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ type CoreAPI interface {
// Routing returns an implementation of Routing API
Routing() RoutingAPI

// ResolvePath resolves the path using Unixfs resolver
ResolvePath(context.Context, path.Path) (path.ImmutablePath, error)
// ResolvePath resolves the path using UnixFS resolver, and returns the resolved
// immutable path, and the remainder of the path segments that cannot be resolved
// within UnixFS.
ResolvePath(context.Context, path.Path) (path.ImmutablePath, []string, error)

// ResolveNode resolves the path (if not resolved already) using Unixfs
// resolver, gets and returns the resolved Node
Expand Down
2 changes: 1 addition & 1 deletion coreiface/tests/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func (tp *TestSuite) TestBlockGet(t *testing.T) {

p := path.NewIPFSPath(res.Path().Cid())

rp, err := api.ResolvePath(ctx, p)
rp, _, err := api.ResolvePath(ctx, p)
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion coreiface/tests/dag.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (tp *TestSuite) TestDagPath(t *testing.T) {
t.Fatal(err)
}

rp, err := api.ResolvePath(ctx, p)
rp, _, err := api.ResolvePath(ctx, p)
if err != nil {
t.Fatal(err)
}
Expand Down
12 changes: 6 additions & 6 deletions coreiface/tests/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ func (tp *TestSuite) TestPathRemainder(t *testing.T) {
p, err := path.Join(path.NewIPFSPath(nd.Cid()), "foo", "bar")
require.NoError(t, err)

rp1, err := api.ResolvePath(ctx, p)
_, remainder, err := api.ResolvePath(ctx, p)
require.NoError(t, err)
require.Equal(t, "/foo/bar", rp1.Remainder())
require.Equal(t, "/foo/bar", path.SegmentsToString(remainder...))
}

func (tp *TestSuite) TestEmptyPathRemainder(t *testing.T) {
Expand All @@ -74,9 +74,9 @@ func (tp *TestSuite) TestEmptyPathRemainder(t *testing.T) {
err = api.Dag().Add(ctx, nd)
require.NoError(t, err)

rp1, err := api.ResolvePath(ctx, path.NewIPFSPath(nd.Cid()))
_, remainder, err := api.ResolvePath(ctx, path.NewIPFSPath(nd.Cid()))
require.NoError(t, err)
require.Empty(t, rp1.Remainder())
require.Empty(t, remainder)
}

func (tp *TestSuite) TestInvalidPathRemainder(t *testing.T) {
Expand All @@ -96,7 +96,7 @@ func (tp *TestSuite) TestInvalidPathRemainder(t *testing.T) {
p, err := path.Join(path.NewIPLDPath(nd.Cid()), "/bar/baz")
require.NoError(t, err)

_, err = api.ResolvePath(ctx, p)
_, _, err = api.ResolvePath(ctx, p)
require.NotNil(t, err)
require.ErrorContains(t, err, `no link named "bar"`)
}
Expand All @@ -122,7 +122,7 @@ func (tp *TestSuite) TestPathRoot(t *testing.T) {
p, err := path.Join(path.NewIPLDPath(nd.Cid()), "/foo")
require.NoError(t, err)

rp, err := api.ResolvePath(ctx, p)
rp, _, err := api.ResolvePath(ctx, p)
require.NoError(t, err)
require.Equal(t, rp.Cid().String(), blk.Path().Cid().String())
}
Expand Down
55 changes: 33 additions & 22 deletions gateway/blocks_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,14 +485,15 @@ func walkGatewaySimpleSelector(ctx context.Context, p path.Path, params CarParam
}

func (bb *BlocksBackend) getNode(ctx context.Context, path path.ImmutablePath) (ContentPathMetadata, format.Node, error) {
roots, lastSeg, err := bb.getPathRoots(ctx, path)
roots, lastSeg, remainder, err := bb.getPathRoots(ctx, path)
if err != nil {
return ContentPathMetadata{}, nil, err
}

md := ContentPathMetadata{
PathSegmentRoots: roots,
LastSegment: lastSeg,
PathSegmentRoots: roots,
LastSegment: lastSeg,
LastSegmentRemainder: remainder,
}

lastRoot := lastSeg.Cid()
Expand All @@ -505,7 +506,7 @@ func (bb *BlocksBackend) getNode(ctx context.Context, path path.ImmutablePath) (
return md, nd, err
}

func (bb *BlocksBackend) getPathRoots(ctx context.Context, contentPath path.ImmutablePath) ([]cid.Cid, path.ImmutablePath, error) {
func (bb *BlocksBackend) getPathRoots(ctx context.Context, contentPath path.ImmutablePath) ([]cid.Cid, path.ImmutablePath, []string, error) {
/*
These are logical roots where each CID represent one path segment
and resolves to either a directory or the root block of a file.
Expand All @@ -529,7 +530,10 @@ func (bb *BlocksBackend) getPathRoots(ctx context.Context, contentPath path.Immu
contentPathStr := contentPath.String()
pathSegments := strings.Split(contentPathStr[6:], "/")
sp.WriteString(contentPathStr[:5]) // /ipfs or /ipns
var lastPath path.ImmutablePath
var (
lastPath path.ImmutablePath
remainder []string
)
for _, root := range pathSegments {
if root == "" {
continue
Expand All @@ -538,23 +542,24 @@ func (bb *BlocksBackend) getPathRoots(ctx context.Context, contentPath path.Immu
sp.WriteString(root)
p, err := path.NewPath(sp.String())
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}

Check warning on line 546 in gateway/blocks_backend.go

View check run for this annotation

Codecov / codecov/patch

gateway/blocks_backend.go#L545-L546

Added lines #L545 - L546 were not covered by tests
resolvedSubPath, err := bb.resolvePath(ctx, p)
resolvedSubPath, remainderSubPath, err := bb.resolvePath(ctx, p)
if err != nil {
// 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, nil, resolver.ErrNoLink{Name: root, Node: lastPath.Cid()}
}
return nil, nil, err
return nil, nil, nil, err

Check warning on line 554 in gateway/blocks_backend.go

View check run for this annotation

Codecov / codecov/patch

gateway/blocks_backend.go#L554

Added line #L554 was not covered by tests
}
lastPath = resolvedSubPath
remainder = remainderSubPath
pathRoots = append(pathRoots, lastPath.Cid())
}

pathRoots = pathRoots[:len(pathRoots)-1]
return pathRoots, lastPath, nil
return pathRoots, lastPath, remainder, nil
}

func (bb *BlocksBackend) ResolveMutable(ctx context.Context, p path.Path) (path.ImmutablePath, error) {
Expand Down Expand Up @@ -605,7 +610,7 @@ func (bb *BlocksBackend) GetDNSLinkRecord(ctx context.Context, hostname string)
}

func (bb *BlocksBackend) IsCached(ctx context.Context, p path.Path) bool {
rp, err := bb.resolvePath(ctx, p)
rp, _, err := bb.resolvePath(ctx, p)

Check warning on line 613 in gateway/blocks_backend.go

View check run for this annotation

Codecov / codecov/patch

gateway/blocks_backend.go#L612-L613

Added lines #L612 - L613 were not covered by tests
if err != nil {
return false
}
Expand All @@ -615,41 +620,47 @@ func (bb *BlocksBackend) IsCached(ctx context.Context, p path.Path) bool {
}

func (bb *BlocksBackend) ResolvePath(ctx context.Context, path path.ImmutablePath) (ContentPathMetadata, error) {
roots, lastSeg, err := bb.getPathRoots(ctx, path)
roots, lastSeg, remainder, err := bb.getPathRoots(ctx, path)
if err != nil {
return ContentPathMetadata{}, err
}
md := ContentPathMetadata{
PathSegmentRoots: roots,
LastSegment: lastSeg,
PathSegmentRoots: roots,
LastSegment: lastSeg,
LastSegmentRemainder: remainder,
}
return md, nil
}

func (bb *BlocksBackend) resolvePath(ctx context.Context, p path.Path) (path.ImmutablePath, error) {
func (bb *BlocksBackend) resolvePath(ctx context.Context, p path.Path) (path.ImmutablePath, []string, error) {
var err error
if p.Namespace() == path.IPNSNamespace {
p, err = resolve.ResolveIPNS(ctx, bb.namesys, p)

Check warning on line 638 in gateway/blocks_backend.go

View check run for this annotation

Codecov / codecov/patch

gateway/blocks_backend.go#L638

Added line #L638 was not covered by tests
if err != nil {
return nil, err
return nil, nil, err

Check warning on line 640 in gateway/blocks_backend.go

View check run for this annotation

Codecov / codecov/patch

gateway/blocks_backend.go#L640

Added line #L640 was not covered by tests
}
}

if p.Namespace() != path.IPFSNamespace {
return nil, fmt.Errorf("unsupported path namespace: %s", p.Namespace())
return nil, nil, fmt.Errorf("unsupported path namespace: %s", p.Namespace())

Check warning on line 645 in gateway/blocks_backend.go

View check run for this annotation

Codecov / codecov/patch

gateway/blocks_backend.go#L645

Added line #L645 was not covered by tests
}

node, rest, err := bb.resolver.ResolveToLastNode(ctx, p)
node, remainder, err := bb.resolver.ResolveToLastNode(ctx, p)
if err != nil {
return nil, err
return nil, nil, err
}

p, err = path.Join(path.NewIPFSPath(node), rest...)
p, err = path.Join(path.NewIPFSPath(node), remainder...)
if err != nil {
return nil, err
return nil, nil, err
}

Check warning on line 656 in gateway/blocks_backend.go

View check run for this annotation

Codecov / codecov/patch

gateway/blocks_backend.go#L655-L656

Added lines #L655 - L656 were not covered by tests

imPath, err := path.NewImmutablePath(p)
if err != nil {
return nil, nil, err

Check warning on line 660 in gateway/blocks_backend.go

View check run for this annotation

Codecov / codecov/patch

gateway/blocks_backend.go#L660

Added line #L660 was not covered by tests
}

return path.NewImmutablePath(p)
return imPath, remainder, nil
}

type nodeGetterToCarExporer struct {
Expand Down
7 changes: 4 additions & 3 deletions gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,10 @@ func (d DuplicateBlocksPolicy) String() string {
}

type ContentPathMetadata struct {
PathSegmentRoots []cid.Cid
LastSegment path.ImmutablePath
ContentType string // Only used for UnixFS requests
PathSegmentRoots []cid.Cid
LastSegment path.ImmutablePath
LastSegmentRemainder []string
ContentType string // Only used for UnixFS requests
}

// ByteRange describes a range request within a UnixFS file. "From" and "To" mostly
Expand Down
7 changes: 4 additions & 3 deletions gateway/handler_codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,10 @@ func (i *handler) renderCodec(ctx context.Context, w http.ResponseWriter, r *htt
// If the resolved path still has some remainder, return error for now.
// TODO: handle this when we have IPLD Patch (https://ipld.io/specs/patch/) via HTTP PUT
// TODO: (depends on https://github.com/ipfs/kubo/issues/4801 and https://github.com/ipfs/kubo/issues/4782)
if resolvedPath.Remainder() != "" {
path := strings.TrimSuffix(resolvedPath.String(), resolvedPath.Remainder())
err := fmt.Errorf("%q of %q could not be returned: reading IPLD Kinds other than Links (CBOR Tag 42) is not implemented: try reading %q instead", resolvedPath.Remainder(), resolvedPath.String(), path)
if len(rq.pathMetadata.LastSegmentRemainder) != 0 {
remainderStr := path.SegmentsToString(rq.pathMetadata.LastSegmentRemainder...)
path := strings.TrimSuffix(resolvedPath.String(), remainderStr)
err := fmt.Errorf("%q of %q could not be returned: reading IPLD Kinds other than Links (CBOR Tag 42) is not implemented: try reading %q instead", remainderStr, resolvedPath.String(), path)

Check warning on line 90 in gateway/handler_codec.go

View check run for this annotation

Codecov / codecov/patch

gateway/handler_codec.go#L88-L90

Added lines #L88 - L90 were not covered by tests
i.webError(w, r, err, http.StatusNotImplemented)
return false
}
Expand Down
79 changes: 40 additions & 39 deletions path/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,7 @@ func (p path) Namespace() Namespace {
}

func (p path) Segments() []string {
// Trim slashes from beginning and end, such that we do not return empty segments.
str := strings.TrimSuffix(p.str, "/")
str = strings.TrimPrefix(str, "/")

return strings.Split(str, "/")
return StringToSegments(p.str)
}

// ImmutablePath is a [Path] which is guaranteed to have an immutable [Namespace].
Expand All @@ -96,9 +92,6 @@ type ImmutablePath interface {

// Cid returns the [cid.Cid] of the root object of the path.
Cid() cid.Cid

// Remainder returns the unresolved parts of the path.
Remainder() string
}

var _ Path = immutablePath{}
Expand Down Expand Up @@ -139,14 +132,6 @@ func (ip immutablePath) Cid() cid.Cid {
return ip.cid
}

func (ip immutablePath) Remainder() string {
remainder := strings.Join(ip.Segments()[2:], "/")
if remainder != "" {
remainder = "/" + remainder
}
return remainder
}

// NewIPFSPath returns a new "/ipfs" path with the provided CID.
func NewIPFSPath(cid cid.Cid) ImmutablePath {
return immutablePath{
Expand Down Expand Up @@ -174,34 +159,31 @@ func NewIPLDPath(cid cid.Cid) ImmutablePath {
// trailing slash. This function returns an error when the given string is not
// a valid content path.
func NewPath(str string) (Path, error) {
cleaned := gopath.Clean(str)
components := strings.Split(cleaned, "/")
segments := StringToSegments(str)

if strings.HasSuffix(str, "/") {
// Do not forget to store the trailing slash!
cleaned += "/"
// Shortest valid path is "/{namespace}/{root}". That yields at least two
// segments: ["{namespace}" "{root}"]. Therefore, here we check if the original
// string begins with "/" (any path must), if we have at least two segments, and if
// the root is non-empty. The namespace is checked further below.
if !strings.HasPrefix(str, "/") || len(segments) < 2 || segments[1] == "" {
return nil, &ErrInvalidPath{err: ErrInsufficientComponents, path: str}
}

// Shortest valid path is "/{namespace}/{element}". That yields at least three
// components: [" " "{namespace}" "{element}"]. The first component must therefore
// be empty.
if len(components) < 3 || components[0] != "" {
return nil, &ErrInvalidPath{err: ErrInsufficientComponents, path: str}
cleaned := SegmentsToString(segments...)
if strings.HasSuffix(str, "/") {
// Do not forget to preserve the trailing slash!
cleaned += "/"
}

switch components[1] {
switch segments[0] {
case "ipfs", "ipld":
if components[2] == "" {
return nil, &ErrInvalidPath{err: ErrInsufficientComponents, path: str}
}

cid, err := cid.Decode(components[2])
cid, err := cid.Decode(segments[1])
if err != nil {
return nil, &ErrInvalidPath{err: err, path: str}
}

ns := IPFSNamespace
if components[1] == "ipld" {
if segments[0] == "ipld" {
ns = IPLDNamespace
}

Expand All @@ -213,16 +195,12 @@ func NewPath(str string) (Path, error) {
cid: cid,
}, nil
case "ipns":
if components[2] == "" {
return nil, &ErrInvalidPath{err: ErrInsufficientComponents, path: str}
}

return path{
str: cleaned,
namespace: IPNSNamespace,
}, nil
default:
return nil, &ErrInvalidPath{err: fmt.Errorf("%w: %q", ErrUnknownNamespace, components[1]), path: str}
return nil, &ErrInvalidPath{err: fmt.Errorf("%w: %q", ErrUnknownNamespace, segments[0]), path: str}
}
}

Expand All @@ -231,7 +209,7 @@ func NewPath(str string) (Path, error) {
// using a forward slash "/" as separator. Please see [Path.Segments] for more
// information about how segments must be structured.
func NewPathFromSegments(segments ...string) (Path, error) {
return NewPath("/" + strings.Join(segments, "/"))
return NewPath(SegmentsToString(segments...))
}

// Join joins a [Path] with certain segments and returns a new [Path].
Expand All @@ -240,3 +218,26 @@ func Join(p Path, segments ...string) (Path, error) {
s = append(s, segments...)
return NewPathFromSegments(s...)
}

// SegmentsToString converts an array of segments into a string. The returned string
// will always be prefixed with a "/" if there are any segments. For example, if the
// given segments array is ["foo", "bar"], the returned value will be "/foo/bar".
// Given an empty array, an empty string is returned.
func SegmentsToString(segments ...string) string {
str := strings.Join(segments, "/")
if str != "" {
str = "/" + str
}
return str
}

// StringToSegments converts a string into an array of segments. This function follows
// the rules of [Path.Segments]: the path is first cleaned through [gopath.Clean] and
// no empty segments are returned.
func StringToSegments(str string) []string {
str = gopath.Clean(str)
// Trim slashes from beginning and end, such that we do not return empty segments.
str = strings.TrimSuffix(str, "/")
str = strings.TrimPrefix(str, "/")
return strings.Split(str, "/")
}
Loading

0 comments on commit c4cf7d4

Please sign in to comment.