Skip to main content

appdb_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use std::collections::HashSet;
4use syn::{
5    Attribute, Data, DeriveInput, Error, Field, Fields, GenericArgument, Meta, PathArguments, Type,
6    TypePath, parse_macro_input,
7};
8
9#[proc_macro_derive(Sensitive, attributes(secure, crypto))]
10pub fn derive_sensitive(input: TokenStream) -> TokenStream {
11    match derive_sensitive_impl(parse_macro_input!(input as DeriveInput)) {
12        Ok(tokens) => tokens.into(),
13        Err(err) => err.to_compile_error().into(),
14    }
15}
16
17#[proc_macro_derive(
18    Store,
19    attributes(
20        unique,
21        secure,
22        foreign,
23        table_as,
24        crypto,
25        relate,
26        back_relate,
27        pagin,
28        fill
29    )
30)]
31pub fn derive_store(input: TokenStream) -> TokenStream {
32    match derive_store_impl(parse_macro_input!(input as DeriveInput)) {
33        Ok(tokens) => tokens.into(),
34        Err(err) => err.to_compile_error().into(),
35    }
36}
37
38#[proc_macro_derive(View, attributes(view))]
39pub fn derive_view(input: TokenStream) -> TokenStream {
40    match derive_view_impl(parse_macro_input!(input as DeriveInput)) {
41        Ok(tokens) => tokens.into(),
42        Err(err) => err.to_compile_error().into(),
43    }
44}
45
46#[proc_macro_derive(Relation, attributes(relation))]
47pub fn derive_relation(input: TokenStream) -> TokenStream {
48    match derive_relation_impl(parse_macro_input!(input as DeriveInput)) {
49        Ok(tokens) => tokens.into(),
50        Err(err) => err.to_compile_error().into(),
51    }
52}
53
54#[proc_macro_derive(Bridge)]
55pub fn derive_bridge(input: TokenStream) -> TokenStream {
56    match derive_bridge_impl(parse_macro_input!(input as DeriveInput)) {
57        Ok(tokens) => tokens.into(),
58        Err(err) => err.to_compile_error().into(),
59    }
60}
61
62fn derive_store_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
63    let struct_ident = input.ident;
64    let vis = input.vis.clone();
65    let table_alias = table_alias_target(&input.attrs)?;
66
67    let named_fields = match input.data {
68        Data::Struct(data) => match data.fields {
69            Fields::Named(fields) => fields.named,
70            _ => {
71                return Err(Error::new_spanned(
72                    struct_ident,
73                    "Store can only be derived for structs with named fields",
74                ));
75            }
76        },
77        _ => {
78            return Err(Error::new_spanned(
79                struct_ident,
80                "Store can only be derived for structs",
81            ));
82        }
83    };
84
85    let id_fields = named_fields
86        .iter()
87        .filter(|field| is_id_type(&field.ty))
88        .map(|field| field.ident.clone().expect("named field"))
89        .collect::<Vec<_>>();
90
91    let secure_fields = named_fields
92        .iter()
93        .filter(|field| has_secure_attr(&field.attrs))
94        .map(|field| field.ident.clone().expect("named field"))
95        .collect::<Vec<_>>();
96
97    let unique_fields = named_fields
98        .iter()
99        .filter(|field| has_unique_attr(&field.attrs))
100        .map(|field| field.ident.clone().expect("named field"))
101        .collect::<Vec<_>>();
102
103    let pagin_fields = named_fields
104        .iter()
105        .filter_map(|field| match field_pagin_attr(field) {
106            Ok(Some(attr)) => Some(parse_pagin_field(field, attr)),
107            Ok(None) => None,
108            Err(err) => Some(Err(err)),
109        })
110        .collect::<syn::Result<Vec<_>>>()?;
111    if pagin_fields.len() > 1 {
112        return Err(Error::new_spanned(
113            &pagin_fields[1].ident,
114            "Store supports at most one #[pagin] field",
115        ));
116    }
117    let pagin_field = pagin_fields.first().cloned();
118
119    let fill_fields = named_fields
120        .iter()
121        .filter_map(|field| match field_fill_attr(field) {
122            Ok(Some(attr)) => Some(parse_fill_field(field, attr)),
123            Ok(None) => None,
124            Err(err) => Some(Err(err)),
125        })
126        .collect::<syn::Result<Vec<_>>>()?;
127
128    if id_fields.len() > 1 {
129        return Err(Error::new_spanned(
130            struct_ident,
131            "Store supports at most one `Id` field for automatic HasId generation",
132        ));
133    }
134
135    if let Some(invalid_field) = named_fields
136        .iter()
137        .find(|field| has_secure_attr(&field.attrs) && has_unique_attr(&field.attrs))
138    {
139        let ident = invalid_field.ident.as_ref().expect("named field");
140        return Err(Error::new_spanned(
141            ident,
142            "#[secure] fields cannot be used as #[unique] lookup keys",
143        ));
144    }
145
146    let foreign_fields = named_fields
147        .iter()
148        .filter_map(|field| match field_foreign_attr(field) {
149            Ok(Some(attr)) => Some(parse_foreign_field(field, attr)),
150            Ok(None) => None,
151            Err(err) => Some(Err(err)),
152        })
153        .collect::<syn::Result<Vec<_>>>()?;
154
155    let relate_fields = named_fields
156        .iter()
157        .filter_map(|field| match field_relation_attr(field) {
158            Ok(Some(attr)) => Some(parse_relate_field(field, attr)),
159            Ok(None) => None,
160            Err(err) => Some(Err(err)),
161        })
162        .collect::<syn::Result<Vec<_>>>()?;
163
164    if let Some(non_store_child) = foreign_fields
165        .iter()
166        .find_map(|field| invalid_foreign_leaf_type(&field.kind.original_ty))
167    {
168        return Err(Error::new_spanned(
169            non_store_child,
170            BINDREF_BRIDGE_STORE_ONLY,
171        ));
172    }
173
174    if let Some(invalid_field) = named_fields.iter().find_map(|field| {
175        field_relation_attr(field)
176            .ok()
177            .flatten()
178            .filter(|_| has_unique_attr(&field.attrs))
179            .map(|attr| (field, attr))
180    }) {
181        let ident = invalid_field.0.ident.as_ref().expect("named field");
182        return Err(Error::new_spanned(
183            ident,
184            format!(
185                "{} fields cannot be used as #[unique] lookup keys",
186                relation_attr_label(invalid_field.1)
187            ),
188        ));
189    }
190
191    if let Some(invalid_field) = named_fields.iter().find_map(|field| {
192        field_relation_attr(field)
193            .ok()
194            .flatten()
195            .filter(|_| has_secure_attr(&field.attrs))
196            .map(|attr| (field, attr))
197    }) {
198        let ident = invalid_field.0.ident.as_ref().expect("named field");
199        return Err(Error::new_spanned(
200            ident,
201            format!(
202                "{} fields cannot be marked #[secure]",
203                relation_attr_label(invalid_field.1)
204            ),
205        ));
206    }
207
208    if let Some(invalid_field) = named_fields.iter().find_map(|field| {
209        field_relation_attr(field)
210            .ok()
211            .flatten()
212            .filter(|_| field_foreign_attr(field).ok().flatten().is_some())
213            .map(|attr| (field, attr))
214    }) {
215        let ident = invalid_field.0.ident.as_ref().expect("named field");
216        return Err(Error::new_spanned(
217            ident,
218            format!(
219                "{} cannot be combined with #[foreign]",
220                relation_attr_label(invalid_field.1)
221            ),
222        ));
223    }
224
225    if let Some(invalid_field) = named_fields.iter().find(|field| {
226        field_pagin_attr(field).ok().flatten().is_some() && has_secure_attr(&field.attrs)
227    }) {
228        let ident = invalid_field.ident.as_ref().expect("named field");
229        return Err(Error::new_spanned(
230            ident,
231            "#[pagin] fields cannot be marked #[secure]",
232        ));
233    }
234
235    if let Some(invalid_field) = named_fields.iter().find(|field| {
236        field_pagin_attr(field).ok().flatten().is_some()
237            && field_foreign_attr(field).ok().flatten().is_some()
238    }) {
239        let ident = invalid_field.ident.as_ref().expect("named field");
240        return Err(Error::new_spanned(
241            ident,
242            "#[pagin] cannot be combined with #[foreign]",
243        ));
244    }
245
246    if let Some(invalid_field) = named_fields.iter().find_map(|field| {
247        field_pagin_attr(field)
248            .ok()
249            .flatten()
250            .and_then(|_| field_relation_attr(field).ok().flatten())
251            .map(|attr| (field, attr))
252    }) {
253        let ident = invalid_field.0.ident.as_ref().expect("named field");
254        return Err(Error::new_spanned(
255            ident,
256            format!(
257                "#[pagin] cannot be combined with {}",
258                relation_attr_label(invalid_field.1)
259            ),
260        ));
261    }
262
263    if let Some(invalid_field) = named_fields.iter().find_map(|field| {
264        field_fill_attr(field)
265            .ok()
266            .flatten()
267            .filter(|_| has_secure_attr(&field.attrs))
268            .map(|attr| (field, attr))
269    }) {
270        let ident = invalid_field.0.ident.as_ref().expect("named field");
271        return Err(Error::new_spanned(
272            ident,
273            "#[fill(...)] fields cannot be marked #[secure]",
274        ));
275    }
276
277    if let Some(invalid_field) = named_fields.iter().find_map(|field| {
278        field_fill_attr(field)
279            .ok()
280            .flatten()
281            .filter(|_| field_foreign_attr(field).ok().flatten().is_some())
282            .map(|attr| (field, attr))
283    }) {
284        let ident = invalid_field.0.ident.as_ref().expect("named field");
285        return Err(Error::new_spanned(
286            ident,
287            "#[fill(...)] cannot be combined with #[foreign]",
288        ));
289    }
290
291    if let Some(invalid_field) = named_fields.iter().find_map(|field| {
292        field_fill_attr(field)
293            .ok()
294            .flatten()
295            .and_then(|_| field_relation_attr(field).ok().flatten())
296            .map(|attr| (field, attr))
297    }) {
298        let ident = invalid_field.0.ident.as_ref().expect("named field");
299        return Err(Error::new_spanned(
300            ident,
301            format!(
302                "#[fill(...)] cannot be combined with {}",
303                relation_attr_label(invalid_field.1)
304            ),
305        ));
306    }
307
308    if let Some(invalid_field) = named_fields.iter().find(|field| {
309        is_autofill_type(&field.ty) && field_fill_attr(field).ok().flatten().is_none()
310    }) {
311        let ident = invalid_field.ident.as_ref().expect("named field");
312        return Err(Error::new_spanned(
313            ident,
314            "appdb::AutoFill fields require #[fill(...)]",
315        ));
316    }
317
318    let mut seen_relation_names = HashSet::new();
319    for field in &relate_fields {
320        if !seen_relation_names.insert(field.relation_name.clone()) {
321            return Err(Error::new_spanned(
322                &field.ident,
323                format!(
324                    "duplicate {} relation name is not supported within one Store model",
325                    field.direction.attr_label()
326                ),
327            ));
328        }
329    }
330
331    let auto_has_id_impl = id_fields.first().map(|field| {
332        quote! {
333            impl ::appdb::model::meta::HasId for #struct_ident {
334                fn id(&self) -> ::surrealdb::types::RecordId {
335                    ::surrealdb::types::RecordId::new(
336                        <Self as ::appdb::model::meta::ModelMeta>::storage_table(),
337                        self.#field.clone(),
338                    )
339                }
340            }
341        }
342    });
343
344    let resolve_record_id_impl = if let Some(field) = id_fields.first() {
345        quote! {
346            #[::async_trait::async_trait]
347            impl ::appdb::model::meta::ResolveRecordId for #struct_ident {
348                async fn resolve_record_id(&self) -> ::anyhow::Result<::surrealdb::types::RecordId> {
349                    Ok(::surrealdb::types::RecordId::new(
350                        <Self as ::appdb::model::meta::ModelMeta>::storage_table(),
351                        self.#field.clone(),
352                    ))
353                }
354            }
355        }
356    } else {
357        quote! {
358            #[::async_trait::async_trait]
359            impl ::appdb::model::meta::ResolveRecordId for #struct_ident {
360                async fn resolve_record_id(&self) -> ::anyhow::Result<::surrealdb::types::RecordId> {
361                    ::appdb::repository::Repo::<Self>::find_unique_id_for(self).await
362                }
363            }
364        }
365    };
366
367    let resolved_table_name_expr = if let Some(target_ty) = &table_alias {
368        quote! { <#target_ty as ::appdb::model::meta::ModelMeta>::table_name() }
369    } else {
370        quote! {
371            {
372                let table = ::appdb::model::meta::default_table_name(stringify!(#struct_ident));
373                ::appdb::model::meta::register_table(stringify!(#struct_ident), table)
374            }
375        }
376    };
377
378    let unique_schema_impls = unique_fields.iter().map(|field| {
379        let field_name = field.to_string();
380        let index_name = format!(
381            "{}_{}_unique",
382            resolved_schema_table_name(&struct_ident, table_alias.as_ref()),
383            field_name
384        );
385        let ddl = format!(
386            "DEFINE INDEX IF NOT EXISTS {index_name} ON {} FIELDS {field_name} UNIQUE;",
387            resolved_schema_table_name(&struct_ident, table_alias.as_ref())
388        );
389
390        quote! {
391            ::inventory::submit! {
392                ::appdb::model::schema::SchemaItem {
393                    ddl: #ddl,
394                }
395            }
396        }
397    });
398
399    let pagin_schema_impl = pagin_field.iter().map(|field| {
400        let field_name = field.ident.to_string();
401        let index_name = format!(
402            "{}_{}_id_pagin",
403            resolved_schema_table_name(&struct_ident, table_alias.as_ref()),
404            field_name
405        );
406        let ddl = if field_name == "id" {
407            format!(
408                "DEFINE INDEX IF NOT EXISTS {index_name} ON {} FIELDS id;",
409                resolved_schema_table_name(&struct_ident, table_alias.as_ref())
410            )
411        } else {
412            format!(
413                "DEFINE INDEX IF NOT EXISTS {index_name} ON {} FIELDS {field_name},id;",
414                resolved_schema_table_name(&struct_ident, table_alias.as_ref())
415            )
416        };
417
418        quote! {
419            ::inventory::submit! {
420                ::appdb::model::schema::SchemaItem {
421                    ddl: #ddl,
422                }
423            }
424        }
425    });
426
427    let lookup_fields = if unique_fields.is_empty() {
428        named_fields
429            .iter()
430            .filter_map(|field| {
431                let ident = field.ident.as_ref()?;
432                if ident == "id"
433                    || secure_fields.iter().any(|secure| secure == ident)
434                    || relate_fields.iter().any(|relate| relate.ident == *ident)
435                {
436                    None
437                } else {
438                    Some(ident.to_string())
439                }
440            })
441            .collect::<Vec<_>>()
442    } else {
443        unique_fields
444            .iter()
445            .map(|field| field.to_string())
446            .collect::<Vec<_>>()
447    };
448
449    let foreign_field_literals = foreign_fields
450        .iter()
451        .map(|field| field.ident.to_string())
452        .map(|field| quote! { #field })
453        .collect::<Vec<_>>();
454    let relate_field_literals = relate_fields
455        .iter()
456        .map(|field| field.ident.to_string())
457        .map(|field| quote! { #field })
458        .collect::<Vec<_>>();
459    if id_fields.is_empty() && lookup_fields.is_empty() {
460        return Err(Error::new_spanned(
461            struct_ident,
462            "Store requires an `Id` field or at least one non-secure lookup field for automatic record resolution",
463        ));
464    }
465    let lookup_field_literals = lookup_fields.iter().map(|field| quote! { #field });
466    let resolve_lookup_field_value_arms = foreign_fields.iter().map(|field| {
467        let ident = &field.ident;
468        let field_name = ident.to_string();
469        let original_ty = &field.kind.original_ty;
470        quote! {
471            #field_name => Ok(::std::option::Option::Some(
472                ::surrealdb::types::SurrealValue::into_value(
473                    <#original_ty as ::appdb::ForeignLookupShape>::resolve_foreign_lookup_shape(&self.#ident).await?
474                )
475            )),
476        }
477    });
478
479    let pagination_meta_impl = if let Some(field) = &pagin_field {
480        let field_name = field.ident.to_string();
481        quote! {
482            impl ::appdb::model::meta::PaginationMeta for #struct_ident {
483                fn pagination_field() -> ::std::option::Option<&'static str> {
484                    ::std::option::Option::Some(#field_name)
485                }
486            }
487        }
488    } else {
489        quote! {
490            impl ::appdb::model::meta::PaginationMeta for #struct_ident {}
491        }
492    };
493
494    let pagination_methods_impl = if pagin_field.is_some() {
495        quote! {
496            pub async fn pagin_desc(
497                count: i64,
498                cursor: ::std::option::Option<::appdb::PageCursor>,
499            ) -> ::anyhow::Result<::appdb::Page<Self>> {
500                ::appdb::repository::Repo::<Self>::pagin_desc(count, cursor).await
501            }
502
503            pub async fn pagin_asc(
504                count: i64,
505                cursor: ::std::option::Option<::appdb::PageCursor>,
506            ) -> ::anyhow::Result<::appdb::Page<Self>> {
507                ::appdb::repository::Repo::<Self>::pagin_asc(count, cursor).await
508            }
509        }
510    } else {
511        quote! {}
512    };
513
514    let stored_model_impl = if !foreign_fields.is_empty() {
515        quote! {}
516    } else if secure_field_count(&named_fields) > 0 {
517        quote! {
518            impl ::appdb::StoredModel for #struct_ident {
519                type Stored = <Self as ::appdb::Sensitive>::Encrypted;
520
521                fn into_stored(self) -> ::anyhow::Result<Self::Stored> {
522                    <Self as ::appdb::Sensitive>::encrypt_with_runtime_resolver(&self)
523                        .map_err(::anyhow::Error::from)
524                }
525
526                fn from_stored(stored: Self::Stored) -> ::anyhow::Result<Self> {
527                    <Self as ::appdb::Sensitive>::decrypt_with_runtime_resolver(&stored)
528                        .map_err(::anyhow::Error::from)
529                }
530
531                fn supports_create_return_id() -> bool {
532                    false
533                }
534            }
535        }
536    } else {
537        quote! {
538            impl ::appdb::StoredModel for #struct_ident {
539                type Stored = Self;
540
541                fn into_stored(self) -> ::anyhow::Result<Self::Stored> {
542                    ::std::result::Result::Ok(self)
543                }
544
545                fn from_stored(stored: Self::Stored) -> ::anyhow::Result<Self> {
546                    ::std::result::Result::Ok(stored)
547                }
548            }
549        }
550    };
551
552    let stored_fields = named_fields.iter().map(|field| {
553        let ident = field.ident.clone().expect("named field");
554        let ty = stored_field_type(field, &foreign_fields);
555        if is_record_id_type(&ty) {
556            quote! {
557                #[serde(deserialize_with = "::appdb::serde_utils::id::deserialize_record_id_or_compat_string")]
558                #ident: #ty
559            }
560        } else {
561            quote! { #ident: #ty }
562        }
563    });
564
565    let into_stored_assignments = named_fields.iter().map(|field| {
566        let ident = field.ident.clone().expect("named field");
567        match foreign_field_kind(&ident, &foreign_fields) {
568            Some(ForeignFieldKind { original_ty, .. }) => quote! {
569                #ident: <#original_ty as ::appdb::ForeignShape>::persist_foreign_shape(value.#ident).await?
570            },
571            None => quote! { #ident: value.#ident },
572        }
573    });
574
575    let into_stored_with_plan_assignments = named_fields.iter().map(|field| {
576        let ident = field.ident.clone().expect("named field");
577        let field_name = ident.to_string();
578        match foreign_field_kind(&ident, &foreign_fields) {
579            Some(ForeignFieldKind {
580                original_ty,
581                stored_ty,
582            }) => quote! {
583                #ident: match foreign_plan.field_shape::<#stored_ty>(#field_name)? {
584                    ::std::option::Option::Some(stored) => stored,
585                    ::std::option::Option::None => {
586                        <#original_ty as ::appdb::ForeignShape>::persist_foreign_shape(value.#ident).await?
587                    }
588                }
589            },
590            None => quote! { #ident: value.#ident },
591        }
592    });
593
594    let from_stored_assignments = named_fields.iter().map(|field| {
595        let ident = field.ident.clone().expect("named field");
596        match foreign_field_kind(&ident, &foreign_fields) {
597            Some(ForeignFieldKind { original_ty, .. }) => quote! {
598                #ident: <#original_ty as ::appdb::ForeignShape>::hydrate_foreign_shape(stored.#ident).await?
599            },
600            None => quote! { #ident: stored.#ident },
601        }
602    });
603
604    let fill_assignments = fill_fields
605        .iter()
606        .map(|field| {
607            let ident = &field.ident;
608            match field.provider {
609                FillProvider::Now => quote! {
610                    value.#ident.fill_now_if_pending();
611                },
612            }
613        })
614        .collect::<Vec<_>>();
615
616    let decode_foreign_fields = foreign_fields.iter().map(|field| {
617        let ident = field.ident.to_string();
618        quote! {
619            if let ::std::option::Option::Some(value) = map.get_mut(#ident) {
620                ::appdb::decode_stored_record_links(value);
621            }
622        }
623    });
624
625    let relation_methods_impl = if relate_fields.is_empty() {
626        quote! {}
627    } else {
628        let strip_relation_fields = relate_fields.iter().map(|field| {
629            let ident = field.ident.to_string();
630            quote! {
631                map.remove(#ident);
632            }
633        });
634
635        let inject_relation_values_from_model = relate_fields.iter().map(|field| {
636            let ident = &field.ident;
637            let name = ident.to_string();
638            quote! {
639                map.insert(#name.to_owned(), ::serde_json::to_value(&self.#ident)?);
640            }
641        });
642
643        let prepare_relation_writes = relate_fields.iter().map(|field| {
644            let ident = &field.ident;
645            let relation_name = &field.relation_name;
646            let field_ty = &field.field_ty;
647            let write_direction = field.direction.write_direction_tokens();
648            let write_edges = field.direction.write_edges_tokens();
649            quote! {
650                {
651                    let ids = <#field_ty as ::appdb::RelateShape>::persist_relate_shape(self.#ident.clone()).await?;
652                    writes.push(::appdb::RelationWrite {
653                        relation: #relation_name,
654                        record: record.clone(),
655                        direction: #write_direction,
656                        edges: #write_edges,
657                    });
658                }
659            }
660        });
661
662        let inject_relation_values_from_db = relate_fields.iter().map(|field| {
663            let relation_name = &field.relation_name;
664            let field_ty = &field.field_ty;
665            let ident = field.ident.to_string();
666            let load_relation_ids = field.direction.load_edges_tokens(relation_name);
667            quote! {
668                {
669                    let value = <#field_ty as ::appdb::RelateShape>::hydrate_relate_shape(#load_relation_ids).await?;
670                    map.insert(#ident.to_owned(), ::serde_json::to_value(value)?);
671                }
672            }
673        });
674
675        quote! {
676            fn has_relation_fields() -> bool {
677                true
678            }
679
680            fn relation_field_names() -> &'static [&'static str] {
681                &[ #( #relate_field_literals ),* ]
682            }
683
684            fn strip_relation_fields(row: &mut ::serde_json::Value) {
685                if let ::serde_json::Value::Object(map) = row {
686                    #( #strip_relation_fields )*
687                }
688            }
689
690            fn inject_relation_values_from_model(
691                &self,
692                row: &mut ::serde_json::Value,
693            ) -> ::anyhow::Result<()> {
694                if let ::serde_json::Value::Object(map) = row {
695                    #( #inject_relation_values_from_model )*
696                }
697                Ok(())
698            }
699
700            fn prepare_relation_writes(
701                &self,
702                record: ::surrealdb::types::RecordId,
703            ) -> impl ::std::future::Future<Output = ::anyhow::Result<::std::vec::Vec<::appdb::RelationWrite>>> + Send {
704                async move {
705                    let mut writes = ::std::vec::Vec::new();
706                    #( #prepare_relation_writes )*
707                    Ok(writes)
708                }
709            }
710
711            fn inject_relation_values_from_db(
712                record: ::surrealdb::types::RecordId,
713                row: &mut ::serde_json::Value,
714            ) -> impl ::std::future::Future<Output = ::anyhow::Result<()>> + Send {
715                async move {
716                    if let ::serde_json::Value::Object(map) = row {
717                        #( #inject_relation_values_from_db )*
718                    }
719                    Ok(())
720                }
721            }
722        }
723    };
724
725    let foreign_model_impl = if foreign_fields.is_empty() {
726        quote! {
727            impl ::appdb::ForeignModel for #struct_ident {
728                async fn persist_foreign(value: Self) -> ::anyhow::Result<Self::Stored> {
729                    let mut value = value;
730                    #( #fill_assignments )*
731                    <Self as ::appdb::StoredModel>::into_stored(value)
732                }
733
734                async fn persist_foreign_with_plan(
735                    value: Self,
736                    foreign_plan: &::appdb::ForeignWritePlan,
737                ) -> ::anyhow::Result<Self::Stored> {
738                    foreign_plan.ensure_known_fields(Self::foreign_field_names())?;
739                    <Self as ::appdb::ForeignModel>::persist_foreign(value).await
740                }
741
742                async fn hydrate_foreign(stored: Self::Stored) -> ::anyhow::Result<Self> {
743                    <Self as ::appdb::StoredModel>::from_stored(stored)
744                }
745
746                fn decode_stored_row(
747                    row: ::surrealdb::types::Value,
748                ) -> ::anyhow::Result<Self::Stored>
749                where
750                    Self::Stored: ::serde::de::DeserializeOwned,
751                {
752                    Ok(::serde_json::from_value(row.into_json_value())?)
753                }
754
755                #relation_methods_impl
756            }
757        }
758    } else {
759        let stored_struct_ident = format_ident!("AppdbStored{}", struct_ident);
760        quote! {
761            #[derive(
762                Debug,
763                Clone,
764                ::serde::Serialize,
765                ::serde::Deserialize,
766                ::surrealdb::types::SurrealValue,
767            )]
768            #vis struct #stored_struct_ident {
769                #( #stored_fields, )*
770            }
771
772            impl ::appdb::StoredModel for #struct_ident {
773                type Stored = #stored_struct_ident;
774
775                fn into_stored(self) -> ::anyhow::Result<Self::Stored> {
776                    unreachable!("foreign fields require async persist_foreign")
777                }
778
779                fn from_stored(_stored: Self::Stored) -> ::anyhow::Result<Self> {
780                    unreachable!("foreign fields require async hydrate_foreign")
781                }
782            }
783
784            impl ::appdb::ForeignModel for #struct_ident {
785                async fn persist_foreign(value: Self) -> ::anyhow::Result<Self::Stored> {
786                    let mut value = value;
787                    #( #fill_assignments )*
788                    Ok(#stored_struct_ident {
789                        #( #into_stored_assignments, )*
790                    })
791                }
792
793                async fn persist_foreign_with_plan(
794                    value: Self,
795                    foreign_plan: &::appdb::ForeignWritePlan,
796                ) -> ::anyhow::Result<Self::Stored> {
797                    foreign_plan.ensure_known_fields(Self::foreign_field_names())?;
798                    let mut value = value;
799                    #( #fill_assignments )*
800                    Ok(#stored_struct_ident {
801                        #( #into_stored_with_plan_assignments, )*
802                    })
803                }
804
805                async fn hydrate_foreign(stored: Self::Stored) -> ::anyhow::Result<Self> {
806                    Ok(Self {
807                        #( #from_stored_assignments, )*
808                    })
809                }
810
811                fn has_foreign_fields() -> bool {
812                    true
813                }
814
815                fn foreign_field_names() -> &'static [&'static str] {
816                    &[ #( #foreign_field_literals ),* ]
817                }
818
819                fn decode_stored_row(
820                    row: ::surrealdb::types::Value,
821                ) -> ::anyhow::Result<Self::Stored>
822                where
823                    Self::Stored: ::serde::de::DeserializeOwned,
824                {
825                    let mut row = row.into_json_value();
826                    if let ::serde_json::Value::Object(map) = &mut row {
827                        #( #decode_foreign_fields )*
828                    }
829                    Ok(::serde_json::from_value(row)?)
830                }
831
832                #relation_methods_impl
833            }
834        }
835    };
836
837    let foreign_write_api_impl = if foreign_fields.is_empty() {
838        quote! {}
839    } else {
840        let foreign_write_ident = format_ident!("AppdbForeignWrite{}", struct_ident);
841        let foreign_write_field_methods = foreign_fields.iter().map(|field| {
842            let ident = &field.ident;
843            let field_name = ident.to_string();
844            let stored_ty = &field.kind.stored_ty;
845            quote! {
846                pub fn #ident(mut self, value: #stored_ty) -> ::anyhow::Result<Self> {
847                    self.query = self.query.set_field_shape(#field_name, value)?;
848                    Ok(self)
849                }
850            }
851        });
852
853        quote! {
854            #vis struct #foreign_write_ident {
855                query: ::appdb::repository::ForeignWriteQuery<#struct_ident>,
856            }
857
858            impl #foreign_write_ident {
859                #( #foreign_write_field_methods )*
860
861                pub async fn create_at(
862                    self,
863                    id: ::surrealdb::types::RecordId,
864                ) -> ::anyhow::Result<#struct_ident> {
865                    self.query.create_at(id).await
866                }
867
868                pub async fn upsert_at(
869                    self,
870                    id: ::surrealdb::types::RecordId,
871                ) -> ::anyhow::Result<#struct_ident> {
872                    self.query.upsert_at(id).await
873                }
874
875                pub async fn update_at(
876                    self,
877                    id: ::surrealdb::types::RecordId,
878                ) -> ::anyhow::Result<#struct_ident> {
879                    self.query.update_at(id).await
880                }
881
882                pub async fn create_at_returning<View>(
883                    self,
884                    id: ::surrealdb::types::RecordId,
885                ) -> ::anyhow::Result<View>
886                where
887                    View: ::appdb::repository::WriteReturnView<#struct_ident>,
888                {
889                    self.query.create_at_returning::<View>(id).await
890                }
891
892                pub async fn upsert_at_returning<View>(
893                    self,
894                    id: ::surrealdb::types::RecordId,
895                ) -> ::anyhow::Result<View>
896                where
897                    View: ::appdb::repository::WriteReturnView<#struct_ident>,
898                {
899                    self.query.upsert_at_returning::<View>(id).await
900                }
901
902                pub async fn update_at_returning<View>(
903                    self,
904                    id: ::surrealdb::types::RecordId,
905                ) -> ::anyhow::Result<View>
906                where
907                    View: ::appdb::repository::WriteReturnView<#struct_ident>,
908                {
909                    self.query.update_at_returning::<View>(id).await
910                }
911            }
912        }
913    };
914
915    let foreign_write_constructor_impl = if foreign_fields.is_empty() {
916        quote! {}
917    } else {
918        let foreign_write_ident = format_ident!("AppdbForeignWrite{}", struct_ident);
919        quote! {
920            pub fn foreign(self) -> #foreign_write_ident {
921                #foreign_write_ident {
922                    query: ::appdb::repository::ForeignWriteQuery::new(self),
923                }
924            }
925        }
926    };
927
928    let store_marker_ident = format_ident!("AppdbStoreMarker{}", struct_ident);
929
930    Ok(quote! {
931        #[doc(hidden)]
932        #vis struct #store_marker_ident;
933
934        impl ::appdb::model::meta::ModelMeta for #struct_ident {
935            fn storage_table() -> &'static str {
936                #resolved_table_name_expr
937            }
938
939            fn table_name() -> &'static str {
940                static TABLE_NAME: ::std::sync::OnceLock<&'static str> = ::std::sync::OnceLock::new();
941                TABLE_NAME.get_or_init(|| {
942                    let table = #resolved_table_name_expr;
943                    ::appdb::model::meta::register_table(stringify!(#struct_ident), table)
944                })
945            }
946        }
947
948        impl ::appdb::model::meta::StoreModelMarker for #struct_ident {}
949        impl ::appdb::model::meta::StoreModelMarker for #store_marker_ident {}
950        #pagination_meta_impl
951
952        impl ::appdb::model::meta::UniqueLookupMeta for #struct_ident {
953            fn lookup_fields() -> &'static [&'static str] {
954                &[ #( #lookup_field_literals ),* ]
955            }
956
957            fn foreign_fields() -> &'static [&'static str] {
958                &[ #( #foreign_field_literals ),* ]
959            }
960
961            fn resolve_lookup_field_value(
962                &self,
963                field: &str,
964            ) -> impl ::std::future::Future<
965                Output = ::anyhow::Result<::std::option::Option<::surrealdb::types::Value>>
966            > {
967                async move {
968                    match field {
969                        #( #resolve_lookup_field_value_arms )*
970                        _ => Ok(::std::option::Option::None),
971                    }
972                }
973            }
974        }
975        #stored_model_impl
976        #foreign_model_impl
977        #foreign_write_api_impl
978
979        #auto_has_id_impl
980        #resolve_record_id_impl
981
982        #( #unique_schema_impls )*
983        #( #pagin_schema_impl )*
984
985        impl ::appdb::repository::Crud for #struct_ident {}
986
987        impl #struct_ident {
988            /// Saves one value through the recommended Store CRUD surface.
989            ///
990            /// Prefer these generated model methods in application code. Lower-level
991            /// `appdb::repository::Repo` helpers exist for library internals and
992            /// advanced integration seams, not as the primary public path.
993            pub async fn save(self) -> ::anyhow::Result<Self> {
994                <Self as ::appdb::repository::Crud>::save(self).await
995            }
996
997            /// Saves many values through the recommended Store CRUD surface.
998            pub async fn save_many(data: ::std::vec::Vec<Self>) -> ::anyhow::Result<::std::vec::Vec<Self>> {
999                <Self as ::appdb::repository::Crud>::save_many(data).await
1000            }
1001
1002            pub async fn get<T>(id: T) -> ::anyhow::Result<Self>
1003            where
1004                ::surrealdb::types::RecordIdKey: From<T>,
1005                T: Send,
1006            {
1007                ::appdb::repository::Repo::<Self>::get(id).await
1008            }
1009
1010            pub fn list() -> ::appdb::repository::ListQuery<Self> {
1011                ::appdb::repository::Repo::<Self>::list()
1012            }
1013
1014            pub async fn list_limit(count: i64) -> ::anyhow::Result<::std::vec::Vec<Self>> {
1015                ::appdb::repository::Repo::<Self>::list_limit(count).await
1016            }
1017            #pagination_methods_impl
1018
1019            pub async fn relate_by_name<Target>(&self, target: &Target, relation: &str) -> ::anyhow::Result<()>
1020            where
1021                Target: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1022            {
1023                <Self as ::appdb::graph::GraphCrud>::relate_by_name(self, target, relation).await
1024            }
1025
1026            pub async fn back_relate_by_name<Target>(&self, target: &Target, relation: &str) -> ::anyhow::Result<()>
1027            where
1028                Target: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1029            {
1030                <Self as ::appdb::graph::GraphCrud>::back_relate_by_name(self, target, relation).await
1031            }
1032
1033            pub async fn unrelate_by_name<Target>(&self, target: &Target, relation: &str) -> ::anyhow::Result<()>
1034            where
1035                Target: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1036            {
1037                <Self as ::appdb::graph::GraphCrud>::unrelate_by_name(self, target, relation).await
1038            }
1039
1040            pub async fn outgoing_ids(&self, relation: &str) -> ::anyhow::Result<::std::vec::Vec<::surrealdb::types::RecordId>> {
1041                <Self as ::appdb::repository::Crud>::outgoing_ids(self, relation).await
1042            }
1043
1044            pub async fn outgoing<Target>(&self, relation: &str) -> ::anyhow::Result<::std::vec::Vec<Target>>
1045            where
1046                Target: ::appdb::model::meta::ModelMeta + ::appdb::StoredModel + ::appdb::ForeignModel,
1047                <Target as ::appdb::StoredModel>::Stored: ::serde::de::DeserializeOwned,
1048            {
1049                <Self as ::appdb::repository::Crud>::outgoing::<Target>(self, relation).await
1050            }
1051
1052            pub async fn outgoing_count(&self, relation: &str) -> ::anyhow::Result<i64> {
1053                <Self as ::appdb::repository::Crud>::outgoing_count(self, relation).await
1054            }
1055
1056            pub async fn outgoing_count_as<Target>(&self, relation: &str) -> ::anyhow::Result<i64>
1057            where
1058                Target: ::appdb::model::meta::ModelMeta + ::appdb::StoredModel + ::appdb::ForeignModel,
1059            {
1060                <Self as ::appdb::repository::Crud>::outgoing_count_as::<Target>(self, relation).await
1061            }
1062
1063            pub async fn incoming_ids(&self, relation: &str) -> ::anyhow::Result<::std::vec::Vec<::surrealdb::types::RecordId>> {
1064                <Self as ::appdb::repository::Crud>::incoming_ids(self, relation).await
1065            }
1066
1067            pub async fn incoming<Target>(&self, relation: &str) -> ::anyhow::Result<::std::vec::Vec<Target>>
1068            where
1069                Target: ::appdb::model::meta::ModelMeta + ::appdb::StoredModel + ::appdb::ForeignModel,
1070                <Target as ::appdb::StoredModel>::Stored: ::serde::de::DeserializeOwned,
1071            {
1072                <Self as ::appdb::repository::Crud>::incoming::<Target>(self, relation).await
1073            }
1074
1075            pub async fn incoming_count(&self, relation: &str) -> ::anyhow::Result<i64> {
1076                <Self as ::appdb::repository::Crud>::incoming_count(self, relation).await
1077            }
1078
1079            pub async fn incoming_count_as<Target>(&self, relation: &str) -> ::anyhow::Result<i64>
1080            where
1081                Target: ::appdb::model::meta::ModelMeta + ::appdb::StoredModel + ::appdb::ForeignModel,
1082            {
1083                <Self as ::appdb::repository::Crud>::incoming_count_as::<Target>(self, relation).await
1084            }
1085
1086            pub async fn delete_all() -> ::anyhow::Result<()> {
1087                ::appdb::repository::Repo::<Self>::delete_all().await
1088            }
1089
1090            pub async fn find_one_id(
1091                k: &str,
1092                v: &str,
1093            ) -> ::anyhow::Result<::surrealdb::types::RecordId> {
1094                ::appdb::repository::Repo::<Self>::find_one_id(k, v).await
1095            }
1096
1097            pub async fn list_record_ids() -> ::anyhow::Result<::std::vec::Vec<::surrealdb::types::RecordId>> {
1098                ::appdb::repository::Repo::<Self>::list_record_ids().await
1099            }
1100
1101            pub async fn create_at(
1102                id: ::surrealdb::types::RecordId,
1103                data: Self,
1104            ) -> ::anyhow::Result<Self> {
1105                ::appdb::repository::Repo::<Self>::create_at(id, data).await
1106            }
1107
1108            pub async fn upsert_at(
1109                id: ::surrealdb::types::RecordId,
1110                data: Self,
1111            ) -> ::anyhow::Result<Self> {
1112                ::appdb::repository::Repo::<Self>::upsert_at(id, data).await
1113            }
1114
1115            pub async fn update_at(
1116                self,
1117                id: ::surrealdb::types::RecordId,
1118            ) -> ::anyhow::Result<Self> {
1119                ::appdb::repository::Repo::<Self>::update_at(id, self).await
1120            }
1121
1122            #foreign_write_constructor_impl
1123
1124            pub async fn delete<T>(id: T) -> ::anyhow::Result<()>
1125            where
1126                ::surrealdb::types::RecordIdKey: From<T>,
1127                T: Send,
1128            {
1129                ::appdb::repository::Repo::<Self>::delete(id).await
1130            }
1131        }
1132    })
1133}
1134
1135fn derive_view_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1136    let struct_ident = input.ident;
1137    let vis = input.vis.clone();
1138    let source_ty = view_source_target(&input.attrs)?;
1139
1140    let named_fields = match input.data {
1141        Data::Struct(data) => match data.fields {
1142            Fields::Named(fields) => fields.named,
1143            _ => {
1144                return Err(Error::new_spanned(
1145                    struct_ident,
1146                    "View can only be derived for structs with named fields",
1147                ));
1148            }
1149        },
1150        _ => {
1151            return Err(Error::new_spanned(
1152                struct_ident,
1153                "View can only be derived for structs",
1154            ));
1155        }
1156    };
1157
1158    let stored_struct_ident = format_ident!("AppdbStoredView{}", struct_ident);
1159    let view_fields = named_fields
1160        .iter()
1161        .map(|field| field.ident.as_ref().expect("named field").to_string())
1162        .collect::<Vec<_>>();
1163    let view_field_literals = view_fields.iter().map(|field| quote! { #field });
1164    let nested_view_fields = named_fields
1165        .iter()
1166        .filter_map(|field| match field_view_nested_attr(field) {
1167            Ok(true) => Some(Ok(field.ident.as_ref().expect("named field").to_string())),
1168            Ok(false) => None,
1169            Err(err) => Some(Err(err)),
1170        })
1171        .collect::<syn::Result<Vec<_>>>()?;
1172    let nested_view_field_literals = nested_view_fields.iter().map(|field| quote! { #field });
1173
1174    let stored_fields = named_fields
1175        .iter()
1176        .map(|field| {
1177            let ident = field.ident.clone().expect("named field");
1178            let ty = view_stored_type(field)?;
1179            if is_record_id_type(&ty) {
1180                Ok(quote! {
1181                    #[serde(deserialize_with = "::appdb::serde_utils::id::deserialize_record_id_or_compat_string")]
1182                    #ident: #ty
1183                })
1184            } else {
1185                Ok(quote! { #ident: #ty })
1186            }
1187        })
1188        .collect::<syn::Result<Vec<_>>>()?;
1189
1190    let hydrate_assignments = named_fields.iter().map(|field| {
1191        let ident = field.ident.clone().expect("named field");
1192        let ty = field.ty.clone();
1193        if nested_view_fields.contains(&ident.to_string()) {
1194            quote! {
1195                #ident: <#ty as ::appdb::ViewShape>::hydrate_view_shape(stored.#ident).await?
1196            }
1197        } else {
1198            quote! {
1199                #ident: stored.#ident
1200            }
1201        }
1202    });
1203
1204    Ok(quote! {
1205        #[derive(
1206            Debug,
1207            Clone,
1208            ::serde::Serialize,
1209            ::serde::Deserialize,
1210            ::surrealdb::types::SurrealValue,
1211        )]
1212        #vis struct #stored_struct_ident {
1213            #( #stored_fields, )*
1214        }
1215
1216        #[::async_trait::async_trait]
1217        impl ::appdb::model::meta::ViewMeta for #struct_ident {
1218            type Source = #source_ty;
1219            type Stored = #stored_struct_ident;
1220
1221            fn view_fields() -> &'static [&'static str] {
1222                &[ #( #view_field_literals ),* ]
1223            }
1224
1225            fn nested_view_fields() -> &'static [&'static str] {
1226                &[ #( #nested_view_field_literals ),* ]
1227            }
1228
1229            fn decode_stored_view_row(
1230                row: ::serde_json::Value,
1231            ) -> ::anyhow::Result<Self::Stored> {
1232                Ok(::serde_json::from_value(row)?)
1233            }
1234
1235            async fn hydrate_view(stored: Self::Stored) -> ::anyhow::Result<Self> {
1236                Ok(Self {
1237                    #( #hydrate_assignments, )*
1238                })
1239            }
1240        }
1241
1242        #[::async_trait::async_trait]
1243        impl ::appdb::ViewShape for #struct_ident {
1244            type Stored = ::surrealdb::types::RecordId;
1245
1246            async fn hydrate_view_shape(stored: Self::Stored) -> ::anyhow::Result<Self> {
1247                ::appdb::repository::ViewRepo::<Self>::get_record(stored).await
1248            }
1249        }
1250
1251        impl #struct_ident {
1252            pub fn list() -> ::appdb::repository::ViewListQuery<Self> {
1253                ::appdb::repository::ViewRepo::<Self>::list()
1254            }
1255
1256            pub async fn get<T>(id: T) -> ::anyhow::Result<Self>
1257            where
1258                ::surrealdb::types::RecordIdKey: ::std::convert::From<T>,
1259                T: Send,
1260            {
1261                ::appdb::repository::ViewRepo::<Self>::get(id).await
1262            }
1263
1264            pub async fn get_record(
1265                id: ::surrealdb::types::RecordId,
1266            ) -> ::anyhow::Result<Self> {
1267                ::appdb::repository::ViewRepo::<Self>::get_record(id).await
1268            }
1269
1270            pub async fn find_one(
1271                k: &str,
1272                v: &str,
1273            ) -> ::anyhow::Result<Self> {
1274                ::appdb::repository::ViewRepo::<Self>::find_one(k, v).await
1275            }
1276
1277            pub async fn find_one_id(
1278                k: &str,
1279                v: &str,
1280            ) -> ::anyhow::Result<::surrealdb::types::RecordId> {
1281                ::appdb::repository::ViewRepo::<Self>::find_one_id(k, v).await
1282            }
1283        }
1284    })
1285}
1286
1287fn derive_bridge_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1288    let enum_ident = input.ident;
1289
1290    let variants = match input.data {
1291        Data::Enum(data) => data.variants,
1292        _ => {
1293            return Err(Error::new_spanned(
1294                enum_ident,
1295                "Bridge can only be derived for enums",
1296            ));
1297        }
1298    };
1299
1300    let payloads = variants
1301        .iter()
1302        .map(parse_bridge_variant)
1303        .collect::<syn::Result<Vec<_>>>()?;
1304
1305    let from_impls = payloads.iter().map(|variant| {
1306        let variant_ident = &variant.variant_ident;
1307        let payload_ty = &variant.payload_ty;
1308
1309        quote! {
1310            impl ::std::convert::From<#payload_ty> for #enum_ident {
1311                fn from(value: #payload_ty) -> Self {
1312                    Self::#variant_ident(value)
1313                }
1314            }
1315        }
1316    });
1317
1318    let persist_match_arms = payloads.iter().map(|variant| {
1319        let variant_ident = &variant.variant_ident;
1320
1321        quote! {
1322            Self::#variant_ident(value) => <_ as ::appdb::Bridge>::persist_foreign(value).await,
1323        }
1324    });
1325
1326    let hydrate_match_arms = payloads.iter().map(|variant| {
1327        let variant_ident = &variant.variant_ident;
1328        let payload_ty = &variant.payload_ty;
1329
1330        quote! {
1331            table if table == <#payload_ty as ::appdb::model::meta::ModelMeta>::storage_table() => {
1332                ::std::result::Result::Ok(Self::#variant_ident(
1333                    <#payload_ty as ::appdb::Bridge>::hydrate_foreign(id).await?,
1334                ))
1335            }
1336        }
1337    });
1338
1339    let lookup_match_arms = payloads.iter().map(|variant| {
1340        let variant_ident = &variant.variant_ident;
1341        let payload_ty = &variant.payload_ty;
1342
1343        quote! {
1344            Self::#variant_ident(value) => {
1345                <#payload_ty as ::appdb::ForeignLookupShape>::resolve_foreign_lookup_shape(value).await
1346            }
1347        }
1348    });
1349
1350    Ok(quote! {
1351        #( #from_impls )*
1352
1353        #[::async_trait::async_trait]
1354        impl ::appdb::Bridge for #enum_ident {
1355            async fn persist_foreign(self) -> ::anyhow::Result<::surrealdb::types::RecordId> {
1356                match self {
1357                    #( #persist_match_arms )*
1358                }
1359            }
1360
1361            async fn hydrate_foreign(
1362                id: ::surrealdb::types::RecordId,
1363            ) -> ::anyhow::Result<Self> {
1364                match id.table.to_string().as_str() {
1365                    #( #hydrate_match_arms, )*
1366                    table => ::anyhow::bail!(
1367                        "unsupported foreign table `{table}` for enum dispatcher `{}`",
1368                        ::std::stringify!(#enum_ident)
1369                    ),
1370                }
1371            }
1372        }
1373
1374        impl ::appdb::ForeignLookupShape for #enum_ident {
1375            type LookupStored = ::surrealdb::types::RecordId;
1376
1377            fn resolve_foreign_lookup_shape(
1378                &self,
1379            ) -> impl ::std::future::Future<Output = ::anyhow::Result<Self::LookupStored>> {
1380                async move {
1381                    match self {
1382                        #( #lookup_match_arms, )*
1383                    }
1384                }
1385            }
1386        }
1387    })
1388}
1389
1390#[derive(Clone)]
1391struct BridgeVariant {
1392    variant_ident: syn::Ident,
1393    payload_ty: Type,
1394}
1395
1396fn parse_bridge_variant(variant: &syn::Variant) -> syn::Result<BridgeVariant> {
1397    let payload_ty = match &variant.fields {
1398        Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
1399            fields.unnamed.first().expect("single field").ty.clone()
1400        }
1401        Fields::Unnamed(_) => {
1402            return Err(Error::new_spanned(
1403                &variant.ident,
1404                "Bridge variants must be single-field tuple variants",
1405            ));
1406        }
1407        Fields::Unit => {
1408            return Err(Error::new_spanned(
1409                &variant.ident,
1410                "Bridge does not support unit variants",
1411            ));
1412        }
1413        Fields::Named(_) => {
1414            return Err(Error::new_spanned(
1415                &variant.ident,
1416                "Bridge does not support struct variants",
1417            ));
1418        }
1419    };
1420
1421    let payload_path = match &payload_ty {
1422        Type::Path(path) => path,
1423        _ => {
1424            return Err(Error::new_spanned(
1425                &payload_ty,
1426                "Bridge payload must implement appdb::Bridge",
1427            ));
1428        }
1429    };
1430
1431    let segment = payload_path.path.segments.last().ok_or_else(|| {
1432        Error::new_spanned(&payload_ty, "Bridge payload must implement appdb::Bridge")
1433    })?;
1434
1435    if !matches!(segment.arguments, PathArguments::None) {
1436        return Err(Error::new_spanned(
1437            &payload_ty,
1438            "Bridge payload must implement appdb::Bridge",
1439        ));
1440    }
1441
1442    Ok(BridgeVariant {
1443        variant_ident: variant.ident.clone(),
1444        payload_ty,
1445    })
1446}
1447
1448fn derive_relation_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1449    let struct_ident = input.ident;
1450    let relation_name = relation_name_override(&input.attrs)?
1451        .unwrap_or_else(|| to_snake_case(&struct_ident.to_string()));
1452
1453    match input.data {
1454        Data::Struct(data) => match data.fields {
1455            Fields::Unit | Fields::Named(_) => {}
1456            _ => {
1457                return Err(Error::new_spanned(
1458                    struct_ident,
1459                    "Relation can only be derived for unit structs or structs with named fields",
1460                ));
1461            }
1462        },
1463        _ => {
1464            return Err(Error::new_spanned(
1465                struct_ident,
1466                "Relation can only be derived for structs",
1467            ));
1468        }
1469    }
1470
1471    Ok(quote! {
1472        impl ::appdb::model::relation::RelationMeta for #struct_ident {
1473            fn relation_name() -> &'static str {
1474                static REL_NAME: ::std::sync::OnceLock<&'static str> = ::std::sync::OnceLock::new();
1475                REL_NAME.get_or_init(|| ::appdb::model::relation::register_relation(#relation_name))
1476            }
1477        }
1478
1479        impl #struct_ident {
1480            pub async fn relate<A, B>(a: &A, b: &B) -> ::anyhow::Result<()>
1481            where
1482                A: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1483                B: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1484            {
1485                ::appdb::graph::relate_at(a.resolve_record_id().await?, b.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name()).await
1486            }
1487
1488            pub async fn back_relate<A, B>(a: &A, b: &B) -> ::anyhow::Result<()>
1489            where
1490                A: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1491                B: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1492            {
1493                ::appdb::graph::back_relate_at(a.resolve_record_id().await?, b.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name()).await
1494            }
1495
1496            pub async fn unrelate<A, B>(a: &A, b: &B) -> ::anyhow::Result<()>
1497            where
1498                A: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1499                B: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1500            {
1501                ::appdb::graph::unrelate_at(a.resolve_record_id().await?, b.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name()).await
1502            }
1503
1504            pub async fn out_ids<A>(a: &A, out_table: &str) -> ::anyhow::Result<::std::vec::Vec<::surrealdb::types::RecordId>>
1505            where
1506                A: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1507            {
1508                ::appdb::graph::out_ids(a.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name(), out_table).await
1509            }
1510
1511            pub async fn in_ids<B>(b: &B, in_table: &str) -> ::anyhow::Result<::std::vec::Vec<::surrealdb::types::RecordId>>
1512            where
1513                B: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1514            {
1515                ::appdb::graph::in_ids(b.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name(), in_table).await
1516            }
1517        }
1518    })
1519}
1520
1521fn derive_sensitive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1522    let struct_ident = input.ident;
1523    let encrypted_ident = format_ident!("Encrypted{}", struct_ident);
1524    let vis = input.vis;
1525    let type_crypto_config = type_crypto_config(&input.attrs)?;
1526    let named_fields = match input.data {
1527        Data::Struct(data) => match data.fields {
1528            Fields::Named(fields) => fields.named,
1529            _ => {
1530                return Err(Error::new_spanned(
1531                    struct_ident,
1532                    "Sensitive can only be derived for structs with named fields",
1533                ));
1534            }
1535        },
1536        _ => {
1537            return Err(Error::new_spanned(
1538                struct_ident,
1539                "Sensitive can only be derived for structs",
1540            ));
1541        }
1542    };
1543
1544    let mut secure_field_count = 0usize;
1545    let mut encrypted_fields = Vec::new();
1546    let mut encrypt_assignments = Vec::new();
1547    let mut decrypt_assignments = Vec::new();
1548    let mut runtime_encrypt_assignments = Vec::new();
1549    let mut runtime_decrypt_assignments = Vec::new();
1550    let mut field_tag_structs = Vec::new();
1551    let mut secure_field_meta_entries = Vec::new();
1552
1553    for field in named_fields.iter() {
1554        let ident = field.ident.clone().expect("named field");
1555        let field_vis = field.vis.clone();
1556        let secure = has_secure_attr(&field.attrs);
1557        let field_crypto_config = field_crypto_config(&field.attrs)?;
1558
1559        if !secure && field_crypto_config.is_present() {
1560            return Err(Error::new_spanned(
1561                ident,
1562                "#[crypto(...)] on a field requires #[secure] on the same field",
1563            ));
1564        }
1565
1566        if secure {
1567            secure_field_count += 1;
1568            let secure_kind = secure_kind(field)?;
1569            let encrypted_ty = secure_kind.encrypted_type();
1570            let field_tag_ident = format_ident!(
1571                "AppdbSensitiveFieldTag{}{}",
1572                struct_ident,
1573                to_pascal_case(&ident.to_string())
1574            );
1575            let field_tag_literal = ident.to_string();
1576            let effective_account = field_crypto_config
1577                .field_account
1578                .clone()
1579                .or_else(|| type_crypto_config.account.clone());
1580            let service_override = type_crypto_config.service.clone();
1581            let account_literal = effective_account
1582                .as_ref()
1583                .map(|value| quote! { ::std::option::Option::Some(#value) })
1584                .unwrap_or_else(|| quote! { ::std::option::Option::None });
1585            let service_literal = service_override
1586                .as_ref()
1587                .map(|value| quote! { ::std::option::Option::Some(#value) })
1588                .unwrap_or_else(|| quote! { ::std::option::Option::None });
1589            let encrypt_expr = secure_kind.encrypt_with_context_expr(&ident);
1590            let decrypt_expr = secure_kind.decrypt_with_context_expr(&ident);
1591            let runtime_encrypt_expr =
1592                secure_kind.encrypt_with_runtime_expr(&ident, &field_tag_ident);
1593            let runtime_decrypt_expr =
1594                secure_kind.decrypt_with_runtime_expr(&ident, &field_tag_ident);
1595            encrypted_fields.push(quote! { #field_vis #ident: #encrypted_ty });
1596            encrypt_assignments.push(quote! { #ident: #encrypt_expr });
1597            decrypt_assignments.push(quote! { #ident: #decrypt_expr });
1598            runtime_encrypt_assignments.push(quote! { #ident: #runtime_encrypt_expr });
1599            runtime_decrypt_assignments.push(quote! { #ident: #runtime_decrypt_expr });
1600            secure_field_meta_entries.push(quote! {
1601                ::appdb::crypto::SensitiveFieldMetadata {
1602                    model_tag: ::std::concat!(::std::module_path!(), "::", ::std::stringify!(#struct_ident)),
1603                    field_tag: #field_tag_literal,
1604                    service: #service_literal,
1605                    account: #account_literal,
1606                    secure_fields: &[],
1607                }
1608            });
1609            field_tag_structs.push(quote! {
1610                #[doc(hidden)]
1611                #vis struct #field_tag_ident;
1612
1613                impl ::appdb::crypto::SensitiveFieldTag for #field_tag_ident {
1614                    fn model_tag() -> &'static str {
1615                        <#struct_ident as ::appdb::crypto::SensitiveModelTag>::model_tag()
1616                    }
1617
1618                    fn field_tag() -> &'static str {
1619                        #field_tag_literal
1620                    }
1621
1622                    fn crypto_metadata() -> &'static ::appdb::crypto::SensitiveFieldMetadata {
1623                        static FIELD_META: ::std::sync::OnceLock<::appdb::crypto::SensitiveFieldMetadata> = ::std::sync::OnceLock::new();
1624                        FIELD_META.get_or_init(|| ::appdb::crypto::SensitiveFieldMetadata {
1625                            model_tag: <#struct_ident as ::appdb::crypto::SensitiveModelTag>::model_tag(),
1626                            field_tag: #field_tag_literal,
1627                            service: #service_literal,
1628                            account: #account_literal,
1629                            secure_fields: &#struct_ident::SECURE_FIELDS,
1630                        })
1631                    }
1632                }
1633            });
1634        } else {
1635            let ty = field.ty.clone();
1636            encrypted_fields.push(quote! { #field_vis #ident: #ty });
1637            encrypt_assignments.push(quote! { #ident: self.#ident.clone() });
1638            decrypt_assignments.push(quote! { #ident: encrypted.#ident.clone() });
1639            runtime_encrypt_assignments.push(quote! { #ident: self.#ident.clone() });
1640            runtime_decrypt_assignments.push(quote! { #ident: encrypted.#ident.clone() });
1641        }
1642    }
1643
1644    if secure_field_count == 0 {
1645        return Err(Error::new_spanned(
1646            struct_ident,
1647            "Sensitive requires at least one #[secure] field",
1648        ));
1649    }
1650
1651    Ok(quote! {
1652        #[derive(
1653            Debug,
1654            Clone,
1655            ::serde::Serialize,
1656            ::serde::Deserialize,
1657            ::surrealdb::types::SurrealValue,
1658        )]
1659        #vis struct #encrypted_ident {
1660            #( #encrypted_fields, )*
1661        }
1662
1663        impl ::appdb::crypto::SensitiveModelTag for #struct_ident {
1664            fn model_tag() -> &'static str {
1665                ::std::concat!(::std::module_path!(), "::", ::std::stringify!(#struct_ident))
1666            }
1667        }
1668
1669        #( #field_tag_structs )*
1670
1671        impl ::appdb::Sensitive for #struct_ident {
1672            type Encrypted = #encrypted_ident;
1673
1674            fn encrypt(
1675                &self,
1676                context: &::appdb::crypto::CryptoContext,
1677            ) -> ::std::result::Result<Self::Encrypted, ::appdb::crypto::CryptoError> {
1678                ::std::result::Result::Ok(#encrypted_ident {
1679                    #( #encrypt_assignments, )*
1680                })
1681            }
1682
1683            fn decrypt(
1684                encrypted: &Self::Encrypted,
1685                context: &::appdb::crypto::CryptoContext,
1686            ) -> ::std::result::Result<Self, ::appdb::crypto::CryptoError> {
1687                ::std::result::Result::Ok(Self {
1688                    #( #decrypt_assignments, )*
1689                })
1690            }
1691
1692            fn encrypt_with_runtime_resolver(
1693                &self,
1694            ) -> ::std::result::Result<Self::Encrypted, ::appdb::crypto::CryptoError> {
1695                ::std::result::Result::Ok(#encrypted_ident {
1696                    #( #runtime_encrypt_assignments, )*
1697                })
1698            }
1699
1700            fn decrypt_with_runtime_resolver(
1701                encrypted: &Self::Encrypted,
1702            ) -> ::std::result::Result<Self, ::appdb::crypto::CryptoError> {
1703                ::std::result::Result::Ok(Self {
1704                    #( #runtime_decrypt_assignments, )*
1705                })
1706            }
1707
1708            fn secure_fields() -> &'static [::appdb::crypto::SensitiveFieldMetadata] {
1709                &Self::SECURE_FIELDS
1710            }
1711        }
1712
1713        impl #struct_ident {
1714            pub const SECURE_FIELDS: [::appdb::crypto::SensitiveFieldMetadata; #secure_field_count] = [
1715                #( #secure_field_meta_entries, )*
1716            ];
1717
1718            pub fn encrypt(
1719                &self,
1720                context: &::appdb::crypto::CryptoContext,
1721            ) -> ::std::result::Result<#encrypted_ident, ::appdb::crypto::CryptoError> {
1722                <Self as ::appdb::Sensitive>::encrypt(self, context)
1723            }
1724        }
1725
1726        impl #encrypted_ident {
1727            pub fn decrypt(
1728                &self,
1729                context: &::appdb::crypto::CryptoContext,
1730            ) -> ::std::result::Result<#struct_ident, ::appdb::crypto::CryptoError> {
1731                <#struct_ident as ::appdb::Sensitive>::decrypt(self, context)
1732            }
1733        }
1734    })
1735}
1736
1737fn has_secure_attr(attrs: &[Attribute]) -> bool {
1738    attrs.iter().any(|attr| attr.path().is_ident("secure"))
1739}
1740
1741fn has_unique_attr(attrs: &[Attribute]) -> bool {
1742    attrs.iter().any(|attr| attr.path().is_ident("unique"))
1743}
1744
1745#[derive(Default, Clone)]
1746struct TypeCryptoConfig {
1747    service: Option<String>,
1748    account: Option<String>,
1749}
1750
1751#[derive(Default, Clone)]
1752struct FieldCryptoConfig {
1753    field_account: Option<String>,
1754}
1755
1756impl FieldCryptoConfig {
1757    fn is_present(&self) -> bool {
1758        self.field_account.is_some()
1759    }
1760}
1761
1762fn type_crypto_config(attrs: &[Attribute]) -> syn::Result<TypeCryptoConfig> {
1763    let mut config = TypeCryptoConfig::default();
1764    let mut seen = HashSet::new();
1765
1766    for attr in attrs {
1767        if !attr.path().is_ident("crypto") {
1768            continue;
1769        }
1770
1771        attr.parse_nested_meta(|meta| {
1772            let key = meta
1773                .path
1774                .get_ident()
1775                .cloned()
1776                .ok_or_else(|| meta.error("unsupported crypto attribute"))?;
1777
1778            if !seen.insert(key.to_string()) {
1779                return Err(meta.error("duplicate crypto attribute key"));
1780            }
1781
1782            let value = meta.value()?;
1783            let literal: syn::LitStr = value.parse()?;
1784            match key.to_string().as_str() {
1785                "service" => config.service = Some(literal.value()),
1786                "account" => config.account = Some(literal.value()),
1787                _ => {
1788                    return Err(
1789                        meta.error("unsupported crypto attribute; expected `service` or `account`")
1790                    );
1791                }
1792            }
1793            Ok(())
1794        })?;
1795    }
1796
1797    Ok(config)
1798}
1799
1800fn field_crypto_config(attrs: &[Attribute]) -> syn::Result<FieldCryptoConfig> {
1801    let mut config = FieldCryptoConfig::default();
1802    let mut seen = HashSet::new();
1803
1804    for attr in attrs {
1805        if attr.path().is_ident("crypto") {
1806            attr.parse_nested_meta(|meta| {
1807                let key = meta
1808                    .path
1809                    .get_ident()
1810                    .cloned()
1811                    .ok_or_else(|| meta.error("unsupported crypto attribute"))?;
1812
1813                if !seen.insert(key.to_string()) {
1814                    return Err(meta.error("duplicate crypto attribute key"));
1815                }
1816
1817                let value = meta.value()?;
1818                let literal: syn::LitStr = value.parse()?;
1819                match key.to_string().as_str() {
1820                    "field_account" => config.field_account = Some(literal.value()),
1821                    _ => {
1822                        return Err(meta.error(
1823                            "unsupported field crypto attribute; expected `field_account`",
1824                        ));
1825                    }
1826                }
1827                Ok(())
1828            })?;
1829        } else if attr.path().is_ident("secure") && matches!(attr.meta, Meta::List(_)) {
1830            return Err(Error::new_spanned(
1831                attr,
1832                "#[secure] does not accept arguments; use #[crypto(field_account = \"...\")] on the field",
1833            ));
1834        }
1835    }
1836
1837    Ok(config)
1838}
1839
1840fn table_alias_target(attrs: &[Attribute]) -> syn::Result<Option<Type>> {
1841    let mut target = None;
1842
1843    for attr in attrs {
1844        if !attr.path().is_ident("table_as") {
1845            continue;
1846        }
1847
1848        if target.is_some() {
1849            return Err(Error::new_spanned(
1850                attr,
1851                "duplicate #[table_as(...)] attribute is not supported",
1852            ));
1853        }
1854
1855        let parsed: Type = attr.parse_args().map_err(|_| {
1856            Error::new_spanned(attr, "#[table_as(...)] requires exactly one target type")
1857        })?;
1858
1859        match parsed {
1860            Type::Path(TypePath { ref path, .. }) if !path.segments.is_empty() => {
1861                target = Some(parsed);
1862            }
1863            _ => {
1864                return Err(Error::new_spanned(
1865                    parsed,
1866                    "#[table_as(...)] target must be a type path",
1867                ));
1868            }
1869        }
1870    }
1871
1872    Ok(target)
1873}
1874
1875fn view_source_target(attrs: &[Attribute]) -> syn::Result<Type> {
1876    let mut target = None;
1877
1878    for attr in attrs {
1879        if !attr.path().is_ident("view") {
1880            continue;
1881        }
1882
1883        if target.is_some() {
1884            return Err(Error::new_spanned(
1885                attr,
1886                "duplicate #[view(...)] attribute is not supported",
1887            ));
1888        }
1889
1890        attr.parse_nested_meta(|meta| {
1891            if meta.path.is_ident("source") {
1892                let value = meta.value()?;
1893                let parsed: Type = value.parse()?;
1894                match parsed {
1895                    Type::Path(TypePath { ref path, .. }) if !path.segments.is_empty() => {
1896                        target = Some(parsed);
1897                        Ok(())
1898                    }
1899                    _ => Err(Error::new_spanned(
1900                        parsed,
1901                        "#[view(source = ...)] source must be a type path",
1902                    )),
1903                }
1904            } else {
1905                Err(meta.error("unsupported view attribute; expected `source = Type`"))
1906            }
1907        })?;
1908    }
1909
1910    target.ok_or_else(|| {
1911        Error::new(
1912            proc_macro2::Span::call_site(),
1913            "View requires #[view(source = SourceStoreType)]",
1914        )
1915    })
1916}
1917
1918fn field_view_nested_attr(field: &Field) -> syn::Result<bool> {
1919    let mut is_nested = false;
1920
1921    for attr in &field.attrs {
1922        if !attr.path().is_ident("view") {
1923            continue;
1924        }
1925
1926        attr.parse_nested_meta(|meta| {
1927            if meta.path.is_ident("nested") {
1928                if is_nested {
1929                    return Err(meta.error("duplicate #[view(nested)] marker is not supported"));
1930                }
1931                is_nested = true;
1932                Ok(())
1933            } else {
1934                Err(meta.error("unsupported view field attribute; expected #[view(nested)]"))
1935            }
1936        })?;
1937    }
1938
1939    Ok(is_nested)
1940}
1941
1942fn resolved_schema_table_name(struct_ident: &syn::Ident, table_alias: Option<&Type>) -> String {
1943    match table_alias {
1944        Some(Type::Path(type_path)) => type_path
1945            .path
1946            .segments
1947            .last()
1948            .map(|segment| to_snake_case(&segment.ident.to_string()))
1949            .unwrap_or_else(|| to_snake_case(&struct_ident.to_string())),
1950        Some(_) => to_snake_case(&struct_ident.to_string()),
1951        None => to_snake_case(&struct_ident.to_string()),
1952    }
1953}
1954
1955fn field_foreign_attr(field: &Field) -> syn::Result<Option<&Attribute>> {
1956    let mut foreign_attr = None;
1957
1958    for attr in &field.attrs {
1959        if !attr.path().is_ident("foreign") {
1960            continue;
1961        }
1962
1963        if foreign_attr.is_some() {
1964            return Err(Error::new_spanned(
1965                attr,
1966                "duplicate nested-ref attribute is not supported",
1967            ));
1968        }
1969
1970        foreign_attr = Some(attr);
1971    }
1972
1973    Ok(foreign_attr)
1974}
1975
1976fn field_relation_attr(field: &Field) -> syn::Result<Option<&Attribute>> {
1977    let mut relate_attr = None;
1978
1979    for attr in &field.attrs {
1980        if !attr.path().is_ident("relate") && !attr.path().is_ident("back_relate") {
1981            continue;
1982        }
1983
1984        if let Some(previous) = relate_attr {
1985            return Err(Error::new_spanned(
1986                attr,
1987                relation_attr_conflict_message(previous, attr),
1988            ));
1989        }
1990
1991        relate_attr = Some(attr);
1992    }
1993
1994    Ok(relate_attr)
1995}
1996
1997fn field_pagin_attr(field: &Field) -> syn::Result<Option<&Attribute>> {
1998    let mut pagin_attr = None;
1999
2000    for attr in &field.attrs {
2001        if !attr.path().is_ident("pagin") {
2002            continue;
2003        }
2004
2005        if pagin_attr.is_some() {
2006            return Err(Error::new_spanned(
2007                attr,
2008                "duplicate #[pagin] attribute is not supported",
2009            ));
2010        }
2011
2012        pagin_attr = Some(attr);
2013    }
2014
2015    Ok(pagin_attr)
2016}
2017
2018fn field_fill_attr(field: &Field) -> syn::Result<Option<&Attribute>> {
2019    let mut fill_attr = None;
2020
2021    for attr in &field.attrs {
2022        if !attr.path().is_ident("fill") {
2023            continue;
2024        }
2025
2026        if fill_attr.is_some() {
2027            return Err(Error::new_spanned(
2028                attr,
2029                "duplicate #[fill(...)] attribute is not supported",
2030            ));
2031        }
2032
2033        fill_attr = Some(attr);
2034    }
2035
2036    Ok(fill_attr)
2037}
2038
2039fn validate_foreign_field(field: &Field, attr: &Attribute) -> syn::Result<Type> {
2040    if attr.path().is_ident("foreign") {
2041        return foreign_leaf_type(&field.ty)
2042            .ok_or_else(|| Error::new_spanned(&field.ty, BINDREF_ACCEPTED_SHAPES));
2043    }
2044
2045    Err(Error::new_spanned(attr, "unsupported foreign attribute"))
2046}
2047
2048const BINDREF_ACCEPTED_SHAPES: &str = "#[foreign] supports recursive Option<_> / Vec<_> shapes whose leaf type implements appdb::Bridge";
2049
2050const BINDREF_BRIDGE_STORE_ONLY: &str =
2051    "#[foreign] leaf types must derive Store or #[derive(Bridge)] dispatcher enums";
2052
2053const RELATE_ACCEPTED_SHAPES: &str = "relation-backed fields support Child / Option<Child> / Vec<Child> / Option<Vec<Child>> shapes whose leaf type implements appdb::Bridge";
2054
2055const PAGIN_ACCEPTED_SHAPES: &str = "#[pagin] supports direct scalar fields plus Id/RecordId; Option<_> and Vec<_> wrappers are not supported";
2056
2057const FILL_ACCEPTED_SHAPES: &str = "#[fill(...)] fields must use appdb::AutoFill";
2058
2059#[derive(Clone, Copy)]
2060enum RelationFieldDirection {
2061    Outgoing,
2062    Incoming,
2063}
2064
2065#[derive(Clone, Copy)]
2066enum FillProvider {
2067    Now,
2068}
2069
2070impl RelationFieldDirection {
2071    fn attr_label(self) -> &'static str {
2072        match self {
2073            Self::Outgoing => "#[relate(...)]",
2074            Self::Incoming => "#[back_relate(...)]",
2075        }
2076    }
2077
2078    fn write_direction_tokens(self) -> proc_macro2::TokenStream {
2079        match self {
2080            Self::Outgoing => quote!(::appdb::RelationWriteDirection::Outgoing),
2081            Self::Incoming => quote!(::appdb::RelationWriteDirection::Incoming),
2082        }
2083    }
2084
2085    fn write_edges_tokens(self) -> proc_macro2::TokenStream {
2086        match self {
2087            Self::Outgoing => quote! {
2088                ids
2089                    .into_iter()
2090                    .enumerate()
2091                    .map(|(position, out)| ::appdb::graph::OrderedRelationEdge {
2092                        _in: ::std::option::Option::Some(record.clone()),
2093                        out,
2094                        position: position as i64,
2095                    })
2096                    .collect()
2097            },
2098            Self::Incoming => quote! {
2099                ids
2100                    .into_iter()
2101                    .enumerate()
2102                    .map(|(position, source)| ::appdb::graph::OrderedRelationEdge {
2103                        _in: ::std::option::Option::Some(source),
2104                        out: record.clone(),
2105                        position: position as i64,
2106                    })
2107                    .collect()
2108            },
2109        }
2110    }
2111
2112    fn load_edges_tokens(self, relation_name: &str) -> proc_macro2::TokenStream {
2113        match self {
2114            Self::Outgoing => quote! {
2115                ::appdb::graph::GraphRepo::out_edges(record.clone(), #relation_name)
2116                    .await?
2117                    .into_iter()
2118                    .map(|edge| edge.out)
2119                    .collect()
2120            },
2121            Self::Incoming => quote! {
2122                {
2123                    let mut ids = ::std::vec::Vec::new();
2124                    for edge in ::appdb::graph::GraphRepo::in_edges(record.clone(), #relation_name).await? {
2125                        let incoming = edge._in.ok_or_else(|| {
2126                            ::anyhow::anyhow!("back relate field received relation edge without `in` record id")
2127                        })?;
2128                        ids.push(incoming);
2129                    }
2130                    ids
2131                }
2132            },
2133        }
2134    }
2135}
2136
2137#[derive(Clone)]
2138struct ForeignField {
2139    ident: syn::Ident,
2140    kind: ForeignFieldKind,
2141}
2142
2143#[derive(Clone)]
2144struct ForeignFieldKind {
2145    original_ty: Type,
2146    stored_ty: Type,
2147}
2148
2149#[derive(Clone)]
2150struct RelateField {
2151    ident: syn::Ident,
2152    relation_name: String,
2153    field_ty: Type,
2154    direction: RelationFieldDirection,
2155}
2156
2157#[derive(Clone)]
2158struct PaginField {
2159    ident: syn::Ident,
2160}
2161
2162#[derive(Clone)]
2163struct FillField {
2164    ident: syn::Ident,
2165    provider: FillProvider,
2166}
2167
2168fn parse_foreign_field(field: &Field, attr: &Attribute) -> syn::Result<ForeignField> {
2169    validate_foreign_field(field, attr)?;
2170    let ident = field.ident.clone().expect("named field");
2171
2172    let kind = ForeignFieldKind {
2173        original_ty: field.ty.clone(),
2174        stored_ty: foreign_stored_type(&field.ty)
2175            .ok_or_else(|| Error::new_spanned(&field.ty, BINDREF_ACCEPTED_SHAPES))?,
2176    };
2177
2178    Ok(ForeignField { ident, kind })
2179}
2180
2181fn parse_relate_field(field: &Field, attr: &Attribute) -> syn::Result<RelateField> {
2182    let direction = parse_relation_direction(attr)?;
2183    let relation_name = attr
2184        .parse_args::<syn::LitStr>()
2185        .map_err(|_| {
2186            Error::new_spanned(
2187                attr,
2188                format!(
2189                    "{} requires exactly one string literal",
2190                    direction.attr_label()
2191                ),
2192            )
2193        })?
2194        .value();
2195    if relation_name.is_empty() {
2196        return Err(Error::new_spanned(
2197            attr,
2198            format!("{} relation name must not be empty", direction.attr_label()),
2199        ));
2200    }
2201
2202    validate_relate_field(field, attr)?;
2203
2204    Ok(RelateField {
2205        ident: field.ident.clone().expect("named field"),
2206        relation_name,
2207        field_ty: field.ty.clone(),
2208        direction,
2209    })
2210}
2211
2212fn parse_pagin_field(field: &Field, attr: &Attribute) -> syn::Result<PaginField> {
2213    validate_pagin_field(field, attr)?;
2214    Ok(PaginField {
2215        ident: field.ident.clone().expect("named field"),
2216    })
2217}
2218
2219fn parse_fill_field(field: &Field, attr: &Attribute) -> syn::Result<FillField> {
2220    let provider = validate_fill_field(field, attr)?;
2221    Ok(FillField {
2222        ident: field.ident.clone().expect("named field"),
2223        provider,
2224    })
2225}
2226
2227fn validate_relate_field(field: &Field, attr: &Attribute) -> syn::Result<Type> {
2228    if !attr.path().is_ident("relate") && !attr.path().is_ident("back_relate") {
2229        return Err(Error::new_spanned(attr, "unsupported relate attribute"));
2230    }
2231
2232    let accepted = relate_leaf_type(&field.ty).cloned().map(Type::Path);
2233
2234    accepted.ok_or_else(|| Error::new_spanned(&field.ty, RELATE_ACCEPTED_SHAPES))
2235}
2236
2237fn validate_pagin_field(field: &Field, attr: &Attribute) -> syn::Result<Type> {
2238    if !attr.path().is_ident("pagin") {
2239        return Err(Error::new_spanned(attr, "unsupported pagination attribute"));
2240    }
2241
2242    if pagination_leaf_type(&field.ty).is_none() {
2243        return Err(Error::new_spanned(&field.ty, PAGIN_ACCEPTED_SHAPES));
2244    }
2245
2246    Ok(field.ty.clone())
2247}
2248
2249fn validate_fill_field(field: &Field, attr: &Attribute) -> syn::Result<FillProvider> {
2250    if !attr.path().is_ident("fill") {
2251        return Err(Error::new_spanned(attr, "unsupported fill attribute"));
2252    }
2253
2254    if !is_autofill_type(&field.ty) {
2255        return Err(Error::new_spanned(&field.ty, FILL_ACCEPTED_SHAPES));
2256    }
2257
2258    parse_fill_provider(attr)
2259}
2260
2261fn parse_fill_provider(attr: &Attribute) -> syn::Result<FillProvider> {
2262    let provider = attr.parse_args::<syn::Path>().map_err(|_| {
2263        Error::new_spanned(
2264            attr,
2265            "#[fill(...)] requires exactly one provider like #[fill(now)]",
2266        )
2267    })?;
2268
2269    if provider.is_ident("now") {
2270        Ok(FillProvider::Now)
2271    } else {
2272        Err(Error::new_spanned(
2273            attr,
2274            "unsupported fill provider; expected #[fill(now)]",
2275        ))
2276    }
2277}
2278
2279fn relate_leaf_type(ty: &Type) -> Option<&TypePath> {
2280    if let Some(leaf) = direct_store_child_type(ty) {
2281        return Some(leaf);
2282    }
2283
2284    if let Some(inner) = option_inner_type(ty) {
2285        if let Some(leaf) = direct_store_child_type(inner) {
2286            return Some(leaf);
2287        }
2288
2289        if let Some(vec_inner) = vec_inner_type(inner) {
2290            return direct_store_child_type(vec_inner);
2291        }
2292
2293        return None;
2294    }
2295
2296    if let Some(inner) = vec_inner_type(ty) {
2297        return direct_store_child_type(inner);
2298    }
2299
2300    None
2301}
2302
2303fn pagination_leaf_type(ty: &Type) -> Option<Type> {
2304    if option_inner_type(ty).is_some() || vec_inner_type(ty).is_some() {
2305        return None;
2306    }
2307
2308    if is_id_type(ty)
2309        || is_record_id_type(ty)
2310        || is_autofill_type(ty)
2311        || is_string_type(ty)
2312        || is_common_non_store_leaf_type(ty)
2313    {
2314        return Some(ty.clone());
2315    }
2316
2317    None
2318}
2319
2320fn parse_relation_direction(attr: &Attribute) -> syn::Result<RelationFieldDirection> {
2321    if attr.path().is_ident("relate") {
2322        return Ok(RelationFieldDirection::Outgoing);
2323    }
2324    if attr.path().is_ident("back_relate") {
2325        return Ok(RelationFieldDirection::Incoming);
2326    }
2327
2328    Err(Error::new_spanned(attr, "unsupported relate attribute"))
2329}
2330
2331fn relation_attr_label(attr: &Attribute) -> &'static str {
2332    if attr.path().is_ident("back_relate") {
2333        "#[back_relate(...)]"
2334    } else {
2335        "#[relate(...)]"
2336    }
2337}
2338
2339fn relation_attr_conflict_message(previous: &Attribute, current: &Attribute) -> String {
2340    match (
2341        previous.path().is_ident("relate"),
2342        previous.path().is_ident("back_relate"),
2343        current.path().is_ident("relate"),
2344        current.path().is_ident("back_relate"),
2345    ) {
2346        (true, false, true, false) => {
2347            "duplicate #[relate(...)] attribute is not supported".to_owned()
2348        }
2349        (false, true, false, true) => {
2350            "duplicate #[back_relate(...)] attribute is not supported".to_owned()
2351        }
2352        _ => "#[relate(...)] cannot be combined with #[back_relate(...)]".to_owned(),
2353    }
2354}
2355
2356fn foreign_field_kind<'a>(
2357    ident: &syn::Ident,
2358    fields: &'a [ForeignField],
2359) -> Option<&'a ForeignFieldKind> {
2360    fields
2361        .iter()
2362        .find(|field| field.ident == *ident)
2363        .map(|field| &field.kind)
2364}
2365
2366fn stored_field_type(field: &Field, foreign_fields: &[ForeignField]) -> Type {
2367    let ident = field.ident.as_ref().expect("named field");
2368    match foreign_field_kind(ident, foreign_fields) {
2369        Some(ForeignFieldKind { stored_ty, .. }) => stored_ty.clone(),
2370        None => field.ty.clone(),
2371    }
2372}
2373
2374fn view_stored_type(field: &Field) -> syn::Result<Type> {
2375    if field_view_nested_attr(field)? {
2376        view_record_shape_type(&field.ty).ok_or_else(|| {
2377            Error::new_spanned(
2378                &field.ty,
2379                "#[view(nested)] fields must be a View type, Option<View>, Vec<View>, or nested Option/Vec wrappers",
2380            )
2381        })
2382    } else {
2383        Ok(field.ty.clone())
2384    }
2385}
2386
2387fn view_record_shape_type(ty: &Type) -> Option<Type> {
2388    if let Some(inner) = option_inner_type(ty) {
2389        let inner = view_record_shape_type(inner)?;
2390        return Some(syn::parse_quote!(::std::option::Option<#inner>));
2391    }
2392
2393    if let Some(inner) = vec_inner_type(ty) {
2394        let inner = view_record_shape_type(inner)?;
2395        return Some(syn::parse_quote!(::std::vec::Vec<#inner>));
2396    }
2397
2398    view_leaf_type(ty).map(|_| syn::parse_quote!(::surrealdb::types::RecordId))
2399}
2400
2401fn view_leaf_type(ty: &Type) -> Option<&TypePath> {
2402    let Type::Path(type_path) = ty else {
2403        return None;
2404    };
2405
2406    let segment = type_path.path.segments.last()?;
2407    if !matches!(segment.arguments, PathArguments::None) {
2408        return None;
2409    }
2410
2411    if is_id_type(ty)
2412        || is_record_id_type(ty)
2413        || is_autofill_type(ty)
2414        || is_string_type(ty)
2415        || is_common_non_store_leaf_type(ty)
2416    {
2417        return None;
2418    }
2419
2420    Some(type_path)
2421}
2422
2423fn foreign_stored_type(ty: &Type) -> Option<Type> {
2424    if let Some(inner) = option_inner_type(ty) {
2425        let inner = foreign_stored_type(inner)?;
2426        return Some(syn::parse_quote!(::std::option::Option<#inner>));
2427    }
2428
2429    if let Some(inner) = vec_inner_type(ty) {
2430        let inner = foreign_stored_type(inner)?;
2431        return Some(syn::parse_quote!(::std::vec::Vec<#inner>));
2432    }
2433
2434    direct_store_child_type(ty)
2435        .cloned()
2436        .map(|_| syn::parse_quote!(::surrealdb::types::RecordId))
2437}
2438
2439fn foreign_leaf_type(ty: &Type) -> Option<Type> {
2440    if let Some(inner) = option_inner_type(ty) {
2441        return foreign_leaf_type(inner);
2442    }
2443
2444    if let Some(inner) = vec_inner_type(ty) {
2445        return foreign_leaf_type(inner);
2446    }
2447
2448    direct_store_child_type(ty).cloned().map(Type::Path)
2449}
2450
2451fn invalid_foreign_leaf_type(ty: &Type) -> Option<Type> {
2452    let leaf = foreign_leaf_type(ty)?;
2453    match &leaf {
2454        Type::Path(type_path) => {
2455            let segment = type_path.path.segments.last()?;
2456            if matches!(segment.arguments, PathArguments::None) {
2457                None
2458            } else {
2459                Some(leaf)
2460            }
2461        }
2462        _ => Some(leaf),
2463    }
2464}
2465
2466fn direct_store_child_type(ty: &Type) -> Option<&TypePath> {
2467    let Type::Path(type_path) = ty else {
2468        return None;
2469    };
2470
2471    let segment = type_path.path.segments.last()?;
2472    if !matches!(segment.arguments, PathArguments::None) {
2473        return None;
2474    }
2475
2476    if is_id_type(ty)
2477        || is_autofill_type(ty)
2478        || is_string_type(ty)
2479        || is_common_non_store_leaf_type(ty)
2480    {
2481        return None;
2482    }
2483
2484    Some(type_path)
2485}
2486
2487fn is_common_non_store_leaf_type(ty: &Type) -> bool {
2488    matches!(
2489        ty,
2490        Type::Path(TypePath { path, .. })
2491            if path.is_ident("bool")
2492                || path.is_ident("u8")
2493                || path.is_ident("u16")
2494                || path.is_ident("u32")
2495                || path.is_ident("u64")
2496                || path.is_ident("u128")
2497                || path.is_ident("usize")
2498                || path.is_ident("i8")
2499                || path.is_ident("i16")
2500                || path.is_ident("i32")
2501                || path.is_ident("i64")
2502                || path.is_ident("i128")
2503                || path.is_ident("isize")
2504                || path.is_ident("f32")
2505                || path.is_ident("f64")
2506                || path.is_ident("char")
2507    )
2508}
2509
2510fn secure_field_count(fields: &syn::punctuated::Punctuated<Field, syn::token::Comma>) -> usize {
2511    fields
2512        .iter()
2513        .filter(|field| has_secure_attr(&field.attrs))
2514        .count()
2515}
2516
2517fn relation_name_override(attrs: &[Attribute]) -> syn::Result<Option<String>> {
2518    for attr in attrs {
2519        if !attr.path().is_ident("relation") {
2520            continue;
2521        }
2522
2523        let mut name = None;
2524        attr.parse_nested_meta(|meta| {
2525            if meta.path.is_ident("name") {
2526                let value = meta.value()?;
2527                let literal: syn::LitStr = value.parse()?;
2528                name = Some(literal.value());
2529                Ok(())
2530            } else {
2531                Err(meta.error("unsupported relation attribute"))
2532            }
2533        })?;
2534        return Ok(name);
2535    }
2536
2537    Ok(None)
2538}
2539
2540enum SecureKind {
2541    Shape(Type),
2542}
2543
2544impl SecureKind {
2545    fn encrypted_type(&self) -> proc_macro2::TokenStream {
2546        match self {
2547            SecureKind::Shape(ty) => quote! { <#ty as ::appdb::SensitiveShape>::Encrypted },
2548        }
2549    }
2550
2551    fn encrypt_with_context_expr(&self, ident: &syn::Ident) -> proc_macro2::TokenStream {
2552        match self {
2553            SecureKind::Shape(ty) => {
2554                quote! { <#ty as ::appdb::SensitiveShape>::encrypt_with_context(&self.#ident, context)? }
2555            }
2556        }
2557    }
2558
2559    fn decrypt_with_context_expr(&self, ident: &syn::Ident) -> proc_macro2::TokenStream {
2560        match self {
2561            SecureKind::Shape(ty) => {
2562                quote! { <#ty as ::appdb::SensitiveShape>::decrypt_with_context(&encrypted.#ident, context)? }
2563            }
2564        }
2565    }
2566
2567    fn encrypt_with_runtime_expr(
2568        &self,
2569        ident: &syn::Ident,
2570        field_tag_ident: &syn::Ident,
2571    ) -> proc_macro2::TokenStream {
2572        match self {
2573            SecureKind::Shape(ty) => {
2574                quote! {{
2575                    let context = ::appdb::crypto::resolve_crypto_context_for::<#field_tag_ident>()?;
2576                    <#ty as ::appdb::SensitiveShape>::encrypt_with_context(&self.#ident, context.as_ref())?
2577                }}
2578            }
2579        }
2580    }
2581
2582    fn decrypt_with_runtime_expr(
2583        &self,
2584        ident: &syn::Ident,
2585        field_tag_ident: &syn::Ident,
2586    ) -> proc_macro2::TokenStream {
2587        match self {
2588            SecureKind::Shape(ty) => {
2589                quote! {{
2590                    let context = ::appdb::crypto::resolve_crypto_context_for::<#field_tag_ident>()?;
2591                    <#ty as ::appdb::SensitiveShape>::decrypt_with_context(&encrypted.#ident, context.as_ref())?
2592                }}
2593            }
2594        }
2595    }
2596}
2597
2598fn secure_kind(field: &Field) -> syn::Result<SecureKind> {
2599    if secure_shape_supported(&field.ty) {
2600        return Ok(SecureKind::Shape(field.ty.clone()));
2601    }
2602
2603    Err(Error::new_spanned(
2604        &field.ty,
2605        secure_shape_error_message(&field.ty),
2606    ))
2607}
2608
2609fn secure_shape_supported(ty: &Type) -> bool {
2610    if is_string_type(ty) {
2611        return true;
2612    }
2613
2614    if sensitive_value_wrapper_inner_type(ty).is_some() {
2615        return true;
2616    }
2617
2618    if let Some(inner) = option_inner_type(ty) {
2619        return secure_shape_supported(inner);
2620    }
2621
2622    if let Some(inner) = vec_inner_type(ty) {
2623        return secure_shape_supported(inner);
2624    }
2625
2626    direct_sensitive_child_type(ty).is_some()
2627}
2628
2629fn secure_shape_error_message(ty: &Type) -> &'static str {
2630    if invalid_secure_leaf_type(ty).is_some() {
2631        "#[secure] child shapes require a direct named Sensitive type leaf with only Option<_> and Vec<_> wrappers"
2632    } else {
2633        "#[secure] supports String, appdb::SensitiveValueOf<T>, and recursive Child / Option<Child> / Vec<Child> shapes where Child implements appdb::Sensitive"
2634    }
2635}
2636
2637fn direct_sensitive_child_type(ty: &Type) -> Option<&TypePath> {
2638    let Type::Path(type_path) = ty else {
2639        return None;
2640    };
2641
2642    let segment = type_path.path.segments.last()?;
2643    if !matches!(segment.arguments, PathArguments::None) {
2644        return None;
2645    }
2646
2647    if is_id_type(ty) || is_string_type(ty) || is_common_non_store_leaf_type(ty) {
2648        return None;
2649    }
2650
2651    Some(type_path)
2652}
2653
2654fn invalid_secure_leaf_type(ty: &Type) -> Option<Type> {
2655    if let Some(inner) = option_inner_type(ty) {
2656        return invalid_secure_leaf_type(inner);
2657    }
2658
2659    if let Some(inner) = vec_inner_type(ty) {
2660        return invalid_secure_leaf_type(inner);
2661    }
2662
2663    let leaf = direct_sensitive_child_type(ty)?.clone();
2664    let segment = leaf.path.segments.last()?;
2665    if matches!(segment.arguments, PathArguments::None) {
2666        None
2667    } else {
2668        Some(Type::Path(leaf))
2669    }
2670}
2671
2672fn is_string_type(ty: &Type) -> bool {
2673    match ty {
2674        Type::Path(TypePath { path, .. }) => path.is_ident("String"),
2675        _ => false,
2676    }
2677}
2678
2679fn is_id_type(ty: &Type) -> bool {
2680    match ty {
2681        Type::Path(TypePath { path, .. }) => path.segments.last().is_some_and(|segment| {
2682            let ident = segment.ident.to_string();
2683            ident == "Id"
2684        }),
2685        _ => false,
2686    }
2687}
2688
2689fn is_autofill_type(ty: &Type) -> bool {
2690    match ty {
2691        Type::Path(TypePath { path, .. }) => path
2692            .segments
2693            .last()
2694            .is_some_and(|segment| segment.ident == "AutoFill"),
2695        _ => false,
2696    }
2697}
2698
2699fn is_record_id_type(ty: &Type) -> bool {
2700    match ty {
2701        Type::Path(TypePath { path, .. }) => path.segments.last().is_some_and(|segment| {
2702            let ident = segment.ident.to_string();
2703            ident == "RecordId"
2704        }),
2705        _ => false,
2706    }
2707}
2708
2709fn option_inner_type(ty: &Type) -> Option<&Type> {
2710    let Type::Path(TypePath { path, .. }) = ty else {
2711        return None;
2712    };
2713    let segment = path.segments.last()?;
2714    if segment.ident != "Option" {
2715        return None;
2716    }
2717    let PathArguments::AngleBracketed(args) = &segment.arguments else {
2718        return None;
2719    };
2720    let GenericArgument::Type(inner) = args.args.first()? else {
2721        return None;
2722    };
2723    Some(inner)
2724}
2725
2726fn vec_inner_type(ty: &Type) -> Option<&Type> {
2727    let Type::Path(TypePath { path, .. }) = ty else {
2728        return None;
2729    };
2730    let segment = path.segments.last()?;
2731    if segment.ident != "Vec" {
2732        return None;
2733    }
2734    let PathArguments::AngleBracketed(args) = &segment.arguments else {
2735        return None;
2736    };
2737    let GenericArgument::Type(inner) = args.args.first()? else {
2738        return None;
2739    };
2740    Some(inner)
2741}
2742
2743fn sensitive_value_wrapper_inner_type(ty: &Type) -> Option<&Type> {
2744    let Type::Path(TypePath { path, .. }) = ty else {
2745        return None;
2746    };
2747    let segment = path.segments.last()?;
2748    if segment.ident != "SensitiveValueOf" {
2749        return None;
2750    }
2751    let PathArguments::AngleBracketed(args) = &segment.arguments else {
2752        return None;
2753    };
2754    let GenericArgument::Type(inner) = args.args.first()? else {
2755        return None;
2756    };
2757    Some(inner)
2758}
2759
2760fn to_snake_case(input: &str) -> String {
2761    let mut out = String::with_capacity(input.len() + 4);
2762    let mut prev_is_lower_or_digit = false;
2763
2764    for ch in input.chars() {
2765        if ch.is_ascii_uppercase() {
2766            if prev_is_lower_or_digit {
2767                out.push('_');
2768            }
2769            out.push(ch.to_ascii_lowercase());
2770            prev_is_lower_or_digit = false;
2771        } else {
2772            out.push(ch);
2773            prev_is_lower_or_digit = ch.is_ascii_lowercase() || ch.is_ascii_digit();
2774        }
2775    }
2776
2777    out
2778}
2779
2780fn to_pascal_case(input: &str) -> String {
2781    let mut out = String::with_capacity(input.len());
2782    let mut uppercase_next = true;
2783
2784    for ch in input.chars() {
2785        if ch == '_' || ch == '-' {
2786            uppercase_next = true;
2787            continue;
2788        }
2789
2790        if uppercase_next {
2791            out.push(ch.to_ascii_uppercase());
2792            uppercase_next = false;
2793        } else {
2794            out.push(ch);
2795        }
2796    }
2797
2798    out
2799}