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    as_ref: bool,
118    default: bool,
119    cast: Option<Path>,
120}
121
122impl EntityFieldCtx {
123    pub(crate) fn cast(&self) -> proc_macro2::TokenStream {
124        self.cast
125            .clone()
126            .map(|cast| {
127                quote::quote! {
128                    as &#cast
129                }
130            })
131            .unwrap_or_default()
132    }
133}
134
135#[derive(Debug, Error)]
136enum ParseCtxError {
137    #[error("The `Entity` macro can only be applied to a struct with named fields")]
138    InvalidApplication,
139}
140
141impl TryFrom<EntityField> for EntityFieldCtx {
142    type Error = ParseCtxError;
143
144    fn try_from(value: EntityField) -> Result<Self, Self::Error> {
145        Ok(Self {
146            ident: value.ident.ok_or(ParseCtxError::InvalidApplication)?,
147            vis: value.vis,
148            ty: value.ty,
149            pk: value.pk,
150            generated: value.generated,
151            deref: value.deref,
152            as_ref: value.as_ref,
153            default: value.default,
154            cast: value.cast,
155        })
156    }
157}
158
159#[derive(Debug, FromDeriveInput)]
160#[darling(attributes(orm), forward_attrs(doc))]
161struct EntityArgs {
162    ident: Ident,
163    vis: syn::Visibility,
164    data: Data<Ignored, EntityField>,
165    table: String,
166    soft_delete: Option<String>,
167}
168
169#[derive(Debug, Clone, FromField)]
170#[darling(attributes(orm), forward_attrs(doc))]
171struct EntityField {
172    ident: Option<Ident>,
173    vis: syn::Visibility,
174    ty: syn::Type,
175    #[darling(default)]
176    pk: bool,
177    #[darling(default)]
178    generated: bool,
179    #[darling(default)]
180    default: bool,
181    #[darling(default)]
182    deref: bool,
183    #[darling(default)]
184    as_ref: bool,
185    cast: Option<syn::Path>,
186}
187
188#[derive(Debug, Error)]
189pub(crate) enum GeneratorError {
190    #[error("{0}")]
191    Syn(
192        #[source]
193        #[from]
194        syn::Error,
195    ),
196    #[error("{0}")]
197    Darling(
198        #[source]
199        #[from]
200        darling::Error,
201    ),
202}
203
204impl GeneratorError {
205    pub(crate) fn write_errors(self) -> proc_macro2::TokenStream {
206        match self {
207            Self::Syn(err) => err.to_compile_error(),
208            Self::Darling(err) => err.write_errors(),
209        }
210    }
211}