fix: make Block().* return correct ABI based ipld.ErrNotFound errors
This commit was moved from ipfs/go-ipfs-http-client@4f5f8e9b144d1f38e0fc37c5080845611dcbcf1e
Jorropo committed
Mar 27, 2022 at 14:11 UTC
b85aa9639f861d659b49e0396b409398462226a2
3 files changed
+167
-8
client/httpapi/abyfy_errors.go
new
+104
@@ -0,0 +1,104 @@
1
+package httpapi
2
+
3
+import (
4
+ "errors"
5
+ "strings"
6
+
7
+ "github.com/ipfs/go-cid"
8
+ ipld "github.com/ipfs/go-ipld-format"
9
+)
10
+
11
+type prePostWrappedNotFoundError struct {
12
+ pre string
13
+ post string
14
+
15
+ wrapped ipld.ErrNotFound
16
+}
17
+
18
+func (e prePostWrappedNotFoundError) String() string {
19
+ return e.Error()
20
+}
21
+
22
+func (e prePostWrappedNotFoundError) Error() string {
23
+ return e.pre + e.wrapped.Error() + e.post
24
+}
25
+
26
+func (e prePostWrappedNotFoundError) Unwrap() error {
27
+ return e.wrapped
28
+}
29
+
30
+func abyfyIpldNotFoundFallbackToMSG(msg string) error {
31
+ err, handled := abyfyIpldNotFound(msg)
32
+ if handled {
33
+ return err
34
+ }
35
+
36
+ return errors.New(msg)
37
+}
38
+
39
+func abyfyIpldNotFoundFallbackToError(msg error) error {
40
+ err, handled := abyfyIpldNotFound(msg.Error())
41
+ if handled {
42
+ return err
43
+ }
44
+
45
+ return msg
46
+}
47
+
48
+// This file handle parsing and returning the correct ABI based errors from error messages
49
+//lint:ignore ST1008 this function is not using the error as a mean to return failure but it massages it to return the correct type
50
+func abyfyIpldNotFound(msg string) (error, bool) {
51
+ if msg == "" {
52
+ return nil, true // Fast path
53
+ }
54
+
55
+ // The patern we search for is:
56
+ // node not found (fallback)
57
+ // or
58
+ // CID not found (here we parse the CID)
59
+ notFoundIndex := strings.LastIndex(msg, " not found")
60
+
61
+ if notFoundIndex == -1 {
62
+ // Unknown, ot found not found
63
+ return nil, false
64
+ }
65
+
66
+ preNotFound := msg[:notFoundIndex]
67
+
68
+ var c cid.Cid
69
+ var preIndex int
70
+ if strings.HasSuffix(preNotFound, "node") {
71
+ // Fallback case
72
+ c = cid.Undef
73
+ preIndex = notFoundIndex - len("node")
74
+ } else {
75
+ // Assume that CIDs does not include whitespace to pull out the CID
76
+ preIndex = strings.LastIndexByte(preNotFound, ' ')
77
+ // + 1 is to normalise not founds to zeros and point to the start of the CID, not the previous space
78
+ preIndex++
79
+ var err error
80
+ c, err = cid.Decode(preNotFound[preIndex:])
81
+ if err != nil {
82
+ // Unknown
83
+ return nil, false
84
+ }
85
+ }
86
+
87
+ postIndex := notFoundIndex + len(" not found")
88
+
89
+ err := ipld.ErrNotFound{Cid: c}
90
+
91
+ pre := msg[:preIndex]
92
+ post := msg[postIndex:]
93
+
94
+ if len(pre) > 0 || len(post) > 0 {
95
+ // We have some text to wrap arround the ErrNotFound one
96
+ return prePostWrappedNotFoundError{
97
+ pre: pre,
98
+ post: post,
99
+ wrapped: err,
100
+ }, true
101
+ }
102
+
103
+ return err, true
104
+}
client/httpapi/abyfy_errors_test.go
new
+60
@@ -0,0 +1,60 @@
1
+package httpapi
2
+
3
+import (
4
+ "errors"
5
+ "fmt"
6
+ "testing"
7
+
8
+ "github.com/ipfs/go-cid"
9
+ ipld "github.com/ipfs/go-ipld-format"
10
+ mh "github.com/multiformats/go-multihash"
11
+)
12
+
13
+var randomSha256MH = mh.Multihash{0x12, 0x20, 0x88, 0x82, 0x73, 0x37, 0x7c, 0xc1, 0xc9, 0x96, 0xad, 0xee, 0xd, 0x26, 0x84, 0x2, 0xc9, 0xc9, 0x5c, 0xf9, 0x5c, 0x4d, 0x9b, 0xc3, 0x3f, 0xfb, 0x4a, 0xd8, 0xaf, 0x28, 0x6b, 0xca, 0x1a, 0xf2}
14
+
15
+func doAbyfyIpldNotFoundTest(t *testing.T, original error) {
16
+ originalMsg := original.Error()
17
+
18
+ rebuilt := abyfyIpldNotFoundFallbackToMSG(originalMsg)
19
+
20
+ rebuiltMsg := rebuilt.Error()
21
+
22
+ if originalMsg != rebuiltMsg {
23
+ t.Errorf("expected message to be %q; got %q", originalMsg, rebuiltMsg)
24
+ }
25
+
26
+ originalNotFound := ipld.IsNotFound(original)
27
+ rebuiltNotFound := ipld.IsNotFound(original)
28
+ if originalNotFound != rebuiltNotFound {
29
+ t.Errorf("expected Ipld.IsNotFound to be %t; got %t", originalNotFound, rebuiltNotFound)
30
+ }
31
+}
32
+
33
+func TestAbyfyIpldNotFound(t *testing.T) {
34
+ if err := abyfyIpldNotFoundFallbackToMSG(""); err != nil {
35
+ t.Errorf("expected empty string to give no error; got %T %q", err, err.Error())
36
+ }
37
+
38
+ for _, wrap := range [...]string{
39
+ "",
40
+ "merkledag: %w",
41
+ "testing: %w the test",
42
+ "%w is wrong",
43
+ } {
44
+ for _, err := range [...]error{
45
+ errors.New("file not found"),
46
+ errors.New(" not found"),
47
+ errors.New("Bad_CID not found"),
48
+ errors.New("network connection timeout"),
49
+ ipld.ErrNotFound{Cid: cid.Undef},
50
+ ipld.ErrNotFound{Cid: cid.NewCidV0(randomSha256MH)},
51
+ ipld.ErrNotFound{Cid: cid.NewCidV1(cid.Raw, randomSha256MH)},
52
+ } {
53
+ if wrap != "" {
54
+ err = fmt.Errorf(wrap, err)
55
+ }
56
+
57
+ doAbyfyIpldNotFoundTest(t, err)
58
+ }
59
+ }
60
+}
client/httpapi/block.go
+3
-8
@@ -3,7 +3,6 @@ package httpapi
3
import (
4
"bytes"
5
"context"
6
- "errors"
6
"fmt"
7
"io"
8
@@ -67,7 +66,7 @@ func (api *BlockAPI) Get(ctx context.Context, p path.Path) (io.Reader, error) {
66
return nil, err
67
}
68
if resp.Error != nil {
70
- return nil, resp.Error
69
+ return nil, abyfyIpldNotFoundFallbackToError(resp.Error)
70
}
71
72
//TODO: make get return ReadCloser to avoid copying
@@ -99,18 +98,14 @@ func (api *BlockAPI) Rm(ctx context.Context, p path.Path, opts ...caopts.BlockRm
98
return err
99
}
100
102
- if removedBlock.Error != "" {
103
- return errors.New(removedBlock.Error)
104
- }
105
-
106
- return nil
101
+ return abyfyIpldNotFoundFallbackToMSG(removedBlock.Error)
102
}
103
104
func (api *BlockAPI) Stat(ctx context.Context, p path.Path) (iface.BlockStat, error) {
105
var out blockStat
106
err := api.core().Request("block/stat", p.String()).Exec(ctx, &out)
107
if err != nil {
113
- return nil, err
108
+ return nil, abyfyIpldNotFoundFallbackToError(err)
109
}
110
out.cid, err = cid.Parse(out.Key)
111
if err != nil {