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 accepts_role(&self, role: &CanisterRole) -> bool {
60        self.accepted_roles.iter().any(|accepted| accepted == role)
61    }
62
63    #[must_use]
64    pub fn single_role(&self) -> Option<&CanisterRole> {
65        match self.accepted_roles.as_slice() {
66            [role] => Some(role),
67            _ => None,
68        }
69    }
70
71    pub fn required_single_role(&self) -> Result<CanisterRole, Error> {
72        self.single_role().cloned().ok_or_else(|| {
73            Error::invalid(format!(
74                "protected internal endpoint '{}' accepts {} roles; choose a caller role explicitly",
75                self.method(),
76                self.accepted_roles.len()
77            ))
78        })
79    }
80}