1use std::fmt;
10
11#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
13pub struct SemanticVersion {
14 pub major: u32,
16 pub minor: u32,
18 pub patch: u32,
20}
21
22impl SemanticVersion {
23 pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
25 Self {
26 major,
27 minor,
28 patch,
29 }
30 }
31
32 pub fn parse(s: &str) -> Result<Self, String> {
34 let parts: Vec<&str> = s.split('.').collect();
35
36 if parts.len() != 3 {
37 return Err("Invalid version format, expected MAJOR.MINOR.PATCH".to_string());
38 }
39
40 let major = parts[0]
41 .parse::<u32>()
42 .map_err(|_| "Major version must be a number")?;
43 let minor = parts[1]
44 .parse::<u32>()
45 .map_err(|_| "Minor version must be a number")?;
46 let patch = parts[2]
47 .parse::<u32>()
48 .map_err(|_| "Patch version must be a number")?;
49
50 Ok(Self {
51 major,
52 minor,
53 patch,
54 })
55 }
56
57 pub fn is_compatible_with(&self, minimum: SemanticVersion) -> bool {
59 if self.major != minimum.major {
60 return self.major > minimum.major;
61 }
62 if self.minor != minimum.minor {
63 return self.minor > minimum.minor;
64 }
65 self.patch >= minimum.patch
66 }
67
68 pub fn bump_major(&mut self) {
70 self.major += 1;
71 self.minor = 0;
72 self.patch = 0;
73 }
74
75 pub fn bump_minor(&mut self) {
77 self.minor += 1;
78 self.patch = 0;
79 }
80
81 pub fn bump_patch(&mut self) {
83 self.patch += 1;
84 }
85}
86
87impl fmt::Display for SemanticVersion {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
90 }
91}
92
93#[derive(Debug, Clone)]
95pub struct ReleaseArtifact {
96 pub version: SemanticVersion,
98 pub target: String,
100 pub checksum: String,
102 pub reproducible: bool,
104}
105
106impl ReleaseArtifact {
107 pub fn new(
109 version: SemanticVersion,
110 target: String,
111 checksum: String,
112 reproducible: bool,
113 ) -> Self {
114 Self {
115 version,
116 target,
117 checksum,
118 reproducible,
119 }
120 }
121
122 pub fn filename(&self) -> String {
124 format!("sentri-{}-{}", self.version, self.target)
125 }
126
127 pub fn verify_checksum(&self, actual_checksum: &str) -> bool {
129 self.checksum.eq_ignore_ascii_case(actual_checksum)
130 }
131}
132
133impl fmt::Display for ReleaseArtifact {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 write!(
136 f,
137 "sentri {} ({}) [{}]",
138 self.version, self.target, self.checksum
139 )
140 }
141}
142
143#[derive(Debug, Clone)]
145pub struct ReproducibleBuildConfig {
146 pub lto: bool,
148 pub opt_level: u32,
150 pub strip: bool,
152 pub rust_version: String,
154}
155
156impl ReproducibleBuildConfig {
157 pub fn default_release() -> Self {
159 Self {
160 lto: true,
161 opt_level: 3,
162 strip: false, rust_version: "1.70.0".to_string(),
164 }
165 }
166
167 pub fn verify_environment(&self, current_rust_version: &str) -> Result<(), String> {
169 if current_rust_version != self.rust_version {
170 return Err(format!(
171 "Rust version mismatch: expected {}, got {}",
172 self.rust_version, current_rust_version
173 ));
174 }
175 Ok(())
176 }
177}
178
179#[derive(Debug, Clone, Copy, Eq, PartialEq)]
181pub enum Platform {
182 LinuxX86_64,
184 LinuxAarch64,
186 MacOSX86_64,
188 MacOSAarch64,
190 WindowsX86_64,
192}
193
194impl Platform {
195 pub fn target_triple(&self) -> &'static str {
197 match self {
198 Self::LinuxX86_64 => "x86_64-unknown-linux-gnu",
199 Self::LinuxAarch64 => "aarch64-unknown-linux-gnu",
200 Self::MacOSX86_64 => "x86_64-apple-darwin",
201 Self::MacOSAarch64 => "aarch64-apple-darwin",
202 Self::WindowsX86_64 => "x86_64-pc-windows-msvc",
203 }
204 }
205
206 pub fn artifact_suffix(&self) -> &'static str {
208 match self {
209 Self::LinuxX86_64 => "linux-x86_64",
210 Self::LinuxAarch64 => "linux-aarch64",
211 Self::MacOSX86_64 => "darwin-x86_64",
212 Self::MacOSAarch64 => "darwin-aarch64",
213 Self::WindowsX86_64 => "windows-x86_64",
214 }
215 }
216
217 pub fn all() -> &'static [Self] {
219 &[
220 Self::LinuxX86_64,
221 Self::LinuxAarch64,
222 Self::MacOSX86_64,
223 Self::MacOSAarch64,
224 Self::WindowsX86_64,
225 ]
226 }
227}
228
229impl fmt::Display for Platform {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 write!(f, "{}", self.artifact_suffix())
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_semver_parse() {
241 let v = SemanticVersion::parse("1.2.3").unwrap();
242 assert_eq!(v.major, 1);
243 assert_eq!(v.minor, 2);
244 assert_eq!(v.patch, 3);
245 }
246
247 #[test]
248 fn test_semver_parse_invalid() {
249 assert!(SemanticVersion::parse("1.2").is_err());
250 assert!(SemanticVersion::parse("1.2.a").is_err());
251 }
252
253 #[test]
254 fn test_semver_display() {
255 let v = SemanticVersion::new(0, 1, 0);
256 assert_eq!(v.to_string(), "0.1.0");
257 }
258
259 #[test]
260 fn test_semver_bump() {
261 let mut v = SemanticVersion::new(0, 1, 0);
262 v.bump_patch();
263 assert_eq!(v, SemanticVersion::new(0, 1, 1));
264
265 v.bump_minor();
266 assert_eq!(v, SemanticVersion::new(0, 2, 0));
267
268 v.bump_major();
269 assert_eq!(v, SemanticVersion::new(1, 0, 0));
270 }
271
272 #[test]
273 fn test_semver_compatibility() {
274 let v1 = SemanticVersion::new(1, 2, 3);
275 let v2 = SemanticVersion::new(1, 2, 0);
276 let v3 = SemanticVersion::new(0, 5, 0);
277
278 assert!(v1.is_compatible_with(v2)); assert!(!v2.is_compatible_with(v1)); assert!(!v3.is_compatible_with(v1)); }
282
283 #[test]
284 fn test_release_artifact_filename() {
285 let artifact = ReleaseArtifact::new(
286 SemanticVersion::new(0, 1, 0),
287 "linux-x86_64".to_string(),
288 "abc123".to_string(),
289 true,
290 );
291 assert_eq!(artifact.filename(), "sentri-0.1.0-linux-x86_64");
292 }
293
294 #[test]
295 fn test_release_artifact_verify_checksum() {
296 let artifact = ReleaseArtifact::new(
297 SemanticVersion::new(0, 1, 0),
298 "linux-x86_64".to_string(),
299 "ABC123".to_string(),
300 true,
301 );
302 assert!(artifact.verify_checksum("abc123")); assert!(!artifact.verify_checksum("xyz789"));
304 }
305
306 #[test]
307 fn test_platform_target_triples() {
308 assert_eq!(
309 Platform::LinuxX86_64.target_triple(),
310 "x86_64-unknown-linux-gnu"
311 );
312 assert_eq!(
313 Platform::MacOSAarch64.target_triple(),
314 "aarch64-apple-darwin"
315 );
316 assert_eq!(
317 Platform::WindowsX86_64.target_triple(),
318 "x86_64-pc-windows-msvc"
319 );
320 }
321
322 #[test]
323 fn test_platform_all() {
324 assert_eq!(Platform::all().len(), 5);
325 }
326}