Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breaking API improvements #94

Merged
merged 4 commits into from
Mar 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
# Change Log #

## 0.26.0

* **BREAKING CHANGE:** The `lookup` and `lookup_prefix` methods now return
`Ok(None)` or `Ok((None, prefix_len))` respectively when an IP address is
valid but not found in the database (or has no associated data record),
instead of returning an `Err(MaxMindDbError::AddressNotFoundError)`.
Code previously matching on `AddressNotFoundError` must be updated to
handle the `Ok(None)` / `Ok((None, prefix_len))` variants.
* **BREAKING CHANGE:** The `MaxMindDBError` enum has been renamed
`MaxMindDbError` and variants have been renamed and refactored. For
example, `IoError` is now `Io`, `InvalidDatabaseError` is now
`InvalidDatabase`, `DecodingError` is now `Decoding`,
`InvalidNetworkError` is now `InvalidNetwork`. The `MapError` variant has
been replaced by `Mmap` (under the `mmap` feature flag). Code explicitly
matching on the old variant names must be updated.
* **BREAKING CHANGE:** `MaxMindDbError` no longer implements `PartialEq`.
This is because underlying error types like `std::io::Error` (now
wrapped by the `Io` and `Mmap` variants) do not implement `PartialEq`.
Code comparing errors directly using `==` or `assert_eq!` must be
updated, typically by using `matches!` or by matching on the error
kind and potentially its contents.
* Refactored `MaxMindDbError` handling using the `thiserror` crate.
Variants like `Io`, `Mmap`, and `InvalidNetwork` now directly wrap
the underlying error types (`std::io::Error`, `ipnetwork::IpNetworkError`).
* Errors wrapping underlying types (`Io`, `Mmap`, `InvalidNetwork`) now
correctly implement `std::error::Error::source()`, allowing inspection
of the original cause.
* The `Display` implementation for `MaxMindDbError` has been refined to
generally show only the specific error details, often including the
message from the source error, rather than prefixing with the variant
name.
* `lookup_prefix` now returns the prefix length of the entry even when the
value is not found.
* Fixed an internal bounds checking error when resolving data pointers.
The previous logic could cause a panic on a corrupt database.


## 0.25.0 - 2025-02-16

* Serde will now skip serialization of the GeoIP2 struct fields
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ serde = { version = "1.0", features = ["derive"] }
memchr = "2.4"
memmap2 = { version = "0.9.0", optional = true }
simdutf8 = { version = "0.1.5", optional = true }
thiserror = "2.0"

[dev-dependencies]
env_logger = "0.11"
Expand Down
2 changes: 1 addition & 1 deletion examples/lookup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn main() -> Result<(), String> {
.ok_or("Second argument must be the IP address, like 128.101.101.101")?
.parse()
.unwrap();
let city: geoip2::City = reader.lookup(ip).unwrap();
let city: Option<geoip2::City> = reader.lookup(ip).unwrap();
println!("{city:#?}");
Ok(())
}
33 changes: 16 additions & 17 deletions src/maxminddb/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ use serde::forward_to_deserialize_any;
use serde::serde_if_integer128;
use std::convert::TryInto;

use super::MaxMindDBError;
use super::MaxMindDBError::DecodingError;
use super::MaxMindDbError;

