Skip to main content

greentic_operator/gmap/
parse.rs

1use std::path::Path;
2
3#[derive(Clone, Debug, Eq, PartialEq)]
4pub enum Policy {
5    Public,
6    Forbidden,
7}
8
9#[derive(Clone, Debug, Eq, PartialEq)]
10pub struct GmapPath {
11    pub pack: Option<String>,
12    pub flow: Option<String>,
13    pub node: Option<String>,
14}
15
16#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct GmapRule {
18    pub path: GmapPath,
19    pub policy: Policy,
20    pub line: usize,
21}
22
23pub fn parse_file(path: &Path) -> anyhow::Result<Vec<GmapRule>> {
24    if !path.exists() {
25        return Ok(Vec::new());
26    }
27    let contents = std::fs::read_to_string(path)?;
28    parse_str(&contents)
29}
30
31pub fn parse_str(contents: &str) -> anyhow::Result<Vec<GmapRule>> {
32    let mut rules = Vec::new();
33    for (idx, line) in contents.lines().enumerate() {
34        let line = line.trim();
35        if line.is_empty() || line.starts_with('#') {
36            continue;
37        }
38        let rule = parse_rule_line(line, idx + 1)?;
39        rules.push(rule);
40    }
41    Ok(rules)
42}
43
44pub fn parse_rule_line(line: &str, line_number: usize) -> anyhow::Result<GmapRule> {
45    let mut parts = line.splitn(2, '=');
46    let raw_path = parts
47        .next()
48        .map(|part| part.trim())
49        .filter(|part| !part.is_empty())
50        .ok_or_else(|| anyhow::anyhow!("Invalid rule line {}: missing path", line_number))?;
51    let raw_policy = parts
52        .next()
53        .map(|part| part.trim())
54        .filter(|part| !part.is_empty())
55        .ok_or_else(|| anyhow::anyhow!("Invalid rule line {}: missing policy", line_number))?;
56
57    let path = parse_path(raw_path, line_number)?;
58    let policy = parse_policy(raw_policy, line_number)?;
59    Ok(GmapRule {
60        path,
61        policy,
62        line: line_number,
63    })
64}
65
66pub fn parse_path(raw: &str, line_number: usize) -> anyhow::Result<GmapPath> {
67    if raw == "_" {
68        return Ok(GmapPath {
69            pack: None,
70            flow: None,
71            node: None,
72        });
73    }
74    let segments: Vec<&str> = raw.split('/').filter(|seg| !seg.is_empty()).collect();
75    if segments.is_empty() {
76        return Err(anyhow::anyhow!(
77            "Invalid path on line {}: empty path",
78            line_number
79        ));
80    }
81    if segments.len() > 3 {
82        return Err(anyhow::anyhow!(
83            "Invalid path on line {}: too many segments",
84            line_number
85        ));
86    }
87    let pack = Some(segments[0].to_string());
88    let flow = segments.get(1).map(|seg| seg.to_string());
89    let node = segments.get(2).map(|seg| seg.to_string());
90    Ok(GmapPath { pack, flow, node })
91}
92
93pub fn parse_policy(raw: &str, line_number: usize) -> anyhow::Result<Policy> {
94    match raw {
95        "public" => Ok(Policy::Public),
96        "forbidden" => Ok(Policy::Forbidden),
97        other => Err(anyhow::anyhow!(
98            "Invalid policy on line {}: {}",
99            line_number,
100            other
101        )),
102    }
103}
104
105impl std::fmt::Display for GmapPath {
106    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        match (&self.pack, &self.flow, &self.node) {
108            (None, None, None) => write!(formatter, "_"),
109            (Some(pack), None, None) => write!(formatter, "{pack}"),
110            (Some(pack), Some(flow), None) => write!(formatter, "{pack}/{flow}"),
111            (Some(pack), Some(flow), Some(node)) => write!(formatter, "{pack}/{flow}/{node}"),
112            _ => write!(formatter, "_"),
113        }
114    }
115}