Skip to content

Commit c4cf7d4

Browse files
committed
refactor!: remove .Remainder, coreiface.ResolvePath returns remainder
1 parent 3214084 commit c4cf7d4

File tree

9 files changed

+104
-90
lines changed

9 files changed

+104
-90
lines changed

coreiface/coreapi.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@ type CoreAPI interface {
4646
// Routing returns an implementation of Routing API
4747
Routing() RoutingAPI
4848

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

5254
// ResolveNode resolves the path (if not resolved already) using Unixfs
5355
// resolver, gets and returns the resolved Node

coreiface/tests/block.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ func (tp *TestSuite) TestBlockGet(t *testing.T) {
220220

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

223-
rp, err := api.ResolvePath(ctx, p)
223+
rp, _, err := api.ResolvePath(ctx, p)
224224
if err != nil {
225225
t.Fatal(err)
226226
}

coreiface/tests/dag.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func (tp *TestSuite) TestDagPath(t *testing.T) {
116116
t.Fatal(err)
117117
}
118118

119-
rp, err := api.ResolvePath(ctx, p)
119+
rp, _, err := api.ResolvePath(ctx, p)
120120
if err != nil {
121121
t.Fatal(err)
122122
}

coreiface/tests/path.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ func (tp *TestSuite) TestPathRemainder(t *testing.T) {
5555
p, err := path.Join(path.NewIPFSPath(nd.Cid()), "foo", "bar")
5656
require.NoError(t, err)
5757

58-
rp1, err := api.ResolvePath(ctx, p)
58+
_, remainder, err := api.ResolvePath(ctx, p)
5959
require.NoError(t, err)
60-
require.Equal(t, "/foo/bar", rp1.Remainder())
60+
require.Equal(t, "/foo/bar", path.SegmentsToString(remainder...))
6161
}
6262

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

77-
rp1, err := api.ResolvePath(ctx, path.NewIPFSPath(nd.Cid()))
77+
_, remainder, err := api.ResolvePath(ctx, path.NewIPFSPath(nd.Cid()))
7878
require.NoError(t, err)
79-
require.Empty(t, rp1.Remainder())
79+
require.Empty(t, remainder)
8080
}
8181

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

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

125-
rp, err := api.ResolvePath(ctx, p)
125+
rp, _, err := api.ResolvePath(ctx, p)
126126
require.NoError(t, err)
127127
require.Equal(t, rp.Cid().String(), blk.Path().Cid().String())
128128
}

gateway/blocks_backend.go

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -485,14 +485,15 @@ func walkGatewaySimpleSelector(ctx context.Context, p path.Path, params CarParam
485485
}
486486

