use std::time::Duration;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase", deny_unknown_fields)]
#[non_exhaustive]
pub enum ReleaseSource {
Github {
owner: String,
repo: String,
#[serde(default = "default_github_host")]
host: String,
},
Gitlab {
project: String,
#[serde(default = "default_gitlab_host")]
host: String,
},
Bitbucket {
workspace: String,
repo_slug: String,
#[serde(default = "default_bitbucket_host")]
host: String,
},
Gitea {
owner: String,
repo: String,
host: String,
},
Codeberg {
owner: String,
repo: String,
},
Direct {
url_template: String,
},
}
fn default_github_host() -> String {
"github.com".into()
}
fn default_gitlab_host() -> String {
"gitlab.com".into()
}
fn default_bitbucket_host() -> String {
"api.bitbucket.org/2.0".into()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum UpdatePolicy {
#[default]
Disabled,
Prompt,
Enabled,
}
const DEFAULT_UPDATE_CHECK_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60);
const fn default_update_check_interval() -> Duration {
DEFAULT_UPDATE_CHECK_INTERVAL
}
#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
#[serde(deny_unknown_fields)]
#[builder(on(String, into))]
pub struct ToolMetadata {
pub name: String,
pub summary: String,
#[serde(default)]
#[builder(default)]
pub description: String,
#[serde(default)]
pub release_source: Option<ReleaseSource>,
#[serde(default)]
#[builder(default)]
pub update_policy: UpdatePolicy,
#[serde(default = "default_update_check_interval")]
#[builder(default = default_update_check_interval())]
pub update_check_interval: Duration,
#[serde(default, skip_serializing)]
pub release_credential: Option<rtb_credentials::CredentialRef>,
#[serde(default)]
#[builder(default)]
pub help: HelpChannel,
#[serde(skip)]
#[builder(default)]
pub update_public_keys: Vec<[u8; 32]>,
#[serde(skip)]
pub update_checksums_asset: Option<&'static str>,
#[serde(skip)]
pub update_asset_pattern: Option<&'static str>,
#[serde(skip)]
pub telemetry_notice: Option<&'static str>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase", deny_unknown_fields)]
#[non_exhaustive]
pub enum HelpChannel {
#[default]
None,
Slack {
team: String,
channel: String,
},
Teams {
team: String,
channel: String,
},
Url {
url: String,
},
}
impl HelpChannel {
#[must_use]
pub fn footer(&self) -> Option<String> {
match self {
Self::None => None,
Self::Slack { team, channel } => Some(format!("support: slack #{channel} (in {team})")),
Self::Teams { team, channel } => Some(format!("support: Teams → {team} / {channel}")),
Self::Url { url } => Some(format!("support: {url}")),
}
}
}
#[cfg(test)]
mod tests {
use super::{default_update_check_interval, ToolMetadata, UpdatePolicy};
use std::time::Duration;
#[test]
fn update_policy_defaults_to_disabled() {
assert_eq!(UpdatePolicy::default(), UpdatePolicy::Disabled);
}
#[test]
fn metadata_update_baseline_defaults() {
let m = ToolMetadata::builder().name("t").summary("s").build();
assert_eq!(m.update_policy, UpdatePolicy::Disabled);
assert_eq!(m.update_check_interval, Duration::from_secs(24 * 60 * 60));
}
#[test]
fn builder_overrides_update_baseline() {
let m = ToolMetadata::builder()
.name("t")
.summary("s")
.update_policy(UpdatePolicy::Enabled)
.update_check_interval(Duration::from_secs(3600))
.build();
assert_eq!(m.update_policy, UpdatePolicy::Enabled);
assert_eq!(m.update_check_interval, Duration::from_secs(3600));
}
#[test]
fn update_policy_serde_is_lowercase() {
assert_eq!(serde_json::to_string(&UpdatePolicy::Enabled).unwrap(), "\"enabled\"");
assert_eq!(
serde_json::from_str::<UpdatePolicy>("\"prompt\"").unwrap(),
UpdatePolicy::Prompt
);
}
#[test]
fn check_interval_default_fn_is_24h() {
assert_eq!(default_update_check_interval(), Duration::from_secs(86_400));
}
}