Skip to content

Commit c8040fe

Browse files
committed
Switch Networks methods to iterators
1 parent 457aad7 commit c8040fe

File tree

7 files changed

+73
-93
lines changed

7 files changed

+73
-93
lines changed

.github/workflows/go.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
name: Build
99
strategy:
1010
matrix:
11-
go-version: [1.21.x, 1.22.x]
11+
go-version: [1.23.0-rc.1]
1212
platform: [ubuntu-latest, macos-latest, windows-latest]
1313
runs-on: ${{ matrix.platform }}
1414
steps:

example_test.go

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,12 @@ func ExampleReader_Networks() {
6767
Domain string `maxminddb:"connection_type"`
6868
}{}
6969

70-
networks := db.Networks()
71-
for networks.Next() {
72-
subnet, err := networks.Network(&record)
70+
for result := range db.Networks() {
71+
err := result.Decode(&record)
7372
if err != nil {
7473
log.Panic(err)
7574
}
76-
fmt.Printf("%s: %s\n", subnet.String(), record.Domain)
77-
}
78-
if networks.Err() != nil {
79-
log.Panic(networks.Err())
75+
fmt.Printf("%s: %s\n", result.Network(), record.Domain)
8076
}
8177
// Output:
8278
// 1.0.0.0/24: Cable/DSL
@@ -123,16 +119,12 @@ func ExampleReader_NetworksWithin() {
123119
log.Panic(err)
124120
}
125121

126-
networks := db.NetworksWithin(prefix)
127-
for networks.Next() {
128-
subnet, err := networks.Network(&record)
122+
for result := range db.NetworksWithin(prefix) {
123+
err := result.Decode(&record)
129124
if err != nil {
130125
log.Panic(err)
131126
}
132-
fmt.Printf("%s: %s\n", subnet.String(), record.Domain)
133-
}
134-
if networks.Err() != nil {
135-
log.Panic(networks.Err())
127+
fmt.Printf("%s: %s\n", result.Network(), record.Domain)
136128
}
137129

138130
// Output:

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/oschwald/maxminddb-golang/v2
22

3-
go 1.21
3+
go 1.23
44

55
require (
66
github.com/stretchr/testify v1.9.0

reader.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -221,14 +221,6 @@ func (r *Reader) traverseTree(ip netip.Addr, node uint, stopBit int) (uint, int)
221221
return node, i
222222
}
223223

224-
func (r *Reader) retrieveData(pointer uint, result any) error {
225-
offset, err := r.resolveDataPointer(pointer)
226-
if err != nil {
227-
return err
228-
}
229-
return Result{decoder: r.decoder, offset: uint(offset)}.Decode(result)
230-
}
231-
232224
func (r *Reader) resolveDataPointer(pointer uint) (uintptr, error) {
233225
resolved := uintptr(pointer - r.Metadata.NodeCount - dataSectionSeparatorSize)
234226

traverse.go

Lines changed: 46 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package maxminddb
33
import (
44
"fmt"
55
"net/netip"
6+
7+
"iter"
68
)
79

810
// Internal structure used to keep track of nodes we still need to visit.
@@ -12,8 +14,8 @@ type netNode struct {
1214
pointer uint
1315
}
1416

15-
// Networks represents a set of subnets that we are iterating over.
16-
type Networks struct {
17+
// networks represents a set of subnets that we are iterating over.
18+
type networks struct {
1719
err error
1820
reader *Reader
1921
nodes []netNode
@@ -27,12 +29,12 @@ var (
2729
)
2830

2931
// NetworksOption are options for Networks and NetworksWithin.
30-
type NetworksOption func(*Networks)
32+
type NetworksOption func(*networks)
3133

3234
// IncludeAliasedNetworks is an option for Networks and NetworksWithin
3335
// that makes them iterate over aliases of the IPv4 subtree in an IPv6
3436
// database, e.g., ::ffff:0:0/96, 2001::/32, and 2002::/16.
35-
func IncludeAliasedNetworks(networks *Networks) {
37+
func IncludeAliasedNetworks(networks *networks) {
3638
networks.includeAliasedNetworks = true
3739
}
3840

@@ -43,15 +45,11 @@ func IncludeAliasedNetworks(networks *Networks) {
4345
// in an IPv6 database. This iterator will only iterate over these once by
4446
// default. To iterate over all the IPv4 network locations, use the
4547
// IncludeAliasedNetworks option.
46-
func (r *Reader) Networks(options ...NetworksOption) *Networks {
47-
var networks *Networks
48+
func (r *Reader) Networks(options ...NetworksOption) iter.Seq[Result] {
4849
if r.Metadata.IPVersion == 6 {
49-
networks = r.NetworksWithin(allIPv6, options...)
50-
} else {
51-
networks = r.NetworksWithin(allIPv4, options...)
50+
return r.NetworksWithin(allIPv6, options...)
5251
}
53-
54-
return networks
52+
return r.NetworksWithin(allIPv4, options...)
5553
}
5654

5755
// NetworksWithin returns an iterator that can be used to traverse all networks
@@ -64,17 +62,49 @@ func (r *Reader) Networks(options ...NetworksOption) *Networks {
6462
//
6563
// If the provided prefix is contained within a network in the database, the
6664
// iterator will iterate over exactly one network, the containing network.
67-
func (r *Reader) NetworksWithin(prefix netip.Prefix, options ...NetworksOption) *Networks {
65+
func (r *Reader) NetworksWithin(prefix netip.Prefix, options ...NetworksOption) iter.Seq[Result] {
66+
n := r.networksWithin(prefix, options...)
67+
return func(yield func(Result) bool) {
68+
for n.next() {
69+
if n.err != nil {
70+
yield(Result{err: n.err})
71+
return
72+
}
73+
74+
ip := n.lastNode.ip
75+
if isInIPv4Subtree(ip) {
76+
ip = v6ToV4(ip)
77+
}
78+
79+
offset, err := r.resolveDataPointer(n.lastNode.pointer)
80+
ok := yield(Result{
81+
decoder: r.decoder,
82+
ip: ip,
83+
offset: uint(offset),
84+
prefixLen: uint8(n.lastNode.bit),
85+
err: err,
86+
})
87+
if !ok {
88+
return
89+
}
90+
}
91+
if n.err != nil {
92+
yield(Result{err: n.err})
93+
}
94+
}
95+
}
96+
97+
func (r *Reader) networksWithin(prefix netip.Prefix, options ...NetworksOption) *networks {
6898
if r.Metadata.IPVersion == 4 && prefix.Addr().Is6() {
69-
return &Networks{
99+
return &networks{
70100
err: fmt.Errorf(
71101
"error getting networks with '%s': you attempted to use an IPv6 network in an IPv4-only database",
72102
prefix,
73103
),
74104
}
75105
}
76106

77-
networks := &Networks{reader: r}
107+
networks := &networks{reader: r}
78108
for _, option := range options {
79109
option(networks)
80110
}
@@ -105,10 +135,10 @@ func (r *Reader) NetworksWithin(prefix netip.Prefix, options ...NetworksOption)
105135
return networks
106136
}
107137

108-
// Next prepares the next network for reading with the Network method. It
138+
// next prepares the next network for reading with the Network method. It
109139
// returns true if there is another network to be processed and false if there
110140
// are no more networks or if there is an error.
111-
func (n *Networks) Next() bool {
141+
func (n *networks) next() bool {
112142
if n.err != nil {
113143
return false
114144
}
@@ -160,32 +190,6 @@ func (n *Networks) Next() bool {
160190
return false
161191
}
162192

163-
// Network returns the current network or an error if there is a problem
164-
// decoding the data for the network. It takes a pointer to a result value to
165-
// decode the network's data into.
166-
func (n *Networks) Network(result any) (netip.Prefix, error) {
167-
if n.err != nil {
168-
return netip.Prefix{}, n.err
169-
}
170-
if err := n.reader.retrieveData(n.lastNode.pointer, result); err != nil {
171-
return netip.Prefix{}, err
172-
}
173-
174-
ip := n.lastNode.ip
175-
prefixLength := int(n.lastNode.bit)
176-
if isInIPv4Subtree(ip) {
177-
ip = v6ToV4(ip)
178-
prefixLength -= 96
179-
}
180-
181-
return netip.PrefixFrom(ip, prefixLength), nil
182-
}
183-
184-
// Err returns an error, if any, that was encountered during iteration.
185-
func (n *Networks) Err() error {
186-
return n.err
187-
}
188-
189193
var ipv4SubtreeBoundary = netip.MustParseAddr("::255.255.255.255").Next()
190194

191195
// isInIPv4Subtree returns true if the IP is in the database's IPv4 subtree.

traverse_test.go

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,18 @@ func TestNetworks(t *testing.T) {
2020
reader, err := Open(fileName)
2121
require.NoError(t, err, "unexpected error while opening database: %v", err)
2222

23-
n := reader.Networks()
24-
for n.Next() {
23+
for result := range reader.Networks() {
2524
record := struct {
2625
IP string `maxminddb:"ip"`
2726
}{}
28-
network, err := n.Network(&record)
27+
err := result.Decode(&record)
2928
require.NoError(t, err)
29+
30+
network := result.Network()
3031
assert.Equal(t, record.IP, network.Addr().String(),
3132
"expected %s got %s", record.IP, network.Addr().String(),
3233
)
3334
}
34-
require.NoError(t, n.Err())
3535
require.NoError(t, reader.Close())
3636
}
3737
}
@@ -41,13 +41,14 @@ func TestNetworksWithInvalidSearchTree(t *testing.T) {
4141
reader, err := Open(testFile("MaxMind-DB-test-broken-search-tree-24.mmdb"))
4242
require.NoError(t, err, "unexpected error while opening database: %v", err)
4343

44-
n := reader.Networks()
45-
for n.Next() {
44+
for result := range reader.Networks() {
4645
var record any
47-
_, err := n.Network(&record)
48-
require.NoError(t, err)
46+
err = result.Decode(&record)
47+
if err != nil {
48+
break
49+
}
4950
}
50-
require.EqualError(t, n.Err(), "invalid search tree at 128.128.128.128/32")
51+
require.EqualError(t, err, "invalid search tree at 128.128.128.128/32")
5152

5253
require.NoError(t, reader.Close())
5354
}
@@ -285,20 +286,18 @@ func TestNetworksWithin(t *testing.T) {
285286
require.NoError(t, err)
286287

287288
require.NoError(t, err)
288-
n := reader.NetworksWithin(network, v.Options...)
289289
var innerIPs []string
290290

291-
for n.Next() {
291+
for result := range reader.NetworksWithin(network, v.Options...) {
292292
record := struct {
293293
IP string `maxminddb:"ip"`
294294
}{}
295-
network, err := n.Network(&record)
295+
err := result.Decode(&record)
296296
require.NoError(t, err)
297-
innerIPs = append(innerIPs, network.String())
297+
innerIPs = append(innerIPs, result.Network().String())
298298
}
299299

300300
assert.Equal(t, v.Expected, innerIPs)
301-
require.NoError(t, n.Err())
302301

303302
require.NoError(t, reader.Close())
304303
})
@@ -326,20 +325,18 @@ func TestGeoIPNetworksWithin(t *testing.T) {
326325

327326
prefix, err := netip.ParsePrefix(v.Network)
328327
require.NoError(t, err)
329-
n := reader.NetworksWithin(prefix)
330328
var innerIPs []string
331329

332-
for n.Next() {
330+
for result := range reader.NetworksWithin(prefix) {
333331
record := struct {
334332
IP string `maxminddb:"ip"`
335333
}{}
336-
network, err := n.Network(&record)
334+
err := result.Decode(&record)
337335
require.NoError(t, err)
338-
innerIPs = append(innerIPs, network.String())
336+
innerIPs = append(innerIPs, result.Network().String())
339337
}
340338

341339
assert.Equal(t, v.Expected, innerIPs)
342-
require.NoError(t, n.Err())
343340

344341
require.NoError(t, reader.Close())
345342
}

verifier.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,11 @@ func (v *verifier) verifyDatabase() error {
102102
func (v *verifier) verifySearchTree() (map[uint]bool, error) {
103103
offsets := make(map[uint]bool)
104104

105-
it := v.reader.Networks()
106-
for it.Next() {
107-
offset, err := v.reader.resolveDataPointer(it.lastNode.pointer)
108-
if err != nil {
105+
for result := range v.reader.Networks() {
106+
if err := result.Err(); err != nil {
109107
return nil, err
110108
}
111-
offsets[uint(offset)] = true
112-
}
113-
if err := it.Err(); err != nil {
114-
return nil, err
109+
offsets[result.offset] = true
115110
}
116111
return offsets, nil
117112
}

0 commit comments

Comments
 (0)