Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DeriveRelatedEntity use async-graphql re-exported by seaography #2469

Merged
merged 5 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading