Skip to main content

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///
62/// Honestly a no-op today: it does not yet perform a real compile-time
63/// API-existence check (that would require `$api`/`$module` to be actual
64/// item paths rather than message fragments, plus a redesign of this
65/// macro's public signature). It previously used a tautological
66/// `assert!(true, ..)` here, which "passed" unconditionally regardless of
67/// whether the referenced API existed — this version is an honest no-op
68/// instead of masquerading as a check.
69#[macro_export]
70macro_rules! require_api {
71    ($api:expr, $module:expr) => {
72        const _: () = {
73            let _ = concat!("API required: ", $module, "::", $api);
74        };
75    };
76}
77
78/// Runtime API compatibility checker
79pub struct ApiCompatibilityChecker {
80    required_apis: Vec<(String, String)>,
81    minimum_version: Option<Version>,
82}
83
84impl Default for ApiCompatibilityChecker {
85    fn default() -> Self {
86        Self::new()
87    }
88}
89
90impl ApiCompatibilityChecker {
91    /// Create a new compatibility checker
92    pub fn new() -> Self {
93        Self {
94            required_apis: Vec::new(),
95            minimum_version: None,
96        }
97    }
98
99    /// Add a required API
100    pub fn require_api(mut self, apiname: impl Into<String>, module: impl Into<String>) -> Self {
101        self.required_apis.push((apiname.into(), module.into()));
102        self
103    }
104
105    /// Set minimum version requirement
106    pub fn minimum_version(mut self, version: Version) -> Self {
107        self.minimum_version = Some(version);
108        self
109    }
110
111    /// Check if all requirements are met
112    pub fn check(&self) -> CoreResult<()> {
113        // Check version compatibility
114        if let Some(min_version) = &self.minimum_version {
115            if !is_version_compatible(min_version) {
116                return Err(CoreError::ValidationError(ErrorContext::new(format!(
117                    "Version {} required, but current version is {}",
118                    min_version,
119                    current_libraryversion()
120                ))));
121            }
122        }
123
124        // Check API availability
125        let apis: Vec<(&str, &str)> = self
126            .required_apis
127            .iter()
128            .map(|(api, module)| (api.as_str(), module.as_str()))
129            .collect();
130
131        check_apis_available(&apis)
132    }
133}