Skip to main content

canic_core/ids/
canister.rs

1//! Strongly-typed identifiers representing canister roles within the project.
2//!
3//! Provides string-backed wrappers with storage traits and helpers for config
4//! parsing while avoiding repeated `Cow` boilerplate around the codebase.
5use crate::memory::impl_storable_bounded;
6use candid::CandidType;
7use serde::{Deserialize, Serialize};
8use std::{borrow::Borrow, borrow::Cow, fmt, str::FromStr};
9
10///
11/// CanisterRole
12///
13/// A human-readable identifier for a canister role/type (e.g., "root", "example").
14///
15/// Stored as `Cow<'static, str>` so known constants can be zero‑copy while
16/// dynamic values allocate only when needed.
17///
18
19const ROOT_ROLE: &str = "root";
20const WASM_STORE_ROLE: &str = "wasm_store";
21
22#[derive(
23    CandidType, Clone, Debug, Eq, Ord, PartialOrd, Deserialize, Serialize, PartialEq, Hash,
24)]
25#[serde(transparent)]
26pub struct CanisterRole(pub Cow<'static, str>);
27
28impl CanisterRole {
29    pub const ROOT: Self = Self(Cow::Borrowed(ROOT_ROLE));
30    pub const WASM_STORE: Self = Self(Cow::Borrowed(WASM_STORE_ROLE));
31
32    #[must_use]
33    pub const fn new(s: &'static str) -> Self {
34        Self(Cow::Borrowed(s))
35    }
36
37    #[must_use]
38    pub const fn owned(s: String) -> Self {
39        Self(Cow::Owned(s))
40    }
41
42    #[must_use]
43    pub fn as_str(&self) -> &str {
44        &self.0
45    }
46
47    /// Returns true if this type represents the built-in ROOT canister.
48    #[must_use]
49    pub fn is_root(&self) -> bool {
50        self.0.as_ref() == ROOT_ROLE
51    }
52
53    /// Returns true if this role represents the built-in wasm store canister.
54    #[must_use]
55    pub fn is_wasm_store(&self) -> bool {
56        self.0.as_ref() == WASM_STORE_ROLE
57    }
58
59    /// Convert into an owned string (avoids an extra allocation for owned variants).
60    #[must_use]
61    pub fn into_string(self) -> String {
62        self.0.into_owned()
63    }
64}
65
66impl FromStr for CanisterRole {
67    type Err = String;
68
69    fn from_str(s: &str) -> Result<Self, Self::Err> {
70        Ok(Self::owned(s.to_string()))
71    }
72}
73
74impl From<&'static str> for CanisterRole {
75    fn from(s: &'static str) -> Self {
76        Self(Cow::Borrowed(s))
77    }
78}
79
80impl From<&String> for CanisterRole {
81    fn from(s: &String) -> Self {
82        Self(Cow::Owned(s.clone()))
83    }
84}
85
86impl From<String> for CanisterRole {
87    fn from(s: String) -> Self {
88        Self(Cow::Owned(s))
89    }
90}
91
92impl From<CanisterRole> for String {
93    fn from(role: CanisterRole) -> Self {
94        role.into_string()
95    }
96}
97
98impl AsRef<str> for CanisterRole {
99    fn as_ref(&self) -> &str {
100        self.as_str()
101    }
102}
103
104impl Borrow<str> for CanisterRole {
105    fn borrow(&self) -> &str {
106        self.as_str()
107    }
108}
109
110impl fmt::Display for CanisterRole {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        f.write_str(self.as_str())
113    }
114}
115
116impl_storable_bounded!(CanisterRole, 64, false);
117
118///
119/// TESTS
120///
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn basic_traits_and_utils() {
128        let a = CanisterRole::ROOT;
129        assert!(a.is_root());
130        assert_eq!(a.as_str(), "root");
131        let b: CanisterRole = "example".into();
132        assert_eq!(b.as_str(), "example");
133        let s: String = b.clone().into();
134        assert_eq!(s, "example");
135        assert_eq!(b.as_ref(), "example");
136    }
137}