whistle_proxy_rule_parser/
lib.rs1use nom::character::is_space;
2use nom::combinator::all_consuming;
3use nom::error::{ErrorKind, ParseError};
4use nom::Err::Error;
5use nom::character::complete::none_of;
6use nom::multi::separated_list0;
7use nom::Parser;
8use nom::{branch::alt, multi::many0, sequence::delimited};
9use nom::{
10 bytes::complete::{tag, take_until, take_till1, take_while, take_while1},
11 character::complete::{multispace0, char as char1},
12 combinator::{opt, map},
13 sequence::{preceded, terminated, tuple},
14 IResult,
15};
16
17pub mod markdown_values;
18
19#[derive(Debug, Clone)]
20pub struct Uri {
21 pub scheme: String,
22 pub host: String,
23 pub path: String,
24 pub query: String,
25}
26
27impl ToString for Uri {
28 fn to_string(&self) -> String {
29 format!("{}://{}{}{}", self.scheme, self.host, self.path, self.query)
30 }
31}
32impl PartialEq for Uri {
33 fn eq(&self, other: &Self) -> bool {
34 self.scheme == other.scheme && self.host == other.host && self.path == other.path && self.query == other.query
35 }
36}
37
38#[derive(Debug, Clone)]
39pub enum OpValue {
40 Inline(String),
41 Value(String),
42 Raw(String),
43 TemplateString(TemplateString),
44}
45
46#[derive(Debug, Clone)]
47pub struct Rule {
48 pub name: String,
49 pub value: OpValue,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum TemplatePart {
54 RawString(String),
55 Value(String),
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct TemplateString {
60 pub parts: Vec<TemplatePart>,
61}
62
63#[derive(Debug, Clone)]
64pub struct ProxyRule {
65 pub source: Uri,
66 pub target: Uri,
67 pub rules: Vec<Rule>,
68}
69
70#[derive(Debug, PartialEq)]
71pub enum CustomError<I> {
72 MyError,
73 Nom(I, ErrorKind),
74}
75
76impl<I> ParseError<I> for CustomError<I> {
77 fn from_error_kind(input: I, kind: ErrorKind) -> Self {
78 CustomError::Nom(input, kind)
79 }
80
81 fn append(_: I, _: ErrorKind, other: Self) -> Self {
82 other
83 }
84}
85
86pub fn error_from_str(_input: &str) -> IResult<&str, &str, CustomError<&str>> {
87 Err(Error(CustomError::MyError))
88}
89
90
91fn whitespace<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> {
92 take_while1(|c: char| c.is_whitespace())(i)
93}
94
95fn not_space(s: &str) -> IResult<&str, &str> {
96 take_while1(|c:char| !c.is_whitespace())(s)
97}
98
99pub fn parse_escaped(input: &str) -> IResult<&str, TemplatePart> {
100 let (input, _) = tag("\\")(input)?;
101 let (input, escaped) = none_of("\\")(input)?;
102 Ok((input, TemplatePart::RawString(escaped.to_string())))
103}
104
105pub fn parse_template_string(input: &str) -> IResult<&str, TemplateString> {
106 let original_input = input;
107 let (mut input, bracket) = opt(char1('('))(input)?;
108 if bracket.is_some() {
109 input = input.strip_suffix(")").expect(&format!("{original_input} format is wrong"));
110 }
111 let (mut input, mut parts) = many0(
112 nom::branch::alt((
113 parse_escaped,
114 map(preceded(tag("${"), terminated(take_until("}"), tag("}"))), |s: &str| TemplatePart::Value(s.to_string())),
115 map(take_until("${"), |s: &str| TemplatePart::RawString(s.to_string())),
116 )),
117 )(input)?;
118
119 if parts.is_empty() {
120 parts.push(TemplatePart::RawString(input.to_string()));
123 input = "";
124 }
125
126 Ok((input, TemplateString { parts }))
127}
128
129pub fn parse_uri(input: &str) -> IResult<&str, Uri> {
130 let (input, (scheme, host, path, query)) = tuple((
131 opt(terminated(
132 take_while1(|c: char| c.is_alphanumeric()),
133 tag("://"),
134 )),
135 opt(take_while1(|c: char| c != '/')),
136 take_while(|c: char| c != '?'),
137 take_while(|c: char| !c.is_whitespace()),
138 ))(input)?;
139
140 Ok((
141 input,
142 Uri {
143 scheme: scheme.unwrap_or_default().to_string(),
144 host: host.unwrap_or_default().to_string(),
145 path: path.to_string(),
146 query: query.to_string(),
147 },
148 ))
149}
150
151pub fn parse_rule_value(input: &str) -> IResult<&str, OpValue> {
152 let (input, opval) = alt((
153 map(delimited(char1('`'), take_while(|c: char|c != ' ' && c != '\t' && c != '`'), char1('`')), |s:&str| OpValue::TemplateString(parse_template_string(s).unwrap().1)),
154 map(delimited(char1('('), take_while(|c: char|c != ' ' && c != '\t' && c != ')'), char1(')')), |s:&str| OpValue::Inline(s.to_string())),
155 map(delimited(char1('{'), take_while(|c: char|c != ' ' && c != '\t' && c != '}'), char1('}')), |s:&str| OpValue::Value(s.to_string())),
156 map(take_while(|c:char| !is_space(c as u8) ), |s: &str| OpValue::Raw(s.to_string())),
157 ))(input)?;
158
159 Ok((
160 input,
161 opval,
162 ))
163}
164
165pub fn parse_rule(input: &str) -> IResult<&str, Rule> {
166 let (input, (name, value)) = tuple((
167 terminated(take_while1(|c: char| c.is_alphanumeric()), tag("://")),
168 map(take_while(|c: char| !c.is_whitespace()), |s:&str| parse_rule_value(s)),
169 ))(input)?;
170
171 let (_, value) = value?;
172
173 Ok((
174 input,
175 Rule {
176 name: name.to_string(),
177 value,
178 },
179 ))
180}
181
182pub fn get_part(input: &str) -> IResult<&str, &str> {
183 preceded(multispace0, take_till1(|c: char| c.is_whitespace()))(input)
184}
185
186pub fn get_rules(input: &str) -> IResult<&str, Vec<Rule>> {
187 let (rest, rules) = preceded(whitespace, separated_list0(whitespace, map(not_space, |s:&str| {
188 parse_rule(s).unwrap().1
189}))).parse(input)?;
190
191 Ok((
192 rest,
193 rules,
194 ))
195}
196
197pub fn parse_proxy_rule(input: &str) -> IResult<&str, ProxyRule> {
201 let (rest, source) = map(get_part, all_consuming(parse_uri))(input)?;
202 let source = source?.1;
203 let (rest, target) = map(get_part, all_consuming(parse_uri))(rest)?;
206 let target = target?.1;
207 let (rest, rules) = if rest.trim().is_empty() {
210 (rest, vec![])
211 } else {
212 get_rules(rest).unwrap()
213 };
214
215 Ok((
216 rest,
217 ProxyRule {
218 source,
219 target,
220 rules,
221 }
222 ))
223}
224
225#[cfg(test)]
226mod test {
227 use super::*;
228 #[test]
229 fn test_uri_to_string(){
230 let str = "http://localhost:8888/x?a=1";
231 let (input, uri) = parse_uri(str).unwrap();
232 assert_eq!(input, "");
233 assert_eq!(uri, Uri {
234 scheme: "http".into(),
235 host: "localhost:8888".into(),
236 path: "/x".into(),
237 query: "?a=1".into(),
238 });
239 assert_eq!(uri.to_string(), str);
240 }
241 #[test]
242 fn test_template_string(){
243 let str = "`x=1&b=2`";
244 let (input, ts) = parse_template_string(str).unwrap();
245 assert_eq!(input, "");
246 assert_eq!(ts.parts, vec![TemplatePart::RawString("`x=1&b=2`".into())]);
247
248 let str = "`(x=1&b=2)`";
250 let (input, ts) = parse_template_string(str).unwrap();
251 assert_eq!(input, "");
252 assert_eq!(ts.parts, vec![TemplatePart::RawString("`(x=1&b=2)`".into())]);
253 }
254}