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#[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}