487487
func (bb *BlocksBackend) getNode(ctx context.Context, path path.ImmutablePath) (ContentPathMetadata, format.Node, error) {
488-
roots, lastSeg, err := bb.getPathRoots(ctx, path)
488+
roots, lastSeg, remainder, err := bb.getPathRoots(ctx, path)
489489
if err != nil {
490490
return ContentPathMetadata{}, nil, err
491491
}
492492

493493
md := ContentPathMetadata{
494-
PathSegmentRoots: roots,
495-
LastSegment: lastSeg,
494+
PathSegmentRoots: roots,
495+
LastSegment: lastSeg,
496+
LastSegmentRemainder: remainder,
496497
}
497498

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

508-
func (bb *BlocksBackend) getPathRoots(ctx context.Context, contentPath path.ImmutablePath) ([]cid.Cid, path.ImmutablePath, error) {
509+
func (bb *BlocksBackend) getPathRoots(ctx context.Context, contentPath path.ImmutablePath) ([]cid.Cid, path.ImmutablePath, []string, error) {
509510
/*
510511
These are logical roots where each CID represent one path segment
511512
and resolves to either a directory or the root block of a file.
@@ -529,7 +530,10 @@ func (bb *BlocksBackend) getPathRoots(ctx context.Context, contentPath path.Immu
529530
contentPathStr := contentPath.String()
530531
pathSegments := strings.Split(contentPathStr[6:], "/")
531532
sp.WriteString(contentPathStr[:5]) // /ipfs or /ipns
532-
var lastPath path.ImmutablePath
533+
var (
534+
lastPath path.ImmutablePath
535+
remainder []string
536+
)
533537
for _, root := range pathSegments {
534538
if root == "" {
535539
continue
@@ -538,23 +542,24 @@ func (bb *BlocksBackend) getPathRoots(ctx context.Context, contentPath path.Immu
538542
sp.WriteString(root)
539543
p, err := path.NewPath(sp.String())
540544
if err != nil {
541-
return nil, nil, err
545+
return nil, nil, nil, err
542546
}
543-
resolvedSubPath, err := bb.resolvePath(ctx, p)
547+
resolvedSubPath, remainderSubPath, err := bb.resolvePath(ctx, p)
544548
if err != nil {
545549
// TODO: should we be more explicit here and is this part of the IPFSBackend contract?
546550
// The issue here was that we returned datamodel.ErrWrongKind instead of this resolver error
547551
if isErrNotFound(err) {
548-
return nil, nil, resolver.ErrNoLink{Name: root, Node: lastPath.Cid()}
552+
return nil, nil, nil, resolver.ErrNoLink{Name: root, Node: lastPath.Cid()}
549553
}
550-
return nil, nil, err
554+
return nil, nil, nil, err
551555
}
552556
lastPath = resolvedSubPath
557+
remainder = remainderSubPath
553558
pathRoots = append(pathRoots, lastPath.Cid())
554559
}
555560

556561
pathRoots = pathRoots[:len(pathRoots)-1]
557-
return pathRoots, lastPath, nil
562+
return pathRoots, lastPath, remainder, nil
558563
}
559564

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

607612
func (bb *BlocksBackend) IsCached(ctx context.Context, p path.Path) bool {
608-
rp, err := bb.resolvePath(ctx, p)
613+
rp, _, err := bb.resolvePath(ctx, p)
609614
if err != nil {
610615
return false
611616
}
@@ -615,41 +620,47 @@ func (bb *BlocksBackend) IsCached(ctx context.Context, p path.Path) bool {
615620
}
616621

617622
func (bb *BlocksBackend) ResolvePath(ctx context.Context, path path.ImmutablePath) (ContentPathMetadata, error) {
618-
roots, lastSeg, err := bb.getPathRoots(ctx, path)
623+
roots, lastSeg, remainder, err := bb.getPathRoots(ctx, path)
619624
if err != nil {
620625
return ContentPathMetadata{}, err
621626
}
622627
md := ContentPathMetadata{
623-
PathSegmentRoots: roots,
624-
LastSegment: lastSeg,
628+
PathSegmentRoots: roots,
629+
LastSegment: lastSeg,
630+
LastSegmentRemainder: remainder,
625631
}
626632
return md, nil
627633
}
628634

629-
func (bb *BlocksBackend) resolvePath(ctx context.Context, p path.Path) (path.ImmutablePath, error) {
635+
func (bb *BlocksBackend) resolvePath(ctx context.Context, p path.Path) (path.ImmutablePath, []string, error) {
630636
var err error
631637
if p.Namespace() == path.IPNSNamespace {
632638
p, err = resolve.ResolveIPNS(ctx, bb.namesys, p)
633639
if err != nil {
634-
return nil, err
640+
return nil, nil, err
635641
}
636642
}
637643

638644
if p.Namespace() != path.IPFSNamespace {
639-
return nil, fmt.Errorf("unsupported path namespace: %s", p.Namespace())
645+
return nil, nil, fmt.Errorf("unsupported path namespace: %s", p.Namespace())
640646
}
641647

642-
node, rest, err := bb.resolver.ResolveToLastNode(ctx, p)
648+
node, remainder, err := bb.resolver.ResolveToLastNode(ctx, p)
643649
if err != nil {
644-
return nil, err
650+
return nil, nil, err
645651
}
646652

647-
p, err = path.Join(path.NewIPFSPath(node), rest...)
653+
p, err = path.Join(path.NewIPFSPath(node), remainder...)
648654
if err != nil {
649-
return nil, err
655+
return nil, nil, err
656+
}
657+
658+
imPath, err := path.NewImmutablePath(p)
659+
if err != nil {
660+
return nil, nil, err
650661
}
651662

652-
return path.NewImmutablePath(p)
663+
return imPath, remainder, nil
653664
}
654665

655666
type nodeGetterToCarExporer struct {

gateway/gateway.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,10 @@ func (d DuplicateBlocksPolicy) String() string {
204204
}
205205

206206
type ContentPathMetadata struct {
207-
PathSegmentRoots []cid.Cid
208-
LastSegment path.ImmutablePath
209-
ContentType string // Only used for UnixFS requests
207+
PathSegmentRoots []cid.Cid
208+
LastSegment path.ImmutablePath
209+
LastSegmentRemainder []string
210+
ContentType string // Only used for UnixFS requests
210211
}
211212

212213
// ByteRange describes a range request within a UnixFS file. "From" and "To" mostly

gateway/handler_codec.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,10 @@ func (i *handler) renderCodec(ctx context.Context, w http.ResponseWriter, r *htt
8484
// If the resolved path still has some remainder, return error for now.
8585
// TODO: handle this when we have IPLD Patch (https://ipld.io/specs/patch/) via HTTP PUT
8686
// TODO: (depends on https://github.com/ipfs/kubo/issues/4801 and https://github.com/ipfs/kubo/issues/4782)
87-
if resolvedPath.Remainder() != "" {
88-
path := strings.TrimSuffix(resolvedPath.String(), resolvedPath.Remainder())
89-
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)
87+
if len(rq.pathMetadata.LastSegmentRemainder) != 0 {
88+
remainderStr := path.SegmentsToString(rq.pathMetadata.LastSegmentRemainder...)
89+
path := strings.TrimSuffix(resolvedPath.String(), remainderStr)
90+
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)
9091
i.webError(w, r, err, http.StatusNotImplemented)
9192
return false
9293
}

path/path.go

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,7 @@ func (p path) Namespace() Namespace {
8383
}
8484

8585
func (p path) Segments() []string {
86-
// Trim slashes from beginning and end, such that we do not return empty segments.
87-
str := strings.TrimSuffix(p.str, "/")
88-
str = strings.TrimPrefix(str, "/")
89-
90-
return strings.Split(str, "/")
86+
return StringToSegments(p.str)
9187
}
9288

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

9793
// Cid returns the [cid.Cid] of the root object of the path.
9894
Cid() cid.Cid
99-
100-
// Remainder returns the unresolved parts of the path.
101-
Remainder() string
10295
}
10396

10497
var _ Path = immutablePath{}
@@ -139,14 +132,6 @@ func (ip immutablePath) Cid() cid.Cid {
139132
return ip.cid
140133
}
141134

142-
func (ip immutablePath) Remainder() string {
143-
remainder := strings.Join(ip.Segments()[2:], "/")
144-
if remainder != "" {
145-
remainder = "/" + remainder
146-
}
147-
return remainder
148-
}
149-
150135
// NewIPFSPath returns a new "/ipfs" path with the provided CID.
151136
func NewIPFSPath(cid cid.Cid) ImmutablePath {
152137
return immutablePath{
@@ -174,34 +159,31 @@ func NewIPLDPath(cid cid.Cid) ImmutablePath {
174159
// trailing slash. This function returns an error when the given string is not
175160
// a valid content path.
176161
func NewPath(str string) (Path, error) {
177-
cleaned := gopath.Clean(str)
178-
components := strings.Split(cleaned, "/")
162+
segments := StringToSegments(str)
179163

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

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

192-
switch components[1] {
178+
switch segments[0] {
193179
case "ipfs", "ipld":
194-
if components[2] == "" {
195-
return nil, &ErrInvalidPath{err: ErrInsufficientComponents, path: str}
196-
}
197-
198-
cid, err := cid.Decode(components[2])
180+
cid, err := cid.Decode(segments[1])
199181
if err != nil {
200182
return nil, &ErrInvalidPath{err: err, path: str}
201183
}
202184

203185
ns := IPFSNamespace
204-
if components[1] == "ipld" {
186+
if segments[0] == "ipld" {
205187
ns = IPLDNamespace
206188
}
207189

@@ -213,16 +195,12 @@ func NewPath(str string) (Path, error) {
213195
cid: cid,
214196
}, nil
215197
case "ipns":
216-
if components[2] == "" {
217-
return nil, &ErrInvalidPath{err: ErrInsufficientComponents, path: str}
218-
}
219-
220198
return path{
221199
str: cleaned,
222200
namespace: IPNSNamespace,
223201
}, nil
224202
default:
225-
return nil, &ErrInvalidPath{err: fmt.Errorf("%w: %q", ErrUnknownNamespace, components[1]), path: str}
203+
return nil, &ErrInvalidPath{err: fmt.Errorf("%w: %q", ErrUnknownNamespace, segments[0]), path: str}
226204
}
227205
}
228206

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

237215
// Join joins a [Path] with certain segments and returns a new [Path].
@@ -240,3 +218,26 @@ func Join(p Path, segments ...string) (Path, error) {
240218
s = append(s, segments...)
241219
return NewPathFromSegments(s...)
242220
}
221+
222+
// SegmentsToString converts an array of segments into a string. The returned string
223+
// will always be prefixed with a "/" if there are any segments. For example, if the
224+
// given segments array is ["foo", "bar"], the returned value will be "/foo/bar".
225+
// Given an empty array, an empty string is returned.
226+
func SegmentsToString(segments ...string) string {
227+
str := strings.Join(segments, "/")
228+
if str != "" {
229+
str = "/" + str
230+
}
231+
return str
232+
}
233+
234+
// StringToSegments converts a string into an array of segments. This function follows
235+
// the rules of [Path.Segments]: the path is first cleaned through [gopath.Clean] and
236+
// no empty segments are returned.
237+
func StringToSegments(str string) []string {
238+
str = gopath.Clean(str)
239+
// Trim slashes from beginning and end, such that we do not return empty segments.
240+
str = strings.TrimSuffix(str, "/")
241+
str = strings.TrimPrefix(str, "/")
242+
return strings.Split(str, "/")
243+
}

0 commit comments

Comments
 (0)