oxide_cli/addons/
detect.rs1use std::path::Path;
2
3use crate::addons::manifest::{DetectBlock, DetectRule, MatchMode};
4
5pub fn detect_variant(detect: &[DetectBlock], project_root: &Path) -> Option<String> {
7 for block in detect {
8 let matches = match block.match_mode {
9 MatchMode::All => block.rules.iter().all(|r| eval_rule(r, project_root)),
10 MatchMode::Any => block.rules.iter().any(|r| eval_rule(r, project_root)),
11 };
12 if matches {
13 return Some(block.id.clone());
14 }
15 }
16 None
17}
18
19fn eval_rule(rule: &DetectRule, project_root: &Path) -> bool {
20 match rule {
21 DetectRule::FileExists { file, negate } => {
22 let result = project_root.join(file).exists();
23 if *negate { !result } else { result }
24 }
25
26 DetectRule::FileContains { file, contains, negate } => {
27 let result = std::fs::read_to_string(project_root.join(file))
28 .map(|s| s.contains(contains.as_str()))
29 .unwrap_or(false);
30 if *negate { !result } else { result }
31 }
32
33 DetectRule::JsonContains { file, key_path, value, negate } => {
34 let result = std::fs::read_to_string(project_root.join(file))
35 .ok()
36 .and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok())
37 .map(|v| traverse_json(&v, key_path, value.as_deref()))
38 .unwrap_or(false);
39 if *negate { !result } else { result }
40 }
41
42 DetectRule::TomlContains { file, key_path, value, negate } => {
43 let result = std::fs::read_to_string(project_root.join(file))
44 .ok()
45 .and_then(|s| toml::from_str::<toml::Value>(&s).ok())
46 .map(|v| traverse_toml(&v, key_path, value.as_deref()))
47 .unwrap_or(false);
48 if *negate { !result } else { result }
49 }
50
51 DetectRule::YamlContains { file, key_path, value, negate } => {
52 let result = std::fs::read_to_string(project_root.join(file))
53 .ok()
54 .and_then(|s| serde_yaml::from_str::<serde_yaml::Value>(&s).ok())
55 .map(|v| traverse_yaml(&v, key_path, value.as_deref()))
56 .unwrap_or(false);
57 if *negate { !result } else { result }
58 }
59 }
60}
61
62fn traverse_json(mut v: &serde_json::Value, key_path: &str, expected: Option<&str>) -> bool {
63 for key in key_path.split('.') {
64 match v.get(key) {
65 Some(next) => v = next,
66 None => return false,
67 }
68 }
69 match expected {
70 None => true,
71 Some(expected) => match v {
72 serde_json::Value::String(s) => s == expected,
73 #[allow(clippy::cmp_owned)]
76 other => other.to_string() == expected,
77 },
78 }
79}
80
81fn traverse_toml(mut v: &toml::Value, key_path: &str, expected: Option<&str>) -> bool {
82 for key in key_path.split('.') {
83 match v.get(key) {
84 Some(next) => v = next,
85 None => return false,
86 }
87 }
88 match expected {
89 None => true,
90 Some(expected) => match v {
91 toml::Value::String(s) => s == expected,
92 #[allow(clippy::cmp_owned)]
94 other => other.to_string() == expected,
95 },
96 }
97}
98
99fn traverse_yaml(mut v: &serde_yaml::Value, key_path: &str, expected: Option<&str>) -> bool {
100 for key in key_path.split('.') {
101 match v.get(key) {
102 Some(next) => v = next,
103 None => return false,
104 }
105 }
106 match expected {
107 None => true,
108 Some(expected) => match v {
109 serde_yaml::Value::String(s) => s == expected,
110 other => format!("{other:?}") == expected,
111 },
112 }
113}
114