#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
pub mod attestation;
pub mod audit;
pub mod backend;
pub mod backend_fs;
pub mod backends;
pub mod cluster;
pub mod config_bridge;
pub mod consensus;
pub mod content;
pub mod fabric;
pub mod face;
pub mod face_store;
pub mod federation;
pub mod format;
pub mod membership;
pub mod policy;
pub mod topology;
pub use audit::{
AuditEvent, AuditLog, AuditingBackend, FileAuditLog, InMemoryAuditLog, NoopAuditLog,
VerbKind,
};
pub use backend::{
StoreBackend, StubBackend, kube_apiserver_stub, nomad_http_stub, raft_stub,
supervised_systemd_stub, systemd_dbus_stub,
};
pub use backend_fs::FileSystemBackend;
pub use backends::{
KubeApiServerBackend, KubeApiServerConfig, NomadHttpBackend, NomadHttpConfig,
RaftBackend, RaftConfig, SupervisedSystemdBackend, SupervisedSystemdConfig,
SystemdDbusBackend, SystemdDbusConfig,
};
pub use format::{
AdapterError, FormatAdapter, HclAdapter, K8sJsonAdapter, K8sYamlAdapter,
NativePassthroughAdapter, encode_envelope,
};
pub use federation::{
FederatedEventKind, FederatedFabric, FederationError, RoutingPolicy,
};
pub use topology::{
Cluster3MNW, MeshAllPeers, NodeId as TopologyNodeId, NodeState, Pair, Phalanx,
Quorum3M, Role, RoleAssignment, Solo, TopologyError, TopologyReactor,
TopologyStrategy, Transition,
};
pub use fabric::{
AttestationConfig, ConsensusConfig, ConsensusKind, ContentConfig, FabricFace,
FabricStrategy, FabricStrategyError, FaceKind, MembershipConfig, PlacementPolicy,
ReconciliationCadence,
};
pub use face::{
BareMetalSupervisorFace, Face, FaceError, FaceWatchEvent, FaceWatchEventKind,
FaceWatchStream, KubernetesFace, NomadFace, PureRaftFace, ResourceFormat, ResourceRef,
SystemdFace, instantiate as instantiate_face,
};
pub use cluster::{
Cluster, ClusterBuilder, ClusterBuilderError, ClusterCoherenceError, ClusterDeclaration,
ClusterHealth, ClusterRuntimeError,
};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct NodeId(pub [u8; 32]);
impl NodeId {
#[must_use]
pub fn new(bytes: [u8; 32]) -> Self {
Self(bytes)
}
#[must_use]
pub fn to_hex(&self) -> String {
let mut out = String::with_capacity(64);
for b in self.0 {
out.push_str(&format!("{b:02x}"));
}
out
}
pub fn from_hex(s: &str) -> Result<Self, String> {
if s.is_empty() || s.len() > 64 {
return Err(format!("hex must be 1..=64 chars, got {}", s.len()));
}
let padded = format!("{s:0>64}");
let mut bytes = [0u8; 32];
for (i, byte) in bytes.iter_mut().enumerate() {
let pair = &padded[i * 2..i * 2 + 2];
*byte = u8::from_str_radix(pair, 16).map_err(|e| e.to_string())?;
}
Ok(Self(bytes))
}
}
impl std::fmt::Display for NodeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for b in &self.0[..6] {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn node_id_hex_round_trips() {
let id = NodeId::new([0xab; 32]);
let hex = id.to_hex();
assert_eq!(hex.len(), 64);
assert!(hex.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn node_id_display_is_short_prefix() {
let id = NodeId::new([0xab; 32]);
assert_eq!(id.to_string(), "abababababab");
}
#[test]
fn node_id_serde_round_trips() {
let id = NodeId::new([0xcd; 32]);
let json = serde_json::to_string(&id).unwrap();
let back: NodeId = serde_json::from_str(&json).unwrap();
assert_eq!(back, id);
}
}