idl-parser 0.1.0

A parser to parse Method URL Data -> Data
Documentation
use pest::{iterators::Pair, Parser};
use pest_derive::Parser;

#[derive(Parser)]
#[grammar = "grammar.pest"] // relative to src
pub struct EndpointParser;

type TypeName = String;

impl EndpointParser {
    pub fn parse_endpoint(input: &str) -> Result<Endpoint, ParseError> {
        let value = Self::parse(Rule::endpoint, input)
            .map_err(Box::new)?
            .next()
            .unwrap();

        match value.as_rule() {
            Rule::endpoint => value.try_into(),
            _ => panic!("unreachable"),
        }
    }
}

#[derive(thiserror::Error, Debug)]
pub enum ParseError {
    #[error("Unexpect rule")]
    UnexpectRule,
    #[error("Unsupport type")]
    UnsupportType,
    #[error("Parse error")]
    PestError(#[from] Box<pest::error::Error<Rule>>),
}

impl TryFrom<Pair<'_, Rule>> for Endpoint {
    type Error = ParseError;

    fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
        if Rule::endpoint == value.as_rule() {
            let mut inner = value.into_inner();
            let method: Method = inner.next().unwrap().try_into()?;
            let path: Vec<Path> = inner
                .next()
                .unwrap()
                .into_inner()
                .map(|v| v.try_into())
                .collect::<Result<Vec<_>, _>>()
                .unwrap();
            let query_params: Vec<Variable> = inner
                .next()
                .unwrap()
                .into_inner()
                .map(|v| v.try_into())
                .collect::<Result<Vec<_>, _>>()
                .unwrap();
            let mut pair = inner.next();
            let req_type: Option<RequestType> = if let Some(Ok(rq)) = pair.as_ref().map(|v| v.try_into()) {
                pair = inner.next();
                Some(rq)
            } else {
                None
            };
            let res_type: Option<ResponseType> = pair.and_then(|v| v.try_into().ok());

            Ok(Self {
                method,
                path,
                query_params,
                request_type: req_type.map(|v| v.0),
                response_type: res_type.map(|v| v.0),
            })
        } else {
            Err(ParseError::UnexpectRule)
        }
    }
}

#[derive(Debug, PartialEq, PartialOrd)]
pub struct Endpoint {
    pub method: Method,
    pub path: Vec<Path>,
    pub query_params: Vec<Variable>,
    pub request_type: Option<TypeName>,
    pub response_type: Option<TypeName>,
}

#[derive(Debug, PartialEq, PartialOrd)]
pub enum Method {
    GET,
    POST,
    PUT,
    DELETE,
}

#[derive(Debug, PartialEq, PartialOrd)]
pub enum Path {
    Segment(String),
    Variable(String, VariableType),
}

#[derive(Debug)]
pub struct QueryParam(String, VariableType);

#[derive(Debug, PartialEq, PartialOrd)]
pub enum VariableType {
    String,
    Short,
    Int,
    Long,
    Float,
    Double,
    Bool,
}

#[derive(Debug)]
pub struct RequestType(String);
#[derive(Debug)]
pub struct ResponseType(String);

impl TryFrom<Pair<'_, Rule>> for VariableType {
    type Error = ParseError;

    fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
        if Rule::variable_type == value.as_rule() {
            match value.as_str().to_lowercase().as_str() {
                "string" => Ok(Self::String),
                "short" => Ok(Self::Short),
                "int" => Ok(Self::Int),
                "long" => Ok(Self::Long),
                "float" => Ok(Self::Float),
                "double" => Ok(Self::Double),
                "bool" => Ok(Self::Bool),
                _ => Err(ParseError::UnsupportType),
            }
        } else {
            Err(ParseError::UnexpectRule)
        }
    }
}

#[derive(Debug, PartialEq, PartialOrd)]
pub struct Variable(String, VariableType);

impl TryFrom<Pair<'_, Rule>> for Variable {
    type Error = ParseError;

    fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
        if Rule::variable == value.as_rule() {
            let mut pairs = value.into_inner();
            let name = pairs.next().unwrap();
            let variable_type = pairs.next().unwrap().try_into()?;

            Ok(Self(name.as_str().to_owned(), variable_type))
        } else {
            Err(ParseError::UnexpectRule)
        }
    }
}

impl TryFrom<Pair<'_, Rule>> for Method {
    type Error = ParseError;

    fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
        if Rule::method == value.as_rule() {
            let Some(method) = value.into_inner().next() else {panic!("Impossible")};
            Ok(match method.as_str().to_uppercase().as_str() {
                "GET" => Self::GET,
                "POST" => Self::POST,
                "PUT" => Self::PUT,
                "DELETE" => Self::DELETE,
                _ => panic!("Impossible"),
            })
        } else {
            Err(ParseError::UnexpectRule)
        }
    }
}

