@cryptotaxi247 / kubo / commits / ee45b8d32

coreapi: make the interfaces path centric

The new coreiface.Path maps a path to the cid.Cid resulting from a full path resolution. The path is internally represented as a go-ipfs/path.Path, but that doesn't matter to the outside. Apart from the path-to-CID mapping, it also aims to hold all resolved segment CIDs of the path. Right now it only exposes Root(), and only for flat paths a la /ipfs/Qmfoo. In other cases, the root is nil. In the future, resolution will internally use go-ipfs/path.Resolver.ResolvePathComponents and thus always return the proper resolved segments, via Root(), or a future Segments() func. - Add coreiface.Path with Cid() and Root(). - Add CoreAPI.ResolvePath() for getting a coreiface.Path. - All functions now expect and return coreiface.Path. - Add ParsePath() and ParseCid() for constructing a coreiface.Path. - Add coreiface.Node and Link which are simply go-ipld-node.Node and Link. - Add CoreAPI.ResolveNode() for getting a Node from a Path. License: MIT Signed-off-by: Lars Gierth <larsg@systemli.org>

Lars Gierth committed Mar 17, 2017 at 03:47 UTC ee45b8d32f90313901429e7536c143e20dd5ff79
5 files changed +128 -55
core/coreapi/coreapi.go
+54 -6
@@ -5,9 +5,9 @@ import (
5
6 core "github.com/ipfs/go-ipfs/core"
7 coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
8 - path "github.com/ipfs/go-ipfs/path"
8 + ipfspath "github.com/ipfs/go-ipfs/path"
9
10 - ipld "gx/ipfs/QmYDscK7dmdo2GZ9aumS8s5auUUAH5mR1jvj5pYhWusfK7/go-ipld-node"
10 + cid "gx/ipfs/QmV5gPoRsjN1Gid3LMdNZTyfCtP2DsvqEbMAmz82RmmiGk/go-cid"
11 )
12
13 type CoreAPI struct {
@@ -23,17 +23,65 @@ func (api *CoreAPI) Unixfs() coreiface.UnixfsAPI {
23 return (*UnixfsAPI)(api)
24 }
25
26 -func resolve(ctx context.Context, n *core.IpfsNode, p string) (ipld.Node, error) {
27 - pp, err := path.ParsePath(p)
26 +func (api *CoreAPI) ResolveNode(ctx context.Context, p coreiface.Path) (coreiface.Node, error) {
27 + p, err := api.ResolvePath(ctx, p)
28 if err != nil {
29 return nil, err
30 }
31
32 - dagnode, err := core.Resolve(ctx, n.Namesys, n.Resolver, pp)
32 + node, err := api.node.DAG.Get(ctx, p.Cid())
33 + if err != nil {
34 + return nil, err
35 + }
36 + return node, nil
37 +}
38 +
39 +// TODO: store all of ipfspath.Resolver.ResolvePathComponents() in Path
40 +func (api *CoreAPI) ResolvePath(ctx context.Context, p coreiface.Path) (coreiface.Path, error) {
41 + if p.Resolved() {
42 + return p, nil
43 + }
44 +
45 + p2 := ipfspath.FromString(p.String())
46 + node, err := core.Resolve(ctx, api.node.Namesys, api.node.Resolver, p2)
47 if err == core.ErrNoNamesys {
48 return nil, coreiface.ErrOffline
49 } else if err != nil {
50 return nil, err
51 }
38 - return dagnode, nil
52 +
53 + var root *cid.Cid
54 + if p2.IsJustAKey() {
55 + root = node.Cid()
56 + }
57 +
58 + return ResolvedPath(p.String(), node.Cid(), root), nil
59 +}
60 +
61 +// Implements coreiface.Path
62 +type path struct {
63 + path ipfspath.Path
64 + cid *cid.Cid
65 + root *cid.Cid
66 +}
67 +
68 +func ParsePath(p string) (coreiface.Path, error) {
69 + pp, err := ipfspath.ParsePath(p)
70 + if err != nil {
71 + return nil, err
72 + }
73 + return &path{path: pp}, nil
74 }
75 +
76 +func ParseCid(c *cid.Cid) coreiface.Path {
77 + return &path{path: ipfspath.FromCid(c), cid: c, root: c}
78 +}
79 +
80 +func ResolvedPath(p string, c *cid.Cid, r *cid.Cid) coreiface.Path {
81 + return &path{path: ipfspath.FromString(p), cid: c, root: r}
82 +}
83 +
84 +func (p *path) String() string { return p.path.String() }
85 +func (p *path) Cid() *cid.Cid { return p.cid }
86 +func (p *path) Root() *cid.Cid { return p.root }
87 +func (p *path) Resolved() bool { return p.cid != nil }
core/coreapi/interface/interface.go
+15 -4
@@ -9,6 +9,16 @@ import (
9 ipld "gx/ipfs/QmYDscK7dmdo2GZ9aumS8s5auUUAH5mR1jvj5pYhWusfK7/go-ipld-node"
10 )
11
12 +type Path interface {
13 + String() string
14 + Cid() *cid.Cid
15 + Root() *cid.Cid
16 + Resolved() bool
17 +}
18 +
19 +// TODO: should we really copy these?
20 +// if we didn't, godoc would generate nice links straight to go-ipld-node
21 +type Node ipld.Node
22 type Link ipld.Link
23
24 type Reader interface {
@@ -18,12 +28,14 @@ type Reader interface {
28
29 type CoreAPI interface {
30 Unixfs() UnixfsAPI
31 + ResolvePath(context.Context, Path) (Path, error)
32 + ResolveNode(context.Context, Path) (Node, error)
33 }
34
35 type UnixfsAPI interface {
24 - Add(context.Context, io.Reader) (*cid.Cid, error)
25 - Cat(context.Context, string) (Reader, error)
26 - Ls(context.Context, string) ([]*Link, error)
36 + Add(context.Context, io.Reader) (Path, error)
37 + Cat(context.Context, Path) (Reader, error)
38 + Ls(context.Context, Path) ([]*Link, error)
39 }
40
41 // type ObjectAPI interface {
@@ -49,5 +61,4 @@ type UnixfsAPI interface {
61 // }
62
63 var ErrIsDir = errors.New("object is a directory")
52 -var ErrIsNonDag = errors.New("not a merkledag object")
64 var ErrOffline = errors.New("can't resolve, ipfs node is offline")
core/coreapi/unixfs.go
+14 -6
@@ -13,16 +13,20 @@ import (
13
14 type UnixfsAPI CoreAPI
15
16 -func (api *UnixfsAPI) Add(ctx context.Context, r io.Reader) (*cid.Cid, error) {
16 +func (api *UnixfsAPI) Add(ctx context.Context, r io.Reader) (coreiface.Path, error) {
17 k, err := coreunix.AddWithContext(ctx, api.node, r)
18 if err != nil {
19 return nil, err
20 }
21 - return cid.Decode(k)
21 + c, err := cid.Decode(k)
22 + if err != nil {
23 + return nil, err
24 + }
25 + return ParseCid(c), nil
26 }
27
24 -func (api *UnixfsAPI) Cat(ctx context.Context, p string) (coreiface.Reader, error) {
25 - dagnode, err := resolve(ctx, api.node, p)
28 +func (api *UnixfsAPI) Cat(ctx context.Context, p coreiface.Path) (coreiface.Reader, error) {
29 + dagnode, err := api.core().ResolveNode(ctx, p)
30 if err != nil {
31 return nil, err
32 }
@@ -36,8 +40,8 @@ func (api *UnixfsAPI) Cat(ctx context.Context, p string) (coreiface.Reader, erro
40 return r, nil
41 }
42
39 -func (api *UnixfsAPI) Ls(ctx context.Context, p string) ([]*coreiface.Link, error) {
40 - dagnode, err := resolve(ctx, api.node, p)
43 +func (api *UnixfsAPI) Ls(ctx context.Context, p coreiface.Path) ([]*coreiface.Link, error) {
44 + dagnode, err := api.core().ResolveNode(ctx, p)
45 if err != nil {
46 return nil, err
47 }
@@ -49,3 +53,7 @@ func (api *UnixfsAPI) Ls(ctx context.Context, p string) ([]*coreiface.Link, erro
53 }
54 return links, nil
55 }
56 +
57 +func (api *UnixfsAPI) core() coreiface.CoreAPI {
58 + return (*CoreAPI)(api)
59 +}
core/coreapi/unixfs_test.go
+32 -26
@@ -19,14 +19,14 @@ import (
19 )
20
21 // `echo -n 'hello, world!' | ipfs add`
22 -var hello = "QmQy2Dw4Wk7rdJKjThjYXzfFJNaRKRHhHP5gHHXroJMYxk"
22 +var hello = coreapi.ResolvedPath("/ipfs/QmQy2Dw4Wk7rdJKjThjYXzfFJNaRKRHhHP5gHHXroJMYxk", nil, nil)
23 var helloStr = "hello, world!"
24
25 // `ipfs object new unixfs-dir`
26 -var emptyUnixfsDir = "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"
26 +var emptyDir = coreapi.ResolvedPath("/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn", nil, nil)
27
28 // `echo -n | ipfs add`
29 -var emptyUnixfsFile = "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH"
29 +var emptyFile = coreapi.ResolvedPath("/ipfs/QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", nil, nil)
30
31 func makeAPI(ctx context.Context) (*core.IpfsNode, coreiface.UnixfsAPI, error) {
32 r := &repo.Mock{
@@ -53,13 +53,13 @@ func TestAdd(t *testing.T) {
53 }
54
55 str := strings.NewReader(helloStr)
56 - c, err := api.Add(ctx, str)
56 + p, err := api.Add(ctx, str)
57 if err != nil {
58 t.Error(err)
59 }
60
61 - if c.String() != hello {
62 - t.Fatalf("expected CID %s, got: %s", hello, c)
61 + if p.String() != hello.String() {
62 + t.Fatalf("expected path %s, got: %s", hello, p)
63 }
64
65 r, err := api.Cat(ctx, hello)
@@ -85,13 +85,13 @@ func TestAddEmptyFile(t *testing.T) {
85 }
86
87 str := strings.NewReader("")
88 - c, err := api.Add(ctx, str)
88 + p, err := api.Add(ctx, str)
89 if err != nil {
90 t.Error(err)
91 }
92
93 - if c.String() != emptyUnixfsFile {
94 - t.Fatalf("expected CID %s, got: %s", hello, c)
93 + if p.String() != emptyFile.String() {
94 + t.Fatalf("expected path %s, got: %s", hello, p)
95 }
96 }
97
@@ -103,16 +103,17 @@ func TestCatBasic(t *testing.T) {
103 }
104
105 hr := strings.NewReader(helloStr)
106 - k, err := coreunix.Add(node, hr)
106 + p, err := coreunix.Add(node, hr)
107 if err != nil {
108 t.Fatal(err)
109 }
110 + p = "/ipfs/" + p
111
111 - if k != hello {
112 - t.Fatalf("expected CID %s, got: %s", hello, k)
112 + if p != hello.String() {
113 + t.Fatalf("expected CID %s, got: %s", hello, p)
114 }
115
115 - r, err := api.Cat(ctx, k)
116 + r, err := api.Cat(ctx, hello)
117 if err != nil {
118 t.Fatal(err)
119 }
@@ -139,7 +140,7 @@ func TestCatEmptyFile(t *testing.T) {
140 t.Fatal(err)
141 }
142
142 - r, err := api.Cat(ctx, emptyUnixfsFile)
143 + r, err := api.Cat(ctx, emptyFile)
144 if err != nil {
145 t.Fatal(err)
146 }
@@ -165,8 +166,13 @@ func TestCatDir(t *testing.T) {
166 if err != nil {
167 t.Error(err)
168 }
169 + p := coreapi.ParseCid(c)
170 +
171 + if p.String() != emptyDir.String() {
172 + t.Fatalf("expected path %s, got: %s", emptyDir, p)
173 + }
174
169 - _, err = api.Cat(ctx, c.String())
175 + _, err = api.Cat(ctx, emptyDir)
176 if err != coreiface.ErrIsDir {
177 t.Fatalf("expected ErrIsDir, got: %s", err)
178 }
@@ -184,7 +190,7 @@ func TestCatNonUnixfs(t *testing.T) {
190 t.Error(err)
191 }
192
187 - _, err = api.Cat(ctx, c.String())
193 + _, err = api.Cat(ctx, coreapi.ParseCid(c))
194 if !strings.Contains(err.Error(), "proto: required field") {
195 t.Fatalf("expected protobuf error, got: %s", err)
196 }
@@ -197,7 +203,7 @@ func TestCatOffline(t *testing.T) {
203 t.Error(err)
204 }
205
200 - _, err = api.Cat(ctx, "/ipns/Qmfoobar")
206 + _, err = api.Cat(ctx, coreapi.ResolvedPath("/ipns/Qmfoobar", nil, nil))
207 if err != coreiface.ErrOffline {
208 t.Fatalf("expected ErrOffline, got: %", err)
209 }
@@ -211,17 +217,17 @@ func TestLs(t *testing.T) {
217 }
218
219 r := strings.NewReader("content-of-file")
214 - p, _, err := coreunix.AddWrapped(node, r, "name-of-file")
220 + k, _, err := coreunix.AddWrapped(node, r, "name-of-file")
221 if err != nil {
222 t.Error(err)
223 }
218 - parts := strings.Split(p, "/")
224 + parts := strings.Split(k, "/")
225 if len(parts) != 2 {
220 - t.Errorf("unexpected path:", p)
226 + t.Errorf("unexpected path:", k)
227 }
222 - k := parts[0]
228 + p := coreapi.ResolvedPath("/ipfs/"+parts[0], nil, nil)
229
224 - links, err := api.Ls(ctx, k)
230 + links, err := api.Ls(ctx, p)
231 if err != nil {
232 t.Error(err)
233 }
@@ -236,7 +242,7 @@ func TestLs(t *testing.T) {
242 t.Fatalf("expected name = name-of-file, got %s", links[0].Name)
243 }
244 if links[0].Cid.String() != "QmX3qQVKxDGz3URVC3861Z3CKtQKGBn6ffXRBBWGMFz9Lr" {
239 - t.Fatalf("expected cid = QmX3qQVKxDGz3URVC3861Z3CKtQKGBn6ffXRBBWGMFz9Lr, got %s", links[0].Cid.String())
245 + t.Fatalf("expected cid = QmX3qQVKxDGz3URVC3861Z3CKtQKGBn6ffXRBBWGMFz9Lr, got %s", links[0].Cid)
246 }
247 }
248
@@ -247,12 +253,12 @@ func TestLsEmptyDir(t *testing.T) {
253 t.Error(err)
254 }
255
250 - c, err := node.DAG.Add(unixfs.EmptyDirNode())
256 + _, err = node.DAG.Add(unixfs.EmptyDirNode())
257 if err != nil {
258 t.Error(err)
259 }
260
255 - links, err := api.Ls(ctx, c.String())
261 + links, err := api.Ls(ctx, emptyDir)
262 if err != nil {
263 t.Error(err)
264 }
@@ -275,7 +281,7 @@ func TestLsNonUnixfs(t *testing.T) {
281 t.Error(err)
282 }
283
278 - links, err := api.Ls(ctx, c.String())
284 + links, err := api.Ls(ctx, coreapi.ParseCid(c))
285 if err != nil {
286 t.Error(err)
287 }
core/corehttp/gateway_handler.go
+13 -13
@@ -12,6 +12,7 @@ import (
12 "time"
13
14 core "github.com/ipfs/go-ipfs/core"
15 + coreapi "github.com/ipfs/go-ipfs/core/coreapi"
16 coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
17 "github.com/ipfs/go-ipfs/importer"
18 chunk "github.com/ipfs/go-ipfs/importer/chunk"
@@ -158,7 +159,13 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr
159 ipnsHostname = true
160 }
161
161 - dr, err := i.api.Unixfs().Cat(ctx, urlPath)
162 + parsedPath, err := coreapi.ParsePath(urlPath)
163 + if err != nil {
164 + webError(w, "invalid ipfs path", err, http.StatusBadRequest)
165 + return
166 + }
167 +
168 + dr, err := i.api.Unixfs().Cat(ctx, parsedPath)
169 dir := false
170 switch err {
171 case nil:
@@ -218,7 +225,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr
225 return
226 }
227
221 - links, err := i.api.Unixfs().Ls(ctx, urlPath)
228 + links, err := i.api.Unixfs().Ls(ctx, parsedPath)
229 if err != nil {
230 internalWebError(w, err)
231 return
@@ -240,14 +247,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr
247 return
248 }
249
243 - p, err := path.ParsePath(urlPath + "/index.html")
244 - if err != nil {
245 - internalWebError(w, err)
246 - return
247 - }
248 -
249 - // return index page instead.
250 - dr, err := i.api.Unixfs().Cat(ctx, p.String())
250 + dr, err := i.api.Unixfs().Cat(ctx, coreapi.ParseCid(link.Cid))
251 if err != nil {
252 internalWebError(w, err)
253 return
@@ -314,15 +314,15 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr
314 }
315
316 func (i *gatewayHandler) postHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
317 - k, err := i.api.Unixfs().Add(ctx, r.Body)
317 + p, err := i.api.Unixfs().Add(ctx, r.Body)
318 if err != nil {
319 internalWebError(w, err)
320 return
321 }
322
323 i.addUserHeaders(w) // ok, _now_ write user's headers.
324 - w.Header().Set("IPFS-Hash", k.String())
325 - http.Redirect(w, r, ipfsPathPrefix+k.String(), http.StatusCreated)
324 + w.Header().Set("IPFS-Hash", p.Cid().String())
325 + http.Redirect(w, r, p.String(), http.StatusCreated)
326 }
327
328 func (i *gatewayHandler) putHandler(w http.ResponseWriter, r *http.Request) {