Skip to content

Switch from ruby-ip to standard library ipaddr#39

Open
bquorning wants to merge 4 commits into
agaridata:mainfrom
bquorning:migrate-ruby-ip-to-ipaddr
Open

Switch from ruby-ip to standard library ipaddr#39
bquorning wants to merge 4 commits into
agaridata:mainfrom
bquorning:migrate-ruby-ip-to-ipaddr

Conversation

@bquorning

@bquorning bquorning commented Jul 11, 2026

Copy link
Copy Markdown

Summary

Replaces the third-party ruby-ip gem with Ruby's standard library IPAddr, removing an external runtime dependency. The last release of ruby-ip was over 10 years ago.

This PR is obviously generated with assistance from AI. Let me (the human) know if you need me to adjust some of the changes.

Changes

  1. Switch IP address handling from ruby-ip to ipaddr across lib/spf/, and drop the ip dependency from spf.gemspec and Gemfile.lock.
  2. Extract CIDR host-bits detection into an SPF::Util helper, since IPAddr does not expose an equivalent to IP#to_hex.
  3. Compute the IPv6 hex representation without IP#to_hex.
  4. Render ip4/ip6 mechanism params from the unmasked address to preserve existing output.

🤖 Generated with Claude Code

bquorning and others added 4 commits July 11, 2026 12:38
Replace the ruby-ip-specific IP#to_hex helper with a computation based
on the integer address value, which produces an identical zero-padded
32-nibble lowercase hex string. This is a behavior-preserving refactor
(verified equal for the values used here) that removes reliance on a
ruby-ip-only method, paving the way for switching to the standard
library ipaddr, which has no to_hex.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ip4 and ip6 mechanisms both duplicate the check that flags a CIDR
netblock whose address has bits set outside its network prefix. Move it
into a single SPF::Util.ip_network_has_host_bits? predicate so the
detection logic lives in one place. This is behavior-preserving; it also
isolates the one piece of address math that must change when switching
from ruby-ip (which exposes #offset) to ipaddr (which masks host bits
away on construction), keeping that later switch a localized change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Derive the ip4 and ip6 mechanism's textual params from the parsed
address string (@ip_address) rather than from the @ip_network object,
and reuse #params when building the InvalidMechCIDRError message instead
of interpolating @ip_network directly.

Under ruby-ip these produce identical output, so this is
behavior-preserving (verified for the ip4/ip6 rendering cases). It
matters for the upcoming switch to ipaddr: ipaddr masks host bits away
on construction (1.2.3.4/24 becomes 1.2.3.0/24), so rendering from the
network object would silently drop the host bits that SPF preserves in
the mechanism's textual form and in the diagnostic. Sourcing params from
the original address keeps that behavior across the switch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace all use of the unmaintained ruby-ip gem with the standard
library's IPAddr across lib/ and the specs, and drop the gem dependency.
The prior refactors reduced the code changes to a near-mechanical
substitution:

  IP.new(str)      -> IPAddr.new(str)
  IP === x         -> IPAddr === x
  IP::V4 === x     -> x.ipv4?
  IP::V6 === x     -> x.ipv6?
  #to_addr         -> #to_s
  #pfxlen          -> #prefix
  #contains?(o)    -> #include?(o)   (removes the IP#contains? monkey-patch)

Two spots are not 1:1, both isolated by the earlier commits:

  - IPAddr masks host bits away on construction, so it cannot report
    them after the fact the way ruby-ip's #offset did. SPF::Util
    .ip_network_has_host_bits? now takes the original address string and
    compares it against the masked network. #params already renders from
    that unmasked address, so mechanism text and the InvalidMechCIDRError
    diagnostic are unchanged.
  - IPAddr::InvalidAddressError is an ArgumentError, so the existing
    "rescue ArgumentError" fallbacks in the network parsers still apply.

The require 'ip' in eval.rb was unused and is dropped. With no code
referencing ruby-ip any more, remove it from the gemspec and regenerate
Gemfile.lock; this retires an unmaintained dependency (ruby-ip's last
release was 0.9.3) with no replacement gem added.

Full suite passes (95 examples); ip4/ip6 mechanism params and error
output verified byte-identical to the ruby-ip behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bquorning
bquorning marked this pull request as ready for review July 14, 2026 10:08
@bquorning

Copy link
Copy Markdown
Author

cc @tedaford just to get eyes on this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant