Skip to content
This repository was archived by the owner on Oct 5, 2023. It is now read-only.

Commit fdbee7c

Browse files
authored
Merge pull request #156 from ipfs/fix/ipld-ErrNotFound
fix: make Block().* return correct ABI based ipld.ErrNotFound errors
2 parents 0675169 + d7208ce commit fdbee7c

File tree

6 files changed

+519
-33
lines changed

6 files changed

+519
-33
lines changed

block.go

+3-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package httpapi
33
import (
44
"bytes"
55
"context"
6-
"errors"
76
"fmt"
87
"io"
98

@@ -67,7 +66,7 @@ func (api *BlockAPI) Get(ctx context.Context, p path.Path) (io.Reader, error) {
6766
return nil, err
6867
}
6968
if resp.Error != nil {
70-
return nil, resp.Error
69+
return nil, parseErrNotFoundWithFallbackToError(resp.Error)
7170
}
7271

7372
//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
9998
return err
10099
}
101100

102-
if removedBlock.Error != "" {
103-
return errors.New(removedBlock.Error)
104-
}
105-
106-
return nil
101+
return parseErrNotFoundWithFallbackToMSG(removedBlock.Error)
107102
}
108103

109104
func (api *BlockAPI) Stat(ctx context.Context, p path.Path) (iface.BlockStat, error) {
110105
var out blockStat
111106
err := api.core().Request("block/stat", p.String()).Exec(ctx, &out)
112107
if err != nil {
113-
return nil, err
108+
return nil, parseErrNotFoundWithFallbackToError(err)
114109
}
115110
out.cid, err = cid.Parse(out.Key)
116111
if err != nil {

errors.go

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package httpapi
2+
3+
import (
4+
"errors"
5+
"strings"
6+
"unicode/utf8"
7+
8+
"github.com/ipfs/go-cid"
9+
ipld "github.com/ipfs/go-ipld-format"
10+
mbase "github.com/multiformats/go-multibase"
11+
)
12+
13+
// This file handle parsing and returning the correct ABI based errors from error messages
14+
15+
type prePostWrappedNotFoundError struct {
16+
pre string
17+
post string
18+
19+
wrapped ipld.ErrNotFound
20+
}
21+
22+
func (e prePostWrappedNotFoundError) String() string {
23+
return e.Error()
24+
}
25+
26+
func (e prePostWrappedNotFoundError) Error() string {
27+
return e.pre + e.wrapped.Error() + e.post
28+
}
29+
30+
func (e prePostWrappedNotFoundError) Unwrap() error {
31+
return e.wrapped
32+
}
33+
34+
func parseErrNotFoundWithFallbackToMSG(msg string) error {
35+
err, handled := parseErrNotFound(msg)
36+
if handled {
37+
return err
38+
}
39+
40+
return errors.New(msg)
41+
}
42+
43+
func parseErrNotFoundWithFallbackToError(msg error) error {
44+
err, handled := parseErrNotFound(msg.Error())
45+
if handled {
46+
return err
47+
}
48+
49+
return msg
50+
}
51+
52+
//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
53+
func parseErrNotFound(msg string) (error, bool) {
54+
if msg == "" {
55+
return nil, true // Fast path
56+
}
57+
58+
if err, handled := parseIPLDErrNotFound(msg); handled {
59+
return err, true
60+
}
61+
62+
if err, handled := parseBlockstoreNotFound(msg); handled {
63+
return err, true
64+
}
65+
66+
return nil, false
67+
}
68+
69+
// Assume CIDs break on:
70+
// - Whitespaces: " \t\n\r\v\f"
71+
// - Semicolon: ";" this is to parse ipld.ErrNotFound wrapped in multierr
72+
// - Double Quotes: "\"" this is for parsing %q and %#v formating
73+
const cidBreakSet = " \t\n\r\v\f;\""
74+
75+
//lint:ignore ST1008 using error as values
76+
func parseIPLDErrNotFound(msg string) (error, bool) {
77+
// The patern we search for is:
78+
const ipldErrNotFoundKey = "ipld: could not find " /*CID*/
79+
// We try to parse the CID, if it's invalid we give up and return a simple text error.
80+
// We also accept "node" in place of the CID because that means it's an Undefined CID.
81+
82+
keyIndex := strings.Index(msg, ipldErrNotFoundKey)
83+
84+
if keyIndex < 0 { // Unknown error
85+
return nil, false
86+
}
87+
88+
cidStart := keyIndex + len(ipldErrNotFoundKey)
89+
90+
msgPostKey := msg[cidStart:]
91+
var c cid.Cid
92+
var postIndex int
93+
if strings.HasPrefix(msgPostKey, "node") {
94+
// Fallback case
95+
c = cid.Undef
96+
postIndex = len("node")
97+
} else {
98+
postIndex = strings.IndexFunc(msgPostKey, func(r rune) bool {
99+
return strings.ContainsAny(string(r), cidBreakSet)
100+
})
101+
if postIndex < 0 {
102+
// no breakage meaning the string look like this something + "ipld: could not find bafy"
103+
postIndex = len(msgPostKey)
104+
}
105+
106+
cidStr := msgPostKey[:postIndex]
107+
108+
var err error
109+
c, err = cid.Decode(cidStr)
110+
if err != nil {
111+
// failed to decode CID give up
112+
return nil, false
113+
}
114+
115+
// check that the CID is either a CIDv0 or a base32 multibase
116+
// because that what ipld.ErrNotFound.Error() -> cid.Cid.String() do currently
117+
if c.Version() != 0 {
118+
baseRune, _ := utf8.DecodeRuneInString(cidStr)
119+
if baseRune == utf8.RuneError || baseRune != mbase.Base32 {
120+
// not a multibase we expect, give up
121+
return nil, false
122+
}
123+
}
124+
}
125+
126+
err := ipld.ErrNotFound{Cid: c}
127+
pre := msg[:keyIndex]
128+
post := msgPostKey[postIndex:]
129+
130+
if len(pre) > 0 || len(post) > 0 {
131+
return prePostWrappedNotFoundError{
132+
pre: pre,
133+
post: post,
134+
wrapped: err,
135+
}, true
136+
}
137+
138+
return err, true
139+
}
140+
141+
// This is a simple error type that just return msg as Error().
142+
// But that also match ipld.ErrNotFound when called with Is(err).
143+
// That is needed to keep compatiblity with code that use string.Contains(err.Error(), "blockstore: block not found")
144+
// and code using ipld.ErrNotFound
145+
type blockstoreNotFoundMatchingIPLDErrNotFound struct {
146+
msg string
147+
}
148+
149+
func (e blockstoreNotFoundMatchingIPLDErrNotFound) String() string {
150+
return e.Error()
151+
}
152+
153+
func (e blockstoreNotFoundMatchingIPLDErrNotFound) Error() string {
154+
return e.msg
155+
}
156+
157+
func (e blockstoreNotFoundMatchingIPLDErrNotFound) Is(err error) bool {
158+
_, ok := err.(ipld.ErrNotFound)
159+
return ok
160+
}
161+
162+
//lint:ignore ST1008 using error as values
163+
func parseBlockstoreNotFound(msg string) (error, bool) {
164+
if !strings.Contains(msg, "blockstore: block not found") {
165+
return nil, false
166+
}
167+
168+
return blockstoreNotFoundMatchingIPLDErrNotFound{msg: msg}, true
169+
}

errors_test.go

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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+
mbase "github.com/multiformats/go-multibase"
11+
mh "github.com/multiformats/go-multihash"
12+
)
13+
14+
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}
15+
16+
func doParseIpldNotFoundTest(t *testing.T, original error) {
17+
originalMsg := original.Error()
18+
19+
rebuilt := parseErrNotFoundWithFallbackToMSG(originalMsg)
20+
21+
rebuiltMsg := rebuilt.Error()
22+
23+
if originalMsg != rebuiltMsg {
24+
t.Errorf("expected message to be %q; got %q", originalMsg, rebuiltMsg)
25+
}
26+
27+
originalNotFound := ipld.IsNotFound(original)
28+
rebuiltNotFound := ipld.IsNotFound(rebuilt)
29+
if originalNotFound != rebuiltNotFound {
30+
t.Errorf("for %q expected Ipld.IsNotFound to be %t; got %t", originalMsg, originalNotFound, rebuiltNotFound)
31+
}
32+
}
33+
34+
func TestParseIPLDNotFound(t *testing.T) {
35+
if err := parseErrNotFoundWithFallbackToMSG(""); err != nil {
36+
t.Errorf("expected empty string to give no error; got %T %q", err, err.Error())
37+
}
38+
39+
cidBreaks := make([]string, len(cidBreakSet))
40+
for i, v := range cidBreakSet {
41+
cidBreaks[i] = "%w" + string(v)
42+
}
43+
44+
base58BTCEncoder, err := mbase.NewEncoder(mbase.Base58BTC)
45+
if err != nil {
46+
t.Fatalf("expected to find Base58BTC encoder; got error %q", err.Error())
47+
}
48+
49+
for _, wrap := range append(cidBreaks,
50+
"",
51+
"merkledag: %w",
52+
"testing: %w the test",
53+
"%w is wrong",
54+
) {
55+
for _, err := range [...]error{
56+
errors.New("ipld: could not find "),
57+
errors.New("ipld: could not find Bad_CID"),
58+
errors.New("ipld: could not find " + cid.NewCidV1(cid.Raw, randomSha256MH).Encode(base58BTCEncoder)), // Test that we only accept CIDv0 and base32 CIDs
59+
errors.New("network connection timeout"),
60+
ipld.ErrNotFound{Cid: cid.Undef},
61+
ipld.ErrNotFound{Cid: cid.NewCidV0(randomSha256MH)},
62+
ipld.ErrNotFound{Cid: cid.NewCidV1(cid.Raw, randomSha256MH)},
63+
} {
64+
if wrap != "" {
65+
err = fmt.Errorf(wrap, err)
66+
}
67+
68+
doParseIpldNotFoundTest(t, err)
69+
}
70+
}
71+
}
72+
73+
func TestBlockstoreNotFoundMatchingIPLDErrNotFound(t *testing.T) {
74+
if !ipld.IsNotFound(blockstoreNotFoundMatchingIPLDErrNotFound{}) {
75+
t.Fatalf("expected blockstoreNotFoundMatchingIPLDErrNotFound to match ipld.IsNotFound; got false")
76+
}
77+
78+
for _, wrap := range [...]string{
79+
"",
80+
"merkledag: %w",
81+
"testing: %w the test",
82+
"%w is wrong",
83+
} {
84+
for _, err := range [...]error{
85+
errors.New("network connection timeout"),
86+
blockstoreNotFoundMatchingIPLDErrNotFound{"blockstore: block not found"},
87+
} {
88+
if wrap != "" {
89+
err = fmt.Errorf(wrap, err)
90+
}
91+
92+
doParseIpldNotFoundTest(t, err)
93+
}
94+
}
95+
}

go.mod

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ require (
55
github.com/ipfs/go-cid v0.0.7
66
github.com/ipfs/go-ipfs-cmds v0.6.0
77
github.com/ipfs/go-ipfs-files v0.0.8
8-
github.com/ipfs/go-ipld-format v0.2.0
9-
github.com/ipfs/go-merkledag v0.4.0
8+
github.com/ipfs/go-ipld-format v0.4.0
9+
github.com/ipfs/go-merkledag v0.6.0
1010
github.com/ipfs/go-path v0.1.1
1111
github.com/ipfs/go-unixfs v0.2.5
12-
github.com/ipfs/interface-go-ipfs-core v0.5.2
12+
github.com/ipfs/interface-go-ipfs-core v0.6.2
1313
github.com/ipfs/iptb v1.4.0
1414
github.com/ipfs/iptb-plugins v0.3.0
1515
github.com/libp2p/go-libp2p-core v0.8.6

0 commit comments

Comments
 (0)