Skip to content

Commit 917895d

Browse files
committed
Rethink the exception hierarchy
Now looks like this: builtins.Exception +-- builtins.ArithmeticError | +-- builtins.OverflowError (Integer was too large during parsing) +-- builtins.TypeError (Invalid parameter type) +-- builtins.LookupError | +-- LookupError +-- ProtocolLookupError (MultiAddr did not contain the requested protocol) +-- builtins.ValueError | +-- ParseError | +-- BinaryParseError (Failed to parse the binary representation) | +-- StringParseError (Failed to parse the text/string representation) +-- ProtocolManagerError +-- ProtocolExistsError (Tried to add protocol already defined) +-- ProtocolNotFoundError (Tried to find non-existing protocol) Additionally all exceptions defined by *py-multiaddr* also derive from `multiaddr.exceptions.Error`.
1 parent edad799 commit 917895d

File tree

12 files changed

+262
-157
lines changed

12 files changed

+262
-157
lines changed

multiaddr/codecs/ip4.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@
1111

1212

1313
def to_bytes(proto, string):
14-
try:
15-
return netaddr.IPAddress(string, version=4).packed
16-
except Exception:
17-
raise ValueError("failed to parse ip4 addr: %s" % string)
14+
return netaddr.IPAddress(string, version=4).packed
1815

1916

2017
def to_string(proto, buf):

multiaddr/codecs/ip6.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@
1111

1212

1313
def to_bytes(proto, string):
14-
try:
15-
return netaddr.IPAddress(string, version=6).packed
16-
except Exception:
17-
raise ValueError("failed to parse ip4 addr: %s" % string)
14+
return netaddr.IPAddress(string, version=6).packed
1815

1916

2017
def to_string(proto, buf):

multiaddr/codecs/onion.py

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,23 @@
1212
def to_bytes(proto, string):
1313
addr = string.split(":")
1414
if len(addr) != 2:
15-
raise ValueError(
16-
"failed to parse %s addr: %s does not contain a port number."
17-
% (proto.name, string))
15+
raise ValueError("Does not contain a port number")
1816

1917
# onion address without the ".onion" substring
2018
if len(addr[0]) != 16:
21-
raise ValueError(
22-
"failed to parse %s addr: %s not a Tor onion address."
23-
% (proto.name, string))
19+
raise ValueError("Invalid onion host address length (must be 16 characters)")
2420
try:
2521
onion_host_bytes = base64.b32decode(addr[0].upper())
26-
except Exception as ex:
27-
raise ValueError(
28-
"failed to decode base32 %s addr: %s %s"
29-
% (proto.name, string, str(ex)))
22+
except Exception as exc:
23+
six.raise_from(ValueError("Cannot decode {0!r} as base32: {1}".format(addr[0], exc)), exc)
3024

3125
# onion port number
3226
try:
33-
port = int(addr[1])
34-
except Exception as ex:
35-
raise ValueError("failed to parse %s addr: %s"
36-
% (proto.name, str(ex)))
37-
if port >= 65536:
38-
raise ValueError("failed to parse %s addr: %s"
39-
% (proto.name, "port greater than 65536"))
40-
if port < 1:
41-
raise ValueError("failed to parse %s addr: %s"
42-
% (proto.name, "port less than 1"))
27+
port = int(addr[1], 10)
28+
except ValueError as exc:
29+
six.raise_from(ValueError("Port number is not an integer"), exc)
30+
if port not in range(1, 65536):
31+
raise ValueError("Port number is not in range(1, 65536)")
4332

4433
return b''.join((onion_host_bytes, struct.pack('>H', port)))
4534

multiaddr/codecs/p2p.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,11 @@
1212

