Skip to main content

abbaye/version_extractors/
mod.rs

1use chrono::{DateTime, Utc};
2use miette::{Result, bail};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6pub mod cargo;
7pub mod git;
8
9use cargo::{CargoVersion, CargoVersionConfig};
10use git::{GitVersion, GitVersionConfig};
11
12/// A version string paired with an optional release date.
13///
14/// Returned by [`VersionExtractor::get_last_version`] and
15/// [`VersionExtractor::get_all_versions`] so that callers can propagate
16/// date information (e.g. to Atom feed entries) without a second round-trip.
17#[derive(Debug, Clone)]
18pub struct VersionInfo {
19    /// The human-readable version string (e.g. `"1.2.3"`).
20    pub version: String,
21    /// The UTC date/time at which this version was released, if known.
22    pub date: Option<DateTime<Utc>>,
23}
24
25#[allow(async_fn_in_trait)]
26pub trait VersionExtractor {
27    type ConfigType: Default + for<'de> Deserialize<'de> + Clone;
28
29    async fn get_last_version(&self, config: Self::ConfigType) -> Result<VersionInfo>;
30
31    async fn get_all_versions(&self, _config: Self::ConfigType) -> Result<Vec<VersionInfo>> {
32        bail!("get_all_versions is not supported by this version extractor")
33    }
34}
35
36#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
37#[serde(tag = "type", rename_all = "snake_case")]
38pub enum AnyVersionExtractor {
39    /// Reads the version from the `version` field in the `[package]` section
40    /// of a `Cargo.toml` file.
41    ///
42    /// ```toml
43    /// [version_extractor]
44    /// type = "cargo"
45    /// manifest_path = "Cargo.toml" # optional, defaults to ./Cargo.toml
46    /// ```
47    Cargo(CargoVersionConfig),
48
49    /// Derives the version from the most recent Git tag using
50    /// `git describe --tags --always`.
51    /// Supports stripping a tag prefix (e.g. `"v"`) and customising the
52    /// suffix appended when the working tree has uncommitted changes.
53    ///
54    /// ```toml
55    /// [version_extractor]
56    /// type = "git"
57    /// tag_prefix = "v"      # optional, strips leading "v"
58    /// dirty_suffix = "-dev" # optional, defaults to "-dirty"
59    /// ```
60    Git(GitVersionConfig),
61}
62
63impl Default for AnyVersionExtractor {
64    fn default() -> Self {
65        Self::Cargo(CargoVersionConfig::default())
66    }
67}
68
69impl AnyVersionExtractor {
70    pub async fn extract(&self) -> Result<VersionInfo> {
71        match self {
72            Self::Cargo(config) => CargoVersion.get_last_version(config.clone()).await,
73            Self::Git(config) => GitVersion.get_last_version(config.clone()).await,
74        }
75    }
76
77    pub async fn extract_all(&self) -> Result<Vec<VersionInfo>> {
78        match self {
79            Self::Cargo(config) => CargoVersion.get_all_versions(config.clone()).await,
80            Self::Git(config) => GitVersion.get_all_versions(config.clone()).await,
81        }
82    }
83
84    /// Returns the full VCS tag name for the given version string.
85    ///
86    /// For Git extractors this re-applies the configured `tag_prefix` (e.g.
87    /// `"v"` → `"v1.2.3"`).  For other extractors the version is returned
88    /// unchanged.
89    pub fn tag_name(&self, version: &str) -> String {
90        match self {
91            Self::Git(config) => match &config.tag_prefix {
92                Some(prefix) => format!("{prefix}{version}"),
93                None => version.to_owned(),
94            },
95            Self::Cargo(_) => version.to_owned(),
96        }
97    }
98}