icydb_model/node/
canister.rs1#[cfg(test)]
8mod tests;
9
10use crate::node::{stable_memory_key, validate_stable_key, validate_stable_key_segment};
11use crate::prelude::*;
12use std::collections::BTreeMap;
13use std::str::FromStr;
14
15pub type MigrationPlanConstructor =
17 fn() -> Result<icydb_schema::SchemaMigrationPlan, icydb_schema::SchemaContractError>;
18
19#[doc(hidden)]
21pub fn migration_literal_from_text(
22 kind: &str,
23 value: &str,
24) -> Result<icydb_schema::ScalarLiteral, icydb_schema::SchemaContractError> {
25 use icydb_schema::{
26 Account, Blob, Date, Decimal, Duration, Principal, ScalarLiteral, Subaccount, Timestamp,
27 U256, Ulid,
28 };
29
30 let invalid = || icydb_schema::SchemaContractError::InvalidLiteral;
31 match kind {
32 "account" => Account::from_str(value)
33 .map(ScalarLiteral::Account)
34 .map_err(|_| invalid()),
35 "blob" => Blob::from_hex_literal(value).map(ScalarLiteral::Blob),
36 "date" => Date::parse(value)
37 .map(ScalarLiteral::Date)
38 .ok_or_else(invalid),
39 "decimal" => Decimal::from_str(value)
40 .map(ScalarLiteral::Decimal)
41 .map_err(|_| invalid()),
42 "duration" => Duration::parse_flexible(value)
43 .map(ScalarLiteral::Duration)
44 .map_err(|_| invalid()),
45 "principal" => Principal::from_str(value)
46 .map(ScalarLiteral::Principal)
47 .map_err(|_| invalid()),
48 "subaccount" => {
49 if value.len() != 64 {
51 return Err(invalid());
52 }
53 Subaccount::from_hex(value).map(ScalarLiteral::Subaccount)
54 }
55 "timestamp" => Timestamp::parse_flexible(value)
56 .map(ScalarLiteral::Timestamp)
57 .map_err(|_| invalid()),
58 "u256" => U256::from_str(value)
59 .map(ScalarLiteral::U256)
60 .map_err(|_| invalid()),
61 "ulid" => Ulid::from_str(value)
62 .map(ScalarLiteral::Ulid)
63 .map_err(|_| invalid()),
64 _ => Err(invalid()),
65 }
66}
67
68#[derive(Clone, Debug, Serialize)]
73pub struct Canister {
74 def: Def,
75 memory_namespace: &'static str,
76 #[serde(skip)]
77 migration_plan: Option<MigrationPlanConstructor>,
78}
79
80impl Canister {
81 #[must_use]
82 pub const fn new(
83 def: Def,
84 memory_namespace: &'static str,
85 migration_plan: Option<MigrationPlanConstructor>,
86 ) -> Self {
87 Self {
88 def,
89 memory_namespace,
90 migration_plan,
91 }
92 }
93
94 #[must_use]
95 pub const fn def(&self) -> &Def {
96 &self.def
97 }
98
99 #[must_use]
100 pub const fn memory_namespace(&self) -> &'static str {
101 self.memory_namespace
102 }
103
104 pub fn migration_plan(
110 &self,
111 ) -> Result<Option<icydb_schema::SchemaMigrationPlan>, icydb_schema::SchemaContractError> {
112 self.migration_plan
113 .map(|constructor| constructor())
114 .transpose()
115 }
116
117 #[must_use]
118 pub fn commit_stable_key(&self) -> String {
119 stable_memory_key(self.memory_namespace(), "commit", "control")
120 }
121
122 #[must_use]
123 pub fn integrity_progress_stable_key(&self) -> String {
124 stable_memory_key(self.memory_namespace(), "integrity", "progress")
125 }
126
127 #[must_use]
128 pub fn startup_stable_key(&self) -> String {
129 stable_memory_key(self.memory_namespace(), "startup", "control")
130 }
131}
132
133impl MacroNode for Canister {
134 fn as_any(&self) -> &dyn std::any::Any {
135 self
136 }
137}
138
139impl ValidateNode for Canister {
140 fn validate(&self) -> Result<(), ErrorTree> {
141 let mut errs = ErrorTree::new();
142 validate_stable_key_segment(
143 &mut errs,
144 "canister memory_namespace",
145 self.memory_namespace(),
146 );
147 for key in [
148 self.commit_stable_key(),
149 self.startup_stable_key(),
150 self.integrity_progress_stable_key(),
151 ] {
152 validate_stable_key(&mut errs, "canister control key", &key);
153 }
154
155 let canister_path = self.def().path();
156 {
157 let schema = schema_read();
158 let mut seen = BTreeMap::new();
159 for (path, store) in
160 schema.filter_nodes::<Store>(|store| store.canister() == canister_path)
161 {
162 if let Some(config) = store.journaled_memory_config()
163 && let Some(previous) = seen.insert(config.key(), path)
164 {
165 err!(
166 errs,
167 "duplicate store key `{}` in canister `{}`: {} conflicts with {}",
168 config.key(),
169 canister_path,
170 previous,
171 path
172 );
173 }
174 }
175 }
176 errs.result()
177 }
178}
179
180impl VisitableNode for Canister {
181 fn route_key(&self) -> String {
182 self.def().path()
183 }
184}