Skip to content
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
25 changes: 23 additions & 2 deletions .github/workflows/doc.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
name: Deploy Docs to GitHub Pages
name: Documentation

on:
push:
branches:
- main
pull_request:

jobs:
release:
doc-check:
name: Rustdoc
runs-on: ubuntu-latest

steps:
- name: Checkout Repository
uses: actions/checkout@v6

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Build documentation with warnings denied
run: cargo doc --all --no-deps
env:
RUSTDOCFLAGS: -D warnings

deploy:
name: GitHub Pages
runs-on: ubuntu-latest
needs: doc-check
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

steps:
- name: Checkout Repository
Expand All @@ -19,6 +38,8 @@ jobs:

- name: Build Documentation
run: cargo doc --all --no-deps
env:
RUSTDOCFLAGS: -D warnings

- name: Create index
run: echo '<meta http-equiv=refresh content=0;url=maxminddb/index.html>' > target/doc/index.html
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Change Log

## 0.29.0 - TBD

- Breaking: `Metadata::build_time()` now returns
`Result<SystemTime, MaxMindDbError>` instead of panicking on
unrepresentable timestamps, and databases whose `build_epoch` cannot be
represented as `SystemTime` are rejected as invalid during open and verify.
- Breaking: `Reader::metadata` is now private. Use `Reader::metadata()` to
access validated metadata by shared reference.
- Breaking: Opening a database now rejects unsupported major format versions,
unsupported IP versions, and zero-node search trees. Minor format versions
are still accepted for forward compatibility.
- Fixed: `within()` and `networks()` now handle IPv6 databases without an
IPv4 subtree without reading terminal data nodes as tree nodes or
panicking while formatting low IPv6 networks.
- Fixed: Skipping ignored or unknown fields now enforces data-section
bounds and the maximum nesting depth instead of accepting truncated
values or overflowing the stack on deeply nested corrupt data.
- Internal: Added focused benchmarks for network iteration and serde
unknown-field skipping, plus metadata build-time conversion, to guard
performance-sensitive fixes.
- Documentation: Fixed intra-doc links so docs build with warnings denied,
and added a CI check to keep them passing.

## 0.28.1 - 2026-04-26

- Fixed: Databases with an impossible declared search tree size are now
Expand Down
10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "maxminddb"
version = "0.28.1"
version = "0.29.0"
authors = [ "Gregory J. Oschwald <oschwald@gmail.com>" ]
description = "Library for reading MaxMind DB format used by GeoIP2 and GeoLite2"
readme = "README.md"
Expand Down Expand Up @@ -47,3 +47,11 @@ harness = false
[[bench]]
name = "serde_usage"
harness = false

[[bench]]
name = "within"
harness = false

[[bench]]
name = "metadata"
harness = false
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ Add this to your `Cargo.toml`:

```toml
[dependencies]
maxminddb = "0.28"
maxminddb = "0.29"
```

Enable optional features as needed:

```toml
[dependencies]
maxminddb = { version = "0.28", features = ["mmap"] }
maxminddb = { version = "0.29", features = ["mmap"] }
```

## Example
Expand Down Expand Up @@ -113,7 +113,7 @@ Enable in `Cargo.toml`:

```toml
[dependencies]
maxminddb = { version = "0.28", features = ["mmap"] }
maxminddb = { version = "0.29", features = ["mmap"] }
```

Note: `simdutf8` and `unsafe-str-decode` are mutually exclusive.
Expand Down
44 changes: 44 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
# Upgrading Guide

## 0.28 to 0.29

### Reader Metadata

`Reader::metadata` is now private. Use `Reader::metadata()` to access
validated metadata by shared reference.

**Before (0.28):**

```rust
let database_type = &reader.metadata.database_type;
```

**After (0.29):**

```rust
let database_type = &reader.metadata().database_type;
```

### Metadata Build Time

`Metadata::build_time()` now returns `Result<SystemTime, MaxMindDbError>`.
Databases whose `build_epoch` cannot be represented as `SystemTime` are
rejected as invalid when opened or verified.

**Before (0.28):**

```rust
let build_time = reader.metadata.build_time();
```

**After (0.29):**

```rust
let build_time = reader.metadata().build_time()?;
```