fn to_usize(base: u8, bytes: &[u8]) -> usize {
bytes
Expand Down Expand Up @@ -134,7 +133,7 @@ impl<'de> Decoder<'de> {
14 => Value::Bool(self.decode_bool(size)?),
15 => Value::F32(self.decode_float(size)?),
u => {
return Err(MaxMindDBError::InvalidDatabaseError(format!(
return Err(MaxMindDbError::InvalidDatabase(format!(
"Unknown data type: {u:?}"
)))
}
Expand All @@ -151,7 +150,7 @@ impl<'de> Decoder<'de> {
fn decode_bool(&mut self, size: usize) -> DecodeResult<bool> {
match size {
0 | 1 => Ok(size != 0),
s => Err(MaxMindDBError::InvalidDatabaseError(format!(
s => Err(MaxMindDbError::InvalidDatabase(format!(
"bool of size {s:?}"
))),
}
Expand All @@ -170,7 +169,7 @@ impl<'de> Decoder<'de> {
let value: [u8; 4] = self.buf[self.current_ptr..new_offset]
.try_into()
.map_err(|_| {
MaxMindDBError::InvalidDatabaseError(format!(
MaxMindDbError::InvalidDatabase(format!(
"float of size {:?}",
new_offset - self.current_ptr
))
Expand All @@ -185,7 +184,7 @@ impl<'de> Decoder<'de> {
let value: [u8; 8] = self.buf[self.current_ptr..new_offset]
.try_into()
.map_err(|_| {
MaxMindDBError::InvalidDatabaseError(format!(
MaxMindDbError::InvalidDatabase(format!(
"double of size {:?}",
new_offset - self.current_ptr
))
Expand All @@ -206,7 +205,7 @@ impl<'de> Decoder<'de> {
self.current_ptr = new_offset;
Ok(value)
}
s => Err(MaxMindDBError::InvalidDatabaseError(format!(
s => Err(MaxMindDbError::InvalidDatabase(format!(
"u64 of size {s:?}"
))),
}
Expand All @@ -227,7 +226,7 @@ impl<'de> Decoder<'de> {
self.current_ptr = new_offset;
Ok(value)
}
s => Err(MaxMindDBError::InvalidDatabaseError(format!(
s => Err(MaxMindDbError::InvalidDatabase(format!(
"u128 of size {s:?}"
))),
}
Expand All @@ -245,7 +244,7 @@ impl<'de> Decoder<'de> {
self.current_ptr = new_offset;
Ok(value)
}
s => Err(MaxMindDBError::InvalidDatabaseError(format!(
s => Err(MaxMindDbError::InvalidDatabase(format!(
"u32 of size {s:?}"
))),
}
Expand All @@ -262,7 +261,7 @@ impl<'de> Decoder<'de> {
self.current_ptr = new_offset;
Ok(value)
}
s => Err(MaxMindDBError::InvalidDatabaseError(format!(
s => Err(MaxMindDbError::InvalidDatabase(format!(
"u16 of size {s:?}"
))),
}
Expand All @@ -279,7 +278,7 @@ impl<'de> Decoder<'de> {
self.current_ptr = new_offset;
Ok(value)
}
s => Err(MaxMindDBError::InvalidDatabaseError(format!(
s => Err(MaxMindDbError::InvalidDatabase(format!(
"int32 of size {s:?}"
))),
}
Expand Down Expand Up @@ -338,17 +337,17 @@ impl<'de> Decoder<'de> {
self.current_ptr = new_offset;
match from_utf8(bytes) {
Ok(v) => Ok(v),
Err(_) => Err(MaxMindDBError::InvalidDatabaseError(
Err(_) => Err(MaxMindDbError::InvalidDatabase(
"error decoding string".to_owned(),
)),
}
}
}

pub type DecodeResult<T> = Result<T, MaxMindDBError>;
pub type DecodeResult<T> = Result<T, MaxMindDbError>;

impl<'de: 'a, 'a> de::Deserializer<'de> for &'a mut Decoder<'de> {
type Error = MaxMindDBError;
type Error = MaxMindDbError;

fn deserialize_any<V>(self, visitor: V) -> DecodeResult<V::Value>
where
Expand Down Expand Up @@ -383,7 +382,7 @@ struct ArrayAccess<'a, 'de: 'a> {
// `SeqAccess` is provided to the `Visitor` to give it the ability to iterate
// through elements of the sequence.
impl<'de> SeqAccess<'de> for ArrayAccess<'_, 'de> {
type Error = MaxMindDBError;
type Error = MaxMindDbError;

fn next_element_seed<T>(&mut self, seed: T) -> DecodeResult<Option<T::Value>>
where
Expand All @@ -408,7 +407,7 @@ struct MapAccessor<'a, 'de: 'a> {
// `MapAccess` is provided to the `Visitor` to give it the ability to iterate
// through entries of the map.
impl<'de> MapAccess<'de> for MapAccessor<'_, 'de> {
type Error = MaxMindDBError;
type Error = MaxMindDbError;

fn next_key_seed<K>(&mut self, seed: K) -> DecodeResult<Option<K::Value>>
where
Expand All @@ -430,7 +429,7 @@ impl<'de> MapAccess<'de> for MapAccessor<'_, 'de> {
{
// Check if there are no more entries.
if self.count == 0 {
return Err(DecodingError("no more entries".to_owned()));
return Err(MaxMindDbError::Decoding("no more entries".to_owned()));
}
self.count -= 1;

Expand Down
Loading
Loading