Skip to main content

sqlite_graphrag/spawn/
executor_version.rs

1//! Executor version parsing (v1.0.75 — G22)
2
3use crate::errors::AppError;
4use semver::Version;
5use serde::{Deserialize, Serialize};
6use std::cmp::Ordering;
7use std::str::FromStr;
8
9/// Executor version.
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11pub struct ExecutorVersion {
12    /// Raw.
13    pub raw: String,
14    /// Semver.
15    pub semver: Option<Version>,
16    /// Channel.
17    pub channel: Option<String>,
18}
19
20impl ExecutorVersion {
21    /// Unknown.
22    pub fn unknown() -> Self {
23        Self {
24            raw: "unknown".to_string(),
25            semver: None,
26            channel: None,
27        }
28    }
29
30    /// Parse from a string.
31    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    /// Major.
64    pub fn major(&self) -> u64 {
65        self.semver.as_ref().map(|v| v.major).unwrap_or(0)
66    }
67
68    /// Minor.
69    pub fn minor(&self) -> u64 {
70        self.semver.as_ref().map(|v| v.minor).unwrap_or(0)
71    }
72
73    /// Patch.
74    pub fn patch(&self) -> u64 {
75        self.semver.as_ref().map(|v| v.patch).unwrap_or(0)
76    }
77
78    /// Is at least.
79    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    /// In range.
87    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    /// Compare.
95    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}