use darling::FromDeriveInput;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{parse_quote, DeriveInput, Generics, Ident};
pub enum ConvertType {
FromInto(Ident),
TryFromInto(Ident),
Transparent(Ident),
}
use ConvertType::*;
impl ConvertType {
fn ident(&self) -> &Ident {
match self {
FromInto(ident) => ident,
TryFromInto(ident) => ident,
Transparent(ident) => ident,
}
}
}
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(sea_orm_newtype))]
struct InputReceiver {
from_into: Option<String>,
try_from_into: Option<String>,
#[darling(default)]
transparent: bool,
#[darling(default)]
primary_key: bool,
#[darling(default)]
type_name: bool,
}
pub fn derive_newtype_inner(input: &DeriveInput) -> syn::Result<TokenStream> {
let InputReceiver {
from_into,
try_from_into,
transparent,
primary_key,
type_name,
} = InputReceiver::from_derive_input(input)?;
let new_type_name = &input.ident;
let convert_type = match (from_into, try_from_into, transparent) {
(Some(from_into), None, false) => FromInto(Ident::new(&from_into, Span::call_site())),
(None, Some(try_from_into), false) => {
TryFromInto(Ident::new(&try_from_into, Span::call_site()))
}
(None, None, true) | (None, None, false) => {
Transparent(get_and_check_transparent_type(input)?)
}
_ => {
return Err(syn::Error::new(
Span::call_site(),
r#"sea_orm_newtype must be set from_into = "BaseType" or try_from_into = "BaseType" or transparent at most one."#,
))
}
};
let generics = &input.generics;
let mod_name = format_ident!("__sea_orm_newtype_{}", new_type_name);
let impl_from_newtype_for_value =
from_newtype_for_value(new_type_name, &convert_type, generics);
let impl_try_getable_for_newtype =
try_getable_for_newtype(new_type_name, &convert_type, generics);
let impl_value_type_for_newtype =
value_type_for_newtype(new_type_name, &convert_type, generics, type_name);
let impl_nullable_for_newtype = nullable_for_newtype(new_type_name, &convert_type, generics);
let impl_try_from_u64_for_newtype =
primary_key.then(|| try_from_u64_for_newtype(new_type_name, &convert_type, generics));
Ok(quote! {
#[allow(non_snake_case)]
mod #mod_name {
use super::*;
#impl_from_newtype_for_value
#impl_try_getable_for_newtype
#impl_value_type_for_newtype
#impl_nullable_for_newtype
#impl_try_from_u64_for_newtype
}
})
}
fn get_and_check_transparent_type(input: &DeriveInput) -> syn::Result<Ident> {
if let syn::Data::Struct(data_struct) = &input.data {
if let syn::Fields::Unnamed(unnamed_fields) = &data_struct.fields {
if unnamed_fields.unnamed.len() == 1 {
let base_type_ty = &(unnamed_fields.unnamed.first().unwrap().ty);
return Ok(parse_quote!(#base_type_ty));
}
}
}
Err(syn::Error::new(
Span::call_site(),
r#"`sea_orm_newtype(transparent)` can only use for tuple struct thats have only one field."#,
))
}
fn from_newtype_for_value(
new_type_name: &Ident,
convert_type: &ConvertType,
generics: &Generics,
) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let from_block = match convert_type {
FromInto(base_type_name) | TryFromInto(base_type_name) => {
quote! { Into::<#base_type_name>::into(value).into() }
}
Transparent(_) => {
quote! { value.0.into() }
}
};
quote! {
impl #impl_generics From<#new_type_name #ty_generics> for ::sea_orm_newtype::Value #where_clause {
fn from(value: #new_type_name #ty_generics) -> Self {
#from_block
}
}
}
}
fn try_getable_for_newtype(
new_type_name: &Ident,
convert_type: &ConvertType,
generics: &Generics,
) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let mut assertion_error_bound = Option::<TokenStream>::None;
let try_get_block = match convert_type {
FromInto(base_type_name) => {
quote! { Ok(Into::<Self>::into(res.try_get_by::<#base_type_name, I>(index)?)) }
}
TryFromInto(base_type_name) => {
assertion_error_bound = {
let assertion_temp_type = format_ident!("__Assertion{}TryFrom", new_type_name);
Some(quote! {
#[allow(non_camel_case_types)]
struct #assertion_temp_type where
<#new_type_name as TryFrom<#base_type_name>>::Error: ::std::error::Error;
})
};
quote! {
Ok(
TryInto::<Self>::try_into(res.try_get_by::<#base_type_name, I>(index)?)
.map_err(|e| ::sea_orm_newtype::sea_orm::DbErr::Custom(e.to_string()))?,
)
}
}
Transparent(base_type_name) => {
quote! { Ok(#new_type_name (res.try_get_by::<#base_type_name, I>(index)?)) }
}
};
quote! {
#assertion_error_bound
impl #impl_generics ::sea_orm_newtype::TryGetable for #new_type_name #ty_generics #where_clause
{
fn try_get_by<I: ::sea_orm_newtype::sea_orm::ColIdx>(
res: &::sea_orm_newtype::sea_orm::QueryResult,
index: I,
) -> Result<Self, ::sea_orm_newtype::sea_orm::TryGetError> {
#try_get_block
}
}
}
}
fn value_type_for_newtype(
new_type_name: &Ident,
convert_type: &ConvertType,
generics: &Generics,
use_type_name: bool,
) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let try_from_block = match convert_type {
FromInto(base_type_name) => {
quote! {
Ok(
Into::<Self>::into(<#base_type_name as ::sea_orm_newtype::ValueType>::try_from(v)?)
)
}
}
TryFromInto(base_type_name) => {
quote! {
TryInto::<Self>::try_into(<#base_type_name as ::sea_orm_newtype::ValueType>::try_from(v)?)
.map_err(|_| ::sea_orm_newtype::sea_query::ValueTypeErr)
}
}
Transparent(base_type_name) => {
quote! {
Ok(#new_type_name(<#base_type_name as ::sea_orm_newtype::ValueType>::try_from(v)?))
}
}
};
let type_name_block = if use_type_name {
quote! {stringify!(#new_type_name #ty_generics).to_owned()}
} else {
let base_type_name = convert_type.ident();
quote! {<#base_type_name as ::sea_orm_newtype::ValueType>::type_name()}
};
{
let base_type_name = convert_type.ident();
quote! {
impl #impl_generics ::sea_orm_newtype::ValueType for #new_type_name #ty_generics #where_clause {
fn try_from(
v: ::sea_orm_newtype::Value,
) -> Result<Self, ::sea_orm_newtype::sea_query::ValueTypeErr> {
#try_from_block
}
fn type_name() -> String {
#type_name_block
}
fn array_type() -> ::sea_orm_newtype::sea_query::ArrayType {
<#base_type_name as ::sea_orm_newtype::ValueType>::array_type()
}
fn column_type() -> ::sea_orm_newtype::sea_query::ColumnType {
<#base_type_name as ::sea_orm_newtype::ValueType>::column_type()
}
}
}
}
}
fn nullable_for_newtype(
new_type_name: &Ident,
convert_type: &ConvertType,
generics: &Generics,
) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let base_type_name = convert_type.ident();
quote! {
impl #impl_generics ::sea_orm_newtype::Nullable for #new_type_name #ty_generics #where_clause
{
fn null() -> ::sea_orm_newtype::Value {
<#base_type_name as ::sea_orm_newtype::Nullable>::null()
}
}
}
}
fn try_from_u64_for_newtype(
new_type_name: &Ident,
convert_type: &ConvertType,
generics: &Generics,
) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let try_from_u64_block = match convert_type {
FromInto(base_type_name) => {
quote! {
Ok(
Into::<Self>::into(<#base_type_name as ::sea_orm_newtype::TryFromU64>::try_from_u64(n)?)
)
}
}
TryFromInto(base_type_name) => {
quote! {
TryInto::<Self>::try_into(<#base_type_name as ::sea_orm_newtype::TryFromU64>::try_from_u64(n)?)
.map_err(|e| ::sea_orm_newtype::sea_orm::DbErr::Custom(e.to_string()))
}
}
Transparent(base_type_name) => {
quote! {
Ok(
#new_type_name(<#base_type_name as ::sea_orm_newtype::TryFromU64>::try_from_u64(n)?)
)
}
}
};
quote! {
impl #impl_generics ::sea_orm_newtype::TryFromU64 for #new_type_name #ty_generics #where_clause
{
fn try_from_u64(n: u64) -> Result<Self, ::sea_orm_newtype::sea_orm::DbErr> {
#try_from_u64_block
}
}
}
}