use std::path::Path;
use std::time::{Duration, UNIX_EPOCH};
use ipnet::IpNet;
use maxminddb::{Reader, WithinOptions};
use crate::error::Error;
use crate::net::IpVersion;
use crate::record_size::RecordSize;
use crate::value::Value;
use crate::writer::Writer;
impl Writer {
pub fn load_from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
let data = std::fs::read(path)?;
Self::load(&data)
}
pub fn load(bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
let reader = Reader::from_source(bytes.as_ref()).map_err(load_err)?;
let meta = reader.metadata();
let ip_version = if meta.ip_version == 4 {
IpVersion::V4
} else {
IpVersion::V6
};
let epoch = UNIX_EPOCH + Duration::from_secs(meta.build_epoch);
let description: Vec<(&str, &str)> = meta
.description
.iter()
.map(|(lang, text)| (lang.as_str(), text.as_str()))
.collect();
let mut writer = Writer::builder(meta.database_type.clone())
.ip_version(ip_version)
.languages(meta.languages.clone())
.description(&description)
.record_size(record_size_from(meta.record_size)?)
.build_epoch(epoch)
.build();
let items = reader
.networks(WithinOptions::default())
.map_err(load_err)?;
for item in items {
let item = item.map_err(load_err)?;
let Some(value) = item.decode::<Value>().map_err(load_err)? else {
continue;
};
let network = item.network().map_err(load_err)?;
let net = IpNet::new(network.ip(), network.prefix())
.map_err(|e| Error::Load(format!("reader produced an invalid network: {e}")))?;
writer.insert_value(net, value)?;
}
Ok(writer)
}
}
#[allow(clippy::needless_pass_by_value)]
fn load_err(e: maxminddb::MaxMindDbError) -> Error {
Error::Load(e.to_string())
}
fn record_size_from(bits: u16) -> Result<RecordSize, Error> {
match bits {
24 => Ok(RecordSize::Bits24),
28 => Ok(RecordSize::Bits28),
32 => Ok(RecordSize::Bits32),
other => Err(Error::Load(format!("unsupported record size {other}"))),
}
}