canic_core/access/
policy.rs

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