### Metadata Validation

Opening a database now rejects unsupported major format versions, unsupported
IP versions, zero-node search trees, and unrepresentable build epochs as
invalid databases. Minor format versions are still accepted for forward
compatibility.

## 0.26 to 0.27

This release includes significant API changes to improve ergonomics and enable
Expand Down
20 changes: 20 additions & 0 deletions benches/metadata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use std::hint::black_box;

use criterion::{criterion_group, criterion_main, Criterion};

const DB_FILE: &str = "GeoLite2-City.mmdb";

pub fn metadata_benchmark(c: &mut Criterion) {
let reader = maxminddb::Reader::open_readfile(DB_FILE).unwrap();

c.bench_function("metadata/build_time", |b| {
b.iter(|| black_box(reader.metadata().build_time().unwrap()))
});
}

criterion_group! {
name = benches;
config = Criterion::default().sample_size(20);
targets = metadata_benchmark
}
criterion_main!(benches);
16 changes: 16 additions & 0 deletions benches/serde_usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ use common::{generate_ipv4, open_reader};

const DB_FILE: &str = "GeoLite2-City.mmdb";

#[derive(serde::Deserialize)]
struct EmptyRecord {}

fn cache_lookups<'a, T>(ips: &[IpAddr], reader: &'a Reader<T>) -> Vec<LookupResult<'a, T>>
where
T: AsRef<[u8]>,
Expand Down Expand Up @@ -50,6 +53,16 @@ where
}
}

fn bench_decode_empty_record<T>(results: &[LookupResult<'_, T>])
where
T: AsRef<[u8]>,
{
for result in results {
let empty: Option<EmptyRecord> = result.decode().unwrap();
black_box(empty);
}
}

fn bench_decode_path_country_iso<T>(results: &[LookupResult<'_, T>])
where
T: AsRef<[u8]>,
Expand Down Expand Up @@ -91,6 +104,9 @@ pub fn serde_usage_benchmark(c: &mut Criterion) {
c.bench_function("serde_usage/decode_country_only", |b| {
b.iter(|| bench_decode_country_only(&cached_results))
});
c.bench_function("serde_usage/decode_empty_record", |b| {
b.iter(|| bench_decode_empty_record(&cached_results))
});
c.bench_function("serde_usage/decode_path_country_iso", |b| {
b.iter(|| bench_decode_path_country_iso(&cached_results))
});
Expand Down
61 changes: 61 additions & 0 deletions benches/within.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use criterion::{criterion_group, criterion_main, Criterion};
use maxminddb::{geoip2, Reader};
use std::hint::black_box;

fn bench_networks_city_test(c: &mut Criterion) {
let reader = Reader::open_readfile("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();

c.bench_function("within/networks_city_test", |b| {
b.iter(|| {
let mut count = 0usize;
for result in reader.networks(Default::default()).unwrap() {
let lookup = result.unwrap();
black_box(lookup.network().unwrap());
count += 1;
}
black_box(count);
});
});
}

fn bench_within_city_subnet(c: &mut Criterion) {
let reader = Reader::open_readfile("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();
let cidr = "81.2.69.0/24".parse().unwrap();

c.bench_function("within/city_subnet", |b| {
b.iter(|| {
let mut count = 0usize;
for result in reader.within(cidr, Default::default()).unwrap() {
let lookup = result.unwrap();
let city: Option<geoip2::City<'_>> = lookup.decode().unwrap();
black_box(city);
count += 1;
}
black_box(count);
});
});
}

fn bench_networks_mixed_test(c: &mut Criterion) {
let reader =
Reader::open_readfile("test-data/test-data/MaxMind-DB-test-mixed-24.mmdb").unwrap();

c.bench_function("within/networks_mixed_test", |b| {
b.iter(|| {
let mut count = 0usize;
for result in reader.networks(Default::default()).unwrap() {
let lookup = result.unwrap();
black_box(lookup.network().unwrap());
count += 1;
}
black_box(count);
});
});
}

criterion_group! {
name = benches;
config = Criterion::default().sample_size(20);
targets = bench_networks_city_test, bench_within_city_subnet, bench_networks_mixed_test
}
criterion_main!(benches);
Loading
Loading