actionqueue_core/
actor.rs1use crate::ids::{ActorId, DepartmentId, TenantId};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct ActorCapabilities {
22 capabilities: Vec<String>,
23}
24
25impl ActorCapabilities {
26 pub fn new(capabilities: Vec<String>) -> Result<Self, String> {
32 if capabilities.is_empty() {
33 return Err("actor must declare at least one capability".to_string());
34 }
35 for cap in &capabilities {
36 if cap.is_empty() {
37 return Err("capability string must be non-empty".to_string());
38 }
39 }
40 Ok(ActorCapabilities { capabilities })
41 }
42
43 pub fn as_slice(&self) -> &[String] {
45 &self.capabilities
46 }
47
48 pub fn satisfies(&self, required: &[String]) -> bool {
50 required.iter().all(|r| self.capabilities.iter().any(|c| c == r))
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60pub struct ActorRegistration {
61 actor_id: ActorId,
62 identity: String,
64 capabilities: ActorCapabilities,
65 department: Option<DepartmentId>,
66 heartbeat_interval_secs: u64,
67 tenant_id: Option<TenantId>,
68}
69
70impl ActorRegistration {
71 pub fn new(
79 actor_id: ActorId,
80 identity: impl Into<String>,
81 capabilities: ActorCapabilities,
82 heartbeat_interval_secs: u64,
83 ) -> Self {
84 let identity = identity.into();
85 assert!(!identity.is_empty(), "actor identity must be non-empty");
86 assert!(heartbeat_interval_secs > 0, "heartbeat_interval_secs must be > 0");
87 ActorRegistration {
88 actor_id,
89 identity,
90 capabilities,
91 department: None,
92 heartbeat_interval_secs,
93 tenant_id: None,
94 }
95 }
96
97 pub fn with_department(mut self, department: DepartmentId) -> Self {
99 self.department = Some(department);
100 self
101 }
102
103 pub fn with_tenant(mut self, tenant_id: TenantId) -> Self {
105 self.tenant_id = Some(tenant_id);
106 self
107 }
108
109 pub fn actor_id(&self) -> ActorId {
111 self.actor_id
112 }
113
114 pub fn identity(&self) -> &str {
116 &self.identity
117 }
118
119 pub fn capabilities(&self) -> &ActorCapabilities {
121 &self.capabilities
122 }
123
124 pub fn department(&self) -> Option<&DepartmentId> {
126 self.department.as_ref()
127 }
128
129 pub fn heartbeat_interval_secs(&self) -> u64 {
131 self.heartbeat_interval_secs
132 }
133
134 pub fn tenant_id(&self) -> Option<TenantId> {
136 self.tenant_id
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
145#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
146pub struct HeartbeatPolicy {
147 interval_secs: u64,
148 timeout_multiplier: u32,
150}
151
152impl HeartbeatPolicy {
153 pub const DEFAULT_MULTIPLIER: u32 = 3;
155
156 pub fn new(interval_secs: u64, timeout_multiplier: u32) -> Self {
162 assert!(interval_secs > 0, "heartbeat interval_secs must be > 0");
163 assert!(timeout_multiplier > 0, "timeout_multiplier must be > 0");
164 HeartbeatPolicy { interval_secs, timeout_multiplier }
165 }
166
167 pub fn with_default_multiplier(interval_secs: u64) -> Self {
169 Self::new(interval_secs, Self::DEFAULT_MULTIPLIER)
170 }
171
172 pub fn interval_secs(&self) -> u64 {
174 self.interval_secs
175 }
176
177 pub fn timeout_multiplier(&self) -> u32 {
179 self.timeout_multiplier
180 }
181
182 pub fn timeout_secs(&self) -> u64 {
184 self.interval_secs.saturating_mul(self.timeout_multiplier as u64)
185 }
186}
187
188impl Default for HeartbeatPolicy {
189 fn default() -> Self {
190 Self::new(30, Self::DEFAULT_MULTIPLIER)
191 }
192}