1use crate::error::OpencodeError;
7use crate::error::Result;
8
9pub const PINNED_OPENCODE_VERSION: &str = "1.3.17";
11
12pub const OPENCODE_BINARY_ENV: &str = "OPENCODE_BINARY";
14
15pub const OPENCODE_BINARY_ARGS_ENV: &str = "OPENCODE_BINARY_ARGS";
22
23pub fn normalize_version(raw: &str) -> &str {
25 let trimmed = raw.trim();
26 trimmed.strip_prefix('v').unwrap_or(trimmed)
27}
28
29pub fn validate_exact_version(actual: Option<&str>) -> Result<()> {
35 let Some(actual) = actual else {
36 return Err(OpencodeError::VersionMismatch {
37 expected: PINNED_OPENCODE_VERSION.to_string(),
38 actual: "None".to_string(),
39 });
40 };
41
42 let normalized = normalize_version(actual);
43 if normalized != PINNED_OPENCODE_VERSION {
44 return Err(OpencodeError::VersionMismatch {
45 expected: PINNED_OPENCODE_VERSION.to_string(),
46 actual: actual.to_string(),
47 });
48 }
49
50 Ok(())
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 #[test]
58 fn test_normalize_version_strips_v_prefix() {
59 assert_eq!(normalize_version("v1.3.17"), "1.3.17");
60 assert_eq!(normalize_version("1.3.17"), "1.3.17");
61 assert_eq!(normalize_version(" v1.3.17 "), "1.3.17");
62 }
63
64 #[test]
65 fn test_validate_exact_version_accepts_matching() {
66 assert!(validate_exact_version(Some(PINNED_OPENCODE_VERSION)).is_ok());
67 assert!(validate_exact_version(Some(&format!("v{PINNED_OPENCODE_VERSION}"))).is_ok());
68 }
69
70 #[test]
71 fn test_validate_exact_version_rejects_mismatch() {
72 assert!(validate_exact_version(Some("1.3.12")).is_err());
73 assert!(validate_exact_version(Some("1.4.0")).is_err());
74 assert!(validate_exact_version(None).is_err());
75 }
76}