use greentic_deploy_spec::CapabilitySlot;
use semver::VersionReq;
use crate::tool_check::ToolCheck;
pub trait EnvPackHandler: std::fmt::Debug + Send + Sync {
fn slot(&self) -> CapabilitySlot;
fn descriptor_path(&self) -> &str;
fn supported_versions(&self) -> VersionReq;
fn preflight(&self) -> Vec<ToolCheck> {
Vec::new()
}
fn deployer_credentials(&self) -> Option<&dyn crate::credentials::DeployerCredentials> {
None
}
fn wizard_qaspec_yaml(&self) -> Option<&'static str> {
None
}
fn as_deployer(&self) -> Option<&dyn super::deployer::Deployer> {
None
}
fn as_manifest_renderer(&self) -> Option<&dyn super::render::ManifestRenderer> {
None
}
}
#[derive(Debug, Clone, Copy)]
pub struct BuiltinHandler {
slot: CapabilitySlot,
descriptor_path: &'static str,
version_req: &'static str,
}
impl EnvPackHandler for BuiltinHandler {
fn slot(&self) -> CapabilitySlot {
self.slot
}
fn descriptor_path(&self) -> &str {
self.descriptor_path
}
fn supported_versions(&self) -> VersionReq {
self.version_req
.parse()
.expect("built-in version-req is valid (guarded by tests)")
}
}
pub const BUILTIN_HANDLERS: &[BuiltinHandler] = &[
BuiltinHandler {
slot: CapabilitySlot::Secrets,
descriptor_path: "greentic.secrets.dev-store",
version_req: "^0.1.0",
},
BuiltinHandler {
slot: CapabilitySlot::Telemetry,
descriptor_path: "greentic.telemetry.stdout",
version_req: "^0.1.0",
},
BuiltinHandler {
slot: CapabilitySlot::Sessions,
descriptor_path: "greentic.sessions.in-memory",
version_req: "^0.1.0",
},
BuiltinHandler {
slot: CapabilitySlot::State,
descriptor_path: "greentic.state.in-memory",
version_req: "^0.1.0",
},
];
#[cfg(test)]
mod tests {
use super::*;
use greentic_deploy_spec::PackDescriptor;
#[test]
fn builtin_table_matches_default_bindings() {
use crate::env_packs::local_process::LocalProcessDeployerHandler;
let mut handlers: Vec<(CapabilitySlot, String)> = BUILTIN_HANDLERS
.iter()
.map(|h| (h.slot, h.descriptor_path.to_string()))
.collect();
let lpdh = LocalProcessDeployerHandler::default();
handlers.push((lpdh.slot(), lpdh.descriptor_path().to_string()));
handlers.sort_by(|a, b| a.1.cmp(&b.1));
let mut defaults: Vec<(CapabilitySlot, String)> = crate::defaults::LOCAL_DEFAULT_BINDINGS
.iter()
.map(|(slot, descriptor)| {
let path = PackDescriptor::try_new(*descriptor)
.expect("default descriptor parses")
.path()
.to_string();
(*slot, path)
})
.collect();
defaults.sort_by(|a, b| a.1.cmp(&b.1));
assert_eq!(handlers, defaults);
}
#[test]
fn builtin_metadata_handlers_ship_no_wizard_qaspec() {
for h in BUILTIN_HANDLERS {
assert!(
h.wizard_qaspec_yaml().is_none(),
"metadata-only built-in `{}` must not ship a wizard QASpec — \
override `wizard_qaspec_yaml` only on handlers that surface \
operator-tunable knobs",
h.descriptor_path(),
);
}
}
#[test]
fn builtin_version_reqs_accept_their_default_binding_version() {
use crate::env_packs::local_process::LocalProcessDeployerHandler;
let lpdh = LocalProcessDeployerHandler::default();
for (slot, descriptor) in crate::defaults::LOCAL_DEFAULT_BINDINGS {
let pd = PackDescriptor::try_new(*descriptor).expect("default descriptor parses");
let req = BUILTIN_HANDLERS
.iter()
.find(|h| h.descriptor_path() == pd.path())
.map(|h| h.supported_versions())
.or_else(|| {
if lpdh.descriptor_path() == pd.path() {
Some(lpdh.supported_versions())
} else {
None
}
})
.unwrap_or_else(|| panic!("no built-in handler for {slot}"));
assert!(
req.matches(&pd.version().0),
"{descriptor}: req {req} rejects its own default version"
);
}
}
}