1use crate::error::{ErrorCode, ParallaxError, Remediation, Result};
7use serde::{Deserialize, Serialize};
8
9pub const PARALLAX_VERSION: &str = env!("CARGO_PKG_VERSION");
11
12pub const PIR_SCHEMA_VERSION: u32 = 1;
14
15pub const PROTOCOL_VERSION: u32 = 1;
17
18pub const SNAPSHOT_FORMAT_VERSION: u32 = 1;
20
21pub const ADAPTER_INTERFACE_VERSION: u32 = 1;
23
24pub const UES_FORMAT_VERSION: u32 = 1;
27
28pub const PCIR_SCHEMA_VERSION: u32 = 1;
31
32pub const PUIR_SCHEMA_VERSION: u32 = 1;
35
36pub const MIRROR_LINK_FORMAT_VERSION: u32 = 1;
38
39#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
41pub struct ComponentVersions {
42 pub parallax: String,
44 pub pir_schema: u32,
46 pub protocol: u32,
48 pub snapshot: u32,
50 pub adapter_interface: u32,
52 pub ues_format: u32,
54 pub pcir_schema: u32,
56 pub puir_schema: u32,
58 pub mirror_link_format: u32,
60}
61
62impl ComponentVersions {
63 pub fn current() -> Self {
65 Self {
66 parallax: PARALLAX_VERSION.to_string(),
67 pir_schema: PIR_SCHEMA_VERSION,
68 protocol: PROTOCOL_VERSION,
69 snapshot: SNAPSHOT_FORMAT_VERSION,
70 adapter_interface: ADAPTER_INTERFACE_VERSION,
71 ues_format: UES_FORMAT_VERSION,
72 pcir_schema: PCIR_SCHEMA_VERSION,
73 puir_schema: PUIR_SCHEMA_VERSION,
74 mirror_link_format: MIRROR_LINK_FORMAT_VERSION,
75 }
76 }
77
78 pub fn format_human(&self) -> String {
80 format!(
81 "parallax {parallax}\n\
82 pir_schema {pir}\n\
83 protocol {proto}\n\
84 snapshot {snap}\n\
85 adapter_interface {iface}\n\
86 ues_format {ues}\n\
87 pcir_schema {pcir}\n\
88 puir_schema {puir}\n\
89 mirror_link_format {mirror}\n",
90 parallax = self.parallax,
91 pir = self.pir_schema,
92 proto = self.protocol,
93 snap = self.snapshot,
94 iface = self.adapter_interface,
95 ues = self.ues_format,
96 pcir = self.pcir_schema,
97 puir = self.puir_schema,
98 mirror = self.mirror_link_format,
99 )
100 }
101}
102
103pub fn check_pir_schema(version: u32) -> Result<()> {
105 if version == 0 || version > PIR_SCHEMA_VERSION {
106 return Err(ParallaxError::new(
107 ErrorCode::InvalidSnapshot,
108 format!(
109 "unsupported PIR schema version {version} (supported: 1..={PIR_SCHEMA_VERSION})"
110 ),
111 )
112 .with_source("parallax-core")
113 .with_operation("check_pir_schema")
114 .context("got", version.to_string())
115 .context("expected_max", PIR_SCHEMA_VERSION.to_string())
116 .remediate(Remediation::new(
117 "Upgrade Parallax, or re-export the document with a supported PIR schema",
118 )));
119 }
120 Ok(())
121}
122
123pub fn check_snapshot_format(version: u32) -> Result<()> {
125 if version != SNAPSHOT_FORMAT_VERSION {
126 return Err(ParallaxError::new(
127 ErrorCode::InvalidSnapshot,
128 format!(
129 "unsupported snapshot format version {version} (expected {SNAPSHOT_FORMAT_VERSION})"
130 ),
131 )
132 .with_source("parallax-core")
133 .with_operation("check_snapshot_format")
134 .context("got", version.to_string())
135 .context("expected", SNAPSHOT_FORMAT_VERSION.to_string())
136 .remediate(Remediation::new(
137 "Upgrade Parallax to read this snapshot, or recreate it with the current format",
138 )));
139 }
140 Ok(())
141}
142
143pub fn check_protocol(version: u32) -> Result<()> {
145 if version != PROTOCOL_VERSION {
146 return Err(ParallaxError::new(
147 ErrorCode::ProtocolViolation,
148 format!("protocol version mismatch: got {version}, expected {PROTOCOL_VERSION}"),
149 )
150 .with_source("parallax-core")
151 .with_operation("check_protocol")
152 .context("got", version.to_string())
153 .context("expected", PROTOCOL_VERSION.to_string())
154 .remediate(Remediation::new(
155 "Ensure host and worker were built from the same Parallax release",
156 )));
157 }
158 Ok(())
159}
160
161pub fn check_adapter_interface(version: u32) -> Result<()> {
163 if version == 0 || version > ADAPTER_INTERFACE_VERSION {
164 return Err(ParallaxError::new(
165 ErrorCode::CapabilityViolation,
166 format!(
167 "unsupported adapter interface version {version} (supported: 1..={ADAPTER_INTERFACE_VERSION})"
168 ),
169 )
170 .with_source("parallax-core")
171 .with_operation("check_adapter_interface")
172 .context("got", version.to_string())
173 .context("expected_max", ADAPTER_INTERFACE_VERSION.to_string())
174 .remediate(Remediation::new(
175 "Upgrade the Parallax host or use an adapter built for this interface version",
176 )));
177 }
178 Ok(())
179}
180
181pub fn check_ues_format(version: u32) -> Result<()> {
183 if version == 0 || version > UES_FORMAT_VERSION {
184 return Err(ParallaxError::new(
185 ErrorCode::InvalidSnapshot,
186 format!(
187 "unsupported UES format version {version} (supported: 1..={UES_FORMAT_VERSION})"
188 ),
189 )
190 .with_source("parallax-core")
191 .with_operation("check_ues_format")
192 .context("got", version.to_string())
193 .context("expected_max", UES_FORMAT_VERSION.to_string())
194 .remediate(Remediation::new(
195 "Upgrade Parallax, or recapture the continuation with a supported UES format",
196 )));
197 }
198 Ok(())
199}
200
201pub fn check_pcir_schema(version: u32) -> Result<()> {
203 if version == 0 || version > PCIR_SCHEMA_VERSION {
204 return Err(ParallaxError::new(
205 ErrorCode::InvalidSnapshot,
206 format!(
207 "unsupported PCIR schema version {version} (supported: 1..={PCIR_SCHEMA_VERSION})"
208 ),
209 )
210 .with_source("parallax-core")
211 .with_operation("check_pcir_schema")
212 .context("got", version.to_string())
213 .context("expected_max", PCIR_SCHEMA_VERSION.to_string())
214 .remediate(Remediation::new(
215 "Upgrade Parallax, or re-export the continuation with a supported PCIR schema",
216 )));
217 }
218 Ok(())
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn current_versions_are_positive() {
227 let v = ComponentVersions::current();
228 assert_eq!(v.parallax, PARALLAX_VERSION);
229 assert!(v.pir_schema >= 1);
230 assert!(v.protocol >= 1);
231 assert!(v.snapshot >= 1);
232 assert!(v.adapter_interface >= 1);
233 assert!(v.ues_format >= 1);
234 assert!(v.pcir_schema >= 1);
235 assert!(v.puir_schema >= 1);
236 assert!(v.mirror_link_format >= 1);
237 }
238
239 #[test]
240 fn rejects_future_ues_and_pcir() {
241 assert_eq!(
242 check_ues_format(UES_FORMAT_VERSION + 1).unwrap_err().code,
243 ErrorCode::InvalidSnapshot
244 );
245 assert_eq!(
246 check_pcir_schema(PCIR_SCHEMA_VERSION + 1).unwrap_err().code,
247 ErrorCode::InvalidSnapshot
248 );
249 check_ues_format(UES_FORMAT_VERSION).unwrap();
250 check_pcir_schema(PCIR_SCHEMA_VERSION).unwrap();
251 }
252
253 #[test]
254 fn rejects_future_pir_schema() {
255 let err = check_pir_schema(PIR_SCHEMA_VERSION + 1).unwrap_err();
256 assert_eq!(err.code, ErrorCode::InvalidSnapshot);
257 assert!(err.message.contains("unsupported PIR schema"));
258 }
259
260 #[test]
261 fn rejects_zero_pir_schema() {
262 assert!(check_pir_schema(0).is_err());
263 }
264
265 #[test]
266 fn accepts_current_pir_schema() {
267 check_pir_schema(PIR_SCHEMA_VERSION).unwrap();
268 }
269
270 #[test]
271 fn rejects_future_snapshot_format() {
272 let err = check_snapshot_format(SNAPSHOT_FORMAT_VERSION + 99).unwrap_err();
273 assert_eq!(err.code, ErrorCode::InvalidSnapshot);
274 }
275
276 #[test]
277 fn rejects_protocol_mismatch() {
278 let err = check_protocol(PROTOCOL_VERSION + 1).unwrap_err();
279 assert_eq!(err.code, ErrorCode::ProtocolViolation);
280 }
281
282 #[test]
283 fn rejects_future_adapter_interface() {
284 let err = check_adapter_interface(ADAPTER_INTERFACE_VERSION + 1).unwrap_err();
285 assert_eq!(err.code, ErrorCode::CapabilityViolation);
286 }
287
288 #[test]
289 fn component_versions_json_round_trip() {
290 let v = ComponentVersions::current();
291 let s = serde_json::to_string(&v).unwrap();
292 let back: ComponentVersions = serde_json::from_str(&s).unwrap();
293 assert_eq!(v, back);
294 assert!(s.contains("adapter_interface"));
295 assert!(s.contains("pir_schema"));
296 assert!(s.contains("ues_format"));
297 assert!(s.contains("pcir_schema"));
298 assert!(s.contains("puir_schema"));
299 }
300}