1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use std::{net::IpAddr, ops::ControlFlow};
use crate::{BatchError, Error, Match, Query, ReaderSet, Target};
impl<B: AsRef<[u8]>> ReaderSet<B> {
/// Visit batches in query order, then source order, then each reader's order.
///
/// Borrowed results and exact-prefix failures follow [`crate::Reader::visit_batch`].
/// No result buffers or reordering; callback positions are query, then source.
///
/// # Errors
/// Returns the failed query position and preserves the original error.
pub fn visit_batch<'a, E: From<Error>>(
&'a self,
queries: &[Query],
mut emit: impl FnMut(usize, usize, Match<'a>) -> Result<(), E>,
) -> Result<(), BatchError<E>> {
for (query_index, &query) in queries.iter().enumerate() {
let result = match query {
Query::Ip(ip) => self.visit_ip(ip, |source, row| emit(query_index, source, row)),
Query::Asn(key) => {
self.visit_asn(key, |source, row| emit(query_index, source, row))
}
Query::Intersecting(target) => {
self.visit_intersecting(target, |source, row| emit(query_index, source, row))
}
};
result.map_err(|error| BatchError { query_index, error })?;
}
Ok(())
}
/// Visit IP matches until a successful `Break(value)`, preserving source order.
///
/// # Errors
/// Preserves reader/visitor failures separately from successful termination.
pub fn try_visit_ip<'a, T, E: From<Error>>(
&'a self,
address: IpAddr,
mut emit: impl FnMut(usize, Match<'a>) -> Result<ControlFlow<T>, E>,
) -> Result<ControlFlow<T>, E> {
for source in crate::coverage::candidates(self.router.mask(address)) {
let reader = self
.readers
.get(source)
.ok_or(Error::Invalid("routed reader position"))?;
if let ControlFlow::Break(value) =
reader.try_visit_ip(address, |row| emit(source, row))?
{
return Ok(ControlFlow::Break(value));
}
}
Ok(ControlFlow::Continue(()))
}
/// Visit exact-ASN matches with successful early stopping and source ordering.
///
/// # Errors
/// Preserves reader and visitor errors; zero is a miss.
pub fn try_visit_asn<'a, T, E: From<Error>>(
&'a self,
number: u32,
mut emit: impl FnMut(usize, Match<'a>) -> Result<ControlFlow<T>, E>,
) -> Result<ControlFlow<T>, E> {
for (source, reader) in self.readers.iter().enumerate() {
if let ControlFlow::Break(value) =
reader.try_visit_asn(number, |row| emit(source, row))?
{
return Ok(ControlFlow::Break(value));
}
}
Ok(ControlFlow::Continue(()))
}
/// Test IP membership, stopping at the first matching source/assertion.
///
/// # Errors
/// Returns a reader error encountered before the first match.
pub fn contains_ip(&self, address: IpAddr) -> Result<bool, Error> {
for source in crate::coverage::candidates(self.router.mask(address)) {
let reader = self
.readers
.get(source)
.ok_or(Error::Invalid("routed reader position"))?;
if reader.contains_ip(address)? {
return Ok(true);
}
}
Ok(false)
}
/// Test exact-ASN membership, stopping at the first match; zero is a miss.
///
/// # Errors
/// Returns a reader error encountered before the first match.
pub fn contains_asn(&self, number: u32) -> Result<bool, Error> {
for reader in &self.readers {
if reader.contains_asn(number)? {
return Ok(true);
}
}
Ok(false)
}
/// Visit inclusive intersections in source order, then each reader's order.
///
/// Each reader follows [`crate::Reader::visit_intersecting`]. All sources are
/// searched; point-query coverage masks cannot safely prune interval queries.
///
/// # Errors
/// Validates the target even for empty sets, then preserves reader/visitor errors.
pub fn visit_intersecting<'a, E: From<Error>>(
&'a self,
target: Target,
mut emit: impl FnMut(usize, Match<'a>) -> Result<(), E>,
) -> Result<(), E> {
target.entry(0)?;
for (source, reader) in self.readers.iter().enumerate() {
reader.visit_intersecting(target, |row| emit(source, row))?;
}
Ok(())
}
/// Visit intersections with successful early stopping, in source order.
///
/// # Errors
/// Validates the target before delivery, preserving reader/visitor errors.
pub fn try_visit_intersecting<'a, T, E: From<Error>>(
&'a self,
target: Target,
mut emit: impl FnMut(usize, Match<'a>) -> Result<ControlFlow<T>, E>,
) -> Result<ControlFlow<T>, E> {
target.entry(0)?;
for (source, reader) in self.readers.iter().enumerate() {
if let ControlFlow::Break(value) =
reader.try_visit_intersecting(target, |row| emit(source, row))?
{
return Ok(ControlFlow::Break(value));
}
}
Ok(ControlFlow::Continue(()))
}
/// Stop a batch successfully, returning the input query position and value.
///
/// # Errors
/// Uses [`Self::visit_batch`]'s exact-prefix error contract; later queries do not run.
pub fn try_visit_batch<'a, T, E: From<Error>>(
&'a self,
queries: &[Query],
mut emit: impl FnMut(usize, usize, Match<'a>) -> Result<ControlFlow<T>, E>,
) -> Result<ControlFlow<(usize, T)>, BatchError<E>> {
for (query_index, &query) in queries.iter().enumerate() {
let result = match query {
Query::Ip(ip) => {
self.try_visit_ip(ip, |source, row| emit(query_index, source, row))
}
Query::Asn(key) => {
self.try_visit_asn(key, |source, row| emit(query_index, source, row))
}
Query::Intersecting(target) => self
.try_visit_intersecting(target, |source, row| emit(query_index, source, row)),
}
.map_err(|error| BatchError { query_index, error })?;
if let ControlFlow::Break(value) = result {
return Ok(ControlFlow::Break((query_index, value)));
}
}
Ok(ControlFlow::Continue(()))
}
}