master
go 166 lines 3.92 KB
Raw
1 package rpc
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 func parseErrNotFound(msg string) (error, bool) {
53 if msg == "" {
54 return nil, true // Fast path
55 }
56
57 if err, handled := parseIPLDErrNotFound(msg); handled {
58 return err, true
59 }
60
61 if err, handled := parseBlockstoreNotFound(msg); handled {
62 return err, true
63 }
64
65 return nil, false
66 }
67
68 // Assume CIDs break on:
69 // - Whitespaces: " \t\n\r\v\f"
70 // - Semicolon: ";" this is to parse ipld.ErrNotFound wrapped in multierr
71 // - Double Quotes: "\"" this is for parsing %q and %#v formatting.
72 const cidBreakSet = " \t\n\r\v\f;\""
73
74 func parseIPLDErrNotFound(msg string) (error, bool) {
75 // The pattern we search for is:
76 const ipldErrNotFoundKey = "ipld: could not find " /*CID*/
77 // We try to parse the CID, if it's invalid we give up and return a simple text error.
78 // We also accept "node" in place of the CID because that means it's an Undefined CID.
79
80 keyIndex := strings.Index(msg, ipldErrNotFoundKey)
81
82 if keyIndex < 0 { // Unknown error
83 return nil, false
84 }
85
86 cidStart := keyIndex + len(ipldErrNotFoundKey)
87
88 msgPostKey := msg[cidStart:]
89 var c cid.Cid
90 var postIndex int
91 if strings.HasPrefix(msgPostKey, "node") {
92 // Fallback case
93 c = cid.Undef
94 postIndex = len("node")
95 } else {
96 postIndex = strings.IndexFunc(msgPostKey, func(r rune) bool {
97 return strings.ContainsAny(string(r), cidBreakSet)
98 })
99 if postIndex < 0 {
100 // no breakage meaning the string look like this something + "ipld: could not find bafy"
101 postIndex = len(msgPostKey)
102 }
103
104 cidStr := msgPostKey[:postIndex]
105
106 var err error
107 c, err = cid.Decode(cidStr)
108 if err != nil {
109 // failed to decode CID give up
110 return nil, false
111 }
112
113 // check that the CID is either a CIDv0 or a base32 multibase
114 // because that what ipld.ErrNotFound.Error() -> cid.Cid.String() do currently
115 if c.Version() != 0 {
116 baseRune, _ := utf8.DecodeRuneInString(cidStr)
117 if baseRune == utf8.RuneError || baseRune != mbase.Base32 {
118 // not a multibase we expect, give up
119 return nil, false
120 }
121 }
122 }
123
124 err := ipld.ErrNotFound{Cid: c}
125 pre := msg[:keyIndex]
126 post := msgPostKey[postIndex:]
127
128 if len(pre) > 0 || len(post) > 0 {
129 return prePostWrappedNotFoundError{
130 pre: pre,
131 post: post,
132 wrapped: err,
133 }, true
134 }
135
136 return err, true
137 }
138
139 // This is a simple error type that just return msg as Error().
140 // But that also match ipld.ErrNotFound when called with Is(err).
141 // That is needed to keep compatibility with code that use string.Contains(err.Error(), "blockstore: block not found")
142 // and code using ipld.ErrNotFound.
143 type blockstoreNotFoundMatchingIPLDErrNotFound struct {
144 msg string
145 }
146
147 func (e blockstoreNotFoundMatchingIPLDErrNotFound) String() string {
148 return e.Error()
149 }
150
151 func (e blockstoreNotFoundMatchingIPLDErrNotFound) Error() string {
152 return e.msg
153 }
154
155 func (e blockstoreNotFoundMatchingIPLDErrNotFound) Is(err error) bool {
156 _, ok := err.(ipld.ErrNotFound)
157 return ok
158 }
159
160 func parseBlockstoreNotFound(msg string) (error, bool) {
161 if !strings.Contains(msg, "blockstore: block not found") {
162 return nil, false
163 }
164
165 return blockstoreNotFoundMatchingIPLDErrNotFound{msg: msg}, true
166 }