Skip to content

Commit

Permalink
DeriveRelatedEntity use async-graphql re-exported by seaography (#…
Browse files Browse the repository at this point in the history
…2469)

* DeriveRelatedEntity use `async-graphql` re-exported by `seaography`

* Optionally compile related_entity

* Update mod.rs

* private

---------

Co-authored-by: Chris Tsang <[email protected]>
  • Loading branch information
billy1624 and tyt2y3 authored Jan 10, 2025
1 parent 1c0bf23 commit 4376a29
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 115 deletions.
3 changes: 2 additions & 1 deletion sea-orm-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ syn = { version = "2", default-features = false, features = ["parsing", "proc-ma
quote = { version = "1", default-features = false }
heck = { version = "0.4", default-features = false }
proc-macro2 = { version = "1", default-features = false }
proc-macro-crate = { version = "3.2.0", optional = true }
unicode-ident = { version = "1" }

[dev-dependencies]
Expand All @@ -34,4 +35,4 @@ default = ["derive"]
postgres-array = []
derive = ["bae"]
strum = []
seaography = []
seaography = ["proc-macro-crate"]
1 change: 1 addition & 0 deletions sea-orm-macros/src/derives/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub mod field_attr {
}
}

#[cfg(feature = "seaography")]
pub mod related_attr {
use bae::FromAttributes;

Expand Down
241 changes: 134 additions & 107 deletions sea-orm-macros/src/derives/related_entity.rs
Original file line number Diff line number Diff line change
@@ -1,125 +1,152 @@
use heck::ToLowerCamelCase;
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
#[cfg(feature = "seaography")]
mod private {
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;

enum Error {
InputNotEnum,
InvalidEntityPath,
Syn(syn::Error),
}

use crate::derives::attributes::related_attr;
struct DeriveRelatedEntity {
entity_ident: TokenStream,
ident: syn::Ident,
variants: syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
}

enum Error {
InputNotEnum,
InvalidEntityPath,
Syn(syn::Error),
}
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,
})
}

struct DeriveRelatedEntity {
entity_ident: TokenStream,
ident: syn::Ident,
variants: syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
}
fn expand(&self) -> syn::Result<TokenStream> {
let ident = &self.ident;
let entity_ident = &self.entity_ident;

let variant_implementations: Vec<TokenStream> = self
.variants
.iter()
.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)
})
}

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,
})
}
})
.collect::<Result<Vec<_>, _>>()?;

fn expand(&self) -> syn::Result<TokenStream> {
let ident = &self.ident;
let entity_ident = &self.entity_ident;

let variant_implementations: Vec<TokenStream> = self
.variants
.iter()
.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)
})
// 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 }
}

})
.collect::<Result<Vec<_>, _>>()?;

Ok(quote! {
impl seaography::RelationBuilder for #ident {
fn get_relation(&self, context: & 'static seaography::BuilderContext) -> async_graphql::dynamic::Field {
let builder = seaography::EntityObjectRelationBuilder { context };
let via_builder = seaography::EntityObjectViaRelationBuilder { context };
match self {
#(#variant_implementations,)*
_ => panic!("No relations for this entity"),
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> {
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")),
/// Method to derive a Related enumeration
pub fn expand_derive_related_entity(input: syn::DeriveInput) -> syn::Result<TokenStream> {
let ident_span = input.ident.span();

match DeriveRelatedEntity::new(input) {
Ok(model) => model.expand(),
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
pub fn expand_derive_related_entity(input: syn::DeriveInput) -> syn::Result<TokenStream> {
let ident_span = input.ident.span();

match DeriveRelatedEntity::new(input) {
Ok(model) => model.expand(),
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),
#[cfg(not(feature = "seaography"))]
mod private {
use proc_macro2::TokenStream;

pub fn expand_derive_related_entity(_: syn::DeriveInput) -> syn::Result<TokenStream> {
Ok(TokenStream::new())
}
}

pub use private::*;
10 changes: 3 additions & 7 deletions sea-orm-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,13 +675,9 @@ pub fn derive_relation(input: TokenStream) -> TokenStream {
#[proc_macro_derive(DeriveRelatedEntity, attributes(sea_orm))]
pub fn derive_related_entity(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
if cfg!(feature = "seaography") {
derives::expand_derive_related_entity(input)
.unwrap_or_else(Error::into_compile_error)
.into()
} else {
TokenStream::new()
}
derives::expand_derive_related_entity(input)
.unwrap_or_else(Error::into_compile_error)
.into()
}

/// The DeriveMigrationName derive macro will implement `sea_orm_migration::MigrationName` for a migration.
Expand Down

0 comments on commit 4376a29

Please sign in to comment.