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