Skip to main content

cranpose_services/
app_info.rs

1//! How the platform packaged this app: the version a user is shown, and the
2//! build version a store orders releases by.
3//!
4//! An app that prints its own version usually knows it at compile time, which
5//! is fine right up until the packaging step adds something the compiler never
6//! saw — an Android `versionNameSuffix`, a CI build number, a store-assigned
7//! build. Then the About screen and the artifact disagree, and the mismatch is
8//! invisible until someone reads a bug report. This asks the platform what it
9//! actually shipped.
10//!
11//! ```rust,no_run
12//! use cranpose_services::app_info;
13//!
14//! // Prefer what the platform packaged; fall back to what was compiled in.
15//! let version = app_info::version_name().unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_string());
16//! ```
17//!
18//! The default implementation answers `None` for both: a desktop binary run
19//! straight out of `target/` was not packaged by anything and has no answer to
20//! give. Platform backends install a real one with
21//! [`set_platform_app_info`].
22
23use std::rc::Rc;
24
25/// Reports the packaged identity of the running app.
26pub trait AppInfo {
27    /// The version a user is shown — Android's `versionName`, Apple's
28    /// `CFBundleShortVersionString`. `None` when the platform has none.
29    fn version_name(&self) -> Option<String>;
30
31    /// The build identifier a store orders releases by — Android's
32    /// `versionCode`, Apple's `CFBundleVersion`. `None` when unknown.
33    ///
34    /// This is a string because Apple build versions may contain multiple
35    /// numeric components, such as `42.3.1`. Android version codes are
36    /// converted without losing their numeric value.
37    fn build_version(&self) -> Option<String>;
38}
39
40pub type AppInfoRef = Rc<dyn AppInfo>;
41
42struct DefaultAppInfo;
43
44impl AppInfo for DefaultAppInfo {
45    fn version_name(&self) -> Option<String> {
46        None
47    }
48
49    fn build_version(&self) -> Option<String> {
50        None
51    }
52}
53
54/// Installs a platform app-info implementation, replacing any previous one.
55pub fn set_platform_app_info(info: AppInfoRef) {
56    crate::thread_state::with(|state| *state.app_info.borrow_mut() = Some(info));
57}
58
59/// Removes any registered platform app info (tests and teardown).
60pub fn clear_platform_app_info() {
61    crate::thread_state::with(|state| *state.app_info.borrow_mut() = None);
62}
63
64/// The active app info: the platform implementation if installed, otherwise
65/// the built-in default.
66pub fn app_info() -> AppInfoRef {
67    crate::thread_state::with(|state| state.app_info.borrow().clone())
68        .unwrap_or_else(|| Rc::new(DefaultAppInfo))
69}
70
71/// The version a user is shown, if the platform knows one.
72pub fn version_name() -> Option<String> {
73    app_info().version_name()
74}
75
76/// The build identifier a store orders releases by, if the platform knows one.
77pub fn build_version() -> Option<String> {
78    app_info().build_version()
79}
80
81#[cfg(test)]
82#[path = "tests/app_info_tests.rs"]
83mod tests;