use std::error::Error;
use std::fmt::{
self,
Write
};
use std::num::ParseIntError;
use std::str::FromStr;
use conciliator::{
Buffer,
Inline,
Paint
};
use rusqlite::{
Row,
Result as SqlResult,
Error as SqlError
};
use rusqlite::types::{
FromSql,
ToSql,
ValueRef,
FromSqlResult,
FromSqlError,
ToSqlOutput
};
#[derive(Debug, Eq, PartialEq)]
pub struct Category {
pub id: u64,
pub path: Path,
pub name: String
}
#[derive(Debug, Eq, PartialEq)]
pub struct Resolved {
pub id: u64,
pub id_path: Path,
pub string_path: String,
pub name: String
}
#[derive(Debug, Eq, PartialEq)]
pub struct Path(pub(crate) Vec<u64>);
#[derive(Debug, Eq, PartialEq)]
pub enum ParsePathError {
ParseInt(ParseIntError),
Empty,
MissingSeparator
}
impl Category {
pub fn resolve(self, string_path: String) -> Resolved {
let Self {id, name, path} = self;
Resolved {id, id_path: path, string_path, name}
}
}
impl<'r> TryFrom<&Row<'r>> for Category {
type Error = SqlError;
fn try_from(value: &Row<'r>) -> Result<Self, Self::Error> {
let (id, name, path_str): (_, _, Box<str>) = value.try_into()?;
let path = path_str.parse().unwrap();
Ok(Self {id, name, path})
}
}
impl Inline for Category {
fn inline(&self, buf: &mut Buffer) {
buf.push_alpha_bold(&self.name);
}
}
impl Resolved {
pub fn revert(self) -> Category {
let Self {id, id_path, name, ..} = self;
Category {id, path: id_path, name}
}
}
impl Inline for Resolved {
fn inline(&self, buffer: &mut Buffer) {
buffer.push_plain(&self.string_path).push_beta(&self.name);
}
}
impl Path {
pub fn serialize(&self) -> String {
self.0.iter().fold(String::from("/"), |mut s, id| {
write!(s, "{id}/").unwrap();
s
})
}
pub fn is_child_of(&self, category: &Category) -> bool {
#[allow(clippy::needless_return)]
return self.0.len() == (category.path.0.len() + 1)
&& self.0.starts_with(&category.path.0)
&& self.0.ends_with(&[category.id]);
}
}
impl FromStr for Path {
type Err = ParsePathError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut split = s.split('/');
match split.next() {
Some("") => match split.next_back() {
Some("") => split
.map(str::parse)
.collect::<Result<_, _>>()
.map_err(ParsePathError::ParseInt)
.map(Self),
_ => Err(ParsePathError::MissingSeparator)
},
Some(_) => Err(ParsePathError::MissingSeparator),
None => Err(ParsePathError::Empty)
}
}
}
impl FromSql for Path {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
String::column_result(value)?
.parse()
.map_err(Into::into)
.map_err(FromSqlError::Other)
}
}
impl ToSql for Path {
fn to_sql(&self) -> SqlResult<ToSqlOutput<'_>> {
Ok(self.serialize().into())
}
}
impl Error for ParsePathError {}
impl fmt::Display for ParsePathError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
#[test]
fn path_parse_and_serialize() {
let valid = [
("/", vec![]),
("/0/", vec![0]),
("/1/", vec![1]),
("/0/1/2/3/", vec![0, 1, 2, 3])
];
let invalid = [
"",
"//",
"0",
"/0",
"0/",
"/0/1",
"0/1",
"a",
"/a/",
"/0/x/1/"
];
for (to_parse, expected) in valid {
println!("Parsing '{to_parse}', expecting {expected:?}");
let parsed = to_parse.parse();
assert_eq!(
parsed,
Ok(Path(expected)),
"failed to parse {to_parse}"
);
assert_eq!(parsed.unwrap().serialize(), to_parse);
}
for to_parse in invalid {
println!("Parsing '{to_parse}', expecting error");
let err = to_parse.parse::<Path>().unwrap_err();
println!("Err: '{err:?}'!)");
}
}