canic_core/access/
rule.rs

1use crate::{
2    Error, ThisError, access::AccessError, infra::ic::network::Network,
3    ops::runtime::network::NetworkOps,
4};
5
6///
7/// RuleAccessError
8///
9
10#[derive(Debug, ThisError)]
11pub enum RuleAccessError {
12    #[error("this endpoint requires a build-time network (DFX_NETWORK) of either 'ic' or 'local'")]
13    BuildNetworkUnknown,
14
15    #[error(
16        "this endpoint is only available when built for '{expected}' (DFX_NETWORK), but was built for '{actual}'"
17    )]
18    BuildNetworkMismatch { expected: Network, actual: Network },
19}
20
21impl From<RuleAccessError> for Error {
22    fn from(err: RuleAccessError) -> Self {
23        AccessError::Rule(err).into()
24    }
25}
26
27///
28/// Rules
29///
30
31/// build_network_ic
32/// Permits access only when `DFX_NETWORK=ic` was set at build time.
33#[allow(clippy::unused_async)]
34pub async fn build_network_ic() -> Result<(), AccessError> {
35    check_build_network(Network::Ic).map_err(AccessError::from)
36}
37
38/// build_network_local
39/// Permits access only when `DFX_NETWORK=local` was set at build time.
40#[allow(clippy::unused_async)]
41pub async fn build_network_local() -> Result<(), AccessError> {
42    check_build_network(Network::Local).map_err(AccessError::from)
43}
44
45///
46/// Helpers
47///
48
49pub fn check_build_network(expected: Network) -> Result<(), RuleAccessError> {
50    let actual = NetworkOps::current_network();
51
52    match actual {
53        Some(actual) if actual == expected => Ok(()),
54        Some(actual) => Err(RuleAccessError::BuildNetworkMismatch { expected, actual }),
55        None => Err(RuleAccessError::BuildNetworkUnknown),
56    }
57}
58
59///
60/// TESTS
61///
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    fn check(expected: Network, actual: Option<Network>) -> Result<(), RuleAccessError> {
68        // Inline the same logic but with injected `actual`
69        match actual {
70            Some(actual) if actual == expected => Ok(()),
71            Some(actual) => Err(RuleAccessError::BuildNetworkMismatch { expected, actual }),
72            None => Err(RuleAccessError::BuildNetworkUnknown),
73        }
74    }
75
76    #[test]
77    fn build_network_matches_expected() {
78        assert!(check(Network::Ic, Some(Network::Ic)).is_ok());
79        assert!(check(Network::Local, Some(Network::Local)).is_ok());
80    }
81
82    #[test]
83    fn build_network_mismatch_errors() {
84        let err = check(Network::Ic, Some(Network::Local)).unwrap_err();
85
86        match err {
87            RuleAccessError::BuildNetworkMismatch { expected, actual } => {
88                assert_eq!(expected, Network::Ic);
89                assert_eq!(actual, Network::Local);
90            }
91            RuleAccessError::BuildNetworkUnknown => panic!("expected BuildNetworkMismatch"),
92        }
93    }
94
95    #[test]
96    fn build_network_unknown_errors() {
97        let err = check(Network::Ic, None).unwrap_err();
98        assert!(matches!(err, RuleAccessError::BuildNetworkUnknown));
99    }
100}