darklua_core/rules/
inject_value.rs

1use crate::nodes::{
2    Block, DecimalNumber, Expression, ParentheseExpression, Prefix, StringExpression, UnaryOperator,
3};
4use crate::process::{IdentifierTracker, NodeProcessor, NodeVisitor, ScopeVisitor};
5use crate::rules::{
6    Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleProperties,
7    RulePropertyValue,
8};
9
10use std::{env, ops};
11
12use super::{verify_property_collisions, verify_required_properties};
13
14#[derive(Debug, Clone)]
15struct ValueInjection {
16    identifier: String,
17    expression: Expression,
18    identifier_tracker: IdentifierTracker,
19}
20
21impl ValueInjection {
22    pub fn new<S: Into<String>, E: Into<Expression>>(identifier: S, expression: E) -> Self {
23        Self {
24            identifier: identifier.into(),
25            expression: expression.into(),
26            identifier_tracker: IdentifierTracker::default(),
27        }
28    }
29}
30
31impl ops::Deref for ValueInjection {
32    type Target = IdentifierTracker;
33
34    fn deref(&self) -> &Self::Target {
35        &self.identifier_tracker
36    }
37}
38
39impl ops::DerefMut for ValueInjection {
40    fn deref_mut(&mut self) -> &mut Self::Target {
41        &mut self.identifier_tracker
42    }
43}
44
45impl NodeProcessor for ValueInjection {
46    fn process_expression(&mut self, expression: &mut Expression) {
47        let replace = match expression {
48            Expression::Identifier(identifier) => {
49                &self.identifier == identifier.get_name()
50                    && !self.is_identifier_used(&self.identifier)
51            }
52            Expression::Field(field) => {
53                &self.identifier == field.get_field().get_name()
54                    && !self.is_identifier_used("_G")
55                    && matches!(field.get_prefix(), Prefix::Identifier(prefix) if prefix.get_name() == "_G")
56            }
57            Expression::Index(index) => {
58                !self.is_identifier_used("_G")
59                    && matches!(index.get_index(), Expression::String(string) if string.get_value() == self.identifier)
60                    && matches!(index.get_prefix(), Prefix::Identifier(prefix) if prefix.get_name() == "_G")
61            }
62            _ => false,
63        };
64
65        if replace {
66            let new_expression = self.expression.clone();
67            *expression = new_expression;
68        }
69    }
70
71    fn process_prefix_expression(&mut self, prefix: &mut Prefix) {
72        let replace = match prefix {
73            Prefix::Identifier(identifier) => &self.identifier == identifier.get_name(),
74            _ => false,
75        };
76
77        if replace {
78            let new_prefix = ParentheseExpression::new(self.expression.clone()).into();
79            *prefix = new_prefix;
80        }
81    }
82}
83
84pub const INJECT_GLOBAL_VALUE_RULE_NAME: &str = "inject_global_value";
85
86/// A rule to replace global variables with values.
87#[derive(Debug, PartialEq, Eq)]
88pub struct InjectGlobalValue {
89    identifier: String,
90    value: Expression,
91}
92
93impl InjectGlobalValue {
94    pub fn nil<S: Into<String>>(identifier: S) -> Self {
95        Self {
96            identifier: identifier.into(),
97            value: Expression::nil(),
98        }
99    }
100
101    pub fn boolean<S: Into<String>>(identifier: S, value: bool) -> Self {
102        Self {
103            identifier: identifier.into(),
104            value: Expression::from(value),
105        }
106    }
107
108    pub fn string<S: Into<String>, S2: Into<String>>(identifier: S, value: S2) -> Self {
109        Self {
110            identifier: identifier.into(),
111            value: StringExpression::from_value(value).into(),
112        }
113    }
114
115    pub fn number<S: Into<String>>(identifier: S, value: f64) -> Self {
116        Self {
117            identifier: identifier.into(),
118            value: Expression::from(value),
119        }
120    }
121}
122
123impl Default for InjectGlobalValue {
124    fn default() -> Self {
125        Self {
126            identifier: "".to_owned(),
127            value: Expression::nil(),
128        }
129    }
130}
131
132impl FlawlessRule for InjectGlobalValue {
133    fn flawless_process(&self, block: &mut Block, _: &Context) {
134        let mut processor = ValueInjection::new(&self.identifier, self.value.clone());
135        ScopeVisitor::visit_block(block, &mut processor);
136    }
137}
138
139impl RuleConfiguration for InjectGlobalValue {
140    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
141        verify_required_properties(&properties, &["identifier"])?;
142        verify_property_collisions(&properties, &["value", "env"])?;
143
144        for (key, value) in properties {
145            match key.as_str() {
146                "identifier" => {
147                    self.identifier = value.expect_string(&key)?;
148                }
149                "value" => match value {
150                    RulePropertyValue::None => {}
151                    RulePropertyValue::String(value) => {
152                        self.value = StringExpression::from_value(value).into();
153                    }
154                    RulePropertyValue::Boolean(value) => {
155                        self.value = Expression::from(value);
156                    }
157                    RulePropertyValue::Usize(value) => {
158                        self.value = DecimalNumber::new(value as f64).into();
159                    }
160                    RulePropertyValue::Float(value) => {
161                        self.value = Expression::from(value);
162                    }
163                    _ => return Err(RuleConfigurationError::UnexpectedValueType(key)),
164                },
165                "env" => {
166                    let variable_name = value.expect_string(&key)?;
167                    if let Some(os_value) = env::var_os(&variable_name) {
168                        if let Some(value) = os_value.to_str() {
169                            self.value = StringExpression::from_value(value).into();
170                        } else {
171                            return Err(RuleConfigurationError::UnexpectedValue {
172                                property: key,
173                                message: format!(
174                                    "invalid string assigned to the `{}` environment variable",
175                                    &variable_name,
176                                ),
177                            });
178                        }
179                    } else {
180                        log::warn!(
181                            "environment variable `{}` is not defined. The rule `{}` will use `nil`",
182                            variable_name,
183                            INJECT_GLOBAL_VALUE_RULE_NAME,
184                        );
185                    };
186                }
187                _ => return Err(RuleConfigurationError::UnexpectedProperty(key)),
188            }
189        }
190
191        Ok(())
192    }
193
194    fn get_name(&self) -> &'static str {
195        INJECT_GLOBAL_VALUE_RULE_NAME
196    }
197
198    fn serialize_to_properties(&self) -> RuleProperties {
199        let mut rules = RuleProperties::new();
200        rules.insert(
201            "identifier".to_owned(),
202            RulePropertyValue::String(self.identifier.clone()),
203        );
204
205        let property_value = match &self.value {
206            Expression::True(_) => RulePropertyValue::Boolean(true),
207            Expression::False(_) => RulePropertyValue::Boolean(false),
208            Expression::Nil(_) => RulePropertyValue::None,
209            Expression::Number(number) => {
210                let value = number.compute_value();
211                if value.trunc() == value && value >= 0.0 && value < usize::MAX as f64 {
212                    RulePropertyValue::Usize(value as usize)
213                } else {
214                    RulePropertyValue::Float(value)
215                }
216            }
217            Expression::String(string) => RulePropertyValue::from(string.get_value()),
218            Expression::Unary(unary) => {
219                if matches!(unary.operator(), UnaryOperator::Minus) {
220                    if let Expression::Number(number) = unary.get_expression() {
221                        RulePropertyValue::Float(-number.compute_value())
222                    } else {
223                        unreachable!(
224                            "unexpected expression for unary minus {:?}",
225                            unary.get_expression()
226                        );
227                    }
228                } else {
229                    unreachable!("unexpected unary operator {:?}", unary.operator());
230                }
231            }
232            _ => unreachable!("unexpected expression {:?}", self.value),
233        };
234        rules.insert("value".to_owned(), property_value);
235
236        rules
237    }
238}
239
240#[cfg(test)]
241mod test {
242    use super::*;
243    use crate::rules::Rule;
244
245    use insta::assert_json_snapshot;
246
247    #[test]
248    fn configure_without_identifier_property_should_error() {
249        let result = json5::from_str::<Box<dyn Rule>>(
250            r#"{
251            rule: 'inject_global_value',
252        }"#,
253        );
254
255        assert!(result.is_err());
256    }
257
258    #[test]
259    fn configure_with_value_and_env_properties_should_error() {
260        let result = json5::from_str::<Box<dyn Rule>>(
261            r#"{
262            rule: 'inject_global_value',
263            value: false,
264            env: "VAR",
265        }"#,
266        );
267
268        assert!(result.is_err());
269    }
270
271    #[test]
272    fn deserialize_from_string_notation_should_error() {
273        let result = json5::from_str::<Box<dyn Rule>>("'inject_global_value'");
274
275        assert!(result.is_err());
276    }
277
278    #[test]
279    fn serialize_inject_nil_as_foo() {
280        let rule: Box<dyn Rule> = Box::new(InjectGlobalValue::nil("foo"));
281
282        assert_json_snapshot!("inject_nil_value_as_foo", rule);
283    }
284
285    #[test]
286    fn serialize_inject_true_as_foo() {
287        let rule: Box<dyn Rule> = Box::new(InjectGlobalValue::boolean("foo", true));
288
289        assert_json_snapshot!("inject_true_value_as_foo", rule);
290    }
291
292    #[test]
293    fn serialize_inject_false_as_foo() {
294        let rule: Box<dyn Rule> = Box::new(InjectGlobalValue::boolean("foo", false));
295
296        assert_json_snapshot!("inject_false_value_as_foo", rule);
297    }
298
299    #[test]
300    fn serialize_inject_string_as_var() {
301        let rule: Box<dyn Rule> = Box::new(InjectGlobalValue::string("VAR", "hello"));
302
303        assert_json_snapshot!("inject_hello_value_as_var", rule);
304    }
305
306    #[test]
307    fn serialize_inject_integer_as_var() {
308        let rule: Box<dyn Rule> = Box::new(InjectGlobalValue::number("VAR", 1.0));
309
310        assert_json_snapshot!("inject_integer_value_as_var", rule);
311    }
312
313    #[test]
314    fn serialize_inject_negative_integer_as_var() {
315        let rule: Box<dyn Rule> = Box::new(InjectGlobalValue::number("VAR", -100.0));
316
317        assert_json_snapshot!("inject_negative_integer_value_as_var", rule);
318    }
319
320    #[test]
321    fn serialize_inject_float_as_var() {
322        let rule: Box<dyn Rule> = Box::new(InjectGlobalValue::number("VAR", 123.45));
323
324        assert_json_snapshot!("inject_float_value_as_var", rule);
325    }
326}