canic_core/access/
rule.rs

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