mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-21 18:37:45 +08:00
Merge pull request ipfs/go-ipfs-http-client#156 from ipfs/fix/ipld-ErrNotFound
fix: make Block().* return correct ABI based ipld.ErrNotFound errors This commit was moved from ipfs/go-ipfs-http-client@fdbee7c788
This commit is contained in:
commit
ebb2807dcc
@ -3,7 +3,6 @@ package httpapi
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
@ -67,7 +66,7 @@ func (api *BlockAPI) Get(ctx context.Context, p path.Path) (io.Reader, error) {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Error != nil {
|
||||
return nil, resp.Error
|
||||
return nil, parseErrNotFoundWithFallbackToError(resp.Error)
|
||||
}
|
||||
|
||||
//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
|
||||
return err
|
||||
}
|
||||
|
||||
if removedBlock.Error != "" {
|
||||
return errors.New(removedBlock.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
return parseErrNotFoundWithFallbackToMSG(removedBlock.Error)
|
||||
}
|
||||
|
||||
func (api *BlockAPI) Stat(ctx context.Context, p path.Path) (iface.BlockStat, error) {
|
||||
var out blockStat
|
||||
err := api.core().Request("block/stat", p.String()).Exec(ctx, &out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, parseErrNotFoundWithFallbackToError(err)
|
||||
}
|
||||
out.cid, err = cid.Parse(out.Key)
|
||||
if err != nil {
|
||||
|
||||
169
client/httpapi/errors.go
Normal file
169
client/httpapi/errors.go
Normal file
@ -0,0 +1,169 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
mbase "github.com/multiformats/go-multibase"
|
||||
)
|
||||
|
||||
// This file handle parsing and returning the correct ABI based errors from error messages
|
||||
|
||||
type prePostWrappedNotFoundError struct {
|
||||
pre string
|
||||
post string
|
||||
|
||||
wrapped ipld.ErrNotFound
|
||||
}
|
||||
|
||||
func (e prePostWrappedNotFoundError) String() string {
|
||||
return e.Error()
|
||||
}
|
||||
|
||||
func (e prePostWrappedNotFoundError) Error() string {
|
||||
return e.pre + e.wrapped.Error() + e.post
|
||||
}
|
||||
|
||||
func (e prePostWrappedNotFoundError) Unwrap() error {
|
||||
return e.wrapped
|
||||
}
|
||||
|
||||
func parseErrNotFoundWithFallbackToMSG(msg string) error {
|
||||
err, handled := parseErrNotFound(msg)
|
||||
if handled {
|
||||
return err
|
||||
}
|
||||
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
func parseErrNotFoundWithFallbackToError(msg error) error {
|
||||
err, handled := parseErrNotFound(msg.Error())
|
||||
if handled {
|
||||
return err
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
//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
|
||||
func parseErrNotFound(msg string) (error, bool) {
|
||||
if msg == "" {
|
||||
return nil, true // Fast path
|
||||
}
|
||||
|
||||
if err, handled := parseIPLDErrNotFound(msg); handled {
|
||||
return err, true
|
||||
}
|
||||
|
||||
if err, handled := parseBlockstoreNotFound(msg); handled {
|
||||
return err, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Assume CIDs break on:
|
||||
// - Whitespaces: " \t\n\r\v\f"
|
||||
// - Semicolon: ";" this is to parse ipld.ErrNotFound wrapped in multierr
|
||||
// - Double Quotes: "\"" this is for parsing %q and %#v formating
|
||||
const cidBreakSet = " \t\n\r\v\f;\""
|
||||
|
||||
//lint:ignore ST1008 using error as values
|
||||
func parseIPLDErrNotFound(msg string) (error, bool) {
|
||||
// The patern we search for is:
|
||||
const ipldErrNotFoundKey = "ipld: could not find " /*CID*/
|
||||
// We try to parse the CID, if it's invalid we give up and return a simple text error.
|
||||
// We also accept "node" in place of the CID because that means it's an Undefined CID.
|
||||
|
||||
keyIndex := strings.Index(msg, ipldErrNotFoundKey)
|
||||
|
||||
if keyIndex < 0 { // Unknown error
|
||||
return nil, false
|
||||
}
|
||||
|
||||
cidStart := keyIndex + len(ipldErrNotFoundKey)
|
||||
|
||||
msgPostKey := msg[cidStart:]
|
||||
var c cid.Cid
|
||||
var postIndex int
|
||||
if strings.HasPrefix(msgPostKey, "node") {
|
||||
// Fallback case
|
||||
c = cid.Undef
|
||||
postIndex = len("node")
|
||||
} else {
|
||||
postIndex = strings.IndexFunc(msgPostKey, func(r rune) bool {
|
||||
return strings.ContainsAny(string(r), cidBreakSet)
|
||||
})
|
||||
if postIndex < 0 {
|
||||
// no breakage meaning the string look like this something + "ipld: could not find bafy"
|
||||
postIndex = len(msgPostKey)
|
||||
}
|
||||
|
||||
cidStr := msgPostKey[:postIndex]
|
||||
|
||||
var err error
|
||||
c, err = cid.Decode(cidStr)
|
||||
if err != nil {
|
||||
// failed to decode CID give up
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// check that the CID is either a CIDv0 or a base32 multibase
|
||||
// because that what ipld.ErrNotFound.Error() -> cid.Cid.String() do currently
|
||||
if c.Version() != 0 {
|
||||
baseRune, _ := utf8.DecodeRuneInString(cidStr)
|
||||
if baseRune == utf8.RuneError || baseRune != mbase.Base32 {
|
||||
// not a multibase we expect, give up
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := ipld.ErrNotFound{Cid: c}
|
||||
pre := msg[:keyIndex]
|
||||
post := msgPostKey[postIndex:]
|
||||
|
||||
if len(pre) > 0 || len(post) > 0 {
|
||||
return prePostWrappedNotFoundError{
|
||||
pre: pre,
|
||||
post: post,
|
||||
wrapped: err,
|
||||
}, true
|
||||
}
|
||||
|
||||
return err, true
|
||||
}
|
||||
|
||||
// This is a simple error type that just return msg as Error().
|
||||
// But that also match ipld.ErrNotFound when called with Is(err).
|
||||
// That is needed to keep compatiblity with code that use string.Contains(err.Error(), "blockstore: block not found")
|
||||
// and code using ipld.ErrNotFound
|
||||
type blockstoreNotFoundMatchingIPLDErrNotFound struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e blockstoreNotFoundMatchingIPLDErrNotFound) String() string {
|
||||
return e.Error()
|
||||
}
|
||||
|
||||
func (e blockstoreNotFoundMatchingIPLDErrNotFound) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
func (e blockstoreNotFoundMatchingIPLDErrNotFound) Is(err error) bool {
|
||||
_, ok := err.(ipld.ErrNotFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
//lint:ignore ST1008 using error as values
|
||||
func parseBlockstoreNotFound(msg string) (error, bool) {
|
||||
if !strings.Contains(msg, "blockstore: block not found") {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return blockstoreNotFoundMatchingIPLDErrNotFound{msg: msg}, true
|
||||
}
|
||||
95
client/httpapi/errors_test.go
Normal file
95
client/httpapi/errors_test.go
Normal file
@ -0,0 +1,95 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
mbase "github.com/multiformats/go-multibase"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
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}
|
||||
|
||||
func doParseIpldNotFoundTest(t *testing.T, original error) {
|
||||
originalMsg := original.Error()
|
||||
|
||||
rebuilt := parseErrNotFoundWithFallbackToMSG(originalMsg)
|
||||
|
||||
rebuiltMsg := rebuilt.Error()
|
||||
|
||||
if originalMsg != rebuiltMsg {
|
||||
t.Errorf("expected message to be %q; got %q", originalMsg, rebuiltMsg)
|
||||
}
|
||||
|
||||
originalNotFound := ipld.IsNotFound(original)
|
||||
rebuiltNotFound := ipld.IsNotFound(rebuilt)
|
||||
if originalNotFound != rebuiltNotFound {
|
||||
t.Errorf("for %q expected Ipld.IsNotFound to be %t; got %t", originalMsg, originalNotFound, rebuiltNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseIPLDNotFound(t *testing.T) {
|
||||
if err := parseErrNotFoundWithFallbackToMSG(""); err != nil {
|
||||
t.Errorf("expected empty string to give no error; got %T %q", err, err.Error())
|
||||
}
|
||||
|
||||
cidBreaks := make([]string, len(cidBreakSet))
|
||||
for i, v := range cidBreakSet {
|
||||
cidBreaks[i] = "%w" + string(v)
|
||||
}
|
||||
|
||||
base58BTCEncoder, err := mbase.NewEncoder(mbase.Base58BTC)
|
||||
if err != nil {
|
||||
t.Fatalf("expected to find Base58BTC encoder; got error %q", err.Error())
|
||||
}
|
||||
|
||||
for _, wrap := range append(cidBreaks,
|
||||
"",
|
||||
"merkledag: %w",
|
||||
"testing: %w the test",
|
||||
"%w is wrong",
|
||||
) {
|
||||
for _, err := range [...]error{
|
||||
errors.New("ipld: could not find "),
|
||||
errors.New("ipld: could not find Bad_CID"),
|
||||
errors.New("ipld: could not find " + cid.NewCidV1(cid.Raw, randomSha256MH).Encode(base58BTCEncoder)), // Test that we only accept CIDv0 and base32 CIDs
|
||||
errors.New("network connection timeout"),
|
||||
ipld.ErrNotFound{Cid: cid.Undef},
|
||||
ipld.ErrNotFound{Cid: cid.NewCidV0(randomSha256MH)},
|
||||
ipld.ErrNotFound{Cid: cid.NewCidV1(cid.Raw, randomSha256MH)},
|
||||
} {
|
||||
if wrap != "" {
|
||||
err = fmt.Errorf(wrap, err)
|
||||
}
|
||||
|
||||
doParseIpldNotFoundTest(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockstoreNotFoundMatchingIPLDErrNotFound(t *testing.T) {
|
||||
if !ipld.IsNotFound(blockstoreNotFoundMatchingIPLDErrNotFound{}) {
|
||||
t.Fatalf("expected blockstoreNotFoundMatchingIPLDErrNotFound to match ipld.IsNotFound; got false")
|
||||
}
|
||||
|
||||
for _, wrap := range [...]string{
|
||||
"",
|
||||
"merkledag: %w",
|
||||
"testing: %w the test",
|
||||
"%w is wrong",
|
||||
} {
|
||||
for _, err := range [...]error{
|
||||
errors.New("network connection timeout"),
|
||||
blockstoreNotFoundMatchingIPLDErrNotFound{"blockstore: block not found"},
|
||||
} {
|
||||
if wrap != "" {
|
||||
err = fmt.Errorf(wrap, err)
|
||||
}
|
||||
|
||||
doParseIpldNotFoundTest(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user