canic_core/config/schema/
mod.rs1mod log;
2mod subnet;
3
4pub use log::*;
5pub use subnet::*;
6
7use crate::{
8 Error,
9 config::ConfigError,
10 ids::{CanisterRole, SubnetRole},
11};
12use candid::Principal;
13use serde::{Deserialize, Serialize};
14use std::collections::{BTreeMap, BTreeSet};
15use thiserror::Error as ThisError;
16
17#[derive(Debug, ThisError)]
22pub enum ConfigSchemaError {
23 #[error("validation error: {0}")]
24 ValidationError(String),
25}
26
27impl From<ConfigSchemaError> for Error {
28 fn from(err: ConfigSchemaError) -> Self {
29 ConfigError::from(err).into()
30 }
31}
32
33pub trait Validate {
38 fn validate(&self) -> Result<(), ConfigSchemaError>;
39}
40
41#[derive(Clone, Debug, Default, Deserialize, Serialize)]
46#[serde(deny_unknown_fields)]
47pub struct ConfigModel {
48 #[serde(default)]
51 pub controllers: Vec<Principal>,
52
53 #[serde(default)]
54 pub standards: Option<Standards>,
55
56 #[serde(default)]
57 pub log: LogConfig,
58
59 #[serde(default)]
60 pub app_directory: BTreeSet<CanisterRole>,
61
62 #[serde(default)]
63 pub subnets: BTreeMap<SubnetRole, SubnetConfig>,
64
65 #[serde(default)]
66 pub whitelist: Option<Whitelist>,
67}
68
69impl ConfigModel {
70 #[must_use]
72 pub fn get_subnet(&self, ty: &SubnetRole) -> Option<SubnetConfig> {
73 self.subnets.get(ty).cloned()
74 }
75
76 #[cfg(test)]
78 #[must_use]
79 pub fn test_default() -> Self {
80 let mut cfg = Self::default();
81 cfg.subnets
82 .insert(SubnetRole::PRIME, SubnetConfig::default());
83 cfg
84 }
85
86 #[must_use]
88 pub fn is_whitelisted(&self, principal: &Principal) -> bool {
89 self.whitelist
90 .as_ref()
91 .is_none_or(|w| w.principals.contains(&principal.to_string()))
92 }
93
94 #[must_use]
96 pub fn icrc21_enabled(&self) -> bool {
97 self.standards.as_ref().is_some_and(|s| s.icrc21)
98 }
99}
100
101impl Validate for ConfigModel {
102 fn validate(&self) -> Result<(), ConfigSchemaError> {
103 let prime = SubnetRole::PRIME;
105 let prime_subnet = self
106 .subnets
107 .get(&prime)
108 .ok_or_else(|| ConfigSchemaError::ValidationError("prime subnet not found".into()))?;
109
110 for canister_ty in &self.app_directory {
112 if !prime_subnet.canisters.contains_key(canister_ty) {
113 return Err(ConfigSchemaError::ValidationError(format!(
114 "app directory canister '{canister_ty}' is not in prime subnet",
115 )));
116 }
117 }
118
119 if let Some(list) = &self.whitelist {
121 list.validate()?;
122 }
123 for subnet in self.subnets.values() {
124 subnet.validate()?;
125 }
126
127 Ok(())
128 }
129}
130
131#[derive(Clone, Debug, Default, Deserialize, Serialize)]
136#[serde(deny_unknown_fields)]
137pub struct Whitelist {
138 #[serde(default)]
142 pub principals: BTreeSet<String>,
143}
144
145impl Validate for Whitelist {
146 fn validate(&self) -> Result<(), ConfigSchemaError> {
147 for (i, s) in self.principals.iter().enumerate() {
148 if Principal::from_text(s).is_err() {
149 return Err(ConfigSchemaError::ValidationError(format!(
150 "principal #{i} {s} is invalid"
151 )));
152 }
153 }
154
155 Ok(())
156 }
157}
158
159#[derive(Clone, Debug, Default, Deserialize, Serialize)]
164#[serde(deny_unknown_fields)]
165pub struct Standards {
166 #[serde(default)]
167 pub icrc21: bool,
168
169 #[serde(default)]
170 pub icrc103: bool,
171}