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 pub async fn save(self) -> ::anyhow::Result<Self> {
994 <Self as ::appdb::repository::Crud>::save(self).await
995 }
996
997 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}