use crate::models::{Capability, CapabilityExt};
use uuid::Uuid;
pub use peat_schema::cell::v1::{CellConfig, CellState};
pub trait CellConfigExt {
fn new(max_size: u32) -> Self;
fn with_id(id: String, max_size: u32) -> Self;
}
impl CellConfigExt for CellConfig {
fn new(max_size: u32) -> Self {
Self {
id: Uuid::new_v4().to_string(),
max_size,
min_size: 2,
created_at: Some(peat_schema::common::v1::Timestamp {
seconds: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
nanos: 0,
}),
}
}
fn with_id(id: String, max_size: u32) -> Self {
Self {
id,
max_size,
min_size: 2,
created_at: Some(peat_schema::common::v1::Timestamp {
seconds: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
nanos: 0,
}),
}
}
}
pub trait CellStateExt {
fn new(config: CellConfig) -> Self;
fn update_timestamp(&mut self);
fn is_full(&self) -> bool;
fn is_valid(&self) -> bool;
fn add_member(&mut self, node_id: String) -> bool;
fn remove_member(&mut self, node_id: &str) -> bool;
fn set_leader(&mut self, node_id: String) -> Result<(), &'static str>;
fn clear_leader(&mut self);
fn add_capability(&mut self, capability: Capability);
fn get_capabilities_by_type(
&self,
capability_type: crate::models::CapabilityType,
) -> Vec<&Capability>;
fn has_capability_type(&self, capability_type: crate::models::CapabilityType) -> bool;
fn assign_platoon(&mut self, platoon_id: String);
fn leave_platoon(&mut self);
fn merge(&mut self, other: &CellState);
fn member_count(&self) -> usize;
fn is_member(&self, node_id: &str) -> bool;
fn is_leader(&self, node_id: &str) -> bool;
fn get_id(&self) -> Option<&str>;
}
impl CellStateExt for CellState {
fn new(config: CellConfig) -> Self {
Self {
config: Some(config),
leader_id: None,
members: Vec::new(),
capabilities: Vec::new(),
platoon_id: None,
timestamp: Some(peat_schema::common::v1::Timestamp {
seconds: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
nanos: 0,
}),
}
}
fn update_timestamp(&mut self) {
self.timestamp = Some(peat_schema::common::v1::Timestamp {
seconds: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
nanos: 0,
});
}
fn is_full(&self) -> bool {
if let Some(ref config) = self.config {
self.members.len() >= config.max_size as usize
} else {
false
}
}
fn is_valid(&self) -> bool {
if let Some(ref config) = self.config {
self.members.len() >= config.min_size as usize
} else {
false
}
}
fn add_member(&mut self, node_id: String) -> bool {
if self.is_full() {
false
} else {
if self.members.contains(&node_id) {
false
} else {
self.members.push(node_id);
self.update_timestamp();
true
}
}
}
fn remove_member(&mut self, node_id: &str) -> bool {
if let Some(pos) = self.members.iter().position(|id| id == node_id) {
self.members.remove(pos);
self.update_timestamp();
if self.leader_id.as_deref() == Some(node_id) {
self.leader_id = None;
}
true
} else {
false
}
}
fn set_leader(&mut self, node_id: String) -> Result<(), &'static str> {
if !self.members.contains(&node_id) {
return Err("Leader must be a squad member");
}
self.leader_id = Some(node_id);
self.update_timestamp();
Ok(())
}
fn clear_leader(&mut self) {
self.leader_id = None;
self.update_timestamp();
}
fn add_capability(&mut self, capability: Capability) {
if !self.capabilities.iter().any(|c| c.id == capability.id) {
self.capabilities.push(capability);
self.update_timestamp();
}
}
fn get_capabilities_by_type(
&self,
capability_type: crate::models::CapabilityType,
) -> Vec<&Capability> {
self.capabilities
.iter()
.filter(|c| c.get_capability_type() == capability_type)
.collect()
}
fn has_capability_type(&self, capability_type: crate::models::CapabilityType) -> bool {
self.capabilities
.iter()
.any(|c| c.get_capability_type() == capability_type)
}
fn assign_platoon(&mut self, platoon_id: String) {
self.platoon_id = Some(platoon_id);
self.update_timestamp();
}
fn leave_platoon(&mut self) {
self.platoon_id = None;
self.update_timestamp();
}
fn merge(&mut self, other: &CellState) {
for member in &other.members {
if !self.members.contains(member) {
self.members.push(member.clone());
}
}
for cap in &other.capabilities {
if !self.capabilities.iter().any(|c| c.id == cap.id) {
self.capabilities.push(cap.clone());
}
}
let self_ts = self.timestamp.as_ref().map(|t| t.seconds).unwrap_or(0);
let other_ts = other.timestamp.as_ref().map(|t| t.seconds).unwrap_or(0);
if other_ts > self_ts {
self.leader_id = other.leader_id.clone();
self.platoon_id = other.platoon_id.clone();
self.timestamp = other.timestamp;
}
}
fn member_count(&self) -> usize {
self.members.len()
}
fn is_member(&self, node_id: &str) -> bool {
self.members.iter().any(|id| id == node_id)
}
fn is_leader(&self, node_id: &str) -> bool {
self.leader_id.as_deref() == Some(node_id)
}
fn get_id(&self) -> Option<&str> {
self.config.as_ref().map(|c| c.id.as_str())
}
}
#[cfg(test)]
mod tests;