use crate::sql::comment::mightbespace; use crate::sql::comment::shouldbespace; use crate::sql::cond::{cond, Cond}; use crate::sql::dir::{dir, Dir}; use crate::sql::error::IResult; use crate::sql::idiom::{idiom, Idiom}; use crate::sql::table::{table, tables, Tables}; use nom::branch::alt; use nom::bytes::complete::tag_no_case; use nom::character::complete::char; use nom::combinator::map; use nom::combinator::opt; use serde::{Deserialize, Serialize}; use std::fmt; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize)] pub struct Graph { pub dir: Dir, pub what: Tables, pub cond: Option, pub alias: Option, } impl Graph { pub fn to_raw(&self) -> String { self.to_string() } } impl fmt::Display for Graph { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.what.0.len() <= 1 && self.cond.is_none() && self.alias.is_none() { write!(f, "{}", self.dir)?; match self.what.len() { 0 => write!(f, "?"), _ => write!(f, "{}", self.what), } } else { write!(f, "{}(", self.dir)?; match self.what.len() { 0 => write!(f, "?"), _ => write!(f, "{}", self.what), }?; if let Some(ref v) = self.cond { write!(f, " {}", v)? } if let Some(ref v) = self.alias { write!(f, " AS {}", v)? } write!(f, ")") } } } pub fn graph(i: &str) -> IResult<&str, Graph> { let (i, dir) = dir(i)?; let (i, (what, cond, alias)) = alt((simple, custom))(i)?; Ok(( i, Graph { dir, what, cond, alias, }, )) } fn simple(i: &str) -> IResult<&str, (Tables, Option, Option)> { let (i, w) = alt((any, one))(i)?; Ok((i, (w, None, None))) } fn custom(i: &str) -> IResult<&str, (Tables, Option, Option)> { let (i, _) = char('(')(i)?; let (i, _) = mightbespace(i)?; let (i, w) = alt((any, tables))(i)?; let (i, c) = opt(|i| { let (i, _) = shouldbespace(i)?; let (i, v) = cond(i)?; Ok((i, v)) })(i)?; let (i, a) = opt(|i| { let (i, _) = shouldbespace(i)?; let (i, _) = tag_no_case("AS")(i)?; let (i, _) = shouldbespace(i)?; let (i, v) = idiom(i)?; Ok((i, v)) })(i)?; let (i, _) = mightbespace(i)?; let (i, _) = char(')')(i)?; Ok((i, (w, c, a))) } fn one(i: &str) -> IResult<&str, Tables> { let (i, v) = table(i)?; Ok((i, Tables::from(v))) } fn any(i: &str) -> IResult<&str, Tables> { map(char('?'), |_| Tables::default())(i) } #[cfg(test)] mod tests { use super::*; #[test] fn graph_in() { let sql = "<-likes"; let res = graph(sql); assert!(res.is_ok()); let out = res.unwrap().1; assert_eq!("<-likes", format!("{}", out)); } #[test] fn graph_out() { let sql = "->likes"; let res = graph(sql); assert!(res.is_ok()); let out = res.unwrap().1; assert_eq!("->likes", format!("{}", out)); } #[test] fn graph_both() { let sql = "<->likes"; let res = graph(sql); assert!(res.is_ok()); let out = res.unwrap().1; assert_eq!("<->likes", format!("{}", out)); } #[test] fn graph_multiple() { let sql = "->(likes, follows)"; let res = graph(sql); assert!(res.is_ok()); let out = res.unwrap().1; assert_eq!("->(likes, follows)", format!("{}", out)); } #[test] fn graph_aliases() { let sql = "->(likes, follows AS connections)"; let res = graph(sql); assert!(res.is_ok()); let out = res.unwrap().1; assert_eq!("->(likes, follows AS connections)", format!("{}", out)); } #[test] fn graph_conditions() { let sql = "->(likes, follows WHERE influencer = true)"; let res = graph(sql); assert!(res.is_ok()); let out = res.unwrap().1; assert_eq!("->(likes, follows WHERE influencer = true)", format!("{}", out)); } #[test] fn graph_conditions_aliases() { let sql = "->(likes, follows WHERE influencer = true AS connections)"; let res = graph(sql); assert!(res.is_ok()); let out = res.unwrap().1; assert_eq!("->(likes, follows WHERE influencer = true AS connections)", format!("{}", out)); } }