canic/config/schema/
mod.rs

1mod log;
2mod subnet;
3
4pub use log::*;
5pub use subnet::*;
6
7use crate::{
8    Error,
9    config::ConfigError,
10    types::{CanisterType, SubnetType},
11};
12use candid::Principal;
13use serde::{Deserialize, Serialize};
14use std::collections::{BTreeMap, BTreeSet};
15use thiserror::Error as ThisError;
16
17///
18/// ConfigSchemaError
19///
20
21#[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
33///
34/// Validate
35///
36
37pub trait Validate {
38    fn validate(&self) -> Result<(), ConfigSchemaError>;
39}
40
41///
42/// ConfigModel
43///
44
45#[derive(Clone, Debug, Default, Deserialize, Serialize)]
46#[serde(deny_unknown_fields)]
47pub struct ConfigModel {
48    // controllers
49    // a vec because we just append it to the controller arguments
50    #[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<CanisterType>,
61
62    #[serde(default)]
63    pub subnets: BTreeMap<SubnetType, SubnetConfig>,
64
65    #[serde(default)]
66    pub whitelist: Option<Whitelist>,
67}
68
69impl ConfigModel {
70    /// Get a subnet configuration by type.
71    #[must_use]
72    pub fn get_subnet(&self, ty: &SubnetType) -> Option<SubnetConfig> {
73        self.subnets.get(ty).cloned()
74    }
75
76    /// Return true if the given principal is present in the whitelist.
77    #[must_use]
78    pub fn is_whitelisted(&self, principal: &Principal) -> bool {
79        self.whitelist
80            .as_ref()
81            .is_none_or(|w| w.principals.contains(&principal.to_string()))
82    }
83
84    /// Return whether ICRC-21 standard support is enabled.
85    #[must_use]
86    pub fn icrc21_enabled(&self) -> bool {
87        self.standards.as_ref().is_some_and(|s| s.icrc21)
88    }
89}
90
91impl Validate for ConfigModel {
92    fn validate(&self) -> Result<(), ConfigSchemaError> {
93        //  Validate that prime subnet exists
94        let prime = SubnetType::PRIME;
95        let prime_subnet = self
96            .subnets
97            .get(&prime)
98            .ok_or_else(|| ConfigSchemaError::ValidationError("prime subnet not found".into()))?;
99
100        //  Validate that every app_directory entry exists in prime.canisters
101        for canister_ty in &self.app_directory {
102            if !prime_subnet.canisters.contains_key(canister_ty) {
103                return Err(ConfigSchemaError::ValidationError(format!(
104                    "app directory canister '{canister_ty}' is not in prime subnet",
105                )));
106            }
107        }
108
109        // child validation
110        if let Some(list) = &self.whitelist {
111            list.validate()?;
112        }
113        for subnet in self.subnets.values() {
114            subnet.validate()?;
115        }
116
117        Ok(())
118    }
119}
120
121///
122/// Whitelist
123///
124
125#[derive(Clone, Debug, Default, Deserialize, Serialize)]
126#[serde(deny_unknown_fields)]
127pub struct Whitelist {
128    // principals
129    // a hashset as we constantly have to do lookups
130    // strings because then we can validate and know if there are any bad ones
131    #[serde(default)]
132    pub principals: BTreeSet<String>,
133}
134
135impl Validate for Whitelist {
136    fn validate(&self) -> Result<(), ConfigSchemaError> {
137        for (i, s) in self.principals.iter().enumerate() {
138            if Principal::from_text(s).is_err() {
139                return Err(ConfigSchemaError::ValidationError(format!(
140                    "principal #{i} {s} is invalid"
141                )));
142            }
143        }
144
145        Ok(())
146    }
147}
148
149///
150/// Standards
151///
152
153#[derive(Clone, Debug, Default, Deserialize, Serialize)]
154#[serde(deny_unknown_fields)]
155pub struct Standards {
156    #[serde(default)]
157    pub icrc21: bool,
158
159    #[serde(default)]
160    pub icrc103: bool,
161}