Skip to main content

greentic_bundle/access/
parse.rs

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