use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use crate::value_objects::{HostAgentIncarnation, RoleId};
use super::{
HostDeliveryTarget, HostDestination, IntegratorBindingId, IntegratorFence, IntegratorScope,
IntegratorScopeKey, LoopProgressMark,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntegratorBinding {
id: IntegratorBindingId,
scope: IntegratorScope,
role_id: RoleId,
destination: HostDestination,
incarnation: HostAgentIncarnation,
fence: IntegratorFence,
#[serde(with = "time::serde::rfc3339")]
bound_at: OffsetDateTime,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde(with = "time::serde::rfc3339::option")]
revoked_at: Option<OffsetDateTime>,
#[serde(default)]
progress: LoopProgressMark,
}
impl IntegratorBinding {
#[must_use]
pub const fn new(
id: IntegratorBindingId,
scope: IntegratorScope,
role_id: RoleId,
destination: HostDestination,
incarnation: HostAgentIncarnation,
bound_at: OffsetDateTime,
) -> Self {
Self {
id,
scope,
role_id,
destination,
incarnation,
fence: IntegratorFence::FIRST,
bound_at,
revoked_at: None,
progress: LoopProgressMark::new(None, 0, 0, 0),
}
}
#[must_use]
pub const fn id(&self) -> &IntegratorBindingId {
&self.id
}
#[must_use]
pub const fn scope(&self) -> &IntegratorScope {
&self.scope
}
#[must_use]
pub fn scope_key(&self) -> IntegratorScopeKey {
self.scope.scope_key()
}
#[must_use]
pub const fn role_id(&self) -> &RoleId {
&self.role_id
}
#[must_use]
pub const fn destination(&self) -> &HostDestination {
&self.destination
}
#[must_use]
pub const fn incarnation(&self) -> &HostAgentIncarnation {
&self.incarnation
}
#[must_use]
pub const fn fence(&self) -> IntegratorFence {
self.fence
}
#[must_use]
pub const fn bound_at(&self) -> OffsetDateTime {
self.bound_at
}
#[must_use]
pub const fn revoked_at(&self) -> Option<OffsetDateTime> {
self.revoked_at
}
#[must_use]
pub const fn progress(&self) -> LoopProgressMark {
self.progress
}
#[must_use]
pub fn observing(&self, progress: LoopProgressMark) -> Self {
Self {
progress,
..self.clone()
}
}
#[must_use]
pub const fn is_live(&self) -> bool {
self.revoked_at.is_none()
}
#[must_use]
pub fn admits(&self, incarnation: &HostAgentIncarnation, fence: IntegratorFence) -> bool {
self.is_live() && &self.incarnation == incarnation && self.fence == fence
}
#[must_use]
pub fn delivery_target(&self) -> HostDeliveryTarget {
HostDeliveryTarget::integrator_binding(self.id.clone())
}
#[must_use]
pub fn replacing(&self, replacement: &Self) -> Self {
Self {
fence: self.fence.next(),
revoked_at: None,
progress: LoopProgressMark::new(None, 0, 0, 0),
..replacement.clone()
}
}
#[must_use]
pub fn revoked(&self, at: OffsetDateTime) -> Self {
Self {
revoked_at: Some(at),
..self.clone()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value_objects::{CeremonyId, HostActivationMode, HostAddress, HostKind};
fn binding(id: &str, incarnation: &str) -> IntegratorBinding {
IntegratorBinding::new(
IntegratorBindingId::new(id).unwrap(),
IntegratorScope::ceremony(CeremonyId::new("c-1").unwrap()),
RoleId::new("INTEGRATOR").unwrap(),
HostDestination::new(
HostKind::new("generic").unwrap(),
HostAddress::new("session-1").unwrap(),
HostActivationMode::None,
),
HostAgentIncarnation::new(incarnation).unwrap(),
OffsetDateTime::UNIX_EPOCH,
)
}
#[test]
fn a_replaced_binding_outranks_the_one_it_replaced() {
let first = binding("b-1", "run-1");
let second = first.replacing(&binding("b-2", "run-2"));
assert_eq!(second.fence(), IntegratorFence::FIRST.next());
assert!(second.admits(&HostAgentIncarnation::new("run-2").unwrap(), second.fence()));
assert!(!second.admits(
&HostAgentIncarnation::new("run-1").unwrap(),
IntegratorFence::FIRST
));
}
#[test]
fn a_revoked_binding_admits_nobody() {
let revoked = binding("b-1", "run-1").revoked(OffsetDateTime::UNIX_EPOCH);
assert!(!revoked.is_live());
assert!(!revoked.admits(
&HostAgentIncarnation::new("run-1").unwrap(),
IntegratorFence::FIRST
));
}
}