Skip to main content

canic_core/access/
env.rs

1//! Environment (self) access checks.
2//!
3//! This bucket is strictly about the canister's own environment state
4//! (prime subnet/root status) and build-time structural rules.
5
6use crate::{
7    access::AccessError,
8    ids::BuildNetwork,
9    ops::{ic::network::NetworkOps, runtime::env::EnvOps},
10};
11
12///
13/// Env Checks
14///
15
16pub fn is_prime_root() -> Result<(), AccessError> {
17    if EnvOps::is_prime_root() {
18        Ok(())
19    } else {
20        Err(AccessError::Denied(
21            "this endpoint is only available on prime root".to_string(),
22        ))
23    }
24}
25
26pub fn is_prime_subnet() -> Result<(), AccessError> {
27    if EnvOps::is_prime_subnet() {
28        Ok(())
29    } else {
30        Err(AccessError::Denied(
31            "this endpoint is only available on the prime subnet".to_string(),
32        ))
33    }
34}
35
36/// build_network_ic
37/// Permits access only when `DFX_NETWORK=ic` was set at build time.
38pub fn build_network_ic() -> Result<(), AccessError> {
39    check_build_network(BuildNetwork::Ic)
40}
41
42/// build_network_local
43/// Permits access only when `DFX_NETWORK=local` was set at build time.
44pub fn build_network_local() -> Result<(), AccessError> {
45    check_build_network(BuildNetwork::Local)
46}
47
48///
49/// Helpers
50///
51
52pub fn check_build_network(expected: BuildNetwork) -> Result<(), AccessError> {
53    let actual = NetworkOps::build_network();
54
55    match actual {
56        Some(actual) if actual == expected => Ok(()),
57        Some(actual) => Err(AccessError::Denied(format!(
58            "this endpoint is only available when built for '{expected}' (DFX_NETWORK), but was built for '{actual}'"
59        ))),
60        None => Err(AccessError::Denied(
61            "this endpoint requires a build-time network (DFX_NETWORK) of either 'ic' or 'local'"
62                .to_string(),
63        )),
64    }
65}
66
67///
68/// TESTS
69///
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    fn check(expected: BuildNetwork, actual: Option<BuildNetwork>) -> Result<(), AccessError> {
76        // Inline the same logic but with injected `actual`.
77        match actual {
78            Some(actual) if actual == expected => Ok(()),
79            Some(actual) => Err(AccessError::Denied(format!(
80                "this endpoint is only available when built for '{expected}' (DFX_NETWORK), but was built for '{actual}'"
81            ))),
82            None => Err(AccessError::Denied(
83                "this endpoint requires a build-time network (DFX_NETWORK) of either 'ic' or 'local'"
84                    .to_string(),
85            )),
86        }
87    }
88
89    #[test]
90    fn build_network_matches_expected() {
91        assert!(check(BuildNetwork::Ic, Some(BuildNetwork::Ic)).is_ok());
92        assert!(check(BuildNetwork::Local, Some(BuildNetwork::Local)).is_ok());
93    }
94
95    #[test]
96    fn build_network_mismatch_errors() {
97        let err = check(BuildNetwork::Ic, Some(BuildNetwork::Local))
98            .unwrap_err()
99            .to_string();
100        assert!(
101            err.contains("this endpoint is only available when built for 'ic' (DFX_NETWORK)"),
102            "unexpected error: {err}"
103        );
104    }
105
106    #[test]
107    fn build_network_unknown_errors() {
108        let err = check(BuildNetwork::Ic, None).unwrap_err().to_string();
109        assert!(
110            err.contains("this endpoint requires a build-time network (DFX_NETWORK)"),
111            "unexpected error: {err}"
112        );
113    }
114}