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}
63
64impl EntityCtx {
65    fn pks(&self) -> impl Iterator<Item = &EntityFieldCtx> {
66        self.data.iter().filter(|field| field.pk)
67    }
68
69    fn columns(&self) -> impl Iterator<Item = String> {
70        self.data.iter().cloned().map(|field| {
71            if let Some(cast) = field.cast {
72                format!(
73                    r#"{ident} AS "{ident}!: {cast}""#,
74                    ident = field.ident,
75                    cast = cast.to_token_stream()
76                )
77            } else {
78                field.ident.to_string()
79            }
80        })
81    }
82}
83
84impl TryFrom<EntityArgs> for EntityCtx {
85    type Error = ParseCtxError;
86
87    fn try_from(value: EntityArgs) -> Result<Self, Self::Error> {
88        let mut data = vec![];
89
90        for row in value
91            .data
92            .take_struct()
93            .ok_or(ParseCtxError::InvalidApplication)?
94        {
95            data.push(row.try_into()?);
96        }
97
98        Ok(Self {
99            ident: value.ident,
100            vis: value.vis,
101            data,
102            table: value.table,
103        })
104    }
105}
106
107#[derive(Debug, Clone)]
108struct EntityFieldCtx {
109    ident: Ident,
110    vis: syn::Visibility,
111    ty: syn::Type,
112    pk: bool,
113    generated: bool,
114    deref: bool,
115    default: bool,
116    cast: Option<Path>,
117}
118
119impl EntityFieldCtx {
120    pub(crate) fn cast(&self) -> proc_macro2::TokenStream {
121        self.cast
122            .clone()
123            .map(|cast| {
124                quote::quote! {
125                    as &#cast
126                }
127            })
128            .unwrap_or_default()
129    }
130}
131
132#[derive(Debug, Error)]
133enum ParseCtxError {
134    #[error("The `Entity` macro can only be applied to a struct with named fields")]
135    InvalidApplication,
136}
137
138impl TryFrom<EntityField> for EntityFieldCtx {
139    type Error = ParseCtxError;
140
141    fn try_from(value: EntityField) -> Result<Self, Self::Error> {
142        Ok(Self {
143            ident: value.ident.ok_or(ParseCtxError::InvalidApplication)?,
144            vis: value.vis,
145            ty: value.ty,
146            pk: value.pk,
147            generated: value.generated,
148            deref: value.deref,
149            default: value.default,
150            cast: value.cast,
151        })
152    }
153}
154
155#[derive(Debug, FromDeriveInput)]
156#[darling(attributes(orm), forward_attrs(doc))]
157struct EntityArgs {
158    ident: Ident,
159    vis: syn::Visibility,
160    data: Data<Ignored, EntityField>,
161    table: String,
162}
163
164#[derive(Debug, Clone, FromField)]
165#[darling(attributes(orm), forward_attrs(doc))]
166struct EntityField {
167    ident: Option<Ident>,
168    vis: syn::Visibility,
169    ty: syn::Type,
170    #[darling(default)]
171    pk: bool,
172    #[darling(default)]
173    generated: bool,
174    #[darling(default)]
175    default: bool,
176    #[darling(default)]
177    deref: bool,
178    cast: Option<syn::Path>,
179}
180
181#[derive(Debug, Error)]
182pub(crate) enum GeneratorError {
183    #[error("{0}")]
184    Syn(
185        #[source]
186        #[from]
187        syn::Error,
188    ),
189    #[error("{0}")]
190    Darling(
191        #[source]
192        #[from]
193        darling::Error,
194    ),
195}
196
197impl GeneratorError {
198    pub(crate) fn write_errors(self) -> proc_macro2::TokenStream {
199        match self {
200            Self::Syn(err) => err.to_compile_error(),
201            Self::Darling(err) => err.write_errors(),
202        }
203    }
204}