darklua_core/rules/
rule_property.rs

1use std::collections::HashMap;
2
3use regex::Regex;
4use serde::{Deserialize, Serialize};
5
6use super::{require::PathRequireMode, RequireMode, RobloxRequireMode, RuleConfigurationError};
7
8pub type RuleProperties = HashMap<String, RulePropertyValue>;
9
10/// In order to be able to weakly-type the properties of any rule, this enum makes it possible to
11/// easily use serde to gather the value associated with a property.
12#[derive(Debug, Serialize, Deserialize, PartialEq)]
13#[serde(untagged, rename_all = "snake_case")]
14pub enum RulePropertyValue {
15    Boolean(bool),
16    String(String),
17    Usize(usize),
18    Float(f64),
19    StringList(Vec<String>),
20    RequireMode(RequireMode),
21    None,
22}
23
24impl RulePropertyValue {
25    pub(crate) fn expect_bool(self, key: &str) -> Result<bool, RuleConfigurationError> {
26        if let Self::Boolean(value) = self {
27            Ok(value)
28        } else {
29            Err(RuleConfigurationError::BooleanExpected(key.to_owned()))
30        }
31    }
32
33    pub(crate) fn expect_string(self, key: &str) -> Result<String, RuleConfigurationError> {
34        if let Self::String(value) = self {
35            Ok(value)
36        } else {
37            Err(RuleConfigurationError::StringExpected(key.to_owned()))
38        }
39    }
40
41    pub(crate) fn expect_string_list(
42        self,
43        key: &str,
44    ) -> Result<Vec<String>, RuleConfigurationError> {
45        if let Self::StringList(value) = self {
46            Ok(value)
47        } else {
48            Err(RuleConfigurationError::StringListExpected(key.to_owned()))
49        }
50    }
51
52    pub(crate) fn expect_regex_list(self, key: &str) -> Result<Vec<Regex>, RuleConfigurationError> {
53        if let Self::StringList(value) = self {
54            value
55                .into_iter()
56                .map(|regex_str| {
57                    Regex::new(&regex_str).map_err(|err| RuleConfigurationError::UnexpectedValue {
58                        property: key.to_owned(),
59                        message: format!("invalid regex provided `{}`\n  {}", regex_str, err),
60                    })
61                })
62                .collect()
63        } else {
64            Err(RuleConfigurationError::StringListExpected(key.to_owned()))
65        }
66    }
67
68    pub(crate) fn expect_require_mode(
69        self,
70        key: &str,
71    ) -> Result<RequireMode, RuleConfigurationError> {
72        match self {
73            Self::RequireMode(require_mode) => Ok(require_mode),
74            Self::String(value) => {
75                value
76                    .parse()
77                    .map_err(|err: String| RuleConfigurationError::UnexpectedValue {
78                        property: key.to_owned(),
79                        message: err,
80                    })
81            }
82            _ => Err(RuleConfigurationError::RequireModeExpected(key.to_owned())),
83        }
84    }
85}
86
87impl From<bool> for RulePropertyValue {
88    fn from(value: bool) -> Self {
89        Self::Boolean(value)
90    }
91}
92
93impl From<&str> for RulePropertyValue {
94    fn from(value: &str) -> Self {
95        Self::String(value.to_owned())
96    }
97}
98
99impl From<&String> for RulePropertyValue {
100    fn from(value: &String) -> Self {
101        Self::String(value.to_owned())
102    }
103}
104
105impl From<String> for RulePropertyValue {
106    fn from(value: String) -> Self {
107        Self::String(value)
108    }
109}
110
111impl From<usize> for RulePropertyValue {
112    fn from(value: usize) -> Self {
113        Self::Usize(value)
114    }
115}
116
117impl From<f64> for RulePropertyValue {
118    fn from(value: f64) -> Self {
119        Self::Float(value)
120    }
121}
122
123impl From<&RequireMode> for RulePropertyValue {
124    fn from(value: &RequireMode) -> Self {
125        match value {
126            RequireMode::Path(mode) => {
127                if mode == &PathRequireMode::default() {
128                    return Self::from("path");
129                }
130            }
131            RequireMode::Roblox(mode) => {
132                if mode == &RobloxRequireMode::default() {
133                    return Self::from("roblox");
134                }
135            }
136        }
137
138        Self::RequireMode(value.clone())
139    }
140}
141
142impl<T: Into<RulePropertyValue>> From<Option<T>> for RulePropertyValue {
143    fn from(value: Option<T>) -> Self {
144        match value {
145            Some(value) => value.into(),
146            None => Self::None,
147        }
148    }
149}
150
151#[cfg(test)]
152mod test {
153    use super::*;
154
155    #[test]
156    fn from_true() {
157        assert_eq!(
158            RulePropertyValue::from(true),
159            RulePropertyValue::Boolean(true)
160        );
161    }
162
163    #[test]
164    fn from_false() {
165        assert_eq!(
166            RulePropertyValue::from(false),
167            RulePropertyValue::Boolean(false)
168        );
169    }
170
171    #[test]
172    fn from_string() {
173        assert_eq!(
174            RulePropertyValue::from(String::from("hello")),
175            RulePropertyValue::String(String::from("hello"))
176        );
177    }
178
179    #[test]
180    fn from_string_ref() {
181        assert_eq!(
182            RulePropertyValue::from(&String::from("hello")),
183            RulePropertyValue::String(String::from("hello"))
184        );
185    }
186
187    #[test]
188    fn from_str() {
189        assert_eq!(
190            RulePropertyValue::from("hello"),
191            RulePropertyValue::String(String::from("hello"))
192        );
193    }
194
195    #[test]
196    fn from_usize() {
197        assert_eq!(RulePropertyValue::from(6), RulePropertyValue::Usize(6));
198    }
199
200    #[test]
201    fn from_float() {
202        assert_eq!(RulePropertyValue::from(1.0), RulePropertyValue::Float(1.0));
203    }
204
205    #[test]
206    fn from_boolean_option_some() {
207        let bool = Some(true);
208        assert_eq!(
209            RulePropertyValue::from(bool),
210            RulePropertyValue::Boolean(true)
211        );
212    }
213
214    #[test]
215    fn from_boolean_option_none() {
216        let bool: Option<bool> = None;
217        assert_eq!(RulePropertyValue::from(bool), RulePropertyValue::None);
218    }
219}