use nom::character::is_space;
use nom::combinator::all_consuming;
use nom::error::{ErrorKind, ParseError};
use nom::Err::Error;
use nom::character::complete::none_of;
use nom::multi::separated_list0;
use nom::Parser;
use nom::{branch::alt, multi::many0, sequence::delimited};
use nom::{
bytes::complete::{tag, take_until, take_till1, take_while, take_while1},
character::complete::{multispace0, char as char1},
combinator::{opt, map},
sequence::{preceded, terminated, tuple},
IResult,
};
pub mod markdown_values;
#[derive(Debug, Clone)]
pub struct Uri {
pub scheme: String,
pub host: String,
pub path: String,
pub query: String,
}
impl ToString for Uri {
fn to_string(&self) -> String {
format!("{}://{}{}{}", self.scheme, self.host, self.path, self.query)
}
}
impl PartialEq for Uri {
fn eq(&self, other: &Self) -> bool {
self.scheme == other.scheme && self.host == other.host && self.path == other.path && self.query == other.query
}
}
#[derive(Debug, Clone)]
pub enum OpValue {
Inline(String),
Value(String),
Raw(String),
TemplateString(TemplateString),
}
#[derive(Debug, Clone)]
pub struct Rule {
pub name: String,
pub value: OpValue,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TemplatePart {
RawString(String),
Value(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TemplateString {
pub parts: Vec<TemplatePart>,
}
#[derive(Debug, Clone)]
pub struct ProxyRule {
pub source: Uri,
pub target: Uri,
pub rules: Vec<Rule>,
}
#[derive(Debug, PartialEq)]
pub enum CustomError<I> {
MyError,
Nom(I, ErrorKind),
}
impl<I> ParseError<I> for CustomError<I> {
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
CustomError::Nom(input, kind)
}
fn append(_: I, _: ErrorKind, other: Self) -> Self {
other
}
}
pub fn error_from_str(_input: &str) -> IResult<&str, &str, CustomError<&str>> {
Err(Error(CustomError::MyError))
}
fn whitespace<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> {
take_while1(|c: char| c.is_whitespace())(i)
}
fn not_space(s: &str) -> IResult<&str, &str> {
take_while1(|c:char| !c.is_whitespace())(s)
}
pub fn parse_escaped(input: &str) -> IResult<&str, TemplatePart> {
let (input, _) = tag("\\")(input)?;
let (input, escaped) = none_of("\\")(input)?;
Ok((input, TemplatePart::RawString(escaped.to_string())))
}
pub fn parse_template_string(input: &str) -> IResult<&str, TemplateString> {
let original_input = input;
let (mut input, bracket) = opt(char1('('))(input)?;
if bracket.is_some() {
input = input.strip_suffix(")").expect(&format!("{original_input} format is wrong"));
}
let (mut input, mut parts) = many0(
nom::branch::alt((
parse_escaped,
map(preceded(tag("${"), terminated(take_until("}"), tag("}"))), |s: &str| TemplatePart::Value(s.to_string())),
map(take_until("${"), |s: &str| TemplatePart::RawString(s.to_string())),
)),
)(input)?;
if parts.is_empty() {
parts.push(TemplatePart::RawString(input.to_string()));
input = "";
}
Ok((input, TemplateString { parts }))
}
pub fn parse_uri(input: &str) -> IResult<&str, Uri> {
let (input, (scheme, host, path, query)) = tuple((
opt(terminated(
take_while1(|c: char| c.is_alphanumeric()),
tag("://"),
)),
opt(take_while1(|c: char| c != '/')),
take_while(|c: char| c != '?'),
take_while(|c: char| !c.is_whitespace()),
))(input)?;
Ok((
input,
Uri {
scheme: scheme.unwrap_or_default().to_string(),
host: host.unwrap_or_default().to_string(),
path: path.to_string(),
query: query.to_string(),
},
))
}
pub fn parse_rule_value(input: &str) -> IResult<&str, OpValue> {
let (input, opval) = alt((
map(delimited(char1('`'), take_while(|c: char|c != ' ' && c != '\t' && c != '`'), char1('`')), |s:&str| OpValue::TemplateString(parse_template_string(s).unwrap().1)),
map(delimited(char1('('), take_while(|c: char|c != ' ' && c != '\t' && c != ')'), char1(')')), |s:&str| OpValue::Inline(s.to_string())),
map(delimited(char1('{'), take_while(|c: char|c != ' ' && c != '\t' && c != '}'), char1('}')), |s:&str| OpValue::Value(s.to_string())),
map(take_while(|c:char| !is_space(c as u8) ), |s: &str| OpValue::Raw(s.to_string())),
))(input)?;
Ok((
input,
opval,
))
}
pub fn parse_rule(input: &str) -> IResult<&str, Rule> {
let (input, (name, value)) = tuple((
terminated(take_while1(|c: char| c.is_alphanumeric()), tag("://")),
map(take_while(|c: char| !c.is_whitespace()), |s:&str| parse_rule_value(s)),
))(input)?;
let (_, value) = value?;
Ok((
input,
Rule {
name: name.to_string(),
value,
},
))
}
pub fn get_part(input: &str) -> IResult<&str, &str> {
preceded(multispace0, take_till1(|c: char| c.is_whitespace()))(input)
}
pub fn get_rules(input: &str) -> IResult<&str, Vec<Rule>> {
let (rest, rules) = preceded(whitespace, separated_list0(whitespace, map(not_space, |s:&str| {
parse_rule(s).unwrap().1
}))).parse(input)?;
Ok((
rest,
rules,
))
}
pub fn parse_proxy_rule(input: &str) -> IResult<&str, ProxyRule> {
let (rest, source) = map(get_part, all_consuming(parse_uri))(input)?;
let source = source?.1;
let (rest, target) = map(get_part, all_consuming(parse_uri))(rest)?;
let target = target?.1;
let (rest, rules) = if rest.trim().is_empty() {
(rest, vec![])
} else {
get_rules(rest).unwrap()
};
Ok((
rest,
ProxyRule {
source,
target,
rules,
}
))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_uri_to_string(){
let str = "http://localhost:8888/x?a=1";
let (input, uri) = parse_uri(str).unwrap();
assert_eq!(input, "");
assert_eq!(uri, Uri {
scheme: "http".into(),
host: "localhost:8888".into(),
path: "/x".into(),
query: "?a=1".into(),
});
assert_eq!(uri.to_string(), str);
}
#[test]
fn test_template_string(){
let str = "`x=1&b=2`";
let (input, ts) = parse_template_string(str).unwrap();
assert_eq!(input, "");
assert_eq!(ts.parts, vec![TemplatePart::RawString("`x=1&b=2`".into())]);
let str = "`(x=1&b=2)`";
let (input, ts) = parse_template_string(str).unwrap();
assert_eq!(input, "");
assert_eq!(ts.parts, vec![TemplatePart::RawString("`(x=1&b=2)`".into())]);
}
}