Skip to main content

mm1_node/config/
validation.rs

1use std::collections::BTreeSet;
2use std::ops::Deref;
3
4use mm1_address::address_range::AddressRange;
5use mm1_address::subnet::NetAddress;
6
7use crate::config::{LocalSubnetKind, Mm1NodeConfig};
8
9#[derive(Debug, Clone)]
10pub struct Valid<C>(C);
11
12#[derive(Debug, thiserror::Error)]
13#[error("validation error:\n{}", errors.iter().map(ToString::to_string).collect::<Vec<_>>().join("\n-"))]
14pub struct ValidationError<T> {
15    pub errors:   Vec<String>,
16    pub rejected: Box<T>,
17}
18
19impl Mm1NodeConfig {
20    pub fn validate(self) -> Result<Valid<Self>, ValidationError<Self>> {
21        TryFrom::try_from(self)
22    }
23}
24
25impl TryFrom<Mm1NodeConfig> for Valid<Mm1NodeConfig> {
26    type Error = ValidationError<Mm1NodeConfig>;
27
28    fn try_from(node_config: Mm1NodeConfig) -> Result<Self, Self::Error> {
29        let used_runtime_keys = node_config.actor.runtime_keys();
30        let runtime_keys = node_config.runtime.runtime_keys().collect();
31
32        let runtime_keys_validation_error_opt = node_config
33            .actor
34            .ensure_runtime_keys_are_valid(&runtime_keys)
35            .err();
36
37        let runtime_time_validation_errors = node_config
38            .runtime
39            .time_driver_validation_errors(&used_runtime_keys);
40
41        let exactly_one_auto_network_error_opt = (node_config
42            .local_subnets
43            .iter()
44            .filter(|d| matches!(d.kind, LocalSubnetKind::Auto))
45            .count()
46            != 1)
47            .then_some("exactly one local-subnet with kind:auto must be defined".into());
48
49        let overlapping_local_nets = {
50            let mut nets = BTreeSet::<AddressRange>::new();
51            let mut conflicts = vec![];
52
53            for this in node_config.local_subnets.iter().map(|d| d.net) {
54                if let Some(that) = nets
55                    .get(&AddressRange::from(this))
56                    .map(|r| NetAddress::from(*r))
57                {
58                    conflicts.push(format!("{} overlaps with {}", this, that));
59                } else {
60                    nets.insert(this.into());
61                }
62            }
63            conflicts
64        };
65
66        let errors = runtime_keys_validation_error_opt
67            .into_iter()
68            .chain(runtime_time_validation_errors)
69            .chain(exactly_one_auto_network_error_opt)
70            .chain(overlapping_local_nets)
71            .collect::<Vec<_>>();
72
73        if errors.is_empty() {
74            Ok(Valid(node_config))
75        } else {
76            Err(ValidationError {
77                errors,
78                rejected: Box::new(node_config),
79            })
80        }
81    }
82}
83
84impl<C> Deref for Valid<C> {
85    type Target = C;
86
87    fn deref(&self) -> &Self::Target {
88        &self.0
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use crate::config::validation::Valid;
95
96    #[test]
97    fn ergonomics() {
98        struct Config {
99            field: u32,
100        }
101        impl Config {
102            fn method(&self) -> u32 {
103                self.field
104            }
105        }
106
107        let valid_value = Valid(Config { field: 42 });
108        assert_eq!(valid_value.field, 42);
109        assert_eq!(valid_value.method(), 42);
110
111        let valid_reference = Valid(&Config { field: 13 });
112        assert_eq!(valid_reference.field, 13);
113        assert_eq!(valid_reference.method(), 13);
114    }
115}