whistle_proxy_rule_parser/
lib.rs

1use 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      // let chars: Vec<char> = input.chars().collect();
121      // let s: String = chars[1..chars.len()-1].into_iter().collect();
122      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
197// The error handler will trigger a 'static str reference, solution is here:
198// https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2de79a2b85310e11e915c674b28a9246
199// Issue: https://github.com/rust-bakery/nom/issues/1571
200pub 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    // println!("source: {:#?}", source);
204
205    let (rest, target) = map(get_part, all_consuming(parse_uri))(rest)?;
206    let target = target?.1;
207    // println!("target: {:#?}", target);
208
209    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    // TODO: make below test remove ()
249    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}