use convert_case::{Boundary, Case, Casing};
use virtue::{
prelude::{Error, FromAttribute, Group, Literal, Result},
utils::{ParsedAttribute, parse_tagged_attribute},
};
pub(crate) fn tab_name(ident: &str) -> String {
ident
.from_case(Case::Pascal)
.remove_boundaries(&[
Boundary::UpperDigit,
Boundary::DigitLower,
Boundary::LowerDigit,
])
.to_case(Case::Snake)
}
pub(crate) fn col_typ(rust_typ: &str) -> &'static str {
match rust_typ {
"bool" | "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" => "INTEGER NOT NULL",
"f32" | "f64" => "REAL NOT NULL",
"&str" | "String" => "TEXT NOT NULL",
"&[u8]" | "Vec<u8>" => "BLOB NOT NULL",
"Option<bool>" | "Option<i8>" | "Option<i16>" | "Option<i32>" | "Option<i64>"
| "Option<u8>" | "Option<u16>" | "Option<u32>" => "INTEGER",
"Option<f32>" | "Option<f64>" => "REAL",
"Option<String>" => "TEXT",
"Option<Vec<u8>>" => "BLOB",
_ => "ANY",
}
}
#[derive(Debug, Default)]
pub(crate) struct TabAttr {
pub(crate) constraint: String, pub(crate) option: String, }
impl FromAttribute for TabAttr {
fn parse(group: &Group) -> Result<Option<Self>> {
let Some(attributes) = parse_tagged_attribute(group, "sql")? else {
return Ok(None);
};
let mut tab = Self::default();
for attr in attributes {
match attr {
ParsedAttribute::Tag(key) => {
return Err(Error::custom_at("unknown table attr", key.span()));
}
ParsedAttribute::Property(key, val) => match key.to_string().as_str() {
"constraint" => tab.constraint = literal_str(val)?,
"option" => tab.option = literal_str(val)?,
_ => return Err(Error::custom_at("unknown table attr", key.span())),
},
_ => {}
}
}
Ok(Some(tab))
}
}
#[derive(Debug, Default)]
pub(crate) struct ColAttr {
pub(crate) typ: String, pub(crate) constraint: String, }
impl FromAttribute for ColAttr {
fn parse(group: &Group) -> Result<Option<Self>> {
let Some(attributes) = parse_tagged_attribute(group, "sql")? else {
return Ok(None);
};
let mut col = Self::default();
for attr in attributes {
match attr {
ParsedAttribute::Tag(i) => {
return Err(Error::custom_at("unknown column attr", i.span()));
}
ParsedAttribute::Property(key, val) => match key.to_string().as_str() {
"typ" => col.typ = literal_str(val)?,
"constraint" => col.constraint = literal_str(val)?,
_ => return Err(Error::custom_at("unknown column attr", key.span())),
},
_ => {}
}
}
Ok(Some(col))
}
}
#[derive(Debug, Default)]
pub(crate) struct AsTabAttr {
pub(crate) from: String, }
impl FromAttribute for AsTabAttr {
fn parse(group: &Group) -> Result<Option<Self>> {
let Some(attributes) = parse_tagged_attribute(group, "sqlas")? else {
return Ok(None);
};
let mut tab = Self::default();
for attr in attributes {
match attr {
ParsedAttribute::Tag(i) => {
return Err(Error::custom_at("unknown table attr", i.span()));
}
ParsedAttribute::Property(key, val) => match key.to_string().as_str() {
"from" => tab.from = literal_str(val)?,
_ => return Err(Error::custom_at("unknown table attr", key.span())),
},
_ => {}
}
}
Ok(Some(tab))
}
}
#[derive(Debug, Default)]
pub(crate) struct AsColAttr {
pub(crate) col: String, }
impl FromAttribute for AsColAttr {
fn parse(group: &Group) -> Result<Option<Self>> {
let Some(attributes) = parse_tagged_attribute(group, "sqlas")? else {
return Ok(None);
};
let mut col = Self::default();
for attr in attributes {
match attr {
ParsedAttribute::Tag(i) => {
return Err(Error::custom_at("unknown column attr", i.span()));
}
ParsedAttribute::Property(key, val) => match key.to_string().as_str() {
"col" => col.col = literal_str(val)?,
_ => return Err(Error::custom_at("unknown column attr", key.span())),
},
_ => {}
}
}
Ok(Some(col))
}
}
fn literal_str(val: Literal) -> Result<String> {
let val_string = val.to_string();
if val_string.starts_with('"') && val_string.ends_with('"') {
Ok(val_string[1..val_string.len() - 1].to_string())
} else {
Err(Error::custom_at("should be a literal str", val.span()))
}
}
#[cfg(test)]
mod tests {
#[test]
fn tab_name() {
fn t(id: &str, tn: &str) {
assert_eq!(tn, super::tab_name(id))
}
t("", "");
t("Record", "record");
t("invalidType", "invalid_type");
t("MyRecord", "my_record");
t("MyLongRecord", "my_long_record");
t("Digit2Upper", "digit2_upper");
t("Digit2Upper3", "digit2_upper3");
t("Digit2Upper4Long", "digit2_upper4_long");
t("U5Tab", "u5_tab");
t("T5loName", "t5lo_name");
t("No6Today", "no6_today");
t("HTTPRequest", "http_request");
}
}