impl TryFrom<Pair<'_, Rule>> for Path {
    type Error = ParseError;

    fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
        if Rule::segment == value.as_rule() {
            Ok(Path::Segment(
                value.into_inner().next().unwrap().as_str().to_string(),
            ))
        } else if Rule::variable == value.as_rule() {
            let Variable(name, var_type) = value.try_into()?;
            Ok(Path::Variable(name, var_type))
        } else {
            Err(ParseError::UnexpectRule)
        }
    }
}

macro_rules! impl_string_type {
    ($type:ident, $rule:expr, $r:ty) => {
        impl TryFrom<$r> for $type {
            type Error = ParseError;

            fn try_from(value: $r) -> Result<Self, Self::Error> {
                if $rule == value.as_rule() {
                    Ok($type(value.as_str().to_string()))
                } else {
                    Err(ParseError::UnexpectRule)
                }
            }
        }
    };
}

impl_string_type!(ResponseType, Rule::response_type, Pair<'_, Rule>);
impl_string_type!(RequestType, Rule::request_type, Pair<'_, Rule>);

impl_string_type!(ResponseType, Rule::response_type, &Pair<'_, Rule>);
impl_string_type!(RequestType, Rule::request_type, &Pair<'_, Rule>);

#[cfg(test)]
mod tests {
    use pest::Parser;

    use super::*;
    use crate::EndpointParser;

    #[test]
    fn test_variable() -> anyhow::Result<()> {
        let mut pairs = EndpointParser::parse(Rule::variable, "Name:string")?;
        let _: Variable = pairs.next().unwrap().try_into()?;
        Ok(())
    }

    #[test]
    fn test_method() -> anyhow::Result<()> {
        let mut pairs = EndpointParser::parse(Rule::method, "GET")?;
        let var: Method = pairs.next().unwrap().try_into()?;
        assert!(var == Method::GET);
        Ok(())
    }

    #[test]
    fn test_path() -> anyhow::Result<()> {
        let mut pairs = EndpointParser::parse(Rule::path, "/seg/{var:string}")?;

        for ele in pairs.next().unwrap().into_inner() {
            let _path: Path = ele.try_into()?;
        }
        Ok(())
    }

    #[test]
    fn test_query_params() -> anyhow::Result<()> {
        let mut pairs = EndpointParser::parse(Rule::query_params, "?a:string&b:bool")?;
        let inner = pairs.next().unwrap().into_inner();

        let _params: Result<Vec<Variable>, _> = inner.into_iter().map(|v| v.try_into()).collect();
        Ok(())
    }

    #[test]
    fn test_sig() -> anyhow::Result<()> {
        let mut pairs = EndpointParser::parse(Rule::request_type, "fasd")?;
        let request_type: RequestType = pairs.next().unwrap().try_into()?;

        println!("{:?}", request_type);

        Ok(())
    }

    #[test]
    fn test_endpoint() -> anyhow::Result<()> {
        let endpoint = EndpointParser::parse_endpoint(
            "GET /register/{id:string}?type:string&order:string RQ -> RS",
        )?;
        assert_eq!(
            Endpoint {
                method: Method::GET,
                path: vec![
                    Path::Segment("register".to_owned()),
                    Path::Variable("id".to_owned(), VariableType::String)
                ],
                query_params: vec![
                    Variable("type".to_owned(), VariableType::String),
                    Variable("order".to_owned(), VariableType::String),
                ],
                request_type: Some("RQ".to_owned()),
                response_type: Some("RS".to_owned())
            },
            endpoint
        );
        Ok(())
    }

    #[test]
    fn test_endpoint_without_sig() -> anyhow::Result<()> {
        let endpoint =
            EndpointParser::parse_endpoint("GET /register/{id:string}?type:string&order:string ")?;
        assert_eq!(
            Endpoint {
                method: Method::GET,
                path: vec![
                    Path::Segment("register".to_owned()),
                    Path::Variable("id".to_owned(), VariableType::String)
                ],
                query_params: vec![
                    Variable("type".to_owned(), VariableType::String),
                    Variable("order".to_owned(), VariableType::String),
                ],
                request_type: None,
                response_type: None
            },
            endpoint
        );
        Ok(())
    }
    #[test]
    fn test_endpoint_without_query_params() -> anyhow::Result<()> {
        let endpoint =
            EndpointParser::parse_endpoint("GET /register/{id:string} RQ -> RS")?;
        assert_eq!(
            Endpoint {
                method: Method::GET,
                path: vec![
                    Path::Segment("register".to_owned()),
                    Path::Variable("id".to_owned(), VariableType::String)
                ],
                query_params: vec![
                ],
                request_type: Some("RQ".to_owned()),
                response_type: Some("RS".to_owned())
            },
            endpoint
        );
        Ok(())
    }
}