use serde::{Deserialize, Serialize};
use super::CodecId;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound(serialize = "P: Serialize", deserialize = "P: Deserialize<'de>"))]
pub struct CompressedSearchPath<P> {
path: P,
codec_id: CodecId,
#[serde(skip_serializing_if = "Option::is_none")]
produced_at: Option<chrono::DateTime<chrono::Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
provenance_label: Option<String>,
approximate_allowed: bool,
}
impl<P> CompressedSearchPath<P>
where
P: Send + Sync,
{
pub fn new(path: P, codec_id: CodecId) -> Self {
Self {
path,
codec_id,
produced_at: None,
provenance_label: None,
approximate_allowed: true,
}
}
pub fn produced_at(mut self, ts: chrono::DateTime<chrono::Utc>) -> Self {
self.produced_at = Some(ts);
self
}
pub fn provenance_label(mut self, label: impl Into<String>) -> Self {
self.provenance_label = Some(label.into());
self
}
pub fn require_exact(mut self) -> Self {
self.approximate_allowed = false;
self
}
pub fn codec_id(&self) -> CodecId {
self.codec_id
}
pub fn path(&self) -> &P {
&self.path
}
pub fn into_path(self) -> P {
self.path
}
pub fn approximate_allowed(&self) -> bool {
self.approximate_allowed
}
pub fn requires_exact_fallback(&self) -> bool {
self.codec_id.requires_exact_fallback() || !self.approximate_allowed
}
pub fn map_path<Q, F>(self, f: F) -> CompressedSearchPath<Q>
where
F: FnOnce(P) -> Q,
{
CompressedSearchPath {
path: f(self.path),
codec_id: self.codec_id,
produced_at: self.produced_at,
provenance_label: self.provenance_label,
approximate_allowed: self.approximate_allowed,
}
}
}
impl<P> std::fmt::Display for CompressedSearchPath<P>
where
P: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"CompressedSearchPath(codec={}, approx={}, path={:?})",
self.codec_id, self.approximate_allowed, self.path
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compressed_search_path_build() {
let path = vec!["ns1", "ns2"];
let csp = CompressedSearchPath::new(path, CodecId::TurboQuant)
.require_exact()
.provenance_label("test:v1");
assert_eq!(csp.codec_id(), CodecId::TurboQuant);
assert!(!csp.approximate_allowed());
assert!(csp.requires_exact_fallback());
}
#[test]
fn codec_id_requires_exact_fallback() {
assert!(CodecId::TurboQuant.requires_exact_fallback());
assert!(CodecId::FibQuant.requires_exact_fallback());
assert!(!CodecId::Uncompressed.requires_exact_fallback());
}
#[test]
fn map_path_transforms_inner() {
let csp: CompressedSearchPath<Vec<&str>> =
CompressedSearchPath::new(vec!["a", "b"], CodecId::FibQuant);
let mapped: CompressedSearchPath<Vec<String>> =
csp.map_path(|v| v.into_iter().map(String::from).collect());
assert_eq!(mapped.into_path(), vec!["a", "b"]);
}
}