Skip to main content

opencode_rs/
version.rs

1//! Version pinning for opencode-rs SDK.
2//!
3//! Provides version constants and validation for ensuring SDK compatibility
4//! with specific opencode server versions.
5
6use crate::error::OpencodeError;
7use crate::error::Result;
8
9/// Pinned opencode server version for SDK compatibility testing.
10pub const PINNED_OPENCODE_VERSION: &str = "1.3.17";
11
12/// Environment variable for the opencode binary path.
13pub const OPENCODE_BINARY_ENV: &str = "OPENCODE_BINARY";
14
15/// Environment variable for extra arguments between binary and `serve` command.
16///
17/// Useful for launchers like `bunx` where the full command is:
18/// `bunx --yes opencode-ai@1.3.17 serve --hostname ... --port ...`
19///
20/// Example: `OPENCODE_BINARY=bunx OPENCODE_BINARY_ARGS="--yes opencode-ai@1.3.17"`
21pub const OPENCODE_BINARY_ARGS_ENV: &str = "OPENCODE_BINARY_ARGS";
22
23/// Normalize a version string by stripping the `v` prefix if present.
24pub fn normalize_version(raw: &str) -> &str {
25    let trimmed = raw.trim();
26    trimmed.strip_prefix('v').unwrap_or(trimmed)
27}
28
29/// Validate that the actual version matches the pinned version exactly.
30///
31/// # Errors
32///
33/// Returns an error if the version is missing or doesn't match.
34pub 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}