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/// In 0.65, the outbound protected-internal client surface is removed, but the
10/// descriptor remains the retained source of method names and accepted-role
11/// metadata for verifier/rejection paths and future replacement work.
12///
13
14#[derive(Clone, Debug)]
15pub struct ProtectedInternalEndpoint {
16    method: &'static str,
17    accepted_roles: Vec<CanisterRole>,
18}
19
20impl ProtectedInternalEndpoint {
21    #[must_use]
22    #[track_caller]
23    pub fn new(method: &'static str, roles: impl IntoIterator<Item = CanisterRole>) -> Self {
24        assert!(
25            !method.trim().is_empty(),
26            "protected internal endpoint descriptor method must not be empty"
27        );
28        let accepted_roles = roles.into_iter().collect::<Vec<_>>();
29        assert!(
30            !accepted_roles.is_empty(),
31            "protected internal endpoint descriptor '{method}' must accept at least one caller role"
32        );
33        for (index, role) in accepted_roles.iter().enumerate() {
34            assert!(
35                !role.as_str().trim().is_empty(),
36                "protected internal endpoint descriptor '{method}' has an empty caller role at index {index}"
37            );
38            assert!(
39                !accepted_roles[..index].iter().any(|prior| prior == role),
40                "protected internal endpoint descriptor '{method}' contains duplicate caller role '{role}'"
41            );
42        }
43        Self {
44            method,
45            accepted_roles,
46        }
47    }
48
49    #[must_use]
50    pub const fn method(&self) -> &'static str {
51        self.method
52    }
53
54    #[must_use]
55    pub fn accepted_roles(&self) -> &[CanisterRole] {
56        &self.accepted_roles
57    }
58
59    #[must_use]
60    pub fn accepted_roles_label(&self) -> String {
61        self.accepted_roles
62            .iter()
63            .map(ToString::to_string)
64            .collect::<Vec<_>>()
65            .join(", ")
66    }
67
68    #[must_use]
69    pub fn accepts_role(&self, role: &CanisterRole) -> bool {
70        self.accepted_roles.iter().any(|accepted| accepted == role)
71    }
72
73    #[must_use]
74    pub fn single_role(&self) -> Option<&CanisterRole> {
75        match self.accepted_roles.as_slice() {
76            [role] => Some(role),
77            _ => None,
78        }
79    }
80
81    pub fn required_single_role(&self) -> Result<CanisterRole, Error> {
82        self.single_role().cloned().ok_or_else(|| {
83            Error::invalid(format!(
84                "protected internal endpoint '{}' accepts {} roles [{}]; choose a caller role explicitly",
85                self.method(),
86                self.accepted_roles.len(),
87                self.accepted_roles_label()
88            ))
89        })
90    }
91}