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 list_records(
1271 ) -> ::anyhow::Result<::std::vec::Vec<::appdb::repository::ViewRecord<Self>>> {
1272 ::appdb::repository::ViewRepo::<Self>::list_records().await
1273 }
1274
1275 pub async fn outgoing_records(
1276 id: ::surrealdb::types::RecordId,
1277 relation: &str,
1278 ) -> ::anyhow::Result<::std::vec::Vec<::appdb::repository::ViewRecord<Self>>> {
1279 ::appdb::repository::ViewRepo::<Self>::outgoing_records(id, relation).await
1280 }
1281
1282 pub async fn outgoing_records_by_owners(
1283 ids: ::std::vec::Vec<::surrealdb::types::RecordId>,
1284 relation: &str,
1285 ) -> ::anyhow::Result<::std::vec::Vec<::appdb::repository::ViewRelatedRecord<Self>>> {
1286 ::appdb::repository::ViewRepo::<Self>::outgoing_records_by_owners(ids, relation).await
1287 }
1288
1289 pub async fn incoming_records(
1290 id: ::surrealdb::types::RecordId,
1291 relation: &str,
1292 ) -> ::anyhow::Result<::std::vec::Vec<::appdb::repository::ViewRecord<Self>>> {
1293 ::appdb::repository::ViewRepo::<Self>::incoming_records(id, relation).await
1294 }
1295
1296 pub async fn incoming_records_by_owners(
1297 ids: ::std::vec::Vec<::surrealdb::types::RecordId>,
1298 relation: &str,
1299 ) -> ::anyhow::Result<::std::vec::Vec<::appdb::repository::ViewRelatedRecord<Self>>> {
1300 ::appdb::repository::ViewRepo::<Self>::incoming_records_by_owners(ids, relation).await
1301 }
1302
1303 pub async fn find_one(
1304 k: &str,
1305 v: &str,
1306 ) -> ::anyhow::Result<Self> {
1307 ::appdb::repository::ViewRepo::<Self>::find_one(k, v).await
1308 }
1309
1310 pub async fn find_one_id(
1311 k: &str,
1312 v: &str,
1313 ) -> ::anyhow::Result<::surrealdb::types::RecordId> {
1314 ::appdb::repository::ViewRepo::<Self>::find_one_id(k, v).await
1315 }
1316 }
1317 })
1318}
1319
1320fn derive_bridge_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1321 let enum_ident = input.ident;
1322
1323 let variants = match input.data {
1324 Data::Enum(data) => data.variants,
1325 _ => {
1326 return Err(Error::new_spanned(
1327 enum_ident,
1328 "Bridge can only be derived for enums",
1329 ));
1330 }
1331 };
1332
1333 let payloads = variants
1334 .iter()
1335 .map(parse_bridge_variant)
1336 .collect::<syn::Result<Vec<_>>>()?;
1337
1338 let from_impls = payloads.iter().map(|variant| {
1339 let variant_ident = &variant.variant_ident;
1340 let payload_ty = &variant.payload_ty;
1341
1342 quote! {
1343 impl ::std::convert::From<#payload_ty> for #enum_ident {
1344 fn from(value: #payload_ty) -> Self {
1345 Self::#variant_ident(value)
1346 }
1347 }
1348 }
1349 });
1350
1351 let persist_match_arms = payloads.iter().map(|variant| {
1352 let variant_ident = &variant.variant_ident;
1353
1354 quote! {
1355 Self::#variant_ident(value) => <_ as ::appdb::Bridge>::persist_foreign(value).await,
1356 }
1357 });
1358
1359 let hydrate_match_arms = payloads.iter().map(|variant| {
1360 let variant_ident = &variant.variant_ident;
1361 let payload_ty = &variant.payload_ty;
1362
1363 quote! {
1364 table if table == <#payload_ty as ::appdb::model::meta::ModelMeta>::storage_table() => {
1365 ::std::result::Result::Ok(Self::#variant_ident(
1366 <#payload_ty as ::appdb::Bridge>::hydrate_foreign(id).await?,
1367 ))
1368 }
1369 }
1370 });
1371
1372 let lookup_match_arms = payloads.iter().map(|variant| {
1373 let variant_ident = &variant.variant_ident;
1374 let payload_ty = &variant.payload_ty;
1375
1376 quote! {
1377 Self::#variant_ident(value) => {
1378 <#payload_ty as ::appdb::ForeignLookupShape>::resolve_foreign_lookup_shape(value).await
1379 }
1380 }
1381 });
1382
1383 Ok(quote! {
1384 #( #from_impls )*
1385
1386 #[::async_trait::async_trait]
1387 impl ::appdb::Bridge for #enum_ident {
1388 async fn persist_foreign(self) -> ::anyhow::Result<::surrealdb::types::RecordId> {
1389 match self {
1390 #( #persist_match_arms )*
1391 }
1392 }
1393
1394 async fn hydrate_foreign(
1395 id: ::surrealdb::types::RecordId,
1396 ) -> ::anyhow::Result<Self> {
1397 match id.table.to_string().as_str() {
1398 #( #hydrate_match_arms, )*
1399 table => ::anyhow::bail!(
1400 "unsupported foreign table `{table}` for enum dispatcher `{}`",
1401 ::std::stringify!(#enum_ident)
1402 ),
1403 }
1404 }
1405 }
1406
1407 impl ::appdb::ForeignLookupShape for #enum_ident {
1408 type LookupStored = ::surrealdb::types::RecordId;
1409
1410 fn resolve_foreign_lookup_shape(
1411 &self,
1412 ) -> impl ::std::future::Future<Output = ::anyhow::Result<Self::LookupStored>> {
1413 async move {
1414 match self {
1415 #( #lookup_match_arms, )*
1416 }
1417 }
1418 }
1419 }
1420 })
1421}
1422
1423#[derive(Clone)]
1424struct BridgeVariant {
1425 variant_ident: syn::Ident,
1426 payload_ty: Type,
1427}
1428
1429fn parse_bridge_variant(variant: &syn::Variant) -> syn::Result<BridgeVariant> {
1430 let payload_ty = match &variant.fields {
1431 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
1432 fields.unnamed.first().expect("single field").ty.clone()
1433 }
1434 Fields::Unnamed(_) => {
1435 return Err(Error::new_spanned(
1436 &variant.ident,
1437 "Bridge variants must be single-field tuple variants",
1438 ));
1439 }
1440 Fields::Unit => {
1441 return Err(Error::new_spanned(
1442 &variant.ident,
1443 "Bridge does not support unit variants",
1444 ));
1445 }
1446 Fields::Named(_) => {
1447 return Err(Error::new_spanned(
1448 &variant.ident,
1449 "Bridge does not support struct variants",
1450 ));
1451 }
1452 };
1453
1454 let payload_path = match &payload_ty {
1455 Type::Path(path) => path,
1456 _ => {
1457 return Err(Error::new_spanned(
1458 &payload_ty,
1459 "Bridge payload must implement appdb::Bridge",
1460 ));
1461 }
1462 };
1463
1464 let segment = payload_path.path.segments.last().ok_or_else(|| {
1465 Error::new_spanned(&payload_ty, "Bridge payload must implement appdb::Bridge")
1466 })?;
1467
1468 if !matches!(segment.arguments, PathArguments::None) {
1469 return Err(Error::new_spanned(
1470 &payload_ty,
1471 "Bridge payload must implement appdb::Bridge",
1472 ));
1473 }
1474
1475 Ok(BridgeVariant {
1476 variant_ident: variant.ident.clone(),
1477 payload_ty,
1478 })
1479}
1480
1481fn derive_relation_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1482 let struct_ident = input.ident;
1483 let relation_name = relation_name_override(&input.attrs)?
1484 .unwrap_or_else(|| to_snake_case(&struct_ident.to_string()));
1485
1486 match input.data {
1487 Data::Struct(data) => match data.fields {
1488 Fields::Unit | Fields::Named(_) => {}
1489 _ => {
1490 return Err(Error::new_spanned(
1491 struct_ident,
1492 "Relation can only be derived for unit structs or structs with named fields",
1493 ));
1494 }
1495 },
1496 _ => {
1497 return Err(Error::new_spanned(
1498 struct_ident,
1499 "Relation can only be derived for structs",
1500 ));
1501 }
1502 }
1503
1504 Ok(quote! {
1505 impl ::appdb::model::relation::RelationMeta for #struct_ident {
1506 fn relation_name() -> &'static str {
1507 static REL_NAME: ::std::sync::OnceLock<&'static str> = ::std::sync::OnceLock::new();
1508 REL_NAME.get_or_init(|| ::appdb::model::relation::register_relation(#relation_name))
1509 }
1510 }
1511
1512 impl #struct_ident {
1513 pub async fn relate<A, B>(a: &A, b: &B) -> ::anyhow::Result<()>
1514 where
1515 A: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1516 B: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1517 {
1518 ::appdb::graph::relate_at(a.resolve_record_id().await?, b.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name()).await
1519 }
1520
1521 pub async fn back_relate<A, B>(a: &A, b: &B) -> ::anyhow::Result<()>
1522 where
1523 A: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1524 B: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1525 {
1526 ::appdb::graph::back_relate_at(a.resolve_record_id().await?, b.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name()).await
1527 }
1528
1529 pub async fn unrelate<A, B>(a: &A, b: &B) -> ::anyhow::Result<()>
1530 where
1531 A: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1532 B: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1533 {
1534 ::appdb::graph::unrelate_at(a.resolve_record_id().await?, b.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name()).await
1535 }
1536
1537 pub async fn out_ids<A>(a: &A, out_table: &str) -> ::anyhow::Result<::std::vec::Vec<::surrealdb::types::RecordId>>
1538 where
1539 A: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1540 {
1541 ::appdb::graph::out_ids(a.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name(), out_table).await
1542 }
1543
1544 pub async fn in_ids<B>(b: &B, in_table: &str) -> ::anyhow::Result<::std::vec::Vec<::surrealdb::types::RecordId>>
1545 where
1546 B: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1547 {
1548 ::appdb::graph::in_ids(b.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name(), in_table).await
1549 }
1550 }
1551 })
1552}
1553
1554fn derive_sensitive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1555 let struct_ident = input.ident;
1556 let encrypted_ident = format_ident!("Encrypted{}", struct_ident);
1557 let vis = input.vis;
1558 let type_crypto_config = type_crypto_config(&input.attrs)?;
1559 let named_fields = match input.data {
1560 Data::Struct(data) => match data.fields {
1561 Fields::Named(fields) => fields.named,
1562 _ => {
1563 return Err(Error::new_spanned(
1564 struct_ident,
1565 "Sensitive can only be derived for structs with named fields",
1566 ));
1567 }
1568 },
1569 _ => {
1570 return Err(Error::new_spanned(
1571 struct_ident,
1572 "Sensitive can only be derived for structs",
1573 ));
1574 }
1575 };
1576
1577 let mut secure_field_count = 0usize;
1578 let mut encrypted_fields = Vec::new();
1579 let mut encrypt_assignments = Vec::new();
1580 let mut decrypt_assignments = Vec::new();
1581 let mut runtime_encrypt_assignments = Vec::new();
1582 let mut runtime_decrypt_assignments = Vec::new();
1583 let mut field_tag_structs = Vec::new();
1584 let mut secure_field_meta_entries = Vec::new();
1585
1586 for field in named_fields.iter() {
1587 let ident = field.ident.clone().expect("named field");
1588 let field_vis = field.vis.clone();
1589 let secure = has_secure_attr(&field.attrs);
1590 let field_crypto_config = field_crypto_config(&field.attrs)?;
1591
1592 if !secure && field_crypto_config.is_present() {
1593 return Err(Error::new_spanned(
1594 ident,
1595 "#[crypto(...)] on a field requires #[secure] on the same field",
1596 ));
1597 }
1598
1599 if secure {
1600 secure_field_count += 1;
1601 let secure_kind = secure_kind(field)?;
1602 let encrypted_ty = secure_kind.encrypted_type();
1603 let field_tag_ident = format_ident!(
1604 "AppdbSensitiveFieldTag{}{}",
1605 struct_ident,
1606 to_pascal_case(&ident.to_string())
1607 );
1608 let field_tag_literal = ident.to_string();
1609 let effective_account = field_crypto_config
1610 .field_account
1611 .clone()
1612 .or_else(|| type_crypto_config.account.clone());
1613 let service_override = type_crypto_config.service.clone();
1614 let account_literal = effective_account
1615 .as_ref()
1616 .map(|value| quote! { ::std::option::Option::Some(#value) })
1617 .unwrap_or_else(|| quote! { ::std::option::Option::None });
1618 let service_literal = service_override
1619 .as_ref()
1620 .map(|value| quote! { ::std::option::Option::Some(#value) })
1621 .unwrap_or_else(|| quote! { ::std::option::Option::None });
1622 let encrypt_expr = secure_kind.encrypt_with_context_expr(&ident);
1623 let decrypt_expr = secure_kind.decrypt_with_context_expr(&ident);
1624 let runtime_encrypt_expr =
1625 secure_kind.encrypt_with_runtime_expr(&ident, &field_tag_ident);
1626 let runtime_decrypt_expr =
1627 secure_kind.decrypt_with_runtime_expr(&ident, &field_tag_ident);
1628 encrypted_fields.push(quote! { #field_vis #ident: #encrypted_ty });
1629 encrypt_assignments.push(quote! { #ident: #encrypt_expr });
1630 decrypt_assignments.push(quote! { #ident: #decrypt_expr });
1631 runtime_encrypt_assignments.push(quote! { #ident: #runtime_encrypt_expr });
1632 runtime_decrypt_assignments.push(quote! { #ident: #runtime_decrypt_expr });
1633 secure_field_meta_entries.push(quote! {
1634 ::appdb::crypto::SensitiveFieldMetadata {
1635 model_tag: ::std::concat!(::std::module_path!(), "::", ::std::stringify!(#struct_ident)),
1636 field_tag: #field_tag_literal,
1637 service: #service_literal,
1638 account: #account_literal,
1639 secure_fields: &[],
1640 }
1641 });
1642 field_tag_structs.push(quote! {
1643 #[doc(hidden)]
1644 #vis struct #field_tag_ident;
1645
1646 impl ::appdb::crypto::SensitiveFieldTag for #field_tag_ident {
1647 fn model_tag() -> &'static str {
1648 <#struct_ident as ::appdb::crypto::SensitiveModelTag>::model_tag()
1649 }
1650
1651 fn field_tag() -> &'static str {
1652 #field_tag_literal
1653 }
1654
1655 fn crypto_metadata() -> &'static ::appdb::crypto::SensitiveFieldMetadata {
1656 static FIELD_META: ::std::sync::OnceLock<::appdb::crypto::SensitiveFieldMetadata> = ::std::sync::OnceLock::new();
1657 FIELD_META.get_or_init(|| ::appdb::crypto::SensitiveFieldMetadata {
1658 model_tag: <#struct_ident as ::appdb::crypto::SensitiveModelTag>::model_tag(),
1659 field_tag: #field_tag_literal,
1660 service: #service_literal,
1661 account: #account_literal,
1662 secure_fields: &#struct_ident::SECURE_FIELDS,
1663 })
1664 }
1665 }
1666 });
1667 } else {
1668 let ty = field.ty.clone();
1669 encrypted_fields.push(quote! { #field_vis #ident: #ty });
1670 encrypt_assignments.push(quote! { #ident: self.#ident.clone() });
1671 decrypt_assignments.push(quote! { #ident: encrypted.#ident.clone() });
1672 runtime_encrypt_assignments.push(quote! { #ident: self.#ident.clone() });
1673 runtime_decrypt_assignments.push(quote! { #ident: encrypted.#ident.clone() });
1674 }
1675 }
1676
1677 if secure_field_count == 0 {
1678 return Err(Error::new_spanned(
1679 struct_ident,
1680 "Sensitive requires at least one #[secure] field",
1681 ));
1682 }
1683
1684 Ok(quote! {
1685 #[derive(
1686 Debug,
1687 Clone,
1688 ::serde::Serialize,
1689 ::serde::Deserialize,
1690 ::surrealdb::types::SurrealValue,
1691 )]
1692 #vis struct #encrypted_ident {
1693 #( #encrypted_fields, )*
1694 }
1695
1696 impl ::appdb::crypto::SensitiveModelTag for #struct_ident {
1697 fn model_tag() -> &'static str {
1698 ::std::concat!(::std::module_path!(), "::", ::std::stringify!(#struct_ident))
1699 }
1700 }
1701
1702 #( #field_tag_structs )*
1703
1704 impl ::appdb::Sensitive for #struct_ident {
1705 type Encrypted = #encrypted_ident;
1706
1707 fn encrypt(
1708 &self,
1709 context: &::appdb::crypto::CryptoContext,
1710 ) -> ::std::result::Result<Self::Encrypted, ::appdb::crypto::CryptoError> {
1711 ::std::result::Result::Ok(#encrypted_ident {
1712 #( #encrypt_assignments, )*
1713 })
1714 }
1715
1716 fn decrypt(
1717 encrypted: &Self::Encrypted,
1718 context: &::appdb::crypto::CryptoContext,
1719 ) -> ::std::result::Result<Self, ::appdb::crypto::CryptoError> {
1720 ::std::result::Result::Ok(Self {
1721 #( #decrypt_assignments, )*
1722 })
1723 }
1724
1725 fn encrypt_with_runtime_resolver(
1726 &self,
1727 ) -> ::std::result::Result<Self::Encrypted, ::appdb::crypto::CryptoError> {
1728 ::std::result::Result::Ok(#encrypted_ident {
1729 #( #runtime_encrypt_assignments, )*
1730 })
1731 }
1732
1733 fn decrypt_with_runtime_resolver(
1734 encrypted: &Self::Encrypted,
1735 ) -> ::std::result::Result<Self, ::appdb::crypto::CryptoError> {
1736 ::std::result::Result::Ok(Self {
1737 #( #runtime_decrypt_assignments, )*
1738 })
1739 }
1740
1741 fn secure_fields() -> &'static [::appdb::crypto::SensitiveFieldMetadata] {
1742 &Self::SECURE_FIELDS
1743 }
1744 }
1745
1746 impl #struct_ident {
1747 pub const SECURE_FIELDS: [::appdb::crypto::SensitiveFieldMetadata; #secure_field_count] = [
1748 #( #secure_field_meta_entries, )*
1749 ];
1750
1751 pub fn encrypt(
1752 &self,
1753 context: &::appdb::crypto::CryptoContext,
1754 ) -> ::std::result::Result<#encrypted_ident, ::appdb::crypto::CryptoError> {
1755 <Self as ::appdb::Sensitive>::encrypt(self, context)
1756 }
1757 }
1758
1759 impl #encrypted_ident {
1760 pub fn decrypt(
1761 &self,
1762 context: &::appdb::crypto::CryptoContext,
1763 ) -> ::std::result::Result<#struct_ident, ::appdb::crypto::CryptoError> {
1764 <#struct_ident as ::appdb::Sensitive>::decrypt(self, context)
1765 }
1766 }
1767 })
1768}
1769
1770fn has_secure_attr(attrs: &[Attribute]) -> bool {
1771 attrs.iter().any(|attr| attr.path().is_ident("secure"))
1772}
1773
1774fn has_unique_attr(attrs: &[Attribute]) -> bool {
1775 attrs.iter().any(|attr| attr.path().is_ident("unique"))
1776}
1777
1778#[derive(Default, Clone)]
1779struct TypeCryptoConfig {
1780 service: Option<String>,
1781 account: Option<String>,
1782}
1783
1784#[derive(Default, Clone)]
1785struct FieldCryptoConfig {
1786 field_account: Option<String>,
1787}
1788
1789impl FieldCryptoConfig {
1790 fn is_present(&self) -> bool {
1791 self.field_account.is_some()
1792 }
1793}
1794
1795fn type_crypto_config(attrs: &[Attribute]) -> syn::Result<TypeCryptoConfig> {
1796 let mut config = TypeCryptoConfig::default();
1797 let mut seen = HashSet::new();
1798
1799 for attr in attrs {
1800 if !attr.path().is_ident("crypto") {
1801 continue;
1802 }
1803
1804 attr.parse_nested_meta(|meta| {
1805 let key = meta
1806 .path
1807 .get_ident()
1808 .cloned()
1809 .ok_or_else(|| meta.error("unsupported crypto attribute"))?;
1810
1811 if !seen.insert(key.to_string()) {
1812 return Err(meta.error("duplicate crypto attribute key"));
1813 }
1814
1815 let value = meta.value()?;
1816 let literal: syn::LitStr = value.parse()?;
1817 match key.to_string().as_str() {
1818 "service" => config.service = Some(literal.value()),
1819 "account" => config.account = Some(literal.value()),
1820 _ => {
1821 return Err(
1822 meta.error("unsupported crypto attribute; expected `service` or `account`")
1823 );
1824 }
1825 }
1826 Ok(())
1827 })?;
1828 }
1829
1830 Ok(config)
1831}
1832
1833fn field_crypto_config(attrs: &[Attribute]) -> syn::Result<FieldCryptoConfig> {
1834 let mut config = FieldCryptoConfig::default();
1835 let mut seen = HashSet::new();
1836
1837 for attr in attrs {
1838 if attr.path().is_ident("crypto") {
1839 attr.parse_nested_meta(|meta| {
1840 let key = meta
1841 .path
1842 .get_ident()
1843 .cloned()
1844 .ok_or_else(|| meta.error("unsupported crypto attribute"))?;
1845
1846 if !seen.insert(key.to_string()) {
1847 return Err(meta.error("duplicate crypto attribute key"));
1848 }
1849
1850 let value = meta.value()?;
1851 let literal: syn::LitStr = value.parse()?;
1852 match key.to_string().as_str() {
1853 "field_account" => config.field_account = Some(literal.value()),
1854 _ => {
1855 return Err(meta.error(
1856 "unsupported field crypto attribute; expected `field_account`",
1857 ));
1858 }
1859 }
1860 Ok(())
1861 })?;
1862 } else if attr.path().is_ident("secure") && matches!(attr.meta, Meta::List(_)) {
1863 return Err(Error::new_spanned(
1864 attr,
1865 "#[secure] does not accept arguments; use #[crypto(field_account = \"...\")] on the field",
1866 ));
1867 }
1868 }
1869
1870 Ok(config)
1871}
1872
1873fn table_alias_target(attrs: &[Attribute]) -> syn::Result<Option<Type>> {
1874 let mut target = None;
1875
1876 for attr in attrs {
1877 if !attr.path().is_ident("table_as") {
1878 continue;
1879 }
1880
1881 if target.is_some() {
1882 return Err(Error::new_spanned(
1883 attr,
1884 "duplicate #[table_as(...)] attribute is not supported",
1885 ));
1886 }
1887
1888 let parsed: Type = attr.parse_args().map_err(|_| {
1889 Error::new_spanned(attr, "#[table_as(...)] requires exactly one target type")
1890 })?;
1891
1892 match parsed {
1893 Type::Path(TypePath { ref path, .. }) if !path.segments.is_empty() => {
1894 target = Some(parsed);
1895 }
1896 _ => {
1897 return Err(Error::new_spanned(
1898 parsed,
1899 "#[table_as(...)] target must be a type path",
1900 ));
1901 }
1902 }
1903 }
1904
1905 Ok(target)
1906}
1907
1908fn view_source_target(attrs: &[Attribute]) -> syn::Result<Type> {
1909 let mut target = None;
1910
1911 for attr in attrs {
1912 if !attr.path().is_ident("view") {
1913 continue;
1914 }
1915
1916 if target.is_some() {
1917 return Err(Error::new_spanned(
1918 attr,
1919 "duplicate #[view(...)] attribute is not supported",
1920 ));
1921 }
1922
1923 attr.parse_nested_meta(|meta| {
1924 if meta.path.is_ident("source") {
1925 let value = meta.value()?;
1926 let parsed: Type = value.parse()?;
1927 match parsed {
1928 Type::Path(TypePath { ref path, .. }) if !path.segments.is_empty() => {
1929 target = Some(parsed);
1930 Ok(())
1931 }
1932 _ => Err(Error::new_spanned(
1933 parsed,
1934 "#[view(source = ...)] source must be a type path",
1935 )),
1936 }
1937 } else {
1938 Err(meta.error("unsupported view attribute; expected `source = Type`"))
1939 }
1940 })?;
1941 }
1942
1943 target.ok_or_else(|| {
1944 Error::new(
1945 proc_macro2::Span::call_site(),
1946 "View requires #[view(source = SourceStoreType)]",
1947 )
1948 })
1949}
1950
1951fn field_view_nested_attr(field: &Field) -> syn::Result<bool> {
1952 let mut is_nested = false;
1953
1954 for attr in &field.attrs {
1955 if !attr.path().is_ident("view") {
1956 continue;
1957 }
1958
1959 attr.parse_nested_meta(|meta| {
1960 if meta.path.is_ident("nested") {
1961 if is_nested {
1962 return Err(meta.error("duplicate #[view(nested)] marker is not supported"));
1963 }
1964 is_nested = true;
1965 Ok(())
1966 } else {
1967 Err(meta.error("unsupported view field attribute; expected #[view(nested)]"))
1968 }
1969 })?;
1970 }
1971
1972 Ok(is_nested)
1973}
1974
1975fn resolved_schema_table_name(struct_ident: &syn::Ident, table_alias: Option<&Type>) -> String {
1976 match table_alias {
1977 Some(Type::Path(type_path)) => type_path
1978 .path
1979 .segments
1980 .last()
1981 .map(|segment| to_snake_case(&segment.ident.to_string()))
1982 .unwrap_or_else(|| to_snake_case(&struct_ident.to_string())),
1983 Some(_) => to_snake_case(&struct_ident.to_string()),
1984 None => to_snake_case(&struct_ident.to_string()),
1985 }
1986}
1987
1988fn field_foreign_attr(field: &Field) -> syn::Result<Option<&Attribute>> {
1989 let mut foreign_attr = None;
1990
1991 for attr in &field.attrs {
1992 if !attr.path().is_ident("foreign") {
1993 continue;
1994 }
1995
1996 if foreign_attr.is_some() {
1997 return Err(Error::new_spanned(
1998 attr,
1999 "duplicate nested-ref attribute is not supported",
2000 ));
2001 }
2002
2003 foreign_attr = Some(attr);
2004 }
2005
2006 Ok(foreign_attr)
2007}
2008
2009fn field_relation_attr(field: &Field) -> syn::Result<Option<&Attribute>> {
2010 let mut relate_attr = None;
2011
2012 for attr in &field.attrs {
2013 if !attr.path().is_ident("relate") && !attr.path().is_ident("back_relate") {
2014 continue;
2015 }
2016
2017 if let Some(previous) = relate_attr {
2018 return Err(Error::new_spanned(
2019 attr,
2020 relation_attr_conflict_message(previous, attr),
2021 ));
2022 }
2023
2024 relate_attr = Some(attr);
2025 }
2026
2027 Ok(relate_attr)
2028}
2029
2030fn field_pagin_attr(field: &Field) -> syn::Result<Option<&Attribute>> {
2031 let mut pagin_attr = None;
2032
2033 for attr in &field.attrs {
2034 if !attr.path().is_ident("pagin") {
2035 continue;
2036 }
2037
2038 if pagin_attr.is_some() {
2039 return Err(Error::new_spanned(
2040 attr,
2041 "duplicate #[pagin] attribute is not supported",
2042 ));
2043 }
2044
2045 pagin_attr = Some(attr);
2046 }
2047
2048 Ok(pagin_attr)
2049}
2050
2051fn field_fill_attr(field: &Field) -> syn::Result<Option<&Attribute>> {
2052 let mut fill_attr = None;
2053
2054 for attr in &field.attrs {
2055 if !attr.path().is_ident("fill") {
2056 continue;
2057 }
2058
2059 if fill_attr.is_some() {
2060 return Err(Error::new_spanned(
2061 attr,
2062 "duplicate #[fill(...)] attribute is not supported",
2063 ));
2064 }
2065
2066 fill_attr = Some(attr);
2067 }
2068
2069 Ok(fill_attr)
2070}
2071
2072fn validate_foreign_field(field: &Field, attr: &Attribute) -> syn::Result<Type> {
2073 if attr.path().is_ident("foreign") {
2074 return foreign_leaf_type(&field.ty)
2075 .ok_or_else(|| Error::new_spanned(&field.ty, BINDREF_ACCEPTED_SHAPES));
2076 }
2077
2078 Err(Error::new_spanned(attr, "unsupported foreign attribute"))
2079}
2080
2081const BINDREF_ACCEPTED_SHAPES: &str = "#[foreign] supports recursive Option<_> / Vec<_> shapes whose leaf type implements appdb::Bridge";
2082
2083const BINDREF_BRIDGE_STORE_ONLY: &str =
2084 "#[foreign] leaf types must derive Store or #[derive(Bridge)] dispatcher enums";
2085
2086const RELATE_ACCEPTED_SHAPES: &str = "relation-backed fields support Child / Option<Child> / Vec<Child> / Option<Vec<Child>> shapes whose leaf type implements appdb::Bridge";
2087
2088const PAGIN_ACCEPTED_SHAPES: &str = "#[pagin] supports direct scalar fields plus Id/RecordId; Option<_> and Vec<_> wrappers are not supported";
2089
2090const FILL_ACCEPTED_SHAPES: &str = "#[fill(...)] fields must use appdb::AutoFill";
2091
2092#[derive(Clone, Copy)]
2093enum RelationFieldDirection {
2094 Outgoing,
2095 Incoming,
2096}
2097
2098#[derive(Clone, Copy)]
2099enum FillProvider {
2100 Now,
2101}
2102
2103impl RelationFieldDirection {
2104 fn attr_label(self) -> &'static str {
2105 match self {
2106 Self::Outgoing => "#[relate(...)]",
2107 Self::Incoming => "#[back_relate(...)]",
2108 }
2109 }
2110
2111 fn write_direction_tokens(self) -> proc_macro2::TokenStream {
2112 match self {
2113 Self::Outgoing => quote!(::appdb::RelationWriteDirection::Outgoing),
2114 Self::Incoming => quote!(::appdb::RelationWriteDirection::Incoming),
2115 }
2116 }
2117
2118 fn write_edges_tokens(self) -> proc_macro2::TokenStream {
2119 match self {
2120 Self::Outgoing => quote! {
2121 ids
2122 .into_iter()
2123 .enumerate()
2124 .map(|(position, out)| ::appdb::graph::OrderedRelationEdge {
2125 _in: ::std::option::Option::Some(record.clone()),
2126 out,
2127 position: position as i64,
2128 })
2129 .collect()
2130 },
2131 Self::Incoming => quote! {
2132 ids
2133 .into_iter()
2134 .enumerate()
2135 .map(|(position, source)| ::appdb::graph::OrderedRelationEdge {
2136 _in: ::std::option::Option::Some(source),
2137 out: record.clone(),
2138 position: position as i64,
2139 })
2140 .collect()
2141 },
2142 }
2143 }
2144
2145 fn load_edges_tokens(self, relation_name: &str) -> proc_macro2::TokenStream {
2146 match self {
2147 Self::Outgoing => quote! {
2148 ::appdb::graph::GraphRepo::out_edges(record.clone(), #relation_name)
2149 .await?
2150 .into_iter()
2151 .map(|edge| edge.out)
2152 .collect()
2153 },
2154 Self::Incoming => quote! {
2155 {
2156 let mut ids = ::std::vec::Vec::new();
2157 for edge in ::appdb::graph::GraphRepo::in_edges(record.clone(), #relation_name).await? {
2158 let incoming = edge._in.ok_or_else(|| {
2159 ::anyhow::anyhow!("back relate field received relation edge without `in` record id")
2160 })?;
2161 ids.push(incoming);
2162 }
2163 ids
2164 }
2165 },
2166 }
2167 }
2168}
2169
2170#[derive(Clone)]
2171struct ForeignField {
2172 ident: syn::Ident,
2173 kind: ForeignFieldKind,
2174}
2175
2176#[derive(Clone)]
2177struct ForeignFieldKind {
2178 original_ty: Type,
2179 stored_ty: Type,
2180}
2181
2182#[derive(Clone)]
2183struct RelateField {
2184 ident: syn::Ident,
2185 relation_name: String,
2186 field_ty: Type,
2187 direction: RelationFieldDirection,
2188}
2189
2190#[derive(Clone)]
2191struct PaginField {
2192 ident: syn::Ident,
2193}
2194
2195#[derive(Clone)]
2196struct FillField {
2197 ident: syn::Ident,
2198 provider: FillProvider,
2199}
2200
2201fn parse_foreign_field(field: &Field, attr: &Attribute) -> syn::Result<ForeignField> {
2202 validate_foreign_field(field, attr)?;
2203 let ident = field.ident.clone().expect("named field");
2204
2205 let kind = ForeignFieldKind {
2206 original_ty: field.ty.clone(),
2207 stored_ty: foreign_stored_type(&field.ty)
2208 .ok_or_else(|| Error::new_spanned(&field.ty, BINDREF_ACCEPTED_SHAPES))?,
2209 };
2210
2211 Ok(ForeignField { ident, kind })
2212}
2213
2214fn parse_relate_field(field: &Field, attr: &Attribute) -> syn::Result<RelateField> {
2215 let direction = parse_relation_direction(attr)?;
2216 let relation_name = attr
2217 .parse_args::<syn::LitStr>()
2218 .map_err(|_| {
2219 Error::new_spanned(
2220 attr,
2221 format!(
2222 "{} requires exactly one string literal",
2223 direction.attr_label()
2224 ),
2225 )
2226 })?
2227 .value();
2228 if relation_name.is_empty() {
2229 return Err(Error::new_spanned(
2230 attr,
2231 format!("{} relation name must not be empty", direction.attr_label()),
2232 ));
2233 }
2234
2235 validate_relate_field(field, attr)?;
2236
2237 Ok(RelateField {
2238 ident: field.ident.clone().expect("named field"),
2239 relation_name,
2240 field_ty: field.ty.clone(),
2241 direction,
2242 })
2243}
2244
2245fn parse_pagin_field(field: &Field, attr: &Attribute) -> syn::Result<PaginField> {
2246 validate_pagin_field(field, attr)?;
2247 Ok(PaginField {
2248 ident: field.ident.clone().expect("named field"),
2249 })
2250}
2251
2252fn parse_fill_field(field: &Field, attr: &Attribute) -> syn::Result<FillField> {
2253 let provider = validate_fill_field(field, attr)?;
2254 Ok(FillField {
2255 ident: field.ident.clone().expect("named field"),
2256 provider,
2257 })
2258}
2259
2260fn validate_relate_field(field: &Field, attr: &Attribute) -> syn::Result<Type> {
2261 if !attr.path().is_ident("relate") && !attr.path().is_ident("back_relate") {
2262 return Err(Error::new_spanned(attr, "unsupported relate attribute"));
2263 }
2264
2265 let accepted = relate_leaf_type(&field.ty).cloned().map(Type::Path);
2266
2267 accepted.ok_or_else(|| Error::new_spanned(&field.ty, RELATE_ACCEPTED_SHAPES))
2268}
2269
2270fn validate_pagin_field(field: &Field, attr: &Attribute) -> syn::Result<Type> {
2271 if !attr.path().is_ident("pagin") {
2272 return Err(Error::new_spanned(attr, "unsupported pagination attribute"));
2273 }
2274
2275 if pagination_leaf_type(&field.ty).is_none() {
2276 return Err(Error::new_spanned(&field.ty, PAGIN_ACCEPTED_SHAPES));
2277 }
2278
2279 Ok(field.ty.clone())
2280}
2281
2282fn validate_fill_field(field: &Field, attr: &Attribute) -> syn::Result<FillProvider> {
2283 if !attr.path().is_ident("fill") {
2284 return Err(Error::new_spanned(attr, "unsupported fill attribute"));
2285 }
2286
2287 if !is_autofill_type(&field.ty) {
2288 return Err(Error::new_spanned(&field.ty, FILL_ACCEPTED_SHAPES));
2289 }
2290
2291 parse_fill_provider(attr)
2292}
2293
2294fn parse_fill_provider(attr: &Attribute) -> syn::Result<FillProvider> {
2295 let provider = attr.parse_args::<syn::Path>().map_err(|_| {
2296 Error::new_spanned(
2297 attr,
2298 "#[fill(...)] requires exactly one provider like #[fill(now)]",
2299 )
2300 })?;
2301
2302 if provider.is_ident("now") {
2303 Ok(FillProvider::Now)
2304 } else {
2305 Err(Error::new_spanned(
2306 attr,
2307 "unsupported fill provider; expected #[fill(now)]",
2308 ))
2309 }
2310}
2311
2312fn relate_leaf_type(ty: &Type) -> Option<&TypePath> {
2313 if let Some(leaf) = direct_store_child_type(ty) {
2314 return Some(leaf);
2315 }
2316
2317 if let Some(inner) = option_inner_type(ty) {
2318 if let Some(leaf) = direct_store_child_type(inner) {
2319 return Some(leaf);
2320 }
2321
2322 if let Some(vec_inner) = vec_inner_type(inner) {
2323 return direct_store_child_type(vec_inner);
2324 }
2325
2326 return None;
2327 }
2328
2329 if let Some(inner) = vec_inner_type(ty) {
2330 return direct_store_child_type(inner);
2331 }
2332
2333 None
2334}
2335
2336fn pagination_leaf_type(ty: &Type) -> Option<Type> {
2337 if option_inner_type(ty).is_some() || vec_inner_type(ty).is_some() {
2338 return None;
2339 }
2340
2341 if is_id_type(ty)
2342 || is_record_id_type(ty)
2343 || is_autofill_type(ty)
2344 || is_string_type(ty)
2345 || is_common_non_store_leaf_type(ty)
2346 {
2347 return Some(ty.clone());
2348 }
2349
2350 None
2351}
2352
2353fn parse_relation_direction(attr: &Attribute) -> syn::Result<RelationFieldDirection> {
2354 if attr.path().is_ident("relate") {
2355 return Ok(RelationFieldDirection::Outgoing);
2356 }
2357 if attr.path().is_ident("back_relate") {
2358 return Ok(RelationFieldDirection::Incoming);
2359 }
2360
2361 Err(Error::new_spanned(attr, "unsupported relate attribute"))
2362}
2363
2364fn relation_attr_label(attr: &Attribute) -> &'static str {
2365 if attr.path().is_ident("back_relate") {
2366 "#[back_relate(...)]"
2367 } else {
2368 "#[relate(...)]"
2369 }
2370}
2371
2372fn relation_attr_conflict_message(previous: &Attribute, current: &Attribute) -> String {
2373 match (
2374 previous.path().is_ident("relate"),
2375 previous.path().is_ident("back_relate"),
2376 current.path().is_ident("relate"),
2377 current.path().is_ident("back_relate"),
2378 ) {
2379 (true, false, true, false) => {
2380 "duplicate #[relate(...)] attribute is not supported".to_owned()
2381 }
2382 (false, true, false, true) => {
2383 "duplicate #[back_relate(...)] attribute is not supported".to_owned()
2384 }
2385 _ => "#[relate(...)] cannot be combined with #[back_relate(...)]".to_owned(),
2386 }
2387}
2388
2389fn foreign_field_kind<'a>(
2390 ident: &syn::Ident,
2391 fields: &'a [ForeignField],
2392) -> Option<&'a ForeignFieldKind> {
2393 fields
2394 .iter()
2395 .find(|field| field.ident == *ident)
2396 .map(|field| &field.kind)
2397}
2398
2399fn stored_field_type(field: &Field, foreign_fields: &[ForeignField]) -> Type {
2400 let ident = field.ident.as_ref().expect("named field");
2401 match foreign_field_kind(ident, foreign_fields) {
2402 Some(ForeignFieldKind { stored_ty, .. }) => stored_ty.clone(),
2403 None => field.ty.clone(),
2404 }
2405}
2406
2407fn view_stored_type(field: &Field) -> syn::Result<Type> {
2408 if field_view_nested_attr(field)? {
2409 view_record_shape_type(&field.ty).ok_or_else(|| {
2410 Error::new_spanned(
2411 &field.ty,
2412 "#[view(nested)] fields must be a View type, Option<View>, Vec<View>, or nested Option/Vec wrappers",
2413 )
2414 })
2415 } else {
2416 Ok(field.ty.clone())
2417 }
2418}
2419
2420fn view_record_shape_type(ty: &Type) -> Option<Type> {
2421 if let Some(inner) = option_inner_type(ty) {
2422 let inner = view_record_shape_type(inner)?;
2423 return Some(syn::parse_quote!(::std::option::Option<#inner>));
2424 }
2425
2426 if let Some(inner) = vec_inner_type(ty) {
2427 let inner = view_record_shape_type(inner)?;
2428 return Some(syn::parse_quote!(::std::vec::Vec<#inner>));
2429 }
2430
2431 view_leaf_type(ty).map(|_| syn::parse_quote!(::surrealdb::types::RecordId))
2432}
2433
2434fn view_leaf_type(ty: &Type) -> Option<&TypePath> {
2435 let Type::Path(type_path) = ty else {
2436 return None;
2437 };
2438
2439 let segment = type_path.path.segments.last()?;
2440 if !matches!(segment.arguments, PathArguments::None) {
2441 return None;
2442 }
2443
2444 if is_id_type(ty)
2445 || is_record_id_type(ty)
2446 || is_autofill_type(ty)
2447 || is_string_type(ty)
2448 || is_common_non_store_leaf_type(ty)
2449 {
2450 return None;
2451 }
2452
2453 Some(type_path)
2454}
2455
2456fn foreign_stored_type(ty: &Type) -> Option<Type> {
2457 if let Some(inner) = option_inner_type(ty) {
2458 let inner = foreign_stored_type(inner)?;
2459 return Some(syn::parse_quote!(::std::option::Option<#inner>));
2460 }
2461
2462 if let Some(inner) = vec_inner_type(ty) {
2463 let inner = foreign_stored_type(inner)?;
2464 return Some(syn::parse_quote!(::std::vec::Vec<#inner>));
2465 }
2466
2467 direct_store_child_type(ty)
2468 .cloned()
2469 .map(|_| syn::parse_quote!(::surrealdb::types::RecordId))
2470}
2471
2472fn foreign_leaf_type(ty: &Type) -> Option<Type> {
2473 if let Some(inner) = option_inner_type(ty) {
2474 return foreign_leaf_type(inner);
2475 }
2476
2477 if let Some(inner) = vec_inner_type(ty) {
2478 return foreign_leaf_type(inner);
2479 }
2480
2481 direct_store_child_type(ty).cloned().map(Type::Path)
2482}
2483
2484fn invalid_foreign_leaf_type(ty: &Type) -> Option<Type> {
2485 let leaf = foreign_leaf_type(ty)?;
2486 match &leaf {
2487 Type::Path(type_path) => {
2488 let segment = type_path.path.segments.last()?;
2489 if matches!(segment.arguments, PathArguments::None) {
2490 None
2491 } else {
2492 Some(leaf)
2493 }
2494 }
2495 _ => Some(leaf),
2496 }
2497}
2498
2499fn direct_store_child_type(ty: &Type) -> Option<&TypePath> {
2500 let Type::Path(type_path) = ty else {
2501 return None;
2502 };
2503
2504 let segment = type_path.path.segments.last()?;
2505 if !matches!(segment.arguments, PathArguments::None) {
2506 return None;
2507 }
2508
2509 if is_id_type(ty)
2510 || is_autofill_type(ty)
2511 || is_string_type(ty)
2512 || is_common_non_store_leaf_type(ty)
2513 {
2514 return None;
2515 }
2516
2517 Some(type_path)
2518}
2519
2520fn is_common_non_store_leaf_type(ty: &Type) -> bool {
2521 matches!(
2522 ty,
2523 Type::Path(TypePath { path, .. })
2524 if path.is_ident("bool")
2525 || path.is_ident("u8")
2526 || path.is_ident("u16")
2527 || path.is_ident("u32")
2528 || path.is_ident("u64")
2529 || path.is_ident("u128")
2530 || path.is_ident("usize")
2531 || path.is_ident("i8")
2532 || path.is_ident("i16")
2533 || path.is_ident("i32")
2534 || path.is_ident("i64")
2535 || path.is_ident("i128")
2536 || path.is_ident("isize")
2537 || path.is_ident("f32")
2538 || path.is_ident("f64")
2539 || path.is_ident("char")
2540 )
2541}
2542
2543fn secure_field_count(fields: &syn::punctuated::Punctuated<Field, syn::token::Comma>) -> usize {
2544 fields
2545 .iter()
2546 .filter(|field| has_secure_attr(&field.attrs))
2547 .count()
2548}
2549
2550fn relation_name_override(attrs: &[Attribute]) -> syn::Result<Option<String>> {
2551 for attr in attrs {
2552 if !attr.path().is_ident("relation") {
2553 continue;
2554 }
2555
2556 let mut name = None;
2557 attr.parse_nested_meta(|meta| {
2558 if meta.path.is_ident("name") {
2559 let value = meta.value()?;
2560 let literal: syn::LitStr = value.parse()?;
2561 name = Some(literal.value());
2562 Ok(())
2563 } else {
2564 Err(meta.error("unsupported relation attribute"))
2565 }
2566 })?;
2567 return Ok(name);
2568 }
2569
2570 Ok(None)
2571}
2572
2573enum SecureKind {
2574 Shape(Type),
2575}
2576
2577impl SecureKind {
2578 fn encrypted_type(&self) -> proc_macro2::TokenStream {
2579 match self {
2580 SecureKind::Shape(ty) => quote! { <#ty as ::appdb::SensitiveShape>::Encrypted },
2581 }
2582 }
2583
2584 fn encrypt_with_context_expr(&self, ident: &syn::Ident) -> proc_macro2::TokenStream {
2585 match self {
2586 SecureKind::Shape(ty) => {
2587 quote! { <#ty as ::appdb::SensitiveShape>::encrypt_with_context(&self.#ident, context)? }
2588 }
2589 }
2590 }
2591
2592 fn decrypt_with_context_expr(&self, ident: &syn::Ident) -> proc_macro2::TokenStream {
2593 match self {
2594 SecureKind::Shape(ty) => {
2595 quote! { <#ty as ::appdb::SensitiveShape>::decrypt_with_context(&encrypted.#ident, context)? }
2596 }
2597 }
2598 }
2599
2600 fn encrypt_with_runtime_expr(
2601 &self,
2602 ident: &syn::Ident,
2603 field_tag_ident: &syn::Ident,
2604 ) -> proc_macro2::TokenStream {
2605 match self {
2606 SecureKind::Shape(ty) => {
2607 quote! {{
2608 let context = ::appdb::crypto::resolve_crypto_context_for::<#field_tag_ident>()?;
2609 <#ty as ::appdb::SensitiveShape>::encrypt_with_context(&self.#ident, context.as_ref())?
2610 }}
2611 }
2612 }
2613 }
2614
2615 fn decrypt_with_runtime_expr(
2616 &self,
2617 ident: &syn::Ident,
2618 field_tag_ident: &syn::Ident,
2619 ) -> proc_macro2::TokenStream {
2620 match self {
2621 SecureKind::Shape(ty) => {
2622 quote! {{
2623 let context = ::appdb::crypto::resolve_crypto_context_for::<#field_tag_ident>()?;
2624 <#ty as ::appdb::SensitiveShape>::decrypt_with_context(&encrypted.#ident, context.as_ref())?
2625 }}
2626 }
2627 }
2628 }
2629}
2630
2631fn secure_kind(field: &Field) -> syn::Result<SecureKind> {
2632 if secure_shape_supported(&field.ty) {
2633 return Ok(SecureKind::Shape(field.ty.clone()));
2634 }
2635
2636 Err(Error::new_spanned(
2637 &field.ty,
2638 secure_shape_error_message(&field.ty),
2639 ))
2640}
2641
2642fn secure_shape_supported(ty: &Type) -> bool {
2643 if is_string_type(ty) {
2644 return true;
2645 }
2646
2647 if sensitive_value_wrapper_inner_type(ty).is_some() {
2648 return true;
2649 }
2650
2651 if let Some(inner) = option_inner_type(ty) {
2652 return secure_shape_supported(inner);
2653 }
2654
2655 if let Some(inner) = vec_inner_type(ty) {
2656 return secure_shape_supported(inner);
2657 }
2658
2659 direct_sensitive_child_type(ty).is_some()
2660}
2661
2662fn secure_shape_error_message(ty: &Type) -> &'static str {
2663 if invalid_secure_leaf_type(ty).is_some() {
2664 "#[secure] child shapes require a direct named Sensitive type leaf with only Option<_> and Vec<_> wrappers"
2665 } else {
2666 "#[secure] supports String, appdb::SensitiveValueOf<T>, and recursive Child / Option<Child> / Vec<Child> shapes where Child implements appdb::Sensitive"
2667 }
2668}
2669
2670fn direct_sensitive_child_type(ty: &Type) -> Option<&TypePath> {
2671 let Type::Path(type_path) = ty else {
2672 return None;
2673 };
2674
2675 let segment = type_path.path.segments.last()?;
2676 if !matches!(segment.arguments, PathArguments::None) {
2677 return None;
2678 }
2679
2680 if is_id_type(ty) || is_string_type(ty) || is_common_non_store_leaf_type(ty) {
2681 return None;
2682 }
2683
2684 Some(type_path)
2685}
2686
2687fn invalid_secure_leaf_type(ty: &Type) -> Option<Type> {
2688 if let Some(inner) = option_inner_type(ty) {
2689 return invalid_secure_leaf_type(inner);
2690 }
2691
2692 if let Some(inner) = vec_inner_type(ty) {
2693 return invalid_secure_leaf_type(inner);
2694 }
2695
2696 let leaf = direct_sensitive_child_type(ty)?.clone();
2697 let segment = leaf.path.segments.last()?;
2698 if matches!(segment.arguments, PathArguments::None) {
2699 None
2700 } else {
2701 Some(Type::Path(leaf))
2702 }
2703}
2704
2705fn is_string_type(ty: &Type) -> bool {
2706 match ty {
2707 Type::Path(TypePath { path, .. }) => path.is_ident("String"),
2708 _ => false,
2709 }
2710}
2711
2712fn is_id_type(ty: &Type) -> bool {
2713 match ty {
2714 Type::Path(TypePath { path, .. }) => path.segments.last().is_some_and(|segment| {
2715 let ident = segment.ident.to_string();
2716 ident == "Id"
2717 }),
2718 _ => false,
2719 }
2720}
2721
2722fn is_autofill_type(ty: &Type) -> bool {
2723 match ty {
2724 Type::Path(TypePath { path, .. }) => path
2725 .segments
2726 .last()
2727 .is_some_and(|segment| segment.ident == "AutoFill"),
2728 _ => false,
2729 }
2730}
2731
2732fn is_record_id_type(ty: &Type) -> bool {
2733 match ty {
2734 Type::Path(TypePath { path, .. }) => path.segments.last().is_some_and(|segment| {
2735 let ident = segment.ident.to_string();
2736 ident == "RecordId"
2737 }),
2738 _ => false,
2739 }
2740}
2741
2742fn option_inner_type(ty: &Type) -> Option<&Type> {
2743 let Type::Path(TypePath { path, .. }) = ty else {
2744 return None;
2745 };
2746 let segment = path.segments.last()?;
2747 if segment.ident != "Option" {
2748 return None;
2749 }
2750 let PathArguments::AngleBracketed(args) = &segment.arguments else {
2751 return None;
2752 };
2753 let GenericArgument::Type(inner) = args.args.first()? else {
2754 return None;
2755 };
2756 Some(inner)
2757}
2758
2759fn vec_inner_type(ty: &Type) -> Option<&Type> {
2760 let Type::Path(TypePath { path, .. }) = ty else {
2761 return None;
2762 };
2763 let segment = path.segments.last()?;
2764 if segment.ident != "Vec" {
2765 return None;
2766 }
2767 let PathArguments::AngleBracketed(args) = &segment.arguments else {
2768 return None;
2769 };
2770 let GenericArgument::Type(inner) = args.args.first()? else {
2771 return None;
2772 };
2773 Some(inner)
2774}
2775
2776fn sensitive_value_wrapper_inner_type(ty: &Type) -> Option<&Type> {
2777 let Type::Path(TypePath { path, .. }) = ty else {
2778 return None;
2779 };
2780 let segment = path.segments.last()?;
2781 if segment.ident != "SensitiveValueOf" {
2782 return None;
2783 }
2784 let PathArguments::AngleBracketed(args) = &segment.arguments else {
2785 return None;
2786 };
2787 let GenericArgument::Type(inner) = args.args.first()? else {
2788 return None;
2789 };
2790 Some(inner)
2791}
2792
2793fn to_snake_case(input: &str) -> String {
2794 let mut out = String::with_capacity(input.len() + 4);
2795 let mut prev_is_lower_or_digit = false;
2796
2797 for ch in input.chars() {
2798 if ch.is_ascii_uppercase() {
2799 if prev_is_lower_or_digit {
2800 out.push('_');
2801 }
2802 out.push(ch.to_ascii_lowercase());
2803 prev_is_lower_or_digit = false;
2804 } else {
2805 out.push(ch);
2806 prev_is_lower_or_digit = ch.is_ascii_lowercase() || ch.is_ascii_digit();
2807 }
2808 }
2809
2810 out
2811}
2812
2813fn to_pascal_case(input: &str) -> String {
2814 let mut out = String::with_capacity(input.len());
2815 let mut uppercase_next = true;
2816
2817 for ch in input.chars() {
2818 if ch == '_' || ch == '-' {
2819 uppercase_next = true;
2820 continue;
2821 }
2822
2823 if uppercase_next {
2824 out.push(ch.to_ascii_uppercase());
2825 uppercase_next = false;
2826 } else {
2827 out.push(ch);
2828 }
2829 }
2830
2831 out
2832}