Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Added meaningful exceptions to IPAddr #173

Closed
wants to merge 2 commits into from

5 participants

Jon Daniel Don't Add Me To Your Organization a.k.a The Travis Bot Aaron Patterson Akinori MUSHA Urabe, Shyouhei
Jon Daniel

At my day job we are doing some processing of an incoming IP address / host name in a Rails middleware and I noticed that the IPAddr class would always raise ArgumentError with only the error message giving any clue as to the cause.

I needed to be able to programmatically determine to cause of the failure to determine how to procede. Matching against the error message was less than ideal so modifying the Exceptions in seemed to be the most logical course of action.

With new exceptions a developer can pragmatically determine the cause of the IPAddr failure in a more effective manor than checking the exception message.

Previously

begin
  ip = IPAddr.new(some_other_variable)
rescue ArgumentError => e
  # Can't programmatically determine cause of the failure
end

With Patch

begin
  ip = IPAddr.new(some_other_variable)
rescue IPAddr::InvalidAddress => e
rescue IPAddr::UnsupportedAddressFamily => e
rescue IPAddr::UnsupportedAddressFamily => e
rescue IPAddr::ZeroFilledNumber => e
  # can use the exception to determine cause of the failure.
end
Jon Daniel binarycleric added meaningful exceptions to IPAddr
Previously IPAddr would throw ArgumentError regardless of the cause of
the exception. More meaningful exceptions were added (extending ArgumentError
for backward compatibility).

With new exceptions a developer can pragmatically determine the cause of the
IPAddr failure in a more effective manor than checking the exception message.
006aaa7
Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged 006aaa7 into 37cc18d).

Aaron Patterson
Collaborator

I like this change, but we need approval from @knu because he is the maintainer. @knu what do you think of this change?

It's annoying to inherit from ArgumentError, but makes sense for backwards compatibility. :+1:

Akinori MUSHA
Collaborator

I like the basics of the idea. Here's my suggestions.

  • I'd like each error class to have a common suffix Error.
  • ...And maybe a common super class like IPAddr::Error (which would inherit ArgumentError for backwards compatibility).
  • I don't see much of a point in having UnsupportedAddressFamily, InconsistentAddressFamily, and UnspecifiedAddressFamily separately. What about just AddressFamilyError that represents these all?
  • InvalidLength would be better called InvalidPrefixError.
  • ZeroFilledNumber might not be worth being a separate class and could be merged into InvalidIPv4, or should at least be a subclass of that.
Akinori MUSHA knu was assigned
Jon Daniel

@knu Thanks for the suggestions, they all make perfect sense. I try to implement these changes today.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged 3589191 into 37cc18d).

Akinori MUSHA
Collaborator

Thanks. On further consideration, I'm going to make the following changes.

  • When the address families mismatch in mask!, raise InvalidPrefixError instead of AddressFamilyError.

  • Merge InvalidIPv4Error and InvalidIPv6Error into InvalidAddressError, because you can't easily tell which version of IP address a given malformed IP address string was intended to be when IPAddr.new accepts both IPv4 and IPv6 addresses. At least the current implementation is not really made for that.

  • Make InvalidPrefixError a subclass of InvalidAddressError because it is caused by a bad network address string.

Urabe, Shyouhei shyouhei closed this pull request from a commit
Akinori MUSHA knu * lib/ipaddr.rb: Introduce several new error classes where only
  ArgumentError and StandardError were used.  IPAddr::Error is
  their common ancestor class that inherits from ArgumentError for
  backward compatibility.  Submitted by Jon Daniel.  Fixes #173 on
  GitHub.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@36868 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
e03d266
Urabe, Shyouhei shyouhei closed this in e03d266
Aaron Patterson tenderlove referenced this pull request from a commit
Akinori MUSHA knu * lib/ipaddr.rb: Introduce several new error classes where only
  ArgumentError and StandardError were used.  IPAddr::Error is
  their common ancestor class that inherits from ArgumentError for
  backward compatibility.  Submitted by Jon Daniel.  Fixes #173 on
  GitHub.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@36868 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
03c53ec
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 30, 2012
  1. Jon Daniel

    added meaningful exceptions to IPAddr

    binarycleric authored
    Previously IPAddr would throw ArgumentError regardless of the cause of
    the exception. More meaningful exceptions were added (extending ArgumentError
    for backward compatibility).
    
    With new exceptions a developer can pragmatically determine the cause of the
    IPAddr failure in a more effective manor than checking the exception message.
  2. Jon Daniel
This page is out of date. Refresh to see the latest.
Showing with 59 additions and 42 deletions.
  1. +59 42 lib/ipaddr.rb
