Skip to main content

ic_backup/model/effect_graph/node/
mod.rs

1//! Passive exact operation identity and bounded explicit prerequisite admission.
2
3use super::EffectGraphError;
4use serde::{Deserialize, Deserializer, Serialize, de};
5use std::fmt;
6
7/// Maximum direct prerequisites on one operation, independently of total graph bounds.
8pub const MAX_EFFECT_DEPENDENCIES: usize = 1024;
9
10/// Passive operation dependency declaration, without an execution request or authority.
11#[derive(Clone, Debug)]
12pub struct EffectNodeRequest {
13    /// Exact opaque operation identity; zero and nonconsecutive u64 values are admitted.
14    pub operation_sequence: u64,
15    /// Exact operations required before this operation; duplicate declarations reject.
16    pub depends_on: Vec<u64>,
17}
18
19/// Canonical immutable operation node with sorted unique explicit dependencies.
20#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
21#[serde(try_from = "NodeFields")]
22pub struct EffectNodeRecord {
23    operation_sequence: u64,
24    depends_on: Vec<u64>,
25}
26#[derive(Deserialize)]
27#[serde(deny_unknown_fields)]
28struct NodeFields {
29    operation_sequence: u64,
30    #[serde(deserialize_with = "bounded_dependencies")]
31    depends_on: Vec<u64>,
32}
33impl TryFrom<NodeFields> for EffectNodeRecord {
34    type Error = EffectGraphError;
35    fn try_from(fields: NodeFields) -> Result<Self, Self::Error> {
36        Self::new(EffectNodeRequest {
37            operation_sequence: fields.operation_sequence,
38            depends_on: fields.depends_on,
39        })
40    }
41}
42impl EffectNodeRecord {
43    /// Admit finite sorted explicit dependencies; graph admission owns closure/cycles.
44    ///
45    /// # Errors
46    /// Rejects excessive direct dependencies and duplicate exact prerequisites.
47    pub fn new(mut request: EffectNodeRequest) -> Result<Self, EffectGraphError> {
48        if request.depends_on.len() > MAX_EFFECT_DEPENDENCIES {
49            return Err(EffectGraphError::TooManyDependencies);
50        }
51        request.depends_on.sort_unstable();
52        for pair in request.depends_on.windows(2) {
53            if pair[0] == pair[1] {
54                return Err(EffectGraphError::DuplicateDependency {
55                    operation_sequence: request.operation_sequence,
56                    dependency: pair[0],
57                });
58            }
59        }
60        Ok(Self {
61            operation_sequence: request.operation_sequence,
62            depends_on: request.depends_on,
63        })
64    }
65    /// Read the exact opaque operation sequence identity.
66    #[must_use]
67    pub const fn operation_sequence(&self) -> u64 {
68        self.operation_sequence
69    }
70    /// Read sorted exact explicit prerequisites.
71    #[must_use]
72    pub fn depends_on(&self) -> &[u64] {
73        &self.depends_on
74    }
75}
76
77fn bounded_dependencies<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u64>, D::Error> {
78    struct DependenciesVisitor;
79    impl<'de> de::Visitor<'de> for DependenciesVisitor {
80        type Value = Vec<u64>;
81        fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82            f.write_str("bounded exact effect dependencies")
83        }
84        fn visit_seq<A: de::SeqAccess<'de>>(
85            self,
86            mut sequence: A,
87        ) -> Result<Self::Value, A::Error> {
88            let mut dependencies = Vec::new();
89            while dependencies.len() < MAX_EFFECT_DEPENDENCIES {
90                match sequence.next_element()? {
91                    Some(dependency) => dependencies.push(dependency),
92                    None => return Ok(dependencies),
93                }
94            }
95            if sequence.next_element::<de::IgnoredAny>()?.is_some() {
96                return Err(de::Error::custom(EffectGraphError::TooManyDependencies));
97            }
98            Ok(dependencies)
99        }
100    }
101    deserializer.deserialize_seq(DependenciesVisitor)
102}