canic_core/api/ic/canic/
endpoint.rs1use crate::{dto::error::Error, ids::CanisterRole};
2
3#[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}