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