DeriveRelatedEntity use async-graphql
re-exported by seaography
(#2469)
* DeriveRelatedEntity use `async-graphql` re-exported by `seaography` * Optionally compile related_entity * Update mod.rs * private --------- Co-authored-by: Chris Tsang <chris.2y3@outlook.com>
This commit is contained in:
parent
1c0bf231af
commit
4376a29918
@ -23,6 +23,7 @@ syn = { version = "2", default-features = false, features = ["parsing", "proc-ma
|
|||||||
quote = { version = "1", default-features = false }
|
quote = { version = "1", default-features = false }
|
||||||
heck = { version = "0.4", default-features = false }
|
heck = { version = "0.4", default-features = false }
|
||||||
proc-macro2 = { version = "1", default-features = false }
|
proc-macro2 = { version = "1", default-features = false }
|
||||||
|
proc-macro-crate = { version = "3.2.0", optional = true }
|
||||||
unicode-ident = { version = "1" }
|
unicode-ident = { version = "1" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
@ -34,4 +35,4 @@ default = ["derive"]
|
|||||||
postgres-array = []
|
postgres-array = []
|
||||||
derive = ["bae"]
|
derive = ["bae"]
|
||||||
strum = []
|
strum = []
|
||||||
seaography = []
|
seaography = ["proc-macro-crate"]
|
||||||
|
@ -38,6 +38,7 @@ pub mod field_attr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "seaography")]
|
||||||
pub mod related_attr {
|
pub mod related_attr {
|
||||||
use bae::FromAttributes;
|
use bae::FromAttributes;
|
||||||
|
|
||||||
|
@ -1,125 +1,152 @@
|
|||||||
use heck::ToLowerCamelCase;
|
#[cfg(feature = "seaography")]
|
||||||
use proc_macro2::TokenStream;
|
mod private {
|
||||||
use quote::{quote, quote_spanned};
|
use heck::ToLowerCamelCase;
|
||||||
|
use proc_macro2::{Ident, Span, TokenStream};
|
||||||
|
use proc_macro_crate::{crate_name, FoundCrate};
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
|
||||||
use crate::derives::attributes::related_attr;
|
use crate::derives::attributes::related_attr;
|
||||||
|
|
||||||
enum Error {
|
enum Error {
|
||||||
InputNotEnum,
|
InputNotEnum,
|
||||||
InvalidEntityPath,
|
InvalidEntityPath,
|
||||||
Syn(syn::Error),
|
Syn(syn::Error),
|
||||||
}
|
|
||||||
|
|
||||||
struct DeriveRelatedEntity {
|
|
||||||
entity_ident: TokenStream,
|
|
||||||
ident: syn::Ident,
|
|
||||||
variants: syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeriveRelatedEntity {
|
|
||||||
fn new(input: syn::DeriveInput) -> Result<Self, Error> {
|
|
||||||
let sea_attr = related_attr::SeaOrm::try_from_attributes(&input.attrs)
|
|
||||||
.map_err(Error::Syn)?
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let ident = input.ident;
|
|
||||||
let entity_ident = match sea_attr.entity.as_ref().map(Self::parse_lit_string) {
|
|
||||||
Some(entity_ident) => entity_ident.map_err(|_| Error::InvalidEntityPath)?,
|
|
||||||
None => quote! { Entity },
|
|
||||||
};
|
|
||||||
|
|
||||||
let variants = match input.data {
|
|
||||||
syn::Data::Enum(syn::DataEnum { variants, .. }) => variants,
|
|
||||||
_ => return Err(Error::InputNotEnum),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(DeriveRelatedEntity {
|
|
||||||
entity_ident,
|
|
||||||
ident,
|
|
||||||
variants,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand(&self) -> syn::Result<TokenStream> {
|
struct DeriveRelatedEntity {
|
||||||
let ident = &self.ident;
|
entity_ident: TokenStream,
|
||||||
let entity_ident = &self.entity_ident;
|
ident: syn::Ident,
|
||||||
|
variants: syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
|
||||||
|
}
|
||||||
|
|
||||||
let variant_implementations: Vec<TokenStream> = self
|
impl DeriveRelatedEntity {
|
||||||
.variants
|
fn new(input: syn::DeriveInput) -> Result<Self, Error> {
|
||||||
.iter()
|
let sea_attr = related_attr::SeaOrm::try_from_attributes(&input.attrs)
|
||||||
.map(|variant| {
|
.map_err(Error::Syn)?
|
||||||
let attr = related_attr::SeaOrm::from_attributes(&variant.attrs)?;
|
.unwrap_or_default();
|
||||||
|
|
||||||
let enum_name = &variant.ident;
|
let ident = input.ident;
|
||||||
|
let entity_ident = match sea_attr.entity.as_ref().map(Self::parse_lit_string) {
|
||||||
|
Some(entity_ident) => entity_ident.map_err(|_| Error::InvalidEntityPath)?,
|
||||||
|
None => quote! { Entity },
|
||||||
|
};
|
||||||
|
|
||||||
let target_entity = attr
|
let variants = match input.data {
|
||||||
.entity
|
syn::Data::Enum(syn::DataEnum { variants, .. }) => variants,
|
||||||
.as_ref()
|
_ => return Err(Error::InputNotEnum),
|
||||||
.map(Self::parse_lit_string)
|
};
|
||||||
.ok_or_else(|| {
|
|
||||||
syn::Error::new_spanned(variant, "Missing value for 'entity'")
|
|
||||||
})??;
|
|
||||||
|
|
||||||
let def = match attr.def {
|
|
||||||
Some(def) => Some(Self::parse_lit_string(&def).map_err(|_| {
|
|
||||||
syn::Error::new_spanned(variant, "Missing value for 'def'")
|
|
||||||
})?),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let name = enum_name.to_string().to_lower_camel_case();
|
|
||||||
|
|
||||||
if let Some(def) = def {
|
|
||||||
Result::<_, syn::Error>::Ok(quote! {
|
|
||||||
Self::#enum_name => builder.get_relation::<#entity_ident, #target_entity>(#name, #def)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Result::<_, syn::Error>::Ok(quote! {
|
|
||||||
Self::#enum_name => via_builder.get_relation::<#entity_ident, #target_entity>(#name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Ok(DeriveRelatedEntity {
|
||||||
|
entity_ident,
|
||||||
|
ident,
|
||||||
|
variants,
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
}
|
||||||
|
|
||||||
Ok(quote! {
|
fn expand(&self) -> syn::Result<TokenStream> {
|
||||||
impl seaography::RelationBuilder for #ident {
|
let ident = &self.ident;
|
||||||
fn get_relation(&self, context: & 'static seaography::BuilderContext) -> async_graphql::dynamic::Field {
|
let entity_ident = &self.entity_ident;
|
||||||
let builder = seaography::EntityObjectRelationBuilder { context };
|
|
||||||
let via_builder = seaography::EntityObjectViaRelationBuilder { context };
|
let variant_implementations: Vec<TokenStream> = self
|
||||||
match self {
|
.variants
|
||||||
#(#variant_implementations,)*
|
.iter()
|
||||||
_ => panic!("No relations for this entity"),
|
.map(|variant| {
|
||||||
|
let attr = related_attr::SeaOrm::from_attributes(&variant.attrs)?;
|
||||||
|
|
||||||
|
let enum_name = &variant.ident;
|
||||||
|
|
||||||
|
let target_entity = attr
|
||||||
|
.entity
|
||||||
|
.as_ref()
|
||||||
|
.map(Self::parse_lit_string)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
syn::Error::new_spanned(variant, "Missing value for 'entity'")
|
||||||
|
})??;
|
||||||
|
|
||||||
|
let def = match attr.def {
|
||||||
|
Some(def) => Some(Self::parse_lit_string(&def).map_err(|_| {
|
||||||
|
syn::Error::new_spanned(variant, "Missing value for 'def'")
|
||||||
|
})?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = enum_name.to_string().to_lower_camel_case();
|
||||||
|
|
||||||
|
if let Some(def) = def {
|
||||||
|
Result::<_, syn::Error>::Ok(quote! {
|
||||||
|
Self::#enum_name => builder.get_relation::<#entity_ident, #target_entity>(#name, #def)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Result::<_, syn::Error>::Ok(quote! {
|
||||||
|
Self::#enum_name => via_builder.get_relation::<#entity_ident, #target_entity>(#name)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
// Get the path of the `async-graphql` on the application's Cargo.toml
|
||||||
|
let async_graphql_crate = match crate_name("async-graphql") {
|
||||||
|
// if found, use application's `async-graphql`
|
||||||
|
Ok(FoundCrate::Name(name)) => {
|
||||||
|
let ident = Ident::new(&name, Span::call_site());
|
||||||
|
quote! { #ident }
|
||||||
|
}
|
||||||
|
Ok(FoundCrate::Itself) => quote! { async_graphql },
|
||||||
|
// if not, then use the `async-graphql` re-exported by `seaography`
|
||||||
|
Err(_) => quote! { seaography::async_graphql },
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
impl seaography::RelationBuilder for #ident {
|
||||||
|
fn get_relation(&self, context: & 'static seaography::BuilderContext) -> #async_graphql_crate::dynamic::Field {
|
||||||
|
let builder = seaography::EntityObjectRelationBuilder { context };
|
||||||
|
let via_builder = seaography::EntityObjectViaRelationBuilder { context };
|
||||||
|
match self {
|
||||||
|
#(#variant_implementations,)*
|
||||||
|
_ => panic!("No relations for this entity"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_lit_string(lit: &syn::Lit) -> syn::Result<TokenStream> {
|
||||||
|
match lit {
|
||||||
|
syn::Lit::Str(lit_str) => lit_str
|
||||||
|
.value()
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| syn::Error::new_spanned(lit, "attribute not valid")),
|
||||||
|
_ => Err(syn::Error::new_spanned(lit, "attribute must be a string")),
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_lit_string(lit: &syn::Lit) -> syn::Result<TokenStream> {
|
/// Method to derive a Related enumeration
|
||||||
match lit {
|
pub fn expand_derive_related_entity(input: syn::DeriveInput) -> syn::Result<TokenStream> {
|
||||||
syn::Lit::Str(lit_str) => lit_str
|
let ident_span = input.ident.span();
|
||||||
.value()
|
|
||||||
.parse()
|
match DeriveRelatedEntity::new(input) {
|
||||||
.map_err(|_| syn::Error::new_spanned(lit, "attribute not valid")),
|
Ok(model) => model.expand(),
|
||||||
_ => Err(syn::Error::new_spanned(lit, "attribute must be a string")),
|
Err(Error::InputNotEnum) => Ok(quote_spanned! {
|
||||||
|
ident_span => compile_error!("you can only derive DeriveRelation on enums");
|
||||||
|
}),
|
||||||
|
Err(Error::InvalidEntityPath) => Ok(quote_spanned! {
|
||||||
|
ident_span => compile_error!("invalid attribute value for 'entity'");
|
||||||
|
}),
|
||||||
|
Err(Error::Syn(err)) => Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Method to derive a Related enumeration
|
#[cfg(not(feature = "seaography"))]
|
||||||
pub fn expand_derive_related_entity(input: syn::DeriveInput) -> syn::Result<TokenStream> {
|
mod private {
|
||||||
let ident_span = input.ident.span();
|
use proc_macro2::TokenStream;
|
||||||
|
|
||||||
match DeriveRelatedEntity::new(input) {
|
pub fn expand_derive_related_entity(_: syn::DeriveInput) -> syn::Result<TokenStream> {
|
||||||
Ok(model) => model.expand(),
|
Ok(TokenStream::new())
|
||||||
Err(Error::InputNotEnum) => Ok(quote_spanned! {
|
|
||||||
ident_span => compile_error!("you can only derive DeriveRelation on enums");
|
|
||||||
}),
|
|
||||||
Err(Error::InvalidEntityPath) => Ok(quote_spanned! {
|
|
||||||
ident_span => compile_error!("invalid attribute value for 'entity'");
|
|
||||||
}),
|
|
||||||
Err(Error::Syn(err)) => Err(err),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use private::*;
|
||||||
|
@ -675,13 +675,9 @@ pub fn derive_relation(input: TokenStream) -> TokenStream {
|
|||||||
#[proc_macro_derive(DeriveRelatedEntity, attributes(sea_orm))]
|
#[proc_macro_derive(DeriveRelatedEntity, attributes(sea_orm))]
|
||||||
pub fn derive_related_entity(input: TokenStream) -> TokenStream {
|
pub fn derive_related_entity(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
if cfg!(feature = "seaography") {
|
derives::expand_derive_related_entity(input)
|
||||||
derives::expand_derive_related_entity(input)
|
.unwrap_or_else(Error::into_compile_error)
|
||||||
.unwrap_or_else(Error::into_compile_error)
|
.into()
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
TokenStream::new()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The DeriveMigrationName derive macro will implement `sea_orm_migration::MigrationName` for a migration.
|
/// The DeriveMigrationName derive macro will implement `sea_orm_migration::MigrationName` for a migration.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user