gremlin_orm_macro/
lib.rs

1//! # `gremlin-orm-macro`
2
3use darling::{FromDeriveInput, FromField, ast::Data, util::Ignored};
4use proc_macro::TokenStream;
5use proc_macro_error2::abort;
6use quote::ToTokens;
7use syn::{DeriveInput, Ident, Path, parse_macro_input};
8use thiserror::Error;
9
10mod delete;
11mod fetch;
12mod insert;
13mod stream;
14mod update;
15
16/// Generate the entity
17#[proc_macro_error2::proc_macro_error]
18#[proc_macro_derive(Entity, attributes(orm))]
19pub fn derive_response(input: TokenStream) -> TokenStream {
20    let args = parse_macro_input!(input as DeriveInput);
21
22    match generate(args) {
23        Ok(stream) => stream,
24        Err(err) => err.write_errors().into(),
25    }
26}
27
28fn generate(args: DeriveInput) -> Result<TokenStream, GeneratorError> {
29    let args: EntityArgs = EntityArgs::from_derive_input(&args)?;
30    let ident = args.ident.clone();
31
32    let args = match EntityCtx::try_from(args) {
33        Ok(v) => v,
34        Err(ParseCtxError::InvalidApplication) => {
35            abort!(ident, ParseCtxError::InvalidApplication)
36        }
37    };
38
39    let insert_stream = insert::generate_insert(&args);
40    let update_stream = update::generate_update(&args);
41    let stream_stream = stream::generate_stream(&args);
42    let delete_stream = delete::generate_delete(&args);
43    let get_by_id_stream = fetch::generate_fetch(&args);
44
45    let stream = quote::quote! {
46        #insert_stream
47        #update_stream
48        #stream_stream
49        #delete_stream
50        #get_by_id_stream
51    };
52
53    Ok(stream.into())
54}
55
56#[derive(Debug, Clone)]
57struct EntityCtx {
58    ident: Ident,
59    vis: syn::Visibility,
60    data: Vec<EntityFieldCtx>,
61    table: String,
62    soft_delete: Option<String>,
63}
64
65impl EntityCtx {
66    fn pks(&self) -> impl Iterator<Item = &EntityFieldCtx> {
67        self.data.iter().filter(|field| field.pk)
68    }
69
70    fn columns(&self) -> impl Iterator<Item = String> {
71        self.data.iter().cloned().map(|field| {
72            if let Some(cast) = field.cast {
73                format!(
74                    r#"{ident} AS "{ident}!: {cast}""#,
75                    ident = field.ident,
76                    cast = cast.to_token_stream()
77                )
78            } else {
79                field.ident.to_string()
80            }
81        })
82    }
83}
84
85impl TryFrom<EntityArgs> for EntityCtx {
86    type Error = ParseCtxError;
87
88    fn try_from(value: EntityArgs) -> Result<Self, Self::Error> {
89        let mut data = vec![];
90
91        for row in value
92            .data
93            .take_struct()
94            .ok_or(ParseCtxError::InvalidApplication)?
95        {
96            data.push(row.try_into()?);
97        }
98
99        Ok(Self {
100            ident: value.ident,
101            vis: value.vis,
102            data,
103            table: value.table,
104            soft_delete: value.soft_delete,
105        })
106    }
107}
108
109#[derive(Debug, Clone)]
110struct EntityFieldCtx {
111    ident: Ident,
112    vis: syn::Visibility,
113    ty: syn::Type,
114    pk: bool,
115    generated: bool,
116    deref: bool,
117    default: bool,
118    cast: Option<Path>,
119}
120
121impl EntityFieldCtx {
122    pub(crate) fn cast(&self) -> proc_macro2::TokenStream {
123        self.cast
124            .clone()
125            .map(|cast| {
126                quote::quote! {
127                    as &#cast
128                }
129            })
130            .unwrap_or_default()
131    }
132}
133
134#[derive(Debug, Error)]
135enum ParseCtxError {
136    #[error("The `Entity` macro can only be applied to a struct with named fields")]
137    InvalidApplication,
138}
139
140impl TryFrom<EntityField> for EntityFieldCtx {
141    type Error = ParseCtxError;
142
143    fn try_from(value: EntityField) -> Result<Self, Self::Error> {
144        Ok(Self {
145            ident: value.ident.ok_or(ParseCtxError::InvalidApplication)?,
146            vis: value.vis,
147            ty: value.ty,
148            pk: value.pk,
149            generated: value.generated,
150            deref: value.deref,
151            default: value.default,
152            cast: value.cast,
153        })
154    }
155}
156
157#[derive(Debug, FromDeriveInput)]
158#[darling(attributes(orm), forward_attrs(doc))]
159struct EntityArgs {
160    ident: Ident,
161    vis: syn::Visibility,
162    data: Data<Ignored, EntityField>,
163    table: String,
164    soft_delete: Option<String>,
165}
166
167#[derive(Debug, Clone, FromField)]
168#[darling(attributes(orm), forward_attrs(doc))]
169struct EntityField {
170    ident: Option<Ident>,
171    vis: syn::Visibility,
172    ty: syn::Type,
173    #[darling(default)]
174    pk: bool,
175    #[darling(default)]
176    generated: bool,
177    #[darling(default)]
178    default: bool,
179    #[darling(default)]
180    deref: bool,
181    cast: Option<syn::Path>,
182}
183
184#[derive(Debug, Error)]
185pub(crate) enum GeneratorError {
186    #[error("{0}")]
187    Syn(
188        #[source]
189        #[from]
190        syn::Error,
191    ),
192    #[error("{0}")]
193    Darling(
194        #[source]
195        #[from]
196        darling::Error,
197    ),
198}
199
200impl GeneratorError {
201    pub(crate) fn write_errors(self) -> proc_macro2::TokenStream {
202        match self {
203            Self::Syn(err) => err.to_compile_error(),
204            Self::Darling(err) => err.write_errors(),
205        }
206    }
207}