pub mod adapter;
use crate::{
InternalError,
dto::{
cascade::StateSnapshotInput,
state::{AppStateInput, SubnetStateInput},
topology::{AppDirectoryArgs, SubnetDirectoryArgs},
},
ops::{
runtime::env::EnvOps,
storage::{
registry::subnet::SubnetRegistryOps,
state::{app::AppStateOps, subnet::SubnetStateOps},
},
topology::directory::{AppDirectoryResolver, SubnetDirectoryResolver},
},
workflow::prelude::*,
};
use std::collections::HashMap;
#[derive(Default)]
pub struct StateSnapshot {
pub app_state: Option<AppStateInput>,
pub subnet_state: Option<SubnetStateInput>,
pub app_directory: Option<AppDirectoryArgs>,
pub subnet_directory: Option<SubnetDirectoryArgs>,
}
pub struct StateSnapshotBuilder {
snapshot: StateSnapshot,
}
impl StateSnapshotBuilder {
pub fn new() -> Result<Self, InternalError> {
EnvOps::require_root()?;
Ok(Self {
snapshot: StateSnapshot::default(),
})
}
#[must_use]
pub fn with_app_state(mut self) -> Self {
self.snapshot.app_state = Some(AppStateOps::snapshot_input());
self
}
#[must_use]
pub fn with_subnet_state(mut self) -> Self {
self.snapshot.subnet_state = Some(SubnetStateOps::snapshot_input());
self
}
pub fn with_app_directory(mut self) -> Result<Self, InternalError> {
self.snapshot.app_directory = Some(AppDirectoryResolver::resolve_input()?);
Ok(self)
}
pub fn with_subnet_directory(mut self) -> Result<Self, InternalError> {
self.snapshot.subnet_directory = Some(SubnetDirectoryResolver::resolve_input()?);
Ok(self)
}
#[must_use]
pub fn build(self) -> StateSnapshot {
self.snapshot
}
}
impl From<StateSnapshotInput> for StateSnapshot {
fn from(snapshot: StateSnapshotInput) -> Self {
Self {
app_state: snapshot.app_state,
subnet_state: snapshot.subnet_state,
app_directory: snapshot.app_directory,
subnet_directory: snapshot.subnet_directory,
}
}
}
#[derive(Clone, Debug)]
pub struct TopologySnapshot {
pub(crate) parents: Vec<TopologyPathNode>,
pub(crate) children_map: HashMap<Principal, Vec<TopologyDirectChild>>,
}
#[derive(Clone, Debug)]
pub struct TopologyPathNode {
pub(crate) pid: Principal,
pub(crate) role: CanisterRole,
pub(crate) parent_pid: Option<Principal>,
}
#[derive(Clone, Debug)]
pub struct TopologyDirectChild {
pub(crate) pid: Principal,
pub(crate) role: CanisterRole,
}
pub struct TopologySnapshotBuilder {
snapshot: TopologySnapshot,
}
impl TopologySnapshotBuilder {
pub(crate) fn for_target(target_pid: Principal) -> Result<Self, InternalError> {
let parents: Vec<TopologyPathNode> = SubnetRegistryOps::parent_chain(target_pid)?
.into_iter()
.map(|(pid, record)| TopologyPathNode {
pid,
role: record.role.clone(),
parent_pid: record.parent_pid,
})
.collect();
let parent_pids: Vec<Principal> = parents.iter().map(|parent| parent.pid).collect();
let raw_children = SubnetRegistryOps::direct_children_map(&parent_pids);
let children_map: HashMap<Principal, Vec<TopologyDirectChild>> = raw_children
.into_iter()
.map(|(parent_pid, children)| {
let mapped = children
.into_iter()
.map(|(pid, record)| TopologyDirectChild {
pid,
role: record.role,
})
.collect();
(parent_pid, mapped)
})
.collect();
Ok(Self {
snapshot: TopologySnapshot {
parents,
children_map,
},
})
}
#[must_use]
pub fn build(self) -> TopologySnapshot {
self.snapshot
}
}
#[must_use]
pub const fn state_snapshot_is_empty(snapshot: &StateSnapshot) -> bool {
snapshot.app_state.is_none()
&& snapshot.subnet_state.is_none()
&& snapshot.app_directory.is_none()
&& snapshot.subnet_directory.is_none()
}
#[must_use]
pub fn state_snapshot_debug(snapshot: &StateSnapshot) -> String {
const fn fmt(present: bool, code: &str) -> &str {
if present { code } else { ".." }
}
format!(
"[{} {} {} {}]",
fmt(snapshot.app_state.is_some(), "as"),
fmt(snapshot.subnet_state.is_some(), "ss"),
fmt(snapshot.app_directory.is_some(), "ad"),
fmt(snapshot.subnet_directory.is_some(), "sd"),
)
}
#[cfg(test)]
mod tests {
use super::StateSnapshot;
use crate::dto::state::{AppMode, AppStateInput, SubnetStateInput};
#[test]
fn state_snapshot_debug_includes_subnet_state_slot() {
let snapshot = StateSnapshot {
app_state: Some(AppStateInput {
mode: AppMode::Enabled,
cycles_funding_enabled: true,
}),
subnet_state: Some(SubnetStateInput),
app_directory: None,
subnet_directory: None,
};
assert_eq!(super::state_snapshot_debug(&snapshot), "[as ss .. ..]");
}
}