Skip to main content

canic_core/api/ic/canic/
endpoint.rs

1use crate::{dto::error::Error, ids::CanisterRole};
2
3///
4/// ProtectedInternalEndpoint
5///
6/// Generated metadata for one protected Canic internal endpoint.
7///
8/// Endpoint macros emit this descriptor next to protected internal endpoints.
9/// Callers should pass it to `CanicInternalClient` instead of repeating method
10/// names and accepted-role metadata by hand.
11///
12
13#[derive(Clone, Debug)]
14pub struct ProtectedInternalEndpoint {
15    method: &'static str,
16    accepted_roles: Vec<CanisterRole>,
17}
18
19impl ProtectedInternalEndpoint {
20    #[must_use]
21    #[track_caller]
22    pub fn new(method: &'static str, roles: impl IntoIterator<Item = CanisterRole>) -> Self {
23        assert!(
24            !method.trim().is_empty(),
25            "protected internal endpoint descriptor method must not be empty"
26        );
27        let accepted_roles = roles.into_iter().collect::<Vec<_>>();
28        assert!(
29            !accepted_roles.is_empty(),
30            "protected internal endpoint descriptor '{method}' must accept at least one caller role"
31        );
32        for (index, role) in accepted_roles.iter().enumerate() {
33            assert!(
34                !role.as_str().trim().is_empty(),
35                "protected internal endpoint descriptor '{method}' has an empty caller role at index {index}"
36            );
37            assert!(
38                !accepted_roles[..index].iter().any(|prior| prior == role),
39                "protected internal endpoint descriptor '{method}' contains duplicate caller role '{role}'"
40            );
41        }
42        Self {
43            method,
44            accepted_roles,
45        }
46    }
47
48    #[must_use]
49    pub const fn method(&self) -> &'static str {
50        self.method
51    }
52
53    #[must_use]
54    pub fn accepted_roles(&self) -> &[CanisterRole] {
55        &self.accepted_roles
56    }
57
58    #[must_use]
59    pub fn accepted_roles_label(&self) -> String {
60        self.accepted_roles
61            .iter()
62            .map(ToString::to_string)
63            .collect::<Vec<_>>()
64            .join(", ")
65    }
66
67    #[must_use]
68    pub fn accepts_role(&self, role: &CanisterRole) -> bool {
69        self.accepted_roles.iter().any(|accepted| accepted == role)
70    }
71
72    #[must_use]
73    pub fn single_role(&self) -> Option<&CanisterRole> {
74        match self.accepted_roles.as_slice() {
75            [role] => Some(role),
76            _ => None,
77        }
78    }
79
80    pub fn required_single_role(&self) -> Result<CanisterRole, Error> {
81        self.single_role().cloned().ok_or_else(|| {
82            Error::invalid(format!(
83                "protected internal endpoint '{}' accepts {} roles [{}]; choose a caller role explicitly with call_update(..., caller_role, args)",
84                self.method(),
85                self.accepted_roles.len(),
86                self.accepted_roles_label()
87            ))
88        })
89    }
90}