1313
def to_bytes(proto, string):
1414
# the address is a base58-encoded string
15-
try:
16-
if six.PY2 and isinstance(string, unicode):
17-
string = string.encode("ascii")
18-
mm = base58.b58decode(string)
19-
except Exception as ex:
20-
raise ValueError("failed to parse p2p addr: %s %s" % (string, str(ex)))
15+
if six.PY2 and isinstance(string, unicode):
16+
string = string.encode("ascii")
17+
mm = base58.b58decode(string)
2118
if len(mm) < 5:
22-
raise ValueError("invalid P2P multihash: %s" % mm)
19+
raise ValueError("P2P MultiHash too short: len() < 5")
2320
return mm
2421

2522

multiaddr/codecs/uint16be.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@
1111
def to_bytes(proto, string):
1212
try:
1313
return struct.pack('>H', int(string, 10))
14-
except ValueError as ex:
15-
raise ValueError("failed to parse %s addr: %s"
16-
% (proto.name, str(ex)))
17-
except struct.error:
18-
raise ValueError("failed to parse %s addr: %s" %
19-
(proto.name, "greater than 65536"))
14+
except ValueError as exc:
15+
six.raise_from(ValueError("Not a base 10 integer"), exc)
16+
except struct.error as exc:
17+
six.raise_from(ValueError("Integer not in range(65536)"), exc)
2018

2119

2220
def to_string(proto, buf):
2321
if len(buf) != 2:
24-
raise ValueError("Not a uint16")
22+
raise ValueError("Invalid integer length (must be 2 bytes / 16 bits)")
2523
return six.text_type(struct.unpack('>H', buf)[0])

multiaddr/exceptions.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
class Error(Exception):
2+
pass
3+
4+
5+
class LookupError(LookupError, Error):
6+
pass
7+
8+
9+
class ProtocolLookupError(LookupError):
10+
"""
11+
MultiAddr did not contain a protocol with the requested code
12+
"""
13+
14+
def __init__(self, proto, string):
15+
self.proto = proto
16+
self.string = string
17+
18+
super(ProtocolLookupError, self).__init__(
19+
"MultiAddr {0!r} does not contain protocol {1}".format(string, proto)
20+
)
21+
22+
23+
class ParseError(ValueError, Error):
24+
pass
25+
26+
27+
class StringParseError(ParseError):
28+
"""
29+
MultiAddr string representation could not be parsed
30+
"""
31+
32+
def __init__(self, message, string, protocol=None, original=None):
33+
self.message = message
34+
self.string = string
35+
self.protocol = protocol
36+
self.original = original
37+
38+
if protocol:
39+
message = "Invalid MultiAddr {0!r} protocol {1}: {2}".format(string, protocol, message)
40+
else:
41+
message = "Invalid MultiAddr {0!r}: {1}".format(string, message)
42+
43+
super(StringParseError, self).__init__(message)
44+
45+
46+
class BinaryParseError(ParseError):
47+
"""
48+
MultiAddr binary representation could not be parsed
49+
"""
50+
51+
def __init__(self, message, binary, protocol, original=None):
52+
self.message = message
53+
self.binary = binary
54+
self.protocol = protocol
55+
self.original = original
56+
57+
message = "Invalid binary MultiAddr protocol {0}: {1}".format(protocol, message)
58+
59+
super(BinaryParseError, self).__init__(message)
60+
61+
62+
class ProtocolManagerError(Error):
63+
pass
64+
65+
66+
class ProtocolExistsError(ProtocolManagerError):
67+
"""
68+
Protocol with the given name or code already exists
69+
"""
70+
def __init__(self, proto, kind="name"):
71+
self.proto = proto
72+
self.kind = kind
73+
74+
super(ProtocolExistsError, self).__init__(
75+
"Protocol with {0} {1!r} already exists".format(kind, getattr(proto, kind))
76+
)
77+
78+
class ProtocolNotFoundError(ProtocolManagerError):
79+
"""
80+
No protocol with the given name or code found
81+
"""
82+
def __init__(self, value, kind="name"):
83+
self.value = value
84+
self.kind = kind
85+
86+
super(ProtocolNotFoundError, self).__init__(
87+
"No protocol with {0} {1!r} found".format(kind, value)
88+
)

