use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "type")]
#[non_exhaustive]
pub enum Flavor {
Forgejo {
major: u32,
minor: u32,
},
Gitea {
major: u32,
minor: u32,
},
Unknown,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Features {
pub fast_forward_only: bool,
pub actions_variables: bool,
pub forgejo_webhooks: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct InstanceInfo {
pub version: String,
pub flavor: Flavor,
pub features: Features,
}
impl InstanceInfo {
pub fn from_version(version: &str) -> Self {
let flavor = parse(version);
let at_least = |maj: u32, min: u32, (a, b): (u32, u32)| (a, b) >= (maj, min);
let features = match flavor {
Flavor::Forgejo { major, minor } => Features {
fast_forward_only: at_least(7, 0, (major, minor)),
actions_variables: at_least(8, 0, (major, minor)),
forgejo_webhooks: true,
},
Flavor::Gitea { major, minor } => Features {
fast_forward_only: at_least(1, 22, (major, minor)),
actions_variables: at_least(1, 22, (major, minor)),
forgejo_webhooks: false,
},
Flavor::Unknown => Features::default(),
};
InstanceInfo {
version: version.to_string(),
flavor,
features,
}
}
}
fn parse(version: &str) -> Flavor {
let (own, gitea) = match version.split_once("+gitea-") {
Some((own, gitea)) => (own, Some(gitea)),
None => (version, None),
};
let Some((major, minor)) = major_minor(own) else {
return Flavor::Unknown;
};
match gitea {
Some(_) => Flavor::Forgejo { major, minor },
None if major >= 7 => Flavor::Forgejo { major, minor },
None if major == 1 => Flavor::Gitea { major, minor },
None => Flavor::Unknown,
}
}
fn major_minor(v: &str) -> Option<(u32, u32)> {
let v = v.trim().trim_start_matches('v');
let mut parts = v.split(['.', '-', '+']);
let major = parts.next()?.parse().ok()?;
let minor = parts.next()?.parse().ok()?;
Some((major, minor))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn versions_parse_and_gate_features() {
let f = InstanceInfo::from_version("9.0.0+gitea-1.22.0");
assert_eq!(f.flavor, Flavor::Forgejo { major: 9, minor: 0 });
assert!(f.features.fast_forward_only && f.features.actions_variables);
assert!(f.features.forgejo_webhooks);
let dev = InstanceInfo::from_version("16.0.0-dev-753-6bcc6da0+gitea-1.22.0");
assert_eq!(
dev.flavor,
Flavor::Forgejo {
major: 16,
minor: 0
}
);
let seven = InstanceInfo::from_version("7.0.5+gitea-1.21.0");
assert!(seven.features.fast_forward_only && !seven.features.actions_variables);
let gitea = InstanceInfo::from_version("1.22.3");
assert_eq!(
gitea.flavor,
Flavor::Gitea {
major: 1,
minor: 22
}
);
assert!(gitea.features.fast_forward_only && !gitea.features.forgejo_webhooks);
let old = InstanceInfo::from_version("1.21.11-1");
assert_eq!(old.features, Features::default());
for junk in ["", "dev", "x.y.z", "3.0.0"] {
assert_eq!(InstanceInfo::from_version(junk).flavor, Flavor::Unknown);
}
}
}