sqlite_graphrag/spawn/
executor_version.rs1use crate::errors::AppError;
4use semver::Version;
5use serde::{Deserialize, Serialize};
6use std::cmp::Ordering;
7use std::str::FromStr;
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct ExecutorVersion {
11 pub raw: String,
12 pub semver: Option<Version>,
13 pub channel: Option<String>,
14}
15
16impl ExecutorVersion {
17 pub fn unknown() -> Self {
18 Self {
19 raw: "unknown".to_string(),
20 semver: None,
21 channel: None,
22 }
23 }
24
25 pub fn parse(s: &str) -> Result<Self, AppError> {
26 let trimmed = s.trim();
27 if trimmed.is_empty() {
28 return Err(AppError::Validation("empty version string".to_string()));
29 }
30 let channel_start = trimmed.find('-');
31 let (numeric_part, channel_part) = match channel_start {
32 Some(idx) => (
33 trimmed[..idx].to_string(),
34 Some(trimmed[idx + 1..].to_string()),
35 ),
36 None => (trimmed.to_string(), None),
37 };
38 let semver = if numeric_part
39 .chars()
40 .next()
41 .map(|c| c.is_ascii_digit())
42 .unwrap_or(false)
43 {
44 Version::from_str(&numeric_part).ok()
45 } else {
46 None
47 };
48 Ok(Self {
49 raw: trimmed.to_string(),
50 semver,
51 channel: channel_part,
52 })
53 }
54
55 pub fn major(&self) -> u64 {
56 self.semver.as_ref().map(|v| v.major).unwrap_or(0)
57 }
58
59 pub fn minor(&self) -> u64 {
60 self.semver.as_ref().map(|v| v.minor).unwrap_or(0)
61 }
62
63 pub fn patch(&self) -> u64 {
64 self.semver.as_ref().map(|v| v.patch).unwrap_or(0)
65 }
66
67 pub fn is_at_least(&self, major: u64, minor: u64, patch: u64) -> bool {
68 match &self.semver {
69 Some(v) => (v.major, v.minor, v.patch) >= (major, minor, patch),
70 None => false,
71 }
72 }
73
74 pub fn in_range(&self, min: (u64, u64, u64), max: (u64, u64, u64)) -> bool {
75 self.is_at_least(min.0, min.1, min.2) && {
76 let current = (self.major(), self.minor(), self.patch());
77 current <= max
78 }
79 }
80
81 pub fn compare(&self, other: &ExecutorVersion) -> Ordering {
82 match (&self.semver, &other.semver) {
83 (Some(a), Some(b)) => a.cmp(b),
84 (Some(_), None) => Ordering::Greater,
85 (None, Some(_)) => Ordering::Less,
86 (None, None) => Ordering::Equal,
87 }
88 }
89}
90
91impl std::fmt::Display for ExecutorVersion {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 write!(f, "{}", self.raw)
94 }
95}
96
97impl Default for ExecutorVersion {
98 fn default() -> Self {
99 Self::unknown()
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn parse_simple_semver() {
109 let v = ExecutorVersion::parse("0.137.0").unwrap();
110 assert_eq!(v.major(), 0);
111 assert_eq!(v.minor(), 137);
112 assert_eq!(v.patch(), 0);
113 assert!(v.is_at_least(0, 130, 0));
114 assert!(!v.is_at_least(1, 0, 0));
115 }
116
117 #[test]
118 fn parse_with_channel() {
119 let v = ExecutorVersion::parse("2.1.0-beta.1").unwrap();
120 assert_eq!(v.major(), 2);
121 assert_eq!(v.minor(), 1);
122 assert_eq!(v.channel.as_deref(), Some("beta.1"));
123 }
124
125 #[test]
126 fn parse_unknown() {
127 let v = ExecutorVersion::parse("n/a").unwrap();
128 assert_eq!(v.semver, None);
129 assert!(!v.is_at_least(0, 0, 0));
130 }
131
132 #[test]
133 fn compare_ordering() {
134 let a = ExecutorVersion::parse("0.137.0").unwrap();
135 let b = ExecutorVersion::parse("0.138.0").unwrap();
136 assert_eq!(a.compare(&b), Ordering::Less);
137 }
138
139 #[test]
140 fn empty_string_is_error() {
141 assert!(ExecutorVersion::parse("").is_err());
142 }
143}