use heck::ToUpperCamelCase;
use syn::{Field, Ident, Meta, MetaNameValue, punctuated::Punctuated, token::Comma};
pub(crate) fn field_not_ignored(field: &Field) -> bool {
let field_type = &field.ty;
let field_type: String = quote::quote! { #field_type }
.to_string() .split_whitespace()
.collect();
if is_compound_field(&field_type) {
return false;
}
field_not_ignored_compound(field)
}
pub(crate) fn field_not_ignored_compound(field: &Field) -> bool {
for attr in field.attrs.iter() {
if let Some(ident) = attr.path().get_ident() {
if ident != "sea_orm" {
continue;
}
} else {
continue;
}
if let Ok(list) = attr.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated) {
for meta in list.iter() {
if let Meta::Path(path) = meta {
if let Some(name) = path.get_ident() {
if name == "ignore" {
return false;
}
}
}
}
}
}
true
}
pub(crate) fn is_compound_field(field_type: &str) -> bool {
((field_type.starts_with("Option<") || field_type.starts_with("Vec<")) && field_type.ends_with("::Entity>"))
|| field_type.starts_with("HasOne<") || field_type.starts_with("HasMany<")
}
pub(crate) fn extract_compound_entity(ty: &str) -> &str {
if ty.starts_with("HasMany<") {
&ty["HasMany<".len()..(ty.len() - 1)]
} else if ty.starts_with("HasOne<") {
&ty["HasOne<".len()..(ty.len() - 1)]
} else if ty.starts_with("Option<") {
&ty["Option<".len()..(ty.len() - 1)]
} else if ty.starts_with("Vec<") {
&ty["Vec<".len()..(ty.len() - 1)]
} else {
panic!("Relation applied to non compound type: {ty}")
}
}
pub(crate) fn format_field_ident(field: &Field) -> Ident {
field.ident.clone().unwrap()
}
pub(crate) fn trim_starting_raw_identifier<T>(string: T) -> String
where
T: ToString,
{
string
.to_string()
.trim_start_matches(RAW_IDENTIFIER)
.to_string()
}
pub(crate) fn escape_rust_keyword<T>(string: T) -> String
where
T: ToString,
{
let string = string.to_string();
if RUST_KEYWORDS.iter().any(|s| s.eq(&string)) {
format!("r#{string}")
} else if RUST_SPECIAL_KEYWORDS.iter().any(|s| s.eq(&string)) {
format!("{string}_")
} else {
string
}
}
pub(crate) fn camel_case_with_escaped_non_uax31<T>(string: T) -> String
where
T: ToString,
{
let additional_chars_to_replace: [char; 2] = ['_', ' '];
let mut rebuilt = string
.to_string()
.chars()
.enumerate()
.map(|(pos, char_)| {
if !additional_chars_to_replace.contains(&char_)
&& match pos {
0 => unicode_ident::is_xid_start(char_),
_ => unicode_ident::is_xid_continue(char_),
}
{
char_.to_string()
} else {
format!("{:#X}", char_ as u32)
}
})
.reduce(
|lhs, rhs| lhs + rhs.as_str(),
)
.map_or(
String::from("__Empty"),
|s| s.to_upper_camel_case(),
);
if rebuilt
.chars()
.next()
.map(char::is_numeric)
.unwrap_or(false)
{
rebuilt = String::from("_") + &rebuilt;
}
rebuilt
}
pub(crate) const RAW_IDENTIFIER: &str = "r#";
pub(crate) const RUST_KEYWORDS: [&str; 49] = [
"as", "async", "await", "break", "const", "continue", "dyn", "else", "enum", "extern", "false",
"fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
"return", "static", "struct", "super", "trait", "true", "type", "union", "unsafe", "use",
"where", "while", "abstract", "become", "box", "do", "final", "macro", "override", "priv",
"try", "typeof", "unsized", "virtual", "yield",
];
pub(crate) const RUST_SPECIAL_KEYWORDS: [&str; 3] = ["crate", "Self", "self"];
pub(crate) trait GetMeta {
fn exists(&self, k: &str) -> bool;
fn get_as_kv(&self, k: &str) -> Option<String>;
fn get_as_kv_with_ident(&self) -> Option<(Ident, String)>;
}
impl GetMeta for Meta {
fn exists(&self, key: &str) -> bool {
let Meta::Path(path) = self else {
return false;
};
path.is_ident(key)
}
fn get_as_kv(&self, key: &str) -> Option<String> {
let Meta::NameValue(MetaNameValue {
path,
value: syn::Expr::Lit(exprlit),
..
}) = self
else {
return None;
};
let syn::Lit::Str(litstr) = &exprlit.lit else {
return None;
};
if path.is_ident(key) {
Some(litstr.value())
} else {
None
}
}
fn get_as_kv_with_ident(&self) -> Option<(Ident, String)> {
let Meta::NameValue(MetaNameValue {
path,
value: syn::Expr::Lit(exprlit),
..
}) = self
else {
return None;
};
let syn::Lit::Str(litstr) = &exprlit.lit else {
return None;
};
path.get_ident()
.map(|ident| (ident.clone(), litstr.value()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_non_uax31_escape() {
assert_eq!(camel_case_with_escaped_non_uax31(""), "__Empty");
assert_eq!(camel_case_with_escaped_non_uax31(" "), "_0x20");
assert_eq!(camel_case_with_escaped_non_uax31(" "), "_0x200x20");
assert_eq!(camel_case_with_escaped_non_uax31("_"), "_0x5F");
assert_eq!(camel_case_with_escaped_non_uax31("foobar"), "Foobar");
assert_eq!(camel_case_with_escaped_non_uax31("foo bar"), "Foo0x20bar");
assert_eq!(camel_case_with_escaped_non_uax31("foo_bar"), "Foo0x5Fbar");
assert_eq!(camel_case_with_escaped_non_uax31("1"), "_0x31");
assert_eq!(
camel_case_with_escaped_non_uax31("1 2 3"),
"_0x310x2020x203"
);
assert_eq!(camel_case_with_escaped_non_uax31("씨오알엠"), "씨오알엠");
assert_eq!(camel_case_with_escaped_non_uax31("A_B"), "A0x5Fb");
assert_eq!(camel_case_with_escaped_non_uax31("AB"), "Ab");
}
}