Skip to main content

rtb_app/
version.rs

1//! Build-time version information.
2
3use semver::Version;
4use serde::{Deserialize, Serialize};
5
6/// Capture the **calling crate's** version into a [`VersionInfo`].
7///
8/// Expands to [`VersionInfo::from_pkg_version`] applied to
9/// `env!("CARGO_PKG_VERSION")`. Because `macro_rules!` expands at the
10/// call site, the `env!` is evaluated while *your* crate is compiled and
11/// therefore yields *your* version.
12///
13/// This has to be a macro. A plain function cannot do it: the `env!`
14/// inside a function body is expanded when the *defining* crate is
15/// compiled, so it would always report rtb-app's version. That is
16/// exactly the bug [`VersionInfo::from_env`] has, and why it is
17/// deprecated.
18///
19/// ```
20/// let v = rtb_app::version_info!();
21/// assert_eq!(v.version.to_string(), env!("CARGO_PKG_VERSION"));
22/// ```
23///
24/// Chain the fluent setters to add build metadata:
25///
26/// ```
27/// let v = rtb_app::version_info!()
28///     .with_commit("deadbeef")
29///     .with_date("2026-08-02T00:00:00Z");
30/// assert_eq!(v.commit.as_deref(), Some("deadbeef"));
31/// ```
32#[macro_export]
33macro_rules! version_info {
34    () => {
35        $crate::version::VersionInfo::from_pkg_version(::core::env!("CARGO_PKG_VERSION"))
36    };
37}
38
39/// Version information captured at build time.
40///
41/// Populate the `version` field with the [`version_info!`] macro, and
42/// inject `commit` / `date` via your `build.rs` (the `vergen` or `built`
43/// crates are canonical).
44///
45/// [`version_info!`]: crate::version_info
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct VersionInfo {
48    /// Parsed semantic version.
49    pub version: Version,
50
51    /// Short commit SHA, if known at build time.
52    #[serde(default)]
53    pub commit: Option<String>,
54
55    /// ISO-8601 build timestamp, if known at build time.
56    #[serde(default)]
57    pub date: Option<String>,
58}
59
60impl VersionInfo {
61    /// Construct from a parsed semver. `commit` and `date` start unset
62    /// — add them with [`Self::with_commit`] / [`Self::with_date`].
63    #[must_use]
64    pub const fn new(version: Version) -> Self {
65        Self { version, commit: None, date: None }
66    }
67
68    /// Fluent setter for the commit SHA.
69    #[must_use]
70    pub fn with_commit(mut self, commit: impl Into<String>) -> Self {
71        self.commit = Some(commit.into());
72        self
73    }
74
75    /// Fluent setter for the build timestamp.
76    #[must_use]
77    pub fn with_date(mut self, date: impl Into<String>) -> Self {
78        self.date = Some(date.into());
79        self
80    }
81
82    /// Parse a `CARGO_PKG_VERSION`-shaped string, with a silent fallback
83    /// to `0.0.0` when parsing fails (which in turn is flagged by
84    /// [`Self::is_development`]).
85    ///
86    /// Prefer the [`version_info!`] macro, which passes the *calling*
87    /// crate's version for you. Call this directly only when the version
88    /// string comes from somewhere else — a `build.rs` output, say.
89    ///
90    /// [`version_info!`]: crate::version_info
91    #[must_use]
92    pub fn from_pkg_version(raw: &str) -> Self {
93        let version = Version::parse(raw).unwrap_or_else(|_| Version::new(0, 0, 0));
94        Self::new(version)
95    }
96
97    /// Parse *rtb-app's own* `CARGO_PKG_VERSION`.
98    ///
99    /// # This is almost certainly not what you want
100    ///
101    /// `env!` is expanded by the compiler wherever it is written — and it
102    /// is written *here*, inside rtb-app. So this returns the version of
103    /// the framework, never the version of the tool calling it. Every
104    /// downstream tool using it reported rtb-app's version as its own.
105    ///
106    /// That is not merely cosmetic: [`crate::metadata`]-driven self-update
107    /// verifies a freshly staged binary by checking the version it
108    /// reports against the release tag it came from. A binary that
109    /// misreports its version fails that check, and the update is
110    /// refused.
111    ///
112    /// Use the [`version_info!`] macro instead — it expands `env!` at
113    /// *your* call site.
114    ///
115    /// [`version_info!`]: crate::version_info
116    #[must_use]
117    #[deprecated(
118        since = "0.9.0",
119        note = "returns rtb-app's version, not the calling tool's — `env!` expands where it is written. Use the `rtb_app::version_info!()` macro instead."
120    )]
121    pub fn from_env() -> Self {
122        Self::from_pkg_version(env!("CARGO_PKG_VERSION"))
123    }
124
125    /// `true` when this build is a development / pre-release build.
126    ///
127    /// Development is any of:
128    ///
129    /// * `major == 0` (pre-1.0 builds are always considered development),
130    /// * a non-empty pre-release identifier (`-alpha`, `-dev.5`, …),
131    /// * version exactly `0.0.0` (the [`Self::from_pkg_version`] fallback).
132    #[must_use]
133    pub fn is_development(&self) -> bool {
134        self.version.major == 0 || !self.version.pre.is_empty()
135    }
136}