tiny_proxy/config/
parser.rs1use crate::config::{Config, Directive, SiteConfig};
2use crate::error::ProxyError;
3use std::collections::HashMap;
4use std::str::FromStr;
5
6#[derive(Debug)]
7struct PendingBlock {
8 directive_type: String, args: Vec<String>, }
11
12impl Config {
13 pub fn from_file(path: &str) -> Result<Self, ProxyError> {
14 let content = std::fs::read_to_string(path)?;
15 content.parse()
16 }
17}
18
19impl FromStr for Config {
20 type Err = ProxyError;
21
22 fn from_str(content: &str) -> Result<Self, Self::Err> {
23 let mut sites = HashMap::new();
24 let mut current_site_address: Option<String> = None;
25
26 let mut directive_stack: Vec<Vec<Directive>> = vec![vec![]];
29
30 let mut block_stack: Vec<PendingBlock> = vec![];
32
33 for (line_num, raw_line) in content.lines().enumerate() {
34 let line = raw_line.trim();
35 if line.is_empty() || line.starts_with('#') {
36 continue;
37 }
38
39 if line.ends_with('{') {
41 let parts: Vec<&str> = line.split_whitespace().collect();
42 if parts.is_empty() {
43 continue;
44 }
45
46 if directive_stack.len() == 1 && current_site_address.is_none() {
48 current_site_address = Some(parts[0].to_string());
49 continue;
50 }
51
52 let directive_type = parts[0].to_string();
54 let args = parts[1..].iter().map(|s| s.to_string()).collect();
55
56 block_stack.push(PendingBlock {
57 directive_type,
58 args,
59 });
60 directive_stack.push(vec![]); continue;
62 }
63
64 if line == "}" {
66 if directive_stack.len() > 1 {
67 let finished_directives = directive_stack.pop().unwrap();
68 let block_info = block_stack.pop().unwrap();
69
70 let completed_directive = match block_info.directive_type.as_str() {
71 "handle_path" => {
72 let pattern = block_info.args.first().cloned().unwrap_or_default();
73 Directive::HandlePath {
74 pattern,
75 directives: finished_directives,
76 }
77 }
78 "method" => Directive::Method {
79 methods: block_info.args,
80 directives: finished_directives,
81 },
82 _ => {
83 return Err(ProxyError::Parse(format!(
84 "Unknown block type: {}",
85 block_info.directive_type
86 )))
87 }
88 };
89
90 directive_stack
92 .last_mut()
93 .unwrap()
94 .push(completed_directive);
95 } else {
96 if let Some(address) = current_site_address.take() {
98 let site_directives = directive_stack.pop().unwrap();
99 sites.insert(
100 address.clone(),
101 SiteConfig {
102 address,
103 directives: site_directives,
104 },
105 );
106 directive_stack.push(vec![]); }
108 }
109 continue;
110 }
111
112 let parts: Vec<&str> = line.split_whitespace().collect();
114 if parts.is_empty() {
115 continue;
116 }
117
118 let directive_name = parts[0];
119 let args = parts[1..].to_vec();
120
121 let directive = match directive_name {
122 "reverse_proxy" => {
123 let to = args.first().cloned().ok_or_else(|| {
124 ProxyError::Parse("Missing backend URL for reverse_proxy".to_string())
125 })?;
126 Directive::ReverseProxy { to: to.to_string() }
127 }
128 "uri_replace" => {
129 let find = args.first().cloned().ok_or_else(|| {
130 ProxyError::Parse("Missing 'find' arg for uri_replace".to_string())
131 })?;
132 let replace = args.get(1).cloned().ok_or_else(|| {
133 ProxyError::Parse("Missing 'replace' arg for uri_replace".to_string())
134 })?;
135 Directive::UriReplace {
136 find: find.to_string(),
137 replace: replace.to_string(),
138 }
139 }
140 "header" => {
141 let name = args.first().cloned().ok_or_else(|| {
142 ProxyError::Parse("Missing 'name' arg for header".to_string())
143 })?;
144 let value = args.get(1).cloned().ok_or_else(|| {
145 ProxyError::Parse("Missing 'value' arg for header".to_string())
146 })?;
147 Directive::Header {
148 name: name.to_string(),
149 value: value.to_string(),
150 }
151 }
152 "respond" => {
153 let status = args.first().and_then(|s| s.parse().ok()).ok_or_else(|| {
154 ProxyError::Parse("Invalid status for respond".to_string())
155 })?;
156 let body = args.get(1).cloned().unwrap_or_default();
157 Directive::Respond {
158 status,
159 body: body.to_string(),
160 }
161 }
162 _ => {
163 return Err(ProxyError::Parse(format!(
164 "Unknown directive '{}' on line {}",
165 directive_name,
166 line_num + 1
167 )))
168 }
169 };
170
171 directive_stack.last_mut().unwrap().push(directive);
173 }
174
175 Ok(Config { sites })
176 }
177}