101 lib/ipaddr.rb
View
@@ -82,6 +82,27 @@ class IPAddr
\z
}xi
+ # Generic IPAddr related error. Exceptions raised in this class should
+ # inherit from Error.
+ class Error < ArgumentError; end
+
+ # Raised when the provided IP address is an invalid address.
+ class InvalidAddressError < Error; end
+
+ # Raised when the address family is invalid such as an address with an
+ # unsupported family, an address with an inconsistent family, or an address
+ # who's family cannot be determined.
+ class AddressFamilyError < Error; end
+
+ # Raised when the address is an invalid IPv4 address.
+ class InvalidIPv4Error < Error; end
+
+ # Raised when the address is an invalid IPv6 address.
+ class InvalidIPv6Error < Error; end
+
+ # Raised when the address is an invalid length.
+ class InvalidPrefixError < Error; end
+
# Returns the address family of this IP address.
attr_reader :family
@@ -100,7 +121,7 @@ def IPAddr::ntop(addr)
when 16
s = IN6FORMAT % addr.unpack('n8')
else
- raise ArgumentError, "unsupported address family"
+ raise AddressFamilyError, "unsupported address family"
end
return s
end
@@ -226,7 +247,7 @@ def hton
(@addr >> (112 - 16 * i)) & 0xffff
}.pack('n8')
else
- raise "unsupported address family"
+ raise AddressFamilyError, "unsupported address family"
end
end
@@ -258,7 +279,7 @@ def ipv4_compat?
# into an IPv4-mapped IPv6 address.
def ipv4_mapped
if !ipv4?
- raise ArgumentError, "not an IPv4 address"
+ raise InvalidIPv4Error, "not an IPv4 address"
end
return self.clone.set(@addr | 0xffff00000000, Socket::AF_INET6)
end
@@ -267,7 +288,7 @@ def ipv4_mapped
# into an IPv4-compatible IPv6 address.
def ipv4_compat
if !ipv4?
- raise ArgumentError, "not an IPv4 address"
+ raise InvalidIPv4Error, "not an IPv4 address"
end
return self.clone.set(@addr, Socket::AF_INET6)
end
@@ -291,14 +312,14 @@ def reverse
when Socket::AF_INET6
return ip6_arpa
else
- raise "unsupported address family"
+ raise AddressFamilyError, "unsupported address family"
end
end
# Returns a string for DNS reverse lookup compatible with RFC3172.
def ip6_arpa
if !ipv6?
- raise ArgumentError, "not an IPv6 address"
+ raise InvalidIPv6Error, "not an IPv6 address"
end
return _reverse + ".ip6.arpa"
end
@@ -306,7 +327,7 @@ def ip6_arpa
# Returns a string for DNS reverse lookup compatible with RFC1886.
def ip6_int
if !ipv6?
- raise ArgumentError, "not an IPv6 address"
+ raise InvalidIPv6Error, "not an IPv6 address"
end
return _reverse + ".ip6.int"
end
@@ -346,7 +367,7 @@ def to_range
when Socket::AF_INET6
end_addr = (@addr | (IN6MASK ^ @mask_addr))
else
- raise "unsupported address family"
+ raise AddressFamilyError, "unsupported address family"
end
return clone.set(begin_addr, @family)..clone.set(end_addr, @family)
@@ -361,7 +382,7 @@ def inspect
when Socket::AF_INET6
af = "IPv6"
else
- raise "unsupported address family"
+ raise AddressFamilyError, "unsupported address family"
end
return sprintf("#<%s: %s:%s/%s>", self.class.name,
af, _to_string(@addr), _to_string(@mask_addr))
@@ -369,6 +390,8 @@ def inspect
protected
+
+
# Set +@addr+, the internal stored ip address, to given +addr+. The
# parameter +addr+ is validated using the first +family+ member,
# which is +Socket::AF_INET+ or +Socket::AF_INET6+.
@@ -376,14 +399,14 @@ def set(addr, *family)
case family[0] ? family[0] : @family
when Socket::AF_INET
if addr < 0 || addr > IN4MASK
- raise ArgumentError, "invalid address"
+ raise InvalidAddressError, "invalid address"
end
when Socket::AF_INET6
if addr < 0 || addr > IN6MASK
- raise ArgumentError, "invalid address"
+ raise InvalidAddressError, "invalid address"
end
else
- raise ArgumentError, "unsupported address family"
+ raise AddressFamilyError, "unsupported address family"
end
@addr = addr
if family[0]
@@ -400,7 +423,7 @@ def mask!(mask)
else
m = IPAddr.new(mask)
if m.family != @family
- raise ArgumentError, "address family is not same"
+ raise AddressFamilyError, "address family is not same"
end
@mask_addr = m.to_i
@addr &= @mask_addr
@@ -412,18 +435,18 @@ def mask!(mask)
case @family
when Socket::AF_INET
if prefixlen < 0 || prefixlen > 32
- raise ArgumentError, "invalid length"
+ raise InvalidPrefixError, "invalid length"
end
masklen = 32 - prefixlen
@mask_addr = ((IN4MASK >> masklen) << masklen)
when Socket::AF_INET6
if prefixlen < 0 || prefixlen > 128
- raise ArgumentError, "invalid length"
+ raise InvalidPrefixError, "invalid length"
end
masklen = 128 - prefixlen
@mask_addr = ((IN6MASK >> masklen) << masklen)
else
- raise "unsupported address family"
+ raise AddressFamilyError, "unsupported address family"
end
@addr = ((@addr >> masklen) << masklen)
return self
@@ -457,9 +480,9 @@ def initialize(addr = '::', family = Socket::AF_UNSPEC)
@mask_addr = (family == Socket::AF_INET) ? IN4MASK : IN6MASK
return
when Socket::AF_UNSPEC
- raise ArgumentError, "address family must be specified"
+ raise AddressFamilyError, "address family must be specified"
else
- raise ArgumentError, "unsupported address family: #{family}"
+ raise AddressFamilyError, "unsupported address family: #{family}"
end
end
prefix, prefixlen = addr.split('/')
@@ -482,7 +505,7 @@ def initialize(addr = '::', family = Socket::AF_UNSPEC)
@family = Socket::AF_INET6
end
if family != Socket::AF_UNSPEC && @family != family
- raise ArgumentError, "address family mismatch"
+ raise AddressFamilyError, "address family mismatch"
end
if prefixlen
mask!(prefixlen)
@@ -511,8 +534,8 @@ def in_addr(addr)
octets = m.captures
end
octets.inject(0) { |i, s|
- (n = s.to_i) < 256 or raise ArgumentError, "invalid address"
- s.match(/\A0./) and raise ArgumentError, "zero-filled number is ambiguous"
+ (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address"
+ s.match(/\A0./) and raise InvalidIPv4Error, "zero-filled number is ambiguous"
i << 8 | n
}
end
@@ -529,18 +552,18 @@ def in6_addr(left)
right = ''
when RE_IPV6ADDRLIKE_COMPRESSED
if $4
- left.count(':') <= 6 or raise ArgumentError, "invalid address"
+ left.count(':') <= 6 or raise InvalidAddressError, "invalid address"
addr = in_addr($~[4,4])
left = $1
right = $3 + '0:0'
else
- left.count(':') <= 7 or raise ArgumentError, "invalid address"
+ left.count(':') <= 7 or raise InvalidAddressError, "invalid address"
left = $1
right = $2
addr = 0
end
else
- raise ArgumentError, "invalid address"
+ raise InvalidAddressError, "invalid address"
end
l = left.split(':')
r = right.split(':')
@@ -560,7 +583,7 @@ def addr_mask(addr)
when Socket::AF_INET6
return addr & IN6MASK
else
- raise "unsupported address family"
+ raise AddressFamilyError, "unsupported address family"
end
end
@@ -573,7 +596,7 @@ def _reverse
when Socket::AF_INET6
return ("%.32x" % @addr).reverse!.gsub!(/.(?!$)/, '\&.')
else
- raise "unsupported address family"
+ raise AddressFamilyError, "unsupported address family"
end
end
@@ -586,7 +609,7 @@ def _to_string(addr)
when Socket::AF_INET6
return (("%.32x" % addr).gsub!(/.{4}(?!$)/, '\&:'))
else
- raise "unsupported address family"
+ raise AddressFamilyError, "unsupported address family"
end
end
@@ -713,19 +736,13 @@ def test_s_new
assert_equal("2001:200:300::", IPAddr.new("[2001:200:300::]/48").to_s)
- [
- ["192.168.0.256"],
- ["192.168.0.011"],
- ["fe80::1%fxp0"],
- ["::1/255.255.255.0"],
- [IPAddr.new("::1").to_i],
- ["::ffff:192.168.1.2/120", Socket::AF_INET],
- ["[192.168.1.2]/120"],
- ].each { |args|
- assert_raises(ArgumentError) {
- IPAddr.new(*args)
- }
- }
+ assert_raises(IPAddr::InvalidAddressError) { IPAddr.new('192.168.0.256') }
+ assert_raises(IPAddr::InvalidIPv4Error) { IPAddr.new('192.168.0.011') }
+ assert_raises(IPAddr::InvalidAddressError) { IPAddr.new("fe80::1%fxp0") }
+ assert_raises(IPAddr::AddressFamilyError) { IPAddr.new("::1/255.255.255.0") }
+ assert_raises(IPAddr::AddressFamilyError) { IPAddr.new(1) }
+ assert_raises(IPAddr::AddressFamilyError) { IPAddr.new("::ffff:192.168.1.2/120", Socket::AF_INET) }
+ assert_raises(IPAddr::InvalidAddressError) { IPAddr.new("[192.168.1.2]/120") }
end
def test_s_new_ntoh
@@ -786,14 +803,14 @@ def test_reverse
def test_ip6_arpa
assert_equal("f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa", IPAddr.new("3ffe:505:2::f").ip6_arpa)
- assert_raises(ArgumentError) {
+ assert_raises(IPAddr::InvalidIPv6Error) {
IPAddr.new("192.168.2.1").ip6_arpa
}
end
def test_ip6_int
assert_equal("f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.int", IPAddr.new("3ffe:505:2::f").ip6_int)
- assert_raises(ArgumentError) {
+ assert_raises(IPAddr::InvalidIPv6Error) {
IPAddr.new("192.168.2.1").ip6_int
}
end
Something went wrong with that request. Please try again.