scr_runtime_compression/
compressed_search_path.rs1use serde::{Deserialize, Serialize};
9
10use super::CodecId;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
23#[serde(bound(serialize = "P: Serialize", deserialize = "P: Deserialize<'de>"))]
24pub struct CompressedSearchPath<P> {
25 path: P,
27
28 codec_id: CodecId,
30
31 #[serde(skip_serializing_if = "Option::is_none")]
33 produced_at: Option<chrono::DateTime<chrono::Utc>>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
38 provenance_label: Option<String>,
39
40 approximate_allowed: bool,
43}
44
45impl<P> CompressedSearchPath<P>
46where
47 P: Send + Sync,
48{
49 pub fn new(path: P, codec_id: CodecId) -> Self {
51 Self {
52 path,
53 codec_id,
54 produced_at: None,
55 provenance_label: None,
56 approximate_allowed: true,
57 }
58 }
59
60 pub fn produced_at(mut self, ts: chrono::DateTime<chrono::Utc>) -> Self {
62 self.produced_at = Some(ts);
63 self
64 }
65
66 pub fn provenance_label(mut self, label: impl Into<String>) -> Self {
68 self.provenance_label = Some(label.into());
69 self
70 }
71
72 pub fn require_exact(mut self) -> Self {
74 self.approximate_allowed = false;
75 self
76 }
77
78 pub fn codec_id(&self) -> CodecId {
80 self.codec_id
81 }
82
83 pub fn path(&self) -> &P {
85 &self.path
86 }
87
88 pub fn into_path(self) -> P {
90 self.path
91 }
92
93 pub fn approximate_allowed(&self) -> bool {
95 self.approximate_allowed
96 }
97
98 pub fn requires_exact_fallback(&self) -> bool {
100 self.codec_id.requires_exact_fallback() || !self.approximate_allowed
101 }
102
103 pub fn map_path<Q, F>(self, f: F) -> CompressedSearchPath<Q>
108 where
109 F: FnOnce(P) -> Q,
110 {
111 CompressedSearchPath {
112 path: f(self.path),
113 codec_id: self.codec_id,
114 produced_at: self.produced_at,
115 provenance_label: self.provenance_label,
116 approximate_allowed: self.approximate_allowed,
117 }
118 }
119}
120
121impl<P> std::fmt::Display for CompressedSearchPath<P>
122where
123 P: std::fmt::Debug,
124{
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 write!(
127 f,
128 "CompressedSearchPath(codec={}, approx={}, path={:?})",
129 self.codec_id, self.approximate_allowed, self.path
130 )
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn compressed_search_path_build() {
140 let path = vec!["ns1", "ns2"];
141 let csp = CompressedSearchPath::new(path, CodecId::TurboQuant)
142 .require_exact()
143 .provenance_label("test:v1");
144
145 assert_eq!(csp.codec_id(), CodecId::TurboQuant);
146 assert!(!csp.approximate_allowed());
147 assert!(csp.requires_exact_fallback());
148 }
149
150 #[test]
151 fn codec_id_requires_exact_fallback() {
152 assert!(CodecId::TurboQuant.requires_exact_fallback());
153 assert!(CodecId::FibQuant.requires_exact_fallback());
154 assert!(!CodecId::Uncompressed.requires_exact_fallback());
155 }
156
157 #[test]
158 fn map_path_transforms_inner() {
159 let csp: CompressedSearchPath<Vec<&str>> =
160 CompressedSearchPath::new(vec!["a", "b"], CodecId::FibQuant);
161 let mapped: CompressedSearchPath<Vec<String>> =
162 csp.map_path(|v| v.into_iter().map(String::from).collect());
163 assert_eq!(mapped.into_path(), vec!["a", "b"]);
164 }
165}