use proc_macro2::TokenStream;
use quote::quote;
use prax_schema::ast::{Field, FieldType, Model, TypeModifier};
use super::{generate_doc_comment, pascal_ident, snake_ident};
use crate::types::field_type_to_rust;
pub fn generate_field_module(field: &Field, model: &Model) -> TokenStream {
let field_name = snake_ident(field.name());
let field_name_pascal = pascal_ident(field.name());
let field_type = field_type_to_rust(&field.field_type, &TypeModifier::Required);
let _full_field_type = field_type_to_rust(&field.field_type, &field.modifier);
let doc = generate_doc_comment(field.documentation.as_ref().map(|d| d.text.as_str()));
let col_name = field
.attributes
.iter()
.find(|a| a.name() == "map")
.and_then(|a| a.first_arg())
.and_then(|v| v.as_string())
.map(|s| s.to_string())
.unwrap_or_else(|| field.name().to_string());
let is_optional = field.modifier.is_optional();
let is_list = field.modifier.is_list();
let is_relation = matches!(field.field_type, FieldType::Model(_));
let order_by = if !is_list && !is_relation {
quote! {
pub fn asc() -> super::OrderByParam {
super::OrderByParam::#field_name_pascal(::prax_orm::_prax_prelude::SortOrder::Asc)
}
pub fn desc() -> super::OrderByParam {
super::OrderByParam::#field_name_pascal(::prax_orm::_prax_prelude::SortOrder::Desc)
}
}
} else {
TokenStream::new()
};
let set_ops = if !is_relation {
let set_type = if is_optional {
quote! { Option<#field_type> }
} else {
field_type.clone()
};
quote! {
pub fn set(value: #set_type) -> super::SetParam {
super::SetParam::#field_name_pascal(value)
}
}
} else {
TokenStream::new()
};
let filters = super::filters::generate_field_filters(field, model.name());
quote! {
#doc
pub mod #field_name {
pub const COLUMN: &str = #col_name;
pub const IS_OPTIONAL: bool = #is_optional;
pub const IS_LIST: bool = #is_list;
pub fn select() -> super::SelectParam {
super::SelectParam::#field_name_pascal
}
#order_by
#set_ops
#filters
}
}
}
pub fn generate_select_param(model: &Model) -> TokenStream {
let variants: Vec<_> = model
.fields
.values()
.filter(|f| !matches!(f.field_type, FieldType::Model(_)))
.map(|f| {
let name = pascal_ident(f.name());
quote! { #name }
})
.collect();
let variant_names: Vec<_> = model
.fields
.values()
.filter(|f| !matches!(f.field_type, FieldType::Model(_)))
.map(|f| {
let name = pascal_ident(f.name());
let col = f
.attributes
.iter()
.find(|a| a.name() == "map")
.and_then(|a| a.first_arg())
.and_then(|v| v.as_string())
.map(|s| s.to_string())
.unwrap_or_else(|| f.name().to_string());
(name, col)
})
.collect();
let column_matches: Vec<_> = variant_names
.iter()
.map(|(name, col)| {
quote! { Self::#name => #col }
})
.collect();
quote! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SelectParam {
#(#variants,)*
}
impl SelectParam {
pub fn column(&self) -> &'static str {
match self {
#(#column_matches,)*
}
}
}
}
}
pub fn generate_order_by_param(model: &Model) -> TokenStream {
let variants: Vec<_> = model
.fields
.values()
.filter(|f| !f.modifier.is_list() && !matches!(f.field_type, FieldType::Model(_)))
.map(|f| {
let name = pascal_ident(f.name());
quote! { #name(::prax_orm::_prax_prelude::SortOrder) }
})
.collect();
let variant_names: Vec<_> = model
.fields
.values()
.filter(|f| !f.modifier.is_list() && !matches!(f.field_type, FieldType::Model(_)))
.map(|f| {
let name = pascal_ident(f.name());
let col = f
.attributes
.iter()
.find(|a| a.name() == "map")
.and_then(|a| a.first_arg())
.and_then(|v| v.as_string())
.map(|s| s.to_string())
.unwrap_or_else(|| f.name().to_string());
(name, col)
})
.collect();
let column_matches: Vec<_> = variant_names
.iter()
.map(|(name, col)| {
quote! { Self::#name(order) => (#col, order) }
})
.collect();
quote! {
#[derive(Debug, Clone, Copy)]
pub enum OrderByParam {
#(#variants,)*
}
impl OrderByParam {
pub fn column_and_order(&self) -> (&'static str, &::prax_orm::_prax_prelude::SortOrder) {
match self {
#(#column_matches,)*
}
}
pub fn to_sql(&self) -> String {
let (col, order) = self.column_and_order();
let dir = match order {
::prax_orm::_prax_prelude::SortOrder::Asc => "ASC",
::prax_orm::_prax_prelude::SortOrder::Desc => "DESC",
};
format!("{} {}", col, dir)
}
}
}
}
pub fn generate_set_param(model: &Model) -> TokenStream {
let variants: Vec<_> = model
.fields
.values()
.filter(|f| !matches!(f.field_type, FieldType::Model(_)))
.map(|f| {
let name = pascal_ident(f.name());
let field_type = field_type_to_rust(&f.field_type, &f.modifier);
quote! { #name(#field_type) }
})
.collect();
let variant_names: Vec<_> = model
.fields
.values()
.filter(|f| !matches!(f.field_type, FieldType::Model(_)))
.map(|f| {
let name = pascal_ident(f.name());
let col = f
.attributes
.iter()
.find(|a| a.name() == "map")
.and_then(|a| a.first_arg())
.and_then(|v| v.as_string())
.map(|s| s.to_string())
.unwrap_or_else(|| f.name().to_string());
(name, col)
})
.collect();
let column_matches: Vec<_> = variant_names
.iter()
.map(|(name, col)| {
quote! { Self::#name(_) => #col }
})
.collect();
quote! {
#[derive(Debug, Clone)]
pub enum SetParam {
#(#variants,)*
}
impl SetParam {
pub fn column(&self) -> &'static str {
match self {
#(#column_matches,)*
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use prax_schema::ast::{Ident, ScalarType, Span};
fn make_span() -> Span {
Span::new(0, 0)
}
fn make_ident(name: &str) -> Ident {
Ident::new(name, make_span())
}
fn make_model() -> Model {
let mut model = Model::new(make_ident("User"), make_span());
model.add_field(Field::new(
make_ident("id"),
FieldType::Scalar(ScalarType::Int),
TypeModifier::Required,
vec![],
make_span(),
));
model.add_field(Field::new(
make_ident("name"),
FieldType::Scalar(ScalarType::String),
TypeModifier::Required,
vec![],
make_span(),
));
model.add_field(Field::new(
make_ident("email"),
FieldType::Scalar(ScalarType::String),
TypeModifier::Optional,
vec![],
make_span(),
));
model
}
#[test]
fn test_generate_select_param() {
let model = make_model();
let select = generate_select_param(&model);
let code = select.to_string();
assert!(code.contains("pub enum SelectParam"));
assert!(code.contains("Id"));
assert!(code.contains("Name"));
assert!(code.contains("Email"));
}
#[test]
fn test_generate_order_by_param() {
let model = make_model();
let order_by = generate_order_by_param(&model);
let code = order_by.to_string();
assert!(code.contains("pub enum OrderByParam"));
assert!(code.contains("SortOrder"));
}
#[test]
fn test_generate_set_param() {
let model = make_model();
let set = generate_set_param(&model);
let code = set.to_string();
assert!(code.contains("pub enum SetParam"));
assert!(code.contains("Id"));
assert!(code.contains("Name"));
}
}