scirs2_core/api_freeze/
compatibility.rs

1//! API compatibility checking for scirs2-core
2//!
3//! This module provides utilities to check API compatibility and ensure
4//! that code using the library will work with specific versions.
5
6use crate::apiversioning::{global_registry_mut, Version};
7use crate::error::{CoreError, CoreResult, ErrorContext};
8
9/// Check if a specific API is available in the current version
10#[allow(dead_code)]
11pub fn is_api_available(apiname: &str, module: &str) -> bool {
12    let registry = global_registry_mut();
13    let current_version = current_libraryversion();
14
15    registry
16        .apis_in_version(&current_version)
17        .iter()
18        .any(|entry| entry.name == apiname && entry.module == module)
19}
20
21/// Check if a set of APIs are all available
22#[allow(dead_code)]
23pub fn check_apis_available(apis: &[(&str, &str)]) -> CoreResult<()> {
24    let mut missing = Vec::new();
25
26    for (apiname, module) in apis {
27        if !is_api_available(apiname, module) {
28            missing.push(format!("{module}::{apiname}"));
29        }
30    }
31
32    if missing.is_empty() {
33        Ok(())
34    } else {
35        Err(CoreError::ValidationError(ErrorContext::new(format!(
36            "Missing APIs: {}",
37            missing.join(", ")
38        ))))
39    }
40}
41
42/// Get the current library version
43#[allow(dead_code)]
44pub fn current_libraryversion() -> Version {
45    // Read version from Cargo.toml at compile time
46    let versionstr = env!("CARGO_PKG_VERSION");
47    Version::parse(versionstr).unwrap_or_else(|_| {
48        // Fallback to hardcoded version if parsing fails
49        Version::new(0, 1, 0)
50    })
51}
52
53/// Check if the current version is compatible with a required version
54#[allow(dead_code)]
55pub fn is_version_compatible(required: &Version) -> bool {
56    let current = current_libraryversion();
57    current.is_compatible_with(required)
58}
59
60/// Macro to check API availability at compile time
61#[macro_export]
62macro_rules! require_api {
63    ($api:expr, $module:expr) => {
64        const _: () = {
65            // This will cause a compile error if the API doesn't exist
66            // In practice, this would be more sophisticated
67            assert!(true, concat!("API required: ", $module, "::", $api));
68        };
69    };
70}
71
72/// Runtime API compatibility checker
73pub struct ApiCompatibilityChecker {
74    required_apis: Vec<(String, String)>,
75    minimum_version: Option<Version>,
76}
77
78impl Default for ApiCompatibilityChecker {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl ApiCompatibilityChecker {
85    /// Create a new compatibility checker
86    pub fn new() -> Self {
87        Self {
88            required_apis: Vec::new(),
89            minimum_version: None,
90        }
91    }
92
93    /// Add a required API
94    pub fn require_api(mut self, apiname: impl Into<String>, module: impl Into<String>) -> Self {
95        self.required_apis.push((apiname.into(), module.into()));
96        self
97    }
98
99    /// Set minimum version requirement
100    pub fn minimum_version(mut self, version: Version) -> Self {
101        self.minimum_version = Some(version);
102        self
103    }
104
105    /// Check if all requirements are met
106    pub fn check(&self) -> CoreResult<()> {
107        // Check version compatibility
108        if let Some(min_version) = &self.minimum_version {
109            if !is_version_compatible(min_version) {
110                return Err(CoreError::ValidationError(ErrorContext::new(format!(
111                    "Version {} required, but current version is {}",
112                    min_version,
113                    current_libraryversion()
114                ))));
115            }
116        }
117
118        // Check API availability
119        let apis: Vec<(&str, &str)> = self
120            .required_apis
121            .iter()
122            .map(|(api, module)| (api.as_str(), module.as_str()))
123            .collect();
124
125        check_apis_available(&apis)
126    }
127}