use proc_macro2::Span;
use syn::ext::IdentExt;
use syn::{DeriveInput, Result};
pub(super) struct HasManyRelation {
pub model: syn::Path,
pub foreign_key: String,
pub method_name: String,
}
pub(super) struct BelongsToRelation {
pub model: syn::Path,
pub foreign_key: String,
pub method_name: String,
}
pub(super) struct HasOneRelation {
pub model: syn::Path,
pub foreign_key: String,
pub method_name: String,
}
pub(super) struct ManyToManyRelation {
pub model: syn::Path,
pub through: String,
pub self_key: String,
pub other_key: String,
pub method_name: String,
}
struct HasManyAttr {
model: syn::Path,
foreign_key: String,
method_name: String,
}
impl syn::parse::Parse for HasManyAttr {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
let ident: syn::Ident = input.parse()?;
if ident != "has_many" {
return Err(syn::Error::new(ident.span(), "expected has_many"));
}
let content;
syn::parenthesized!(content in input);
let model: syn::Path = content.parse()?;
let mut foreign_key: Option<String> = None;
let mut method_name: Option<String> = None;
while content.peek(syn::Token![,]) {
let _: syn::Token![,] = content.parse()?;
if content.is_empty() {
break;
}
let key = syn::Ident::parse_any(&content)?;
let _: syn::Token![=] = content.parse()?;
let value: syn::LitStr = content.parse()?;
if key == "foreign_key" {
foreign_key = Some(value.value());
} else if key == "as" || key == "name" {
method_name = Some(value.value());
}
}
let fk = foreign_key.ok_or_else(|| {
syn::Error::new(Span::call_site(), "has_many requires foreign_key = \"...\"")
})?;
let name = method_name.unwrap_or_else(|| {
let model_name = model.segments.last().unwrap().ident.to_string();
format!("{}s", model_name.to_lowercase())
});
Ok(HasManyAttr {
model,
foreign_key: fk,
method_name: name,
})
}
}
struct BelongsToAttr {
model: syn::Path,
foreign_key: String,
method_name: String,
}
struct HasOneAttr {
model: syn::Path,
foreign_key: String,
method_name: String,
}
struct ManyToManyAttr {
model: syn::Path,
through: String,
self_key: String,
other_key: String,
method_name: String,
}
impl syn::parse::Parse for BelongsToAttr {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
let ident: syn::Ident = input.parse()?;
if ident != "belongs_to" {
return Err(syn::Error::new(ident.span(), "expected belongs_to"));
}
let content;
syn::parenthesized!(content in input);
let model: syn::Path = content.parse()?;
let mut foreign_key: Option<String> = None;
let mut method_name: Option<String> = None;
while content.peek(syn::Token![,]) {
let _: syn::Token![,] = content.parse()?;
if content.is_empty() {
break;
}
let key = syn::Ident::parse_any(&content)?;
let _: syn::Token![=] = content.parse()?;
let value: syn::LitStr = content.parse()?;
if key == "foreign_key" {
foreign_key = Some(value.value());
} else if key == "as" || key == "name" {
method_name = Some(value.value());
}
}
let fk = foreign_key.ok_or_else(|| {
syn::Error::new(
Span::call_site(),
"belongs_to requires foreign_key = \"...\"",
)
})?;
let name = method_name.unwrap_or_else(|| {
let model_name = model.segments.last().unwrap().ident.to_string();
model_name.to_lowercase()
});
Ok(BelongsToAttr {
model,
foreign_key: fk,
method_name: name,
})
}
}
impl syn::parse::Parse for HasOneAttr {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
let ident: syn::Ident = input.parse()?;
if ident != "has_one" {
return Err(syn::Error::new(ident.span(), "expected has_one"));
}
let content;
syn::parenthesized!(content in input);
let model: syn::Path = content.parse()?;
let mut foreign_key: Option<String> = None;
let mut method_name: Option<String> = None;
while content.peek(syn::Token![,]) {
let _: syn::Token![,] = content.parse()?;
if content.is_empty() {
break;
}
let key = syn::Ident::parse_any(&content)?;
let _: syn::Token![=] = content.parse()?;
let value: syn::LitStr = content.parse()?;
if key == "foreign_key" {
foreign_key = Some(value.value());
} else if key == "as" || key == "name" {
method_name = Some(value.value());
}
}
let fk = foreign_key.ok_or_else(|| {
syn::Error::new(Span::call_site(), "has_one requires foreign_key = \"...\"")
})?;
let name = method_name.unwrap_or_else(|| {
let model_name = model.segments.last().unwrap().ident.to_string();
model_name.to_lowercase()
});
Ok(HasOneAttr {
model,
foreign_key: fk,
method_name: name,
})
}
}
impl syn::parse::Parse for ManyToManyAttr {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
let ident: syn::Ident = input.parse()?;
if ident != "many_to_many" {
return Err(syn::Error::new(ident.span(), "expected many_to_many"));
}
let content;
syn::parenthesized!(content in input);
let model: syn::Path = content.parse()?;
let mut through: Option<String> = None;
let mut self_key: Option<String> = None;
let mut other_key: Option<String> = None;
let mut method_name: Option<String> = None;
while content.peek(syn::Token![,]) {
let _: syn::Token![,] = content.parse()?;
if content.is_empty() {
break;
}
let key = syn::Ident::parse_any(&content)?;
let _: syn::Token![=] = content.parse()?;
let value: syn::LitStr = content.parse()?;
if key == "through" {
through = Some(value.value());
} else if key == "self_key" {
self_key = Some(value.value());
} else if key == "other_key" {
other_key = Some(value.value());
} else if key == "as" || key == "name" {
method_name = Some(value.value());
}
}
let through = through.ok_or_else(|| {
syn::Error::new(Span::call_site(), "many_to_many requires through = \"...\"")
})?;
let self_key = self_key.ok_or_else(|| {
syn::Error::new(
Span::call_site(),
"many_to_many requires self_key = \"...\"",
)
})?;
let other_key = other_key.ok_or_else(|| {
syn::Error::new(
Span::call_site(),
"many_to_many requires other_key = \"...\"",
)
})?;
let name = method_name.unwrap_or_else(|| {
let model_name = model.segments.last().unwrap().ident.to_string();
format!("{}s", model_name.to_lowercase())
});
Ok(ManyToManyAttr {
model,
through,
self_key,
other_key,
method_name: name,
})
}
}
pub(super) fn get_has_many_relations(input: &DeriveInput) -> Result<Vec<HasManyRelation>> {
let mut relations = Vec::new();
for attr in &input.attrs {
if attr.path().is_ident("orm") {
if let syn::Meta::List(meta_list) = &attr.meta {
let tokens = meta_list.tokens.clone();
if let Ok(parsed) = syn::parse2::<HasManyAttr>(tokens) {
relations.push(HasManyRelation {
model: parsed.model,
foreign_key: parsed.foreign_key,
method_name: parsed.method_name,
});
}
}
}
}
Ok(relations)
}
pub(super) fn get_has_one_relations(input: &DeriveInput) -> Result<Vec<HasOneRelation>> {
let mut relations = Vec::new();
for attr in &input.attrs {
if attr.path().is_ident("orm") {
if let syn::Meta::List(meta_list) = &attr.meta {
let tokens = meta_list.tokens.clone();
if let Ok(parsed) = syn::parse2::<HasOneAttr>(tokens) {
relations.push(HasOneRelation {
model: parsed.model,
foreign_key: parsed.foreign_key,
method_name: parsed.method_name,
});
}
}
}
}
Ok(relations)
}
pub(super) fn get_belongs_to_relations(input: &DeriveInput) -> Result<Vec<BelongsToRelation>> {
let mut relations = Vec::new();
for attr in &input.attrs {
if attr.path().is_ident("orm") {
if let syn::Meta::List(meta_list) = &attr.meta {
let tokens = meta_list.tokens.clone();
if let Ok(parsed) = syn::parse2::<BelongsToAttr>(tokens) {
relations.push(BelongsToRelation {
model: parsed.model,
foreign_key: parsed.foreign_key,
method_name: parsed.method_name,
});
}
}
}
}
Ok(relations)
}
pub(super) fn get_many_to_many_relations(input: &DeriveInput) -> Result<Vec<ManyToManyRelation>> {
let mut relations = Vec::new();
for attr in &input.attrs {
if attr.path().is_ident("orm") {
if let syn::Meta::List(meta_list) = &attr.meta {
let tokens = meta_list.tokens.clone();
if let Ok(parsed) = syn::parse2::<ManyToManyAttr>(tokens) {
relations.push(ManyToManyRelation {
model: parsed.model,
through: parsed.through,
self_key: parsed.self_key,
other_key: parsed.other_key,
method_name: parsed.method_name,
});
}
}
}
}
Ok(relations)
}