greentic_bundle/access/
parse.rs1use 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}