1use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
12pub enum DependencyStatus {
13 Installed {
15 version: String,
17 },
18 ToInstall,
20 ToUpgrade {
22 current: String,
24 required: String,
26 },
27 Conflict {
29 reason: String,
31 },
32 Missing,
34}
35
36impl DependencyStatus {
37 #[must_use]
48 pub const fn is_installed(&self) -> bool {
49 matches!(self, Self::Installed { .. } | Self::ToUpgrade { .. })
50 }
51
52 #[must_use]
63 pub const fn needs_action(&self) -> bool {
64 matches!(self, Self::ToInstall | Self::ToUpgrade { .. })
65 }
66
67 #[must_use]
78 pub const fn is_conflict(&self) -> bool {
79 matches!(self, Self::Conflict { .. })
80 }
81
82 #[must_use]
93 pub const fn priority(&self) -> u8 {
94 match self {
95 Self::Conflict { .. } => 0,
96 Self::Missing => 1,
97 Self::ToInstall => 2,
98 Self::ToUpgrade { .. } => 3,
99 Self::Installed { .. } => 4,
100 }
101 }
102}
103
104impl std::fmt::Display for DependencyStatus {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 match self {
107 Self::Installed { version } => write!(f, "Installed ({version})"),
108 Self::ToInstall => write!(f, "To Install"),
109 Self::ToUpgrade { current, required } => {
110 write!(f, "To Upgrade ({current} -> {required})")
111 }
112 Self::Conflict { reason } => write!(f, "Conflict: {reason}"),
113 Self::Missing => write!(f, "Missing"),
114 }
115 }
116}
117
118#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
123pub enum DependencySource {
124 Official {
126 repo: String,
128 },
129 Aur,
131 Local,
133}
134
135impl std::fmt::Display for DependencySource {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 match self {
138 Self::Official { repo } => write!(f, "Official ({repo})"),
139 Self::Aur => write!(f, "AUR"),
140 Self::Local => write!(f, "Local"),
141 }
142 }
143}
144
145#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
150pub enum PackageSource {
151 Official {
153 repo: String,
155 arch: String,
157 },
158 Aur,
160}
161
162impl std::fmt::Display for PackageSource {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 match self {
165 Self::Official { repo, arch } => write!(f, "Official ({repo}/{arch})"),
166 Self::Aur => write!(f, "AUR"),
167 }
168 }
169}
170
171#[derive(Clone, Debug, Serialize, Deserialize)]
178pub struct Dependency {
179 pub name: String,
181 pub version_req: String,
183 pub status: DependencyStatus,
185 pub source: DependencySource,
187 pub required_by: Vec<String>,
189 pub depends_on: Vec<String>,
191 pub is_core: bool,
193 pub is_system: bool,
195}
196
197#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
202pub struct PackageRef {
203 pub name: String,
205 pub version: String,
207 pub source: PackageSource,
209}
210
211#[derive(Clone, Debug, PartialEq, Eq, Default)]
215pub struct DependencySpec {
216 pub name: String,
218 pub version_req: String,
220}
221
222impl DependencySpec {
223 #[must_use]
234 pub fn new(name: impl Into<String>) -> Self {
235 Self {
236 name: name.into(),
237 version_req: String::new(),
238 }
239 }
240
241 #[must_use]
253 pub fn with_version(name: impl Into<String>, version_req: impl Into<String>) -> Self {
254 Self {
255 name: name.into(),
256 version_req: version_req.into(),
257 }
258 }
259
260 #[must_use]
271 pub const fn has_version_req(&self) -> bool {
272 !self.version_req.is_empty()
273 }
274}
275
276impl std::fmt::Display for DependencySpec {
277 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278 if self.version_req.is_empty() {
279 write!(f, "{}", self.name)
280 } else {
281 write!(f, "{}{}", self.name, self.version_req)
282 }
283 }
284}
285
286#[derive(Clone, Debug, Default)]
291pub struct ReverseDependencySummary {
292 pub package: String,
294 pub direct_dependents: usize,
296 pub transitive_dependents: usize,
298 pub total_dependents: usize,
300}
301
302#[derive(Clone, Debug, Default, Serialize, Deserialize)]
307pub struct SrcinfoData {
308 pub pkgbase: String,
310 pub pkgname: String,
312 pub pkgver: String,
314 pub pkgrel: String,
316 pub depends: Vec<String>,
318 pub makedepends: Vec<String>,
320 pub checkdepends: Vec<String>,
322 pub optdepends: Vec<String>,
324 pub conflicts: Vec<String>,
326 pub provides: Vec<String>,
328 pub replaces: Vec<String>,
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn dependency_status_priority_ordering() {
338 let conflict = DependencyStatus::Conflict {
339 reason: "test".to_string(),
340 };
341 let missing = DependencyStatus::Missing;
342 let to_install = DependencyStatus::ToInstall;
343 let to_upgrade = DependencyStatus::ToUpgrade {
344 current: "1.0".to_string(),
345 required: "2.0".to_string(),
346 };
347 let installed = DependencyStatus::Installed {
348 version: "1.0".to_string(),
349 };
350
351 assert!(conflict.priority() < missing.priority());
352 assert!(missing.priority() < to_install.priority());
353 assert!(to_install.priority() < to_upgrade.priority());
354 assert!(to_upgrade.priority() < installed.priority());
355 }
356
357 #[test]
358 fn dependency_status_helper_methods() {
359 let installed = DependencyStatus::Installed {
360 version: "1.0".to_string(),
361 };
362 assert!(installed.is_installed());
363 assert!(!installed.needs_action());
364 assert!(!installed.is_conflict());
365
366 let to_install = DependencyStatus::ToInstall;
367 assert!(!to_install.is_installed());
368 assert!(to_install.needs_action());
369 assert!(!to_install.is_conflict());
370
371 let conflict = DependencyStatus::Conflict {
372 reason: "test".to_string(),
373 };
374 assert!(!conflict.is_installed());
375 assert!(!conflict.needs_action());
376 assert!(conflict.is_conflict());
377 }
378
379 #[test]
380 fn dependency_spec_constructors() {
381 let spec1 = DependencySpec::new("glibc");
382 assert_eq!(spec1.name, "glibc");
383 assert!(spec1.version_req.is_empty());
384 assert!(!spec1.has_version_req());
385
386 let spec2 = DependencySpec::with_version("python", ">=3.12");
387 assert_eq!(spec2.name, "python");
388 assert_eq!(spec2.version_req, ">=3.12");
389 assert!(spec2.has_version_req());
390 }
391
392 #[test]
393 fn dependency_spec_display() {
394 let spec1 = DependencySpec::new("glibc");
395 assert_eq!(spec1.to_string(), "glibc");
396
397 let spec2 = DependencySpec::with_version("python", ">=3.12");
398 assert_eq!(spec2.to_string(), "python>=3.12");
399 }
400
401 #[test]
402 fn dependency_status_display() {
403 let installed = DependencyStatus::Installed {
404 version: "1.0".to_string(),
405 };
406 assert!(installed.to_string().contains("Installed"));
407 assert!(installed.to_string().contains("1.0"));
408
409 let to_install = DependencyStatus::ToInstall;
410 assert_eq!(to_install.to_string(), "To Install");
411
412 let to_upgrade = DependencyStatus::ToUpgrade {
413 current: "1.0".to_string(),
414 required: "2.0".to_string(),
415 };
416 assert!(to_upgrade.to_string().contains("To Upgrade"));
417 assert!(to_upgrade.to_string().contains("1.0"));
418 assert!(to_upgrade.to_string().contains("2.0"));
419
420 let conflict = DependencyStatus::Conflict {
421 reason: "test reason".to_string(),
422 };
423 assert!(conflict.to_string().contains("Conflict"));
424 assert!(conflict.to_string().contains("test reason"));
425
426 let missing = DependencyStatus::Missing;
427 assert_eq!(missing.to_string(), "Missing");
428 }
429
430 #[test]
431 fn dependency_source_display() {
432 let official = DependencySource::Official {
433 repo: "core".to_string(),
434 };
435 assert!(official.to_string().contains("Official"));
436 assert!(official.to_string().contains("core"));
437
438 let aur = DependencySource::Aur;
439 assert_eq!(aur.to_string(), "AUR");
440
441 let local = DependencySource::Local;
442 assert_eq!(local.to_string(), "Local");
443 }
444
445 #[test]
446 fn package_source_display() {
447 let official = PackageSource::Official {
448 repo: "extra".to_string(),
449 arch: "x86_64".to_string(),
450 };
451 assert!(official.to_string().contains("Official"));
452 assert!(official.to_string().contains("extra"));
453 assert!(official.to_string().contains("x86_64"));
454
455 let aur = PackageSource::Aur;
456 assert_eq!(aur.to_string(), "AUR");
457 }
458
459 #[test]
460 fn serde_roundtrip_dependency_status() {
461 let statuses = vec![
462 DependencyStatus::Installed {
463 version: "1.0.0".to_string(),
464 },
465 DependencyStatus::ToInstall,
466 DependencyStatus::ToUpgrade {
467 current: "1.0.0".to_string(),
468 required: "2.0.0".to_string(),
469 },
470 DependencyStatus::Conflict {
471 reason: "test conflict".to_string(),
472 },
473 DependencyStatus::Missing,
474 ];
475
476 for status in statuses {
477 let json = serde_json::to_string(&status).expect("serialization should succeed");
478 let deserialized: DependencyStatus =
479 serde_json::from_str(&json).expect("deserialization should succeed");
480 assert_eq!(status, deserialized);
481 }
482 }
483
484 #[test]
485 fn serde_roundtrip_dependency_source() {
486 let sources = vec![
487 DependencySource::Official {
488 repo: "core".to_string(),
489 },
490 DependencySource::Aur,
491 DependencySource::Local,
492 ];
493
494 for source in sources {
495 let json = serde_json::to_string(&source).expect("serialization should succeed");
496 let deserialized: DependencySource =
497 serde_json::from_str(&json).expect("deserialization should succeed");
498 assert_eq!(source, deserialized);
499 }
500 }
501
502 #[test]
503 fn serde_roundtrip_dependency() {
504 let dep = Dependency {
505 name: "glibc".to_string(),
506 version_req: ">=2.35".to_string(),
507 status: DependencyStatus::Installed {
508 version: "2.35".to_string(),
509 },
510 source: DependencySource::Official {
511 repo: "core".to_string(),
512 },
513 required_by: vec!["firefox".to_string(), "chromium".to_string()],
514 depends_on: vec!["linux-api-headers".to_string()],
515 is_core: true,
516 is_system: true,
517 };
518
519 let json = serde_json::to_string(&dep).expect("serialization should succeed");
520 let deserialized: Dependency =
521 serde_json::from_str(&json).expect("deserialization should succeed");
522 assert_eq!(dep.name, deserialized.name);
523 assert_eq!(dep.version_req, deserialized.version_req);
524 assert_eq!(dep.status, deserialized.status);
525 assert_eq!(dep.source, deserialized.source);
526 assert_eq!(dep.required_by, deserialized.required_by);
527 assert_eq!(dep.depends_on, deserialized.depends_on);
528 assert_eq!(dep.is_core, deserialized.is_core);
529 assert_eq!(dep.is_system, deserialized.is_system);
530 }
531
532 #[test]
533 fn serde_roundtrip_srcinfo_data() {
534 let srcinfo = SrcinfoData {
535 pkgbase: "test-package".to_string(),
536 pkgname: "test-package".to_string(),
537 pkgver: "1.0.0".to_string(),
538 pkgrel: "1".to_string(),
539 depends: vec!["glibc".to_string(), "python>=3.12".to_string()],
540 makedepends: vec!["make".to_string(), "gcc".to_string()],
541 checkdepends: vec!["check".to_string()],
542 optdepends: vec!["optional: optional-package".to_string()],
543 conflicts: vec!["conflicting-pkg".to_string()],
544 provides: vec!["provided-pkg".to_string()],
545 replaces: vec!["replaced-pkg".to_string()],
546 };
547
548 let json = serde_json::to_string(&srcinfo).expect("serialization should succeed");
549 let deserialized: SrcinfoData =
550 serde_json::from_str(&json).expect("deserialization should succeed");
551 assert_eq!(srcinfo.pkgbase, deserialized.pkgbase);
552 assert_eq!(srcinfo.pkgname, deserialized.pkgname);
553 assert_eq!(srcinfo.pkgver, deserialized.pkgver);
554 assert_eq!(srcinfo.pkgrel, deserialized.pkgrel);
555 assert_eq!(srcinfo.depends, deserialized.depends);
556 assert_eq!(srcinfo.makedepends, deserialized.makedepends);
557 assert_eq!(srcinfo.checkdepends, deserialized.checkdepends);
558 assert_eq!(srcinfo.optdepends, deserialized.optdepends);
559 assert_eq!(srcinfo.conflicts, deserialized.conflicts);
560 assert_eq!(srcinfo.provides, deserialized.provides);
561 assert_eq!(srcinfo.replaces, deserialized.replaces);
562 }
563
564 #[test]
565 fn serde_roundtrip_package_ref() {
566 let pkg_ref = PackageRef {
567 name: "firefox".to_string(),
568 version: "121.0".to_string(),
569 source: PackageSource::Official {
570 repo: "extra".to_string(),
571 arch: "x86_64".to_string(),
572 },
573 };
574
575 let json = serde_json::to_string(&pkg_ref).expect("serialization should succeed");
576 let deserialized: PackageRef =
577 serde_json::from_str(&json).expect("deserialization should succeed");
578 assert_eq!(pkg_ref, deserialized);
579 }
580}