refactor(claw): the forge kind is the integration's — config.rs carries no forge code
The last forge content in config.rs was vocabulary, not data: `ForgeKind` (name/parse/token_env/default_base) and the pure `resolve_token` helper. By the same rule that moved `Watch` to the watch table and left `CalendarWatch` in the calendar integration, the kind belongs to the crate that speaks the forges. - `rhema-forge-integration` gains `kind::ForgeKind` and `resolve_token`, with the parse and token-fallback tests moved alongside — the claw's config tests were testing the integration's type through a re-export. - The claw's surfaces import the type from the integration directly; the crate root re-exports it, so `rhema_claw::ForgeKind` keeps resolving for everything downstream. config.rs is now config: credentials, the surface, paths, cadence, and nothing that knows what a forge or a calendar is.
This commit is contained in:
1 parent
5047ff8803
commit
90212d1e0f
11 files changed
+160
-150
No files matched your search
@@ -0,0 +1,128 @@
|
||||
//! The forge kinds: the vocabulary every forge surface shares — the
|
||||
//! watch table's rows, the account store's `forge` column, the HTTP
|
||||
//! routes' spelling, the source names the dispatcher registers, the
|
||||
//! resource keys (`github:acme/w#7`). One type, owned by the crate
|
||||
//! that speaks the forges; the claw's config no longer carries it.
|
||||
|
||||
/// Which forge a watch polls. The wire spelling is the source name the
|
||||
/// dispatcher registers and the resource key carries (`github:acme/w#7`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ForgeKind {
|
||||
/// GitHub (api.github.com, or a GitHub Enterprise host).
|
||||
GitHub,
|
||||
/// Gitea — self-hosted; a watch must name its instance.
|
||||
Gitea,
|
||||
/// GitLab (gitlab.com, or a self-hosted instance).
|
||||
GitLab,
|
||||
}
|
||||
|
||||
impl ForgeKind {
|
||||
/// Every kind the claw ships a source for, in registration order.
|
||||
/// A kind missing here has no watch poller, whatever the table
|
||||
/// holds.
|
||||
pub const ALL: [ForgeKind; 3] = [ForgeKind::GitHub, ForgeKind::Gitea, ForgeKind::GitLab];
|
||||
|
||||
/// The source name this kind registers and dispatches under.
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
ForgeKind::GitHub => "github",
|
||||
ForgeKind::Gitea => "gitea",
|
||||
ForgeKind::GitLab => "gitlab",
|
||||
}
|
||||
}
|
||||
|
||||
/// The environment variable the token falls back to.
|
||||
pub fn token_env(self) -> &'static str {
|
||||
match self {
|
||||
ForgeKind::GitHub => "GITHUB_TOKEN",
|
||||
ForgeKind::Gitea => "GITEA_TOKEN",
|
||||
ForgeKind::GitLab => "GITLAB_TOKEN",
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a forge name as the config, the HTTP surface, and the
|
||||
/// watch tools spell it: the source name (`github`, `gitea`,
|
||||
/// `gitlab`). `None` for anything else.
|
||||
pub fn parse(name: &str) -> Option<Self> {
|
||||
match name.trim() {
|
||||
"github" => Some(ForgeKind::GitHub),
|
||||
"gitea" => Some(ForgeKind::Gitea),
|
||||
"gitlab" => Some(ForgeKind::GitLab),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The production base URL; `None` for the self-hosted-only forges.
|
||||
pub fn default_base(self) -> Option<&'static str> {
|
||||
match self {
|
||||
ForgeKind::GitHub => Some(crate::github::DEFAULT_BASE_URL),
|
||||
ForgeKind::Gitea => None,
|
||||
ForgeKind::GitLab => Some(crate::gitlab::DEFAULT_BASE_URL),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pure token resolution: the stored value first, then the forge's env
|
||||
/// variable; the first non-blank wins. Parameterized over the env
|
||||
/// lookup so tests can hold the environment still.
|
||||
pub fn resolve_token(
|
||||
get_env: impl Fn(&str) -> Result<String, std::env::VarError>,
|
||||
forge: ForgeKind,
|
||||
stored: Option<&str>,
|
||||
) -> Option<String> {
|
||||
stored
|
||||
.map(str::trim)
|
||||
.filter(|token| !token.is_empty())
|
||||
.map(str::to_owned)
|
||||
.or_else(|| {
|
||||
get_env(forge.token_env())
|
||||
.ok()
|
||||
.map(|token| token.trim().to_owned())
|
||||
.filter(|token| !token.is_empty())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn forge_names_parse_as_the_sources_spell_them() {
|
||||
assert_eq!(ForgeKind::parse("github"), Some(ForgeKind::GitHub));
|
||||
assert_eq!(ForgeKind::parse(" gitea "), Some(ForgeKind::Gitea));
|
||||
assert_eq!(ForgeKind::parse("gitlab"), Some(ForgeKind::GitLab));
|
||||
assert_eq!(ForgeKind::parse("sourcehut"), None);
|
||||
assert_eq!(ForgeKind::parse(""), None);
|
||||
for kind in ForgeKind::ALL {
|
||||
assert_eq!(ForgeKind::parse(kind.name()), Some(kind), "{}", kind.name());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_token_falls_back_to_the_env_and_blank_counts_as_unset() {
|
||||
let env = |name: &str| match name {
|
||||
"GITHUB_TOKEN" => Ok("from-env".to_owned()),
|
||||
_ => Err(std::env::VarError::NotPresent),
|
||||
};
|
||||
assert_eq!(
|
||||
resolve_token(env, ForgeKind::GitHub, Some(" from-config ")),
|
||||
Some("from-config".to_owned()),
|
||||
"the stored token wins"
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_token(env, ForgeKind::GitHub, None),
|
||||
Some("from-env".to_owned()),
|
||||
"the env token serves when no stored token"
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_token(
|
||||
|_| Err(std::env::VarError::NotPresent),
|
||||
ForgeKind::GitLab,
|
||||
Some(" ")
|
||||
),
|
||||
None,
|
||||
"a blank token counts as unset"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,8 @@ pub mod error;
|
||||
pub mod event;
|
||||
pub mod forge;
|
||||
pub mod gitea;
|
||||
pub mod kind;
|
||||
pub use kind::{ForgeKind, resolve_token};
|
||||
pub mod github;
|
||||
pub mod gitlab;
|
||||
pub mod triage;
|
||||
|
||||
Reference in new issue
Block a user