multiaddr/multiaddr.py

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,20 @@
33

44
import six
55

6+
from . import exceptions, protocols
7+
68
from .transforms import bytes_iter
79
from .transforms import string_to_bytes
810
from .transforms import bytes_to_string
911

1012

11-
class ProtocolNotFoundException(Exception):
12-
pass
13-
14-
1513
class Multiaddr(object):
1614
"""Multiaddr is a representation of multiple nested internet addresses.
1715
1816
Multiaddr is a cross-protocol, cross-platform format for representing
1917
internet addresses. It emphasizes explicitness and self-description.
2018
21-
Learn more here: https://github.com/jbenet/multiaddr
19+
Learn more here: http://multiformats.io/multiaddr/
2220
2321
Multiaddrs have both a binary and string representation.
2422
@@ -49,7 +47,7 @@ def __init__(self, addr):
4947
elif isinstance(addr, six.binary_type):
5048
self._bytes = addr
5149
else:
52-
raise ValueError("Invalid address type, must be bytes or str")
50+
raise TypeError("MultiAddr must be bytes or str")
5351

5452
def __eq__(self, other):
5553
"""Checks if two Multiaddr objects are exactly equal."""
@@ -61,13 +59,9 @@ def __ne__(self, other):
6159
def __str__(self):
6260
"""Return the string representation of this Multiaddr.
6361
64-
May raise an exception if the internal state of the Multiaddr is
65-
corrupted."""
66-
try:
67-
return bytes_to_string(self._bytes)
68-
except Exception:
69-
raise ValueError(
70-
"multiaddr failed to convert back to string. corrupted?")
62+
May raise a :class:`~multiaddr.exceptions.BinaryParseError` if the
63+
stored MultiAddr binary representation is invalid."""
64+
return bytes_to_string(self._bytes)
7165

7266
# On Python 2 __str__ needs to return binary text, so expose the original
7367
# function as __unicode__ and transparently encode its returned text based
@@ -113,25 +107,28 @@ def decapsulate(self, other):
113107
except ValueError:
114108
# if multiaddr not contained, returns a copy
115109
return copy(self)
116-
try:
117-
return Multiaddr(s1[:idx])
118-
except Exception as ex:
119-
raise ValueError(
120-
"Multiaddr.decapsulate incorrect byte boundaries: %s"
121-
% str(ex))
110+
return Multiaddr(s1[:idx])
122111

123-
def value_for_protocol(self, code):
112+
def value_for_protocol(self, proto):
124113
"""Return the value (if any) following the specified protocol."""
125-
if not isinstance(code, int):
126-
raise ValueError("code type should be `int`, code={}".format(code))
127-
128-
for proto, codec, part in bytes_iter(self.to_bytes()):
129-
if proto.code == code:
114+
if not isinstance(proto, protocols.Protocol):
115+
if isinstance(proto, int):
116+
proto = protocols.protocol_with_code(proto)
117+
elif isinstance(proto, six.string_types):
118+
proto = protocols.protocol_with_name(proto)
119+
else:
120+
raise TypeError("Protocol object, name or code expected, got {0!r}".format(proto))
121+
122+
for proto2, codec, part in bytes_iter(self.to_bytes()):
123+
if proto2 == proto:
130124
if codec.SIZE != 0:
131-
# If we have an address, return it
132-
return codec.to_string(proto, part)
125+
try:
126+
# If we have an address, return it
127+
return codec.to_string(proto2, part)
128+
except Exception as exc:
129+
six.raise_from(exceptions.BinaryParseError(str(exc), self.to_bytes(), proto2.name, exc), exc)
133130
else:
134131
# We were given something like '/utp', which doesn't have
135132
# an address, so return ''
136133
return ''
137-
raise ProtocolNotFoundException()
134+
raise exceptions.ProtocolLookupError(proto, str(self))

0 commit comments

Comments
 (0)