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(unique, secure, foreign, table_as, crypto, relate, back_relate, pagin)
20)]
21pub fn derive_store(input: TokenStream) -> TokenStream {
22 match derive_store_impl(parse_macro_input!(input as DeriveInput)) {
23 Ok(tokens) => tokens.into(),
24 Err(err) => err.to_compile_error().into(),
25 }
26}
27
28#[proc_macro_derive(Relation, attributes(relation))]
29pub fn derive_relation(input: TokenStream) -> TokenStream {
30 match derive_relation_impl(parse_macro_input!(input as DeriveInput)) {
31 Ok(tokens) => tokens.into(),
32 Err(err) => err.to_compile_error().into(),
33 }
34}
35
36#[proc_macro_derive(Bridge)]
37pub fn derive_bridge(input: TokenStream) -> TokenStream {
38 match derive_bridge_impl(parse_macro_input!(input as DeriveInput)) {
39 Ok(tokens) => tokens.into(),
40 Err(err) => err.to_compile_error().into(),
41 }
42}
43
44fn derive_store_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
45 let struct_ident = input.ident;
46 let vis = input.vis.clone();
47 let table_alias = table_alias_target(&input.attrs)?;
48
49 let named_fields = match input.data {
50 Data::Struct(data) => match data.fields {
51 Fields::Named(fields) => fields.named,
52 _ => {
53 return Err(Error::new_spanned(
54 struct_ident,
55 "Store can only be derived for structs with named fields",
56 ));
57 }
58 },
59 _ => {
60 return Err(Error::new_spanned(
61 struct_ident,
62 "Store can only be derived for structs",
63 ));
64 }
65 };
66
67 let id_fields = named_fields
68 .iter()
69 .filter(|field| is_id_type(&field.ty))
70 .map(|field| field.ident.clone().expect("named field"))
71 .collect::<Vec<_>>();
72
73 let secure_fields = named_fields
74 .iter()
75 .filter(|field| has_secure_attr(&field.attrs))
76 .map(|field| field.ident.clone().expect("named field"))
77 .collect::<Vec<_>>();
78
79 let unique_fields = named_fields
80 .iter()
81 .filter(|field| has_unique_attr(&field.attrs))
82 .map(|field| field.ident.clone().expect("named field"))
83 .collect::<Vec<_>>();
84
85 let pagin_fields = named_fields
86 .iter()
87 .filter_map(|field| match field_pagin_attr(field) {
88 Ok(Some(attr)) => Some(parse_pagin_field(field, attr)),
89 Ok(None) => None,
90 Err(err) => Some(Err(err)),
91 })
92 .collect::<syn::Result<Vec<_>>>()?;
93 if pagin_fields.len() > 1 {
94 return Err(Error::new_spanned(
95 &pagin_fields[1].ident,
96 "Store supports at most one #[pagin] field",
97 ));
98 }
99 let pagin_field = pagin_fields.first().cloned();
100
101 if id_fields.len() > 1 {
102 return Err(Error::new_spanned(
103 struct_ident,
104 "Store supports at most one `Id` field for automatic HasId generation",
105 ));
106 }
107
108 if let Some(invalid_field) = named_fields
109 .iter()
110 .find(|field| has_secure_attr(&field.attrs) && has_unique_attr(&field.attrs))
111 {
112 let ident = invalid_field.ident.as_ref().expect("named field");
113 return Err(Error::new_spanned(
114 ident,
115 "#[secure] fields cannot be used as #[unique] lookup keys",
116 ));
117 }
118
119 let foreign_fields = named_fields
120 .iter()
121 .filter_map(|field| match field_foreign_attr(field) {
122 Ok(Some(attr)) => Some(parse_foreign_field(field, attr)),
123 Ok(None) => None,
124 Err(err) => Some(Err(err)),
125 })
126 .collect::<syn::Result<Vec<_>>>()?;
127
128 let relate_fields = named_fields
129 .iter()
130 .filter_map(|field| match field_relation_attr(field) {
131 Ok(Some(attr)) => Some(parse_relate_field(field, attr)),
132 Ok(None) => None,
133 Err(err) => Some(Err(err)),
134 })
135 .collect::<syn::Result<Vec<_>>>()?;
136
137 if let Some(non_store_child) = foreign_fields
138 .iter()
139 .find_map(|field| invalid_foreign_leaf_type(&field.kind.original_ty))
140 {
141 return Err(Error::new_spanned(
142 non_store_child,
143 BINDREF_BRIDGE_STORE_ONLY,
144 ));
145 }
146
147 if let Some(invalid_field) = named_fields.iter().find(|field| {
148 field_foreign_attr(field).ok().flatten().is_some() && has_unique_attr(&field.attrs)
149 }) {
150 let ident = invalid_field.ident.as_ref().expect("named field");
151 return Err(Error::new_spanned(
152 ident,
153 "#[foreign] fields cannot be used as #[unique] lookup keys",
154 ));
155 }
156
157 if let Some(invalid_field) = named_fields.iter().find_map(|field| {
158 field_relation_attr(field)
159 .ok()
160 .flatten()
161 .filter(|_| has_unique_attr(&field.attrs))
162 .map(|attr| (field, attr))
163 }) {
164 let ident = invalid_field.0.ident.as_ref().expect("named field");
165 return Err(Error::new_spanned(
166 ident,
167 format!(
168 "{} fields cannot be used as #[unique] lookup keys",
169 relation_attr_label(invalid_field.1)
170 ),
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_secure_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 marked #[secure]",
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(|_| field_foreign_attr(field).ok().flatten().is_some())
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 "{} cannot be combined with #[foreign]",
203 relation_attr_label(invalid_field.1)
204 ),
205 ));
206 }
207
208 if let Some(invalid_field) = named_fields.iter().find(|field| {
209 field_pagin_attr(field).ok().flatten().is_some() && has_secure_attr(&field.attrs)
210 }) {
211 let ident = invalid_field.ident.as_ref().expect("named field");
212 return Err(Error::new_spanned(
213 ident,
214 "#[pagin] fields cannot be marked #[secure]",
215 ));
216 }
217
218 if let Some(invalid_field) = named_fields.iter().find(|field| {
219 field_pagin_attr(field).ok().flatten().is_some()
220 && field_foreign_attr(field).ok().flatten().is_some()
221 }) {
222 let ident = invalid_field.ident.as_ref().expect("named field");
223 return Err(Error::new_spanned(
224 ident,
225 "#[pagin] cannot be combined with #[foreign]",
226 ));
227 }
228
229 if let Some(invalid_field) = named_fields.iter().find_map(|field| {
230 field_pagin_attr(field)
231 .ok()
232 .flatten()
233 .and_then(|_| field_relation_attr(field).ok().flatten())
234 .map(|attr| (field, attr))
235 }) {
236 let ident = invalid_field.0.ident.as_ref().expect("named field");
237 return Err(Error::new_spanned(
238 ident,
239 format!(
240 "#[pagin] cannot be combined with {}",
241 relation_attr_label(invalid_field.1)
242 ),
243 ));
244 }
245
246 let mut seen_relation_names = HashSet::new();
247 for field in &relate_fields {
248 if !seen_relation_names.insert(field.relation_name.clone()) {
249 return Err(Error::new_spanned(
250 &field.ident,
251 format!(
252 "duplicate {} relation name is not supported within one Store model",
253 field.direction.attr_label()
254 ),
255 ));
256 }
257 }
258
259 let auto_has_id_impl = id_fields.first().map(|field| {
260 quote! {
261 impl ::appdb::model::meta::HasId for #struct_ident {
262 fn id(&self) -> ::surrealdb::types::RecordId {
263 ::surrealdb::types::RecordId::new(
264 <Self as ::appdb::model::meta::ModelMeta>::storage_table(),
265 self.#field.clone(),
266 )
267 }
268 }
269 }
270 });
271
272 let resolve_record_id_impl = if let Some(field) = id_fields.first() {
273 quote! {
274 #[::async_trait::async_trait]
275 impl ::appdb::model::meta::ResolveRecordId for #struct_ident {
276 async fn resolve_record_id(&self) -> ::anyhow::Result<::surrealdb::types::RecordId> {
277 Ok(::surrealdb::types::RecordId::new(
278 <Self as ::appdb::model::meta::ModelMeta>::storage_table(),
279 self.#field.clone(),
280 ))
281 }
282 }
283 }
284 } else {
285 quote! {
286 #[::async_trait::async_trait]
287 impl ::appdb::model::meta::ResolveRecordId for #struct_ident {
288 async fn resolve_record_id(&self) -> ::anyhow::Result<::surrealdb::types::RecordId> {
289 ::appdb::repository::Repo::<Self>::find_unique_id_for(self).await
290 }
291 }
292 }
293 };
294
295 let resolved_table_name_expr = if let Some(target_ty) = &table_alias {
296 quote! { <#target_ty as ::appdb::model::meta::ModelMeta>::table_name() }
297 } else {
298 quote! {
299 {
300 let table = ::appdb::model::meta::default_table_name(stringify!(#struct_ident));
301 ::appdb::model::meta::register_table(stringify!(#struct_ident), table)
302 }
303 }
304 };
305
306 let unique_schema_impls = unique_fields.iter().map(|field| {
307 let field_name = field.to_string();
308 let index_name = format!(
309 "{}_{}_unique",
310 resolved_schema_table_name(&struct_ident, table_alias.as_ref()),
311 field_name
312 );
313 let ddl = format!(
314 "DEFINE INDEX IF NOT EXISTS {index_name} ON {} FIELDS {field_name} UNIQUE;",
315 resolved_schema_table_name(&struct_ident, table_alias.as_ref())
316 );
317
318 quote! {
319 ::inventory::submit! {
320 ::appdb::model::schema::SchemaItem {
321 ddl: #ddl,
322 }
323 }
324 }
325 });
326
327 let pagin_schema_impl = pagin_field.iter().map(|field| {
328 let field_name = field.ident.to_string();
329 let index_name = format!(
330 "{}_{}_id_pagin",
331 resolved_schema_table_name(&struct_ident, table_alias.as_ref()),
332 field_name
333 );
334 let ddl = if field_name == "id" {
335 format!(
336 "DEFINE INDEX IF NOT EXISTS {index_name} ON {} FIELDS id;",
337 resolved_schema_table_name(&struct_ident, table_alias.as_ref())
338 )
339 } else {
340 format!(
341 "DEFINE INDEX IF NOT EXISTS {index_name} ON {} FIELDS {field_name},id;",
342 resolved_schema_table_name(&struct_ident, table_alias.as_ref())
343 )
344 };
345
346 quote! {
347 ::inventory::submit! {
348 ::appdb::model::schema::SchemaItem {
349 ddl: #ddl,
350 }
351 }
352 }
353 });
354
355 let lookup_fields = if unique_fields.is_empty() {
356 named_fields
357 .iter()
358 .filter_map(|field| {
359 let ident = field.ident.as_ref()?;
360 if ident == "id"
361 || secure_fields.iter().any(|secure| secure == ident)
362 || foreign_fields.iter().any(|foreign| foreign.ident == *ident)
363 || relate_fields.iter().any(|relate| relate.ident == *ident)
364 {
365 None
366 } else {
367 Some(ident.to_string())
368 }
369 })
370 .collect::<Vec<_>>()
371 } else {
372 unique_fields
373 .iter()
374 .map(|field| field.to_string())
375 .collect::<Vec<_>>()
376 };
377
378 let foreign_field_literals = foreign_fields
379 .iter()
380 .map(|field| field.ident.to_string())
381 .map(|field| quote! { #field })
382 .collect::<Vec<_>>();
383 let relate_field_literals = relate_fields
384 .iter()
385 .map(|field| field.ident.to_string())
386 .map(|field| quote! { #field })
387 .collect::<Vec<_>>();
388 if id_fields.is_empty() && lookup_fields.is_empty() {
389 return Err(Error::new_spanned(
390 struct_ident,
391 "Store requires an `Id` field or at least one non-secure lookup field for automatic record resolution",
392 ));
393 }
394 let lookup_field_literals = lookup_fields.iter().map(|field| quote! { #field });
395
396 let pagination_meta_impl = if let Some(field) = &pagin_field {
397 let field_name = field.ident.to_string();
398 quote! {
399 impl ::appdb::model::meta::PaginationMeta for #struct_ident {
400 fn pagination_field() -> ::std::option::Option<&'static str> {
401 ::std::option::Option::Some(#field_name)
402 }
403 }
404 }
405 } else {
406 quote! {
407 impl ::appdb::model::meta::PaginationMeta for #struct_ident {}
408 }
409 };
410
411 let pagination_methods_impl = if pagin_field.is_some() {
412 quote! {
413 pub async fn pagin_desc(
414 count: i64,
415 cursor: ::std::option::Option<::appdb::PageCursor>,
416 ) -> ::anyhow::Result<::appdb::Page<Self>> {
417 ::appdb::repository::Repo::<Self>::pagin_desc(count, cursor).await
418 }
419
420 pub async fn pagin_asc(
421 count: i64,
422 cursor: ::std::option::Option<::appdb::PageCursor>,
423 ) -> ::anyhow::Result<::appdb::Page<Self>> {
424 ::appdb::repository::Repo::<Self>::pagin_asc(count, cursor).await
425 }
426 }
427 } else {
428 quote! {}
429 };
430
431 let stored_model_impl = if !foreign_fields.is_empty() {
432 quote! {}
433 } else if secure_field_count(&named_fields) > 0 {
434 quote! {
435 impl ::appdb::StoredModel for #struct_ident {
436 type Stored = <Self as ::appdb::Sensitive>::Encrypted;
437
438 fn into_stored(self) -> ::anyhow::Result<Self::Stored> {
439 <Self as ::appdb::Sensitive>::encrypt_with_runtime_resolver(&self)
440 .map_err(::anyhow::Error::from)
441 }
442
443 fn from_stored(stored: Self::Stored) -> ::anyhow::Result<Self> {
444 <Self as ::appdb::Sensitive>::decrypt_with_runtime_resolver(&stored)
445 .map_err(::anyhow::Error::from)
446 }
447
448 fn supports_create_return_id() -> bool {
449 false
450 }
451 }
452 }
453 } else {
454 quote! {
455 impl ::appdb::StoredModel for #struct_ident {
456 type Stored = Self;
457
458 fn into_stored(self) -> ::anyhow::Result<Self::Stored> {
459 ::std::result::Result::Ok(self)
460 }
461
462 fn from_stored(stored: Self::Stored) -> ::anyhow::Result<Self> {
463 ::std::result::Result::Ok(stored)
464 }
465 }
466 }
467 };
468
469 let stored_fields = named_fields.iter().map(|field| {
470 let ident = field.ident.clone().expect("named field");
471 let ty = stored_field_type(field, &foreign_fields);
472 if is_record_id_type(&ty) {
473 quote! {
474 #[serde(deserialize_with = "::appdb::serde_utils::id::deserialize_record_id_or_compat_string")]
475 #ident: #ty
476 }
477 } else {
478 quote! { #ident: #ty }
479 }
480 });
481
482 let into_stored_assignments = named_fields.iter().map(|field| {
483 let ident = field.ident.clone().expect("named field");
484 match foreign_field_kind(&ident, &foreign_fields) {
485 Some(ForeignFieldKind { original_ty, .. }) => quote! {
486 #ident: <#original_ty as ::appdb::ForeignShape>::persist_foreign_shape(value.#ident).await?
487 },
488 None => quote! { #ident: value.#ident },
489 }
490 });
491
492 let from_stored_assignments = named_fields.iter().map(|field| {
493 let ident = field.ident.clone().expect("named field");
494 match foreign_field_kind(&ident, &foreign_fields) {
495 Some(ForeignFieldKind { original_ty, .. }) => quote! {
496 #ident: <#original_ty as ::appdb::ForeignShape>::hydrate_foreign_shape(stored.#ident).await?
497 },
498 None => quote! { #ident: stored.#ident },
499 }
500 });
501
502 let decode_foreign_fields = foreign_fields.iter().map(|field| {
503 let ident = field.ident.to_string();
504 quote! {
505 if let ::std::option::Option::Some(value) = map.get_mut(#ident) {
506 ::appdb::decode_stored_record_links(value);
507 }
508 }
509 });
510
511 let relation_methods_impl = if relate_fields.is_empty() {
512 quote! {}
513 } else {
514 let strip_relation_fields = relate_fields.iter().map(|field| {
515 let ident = field.ident.to_string();
516 quote! {
517 map.remove(#ident);
518 }
519 });
520
521 let inject_relation_values_from_model = relate_fields.iter().map(|field| {
522 let ident = &field.ident;
523 let name = ident.to_string();
524 quote! {
525 map.insert(#name.to_owned(), ::serde_json::to_value(&self.#ident)?);
526 }
527 });
528
529 let prepare_relation_writes = relate_fields.iter().map(|field| {
530 let ident = &field.ident;
531 let relation_name = &field.relation_name;
532 let field_ty = &field.field_ty;
533 let write_direction = field.direction.write_direction_tokens();
534 let write_edges = field.direction.write_edges_tokens();
535 quote! {
536 {
537 let ids = <#field_ty as ::appdb::RelateShape>::persist_relate_shape(self.#ident.clone()).await?;
538 writes.push(::appdb::RelationWrite {
539 relation: #relation_name,
540 record: record.clone(),
541 direction: #write_direction,
542 edges: #write_edges,
543 });
544 }
545 }
546 });
547
548 let inject_relation_values_from_db = relate_fields.iter().map(|field| {
549 let relation_name = &field.relation_name;
550 let field_ty = &field.field_ty;
551 let ident = field.ident.to_string();
552 let load_relation_ids = field.direction.load_edges_tokens(relation_name);
553 quote! {
554 {
555 let value = <#field_ty as ::appdb::RelateShape>::hydrate_relate_shape(#load_relation_ids).await?;
556 map.insert(#ident.to_owned(), ::serde_json::to_value(value)?);
557 }
558 }
559 });
560
561 quote! {
562 fn has_relation_fields() -> bool {
563 true
564 }
565
566 fn relation_field_names() -> &'static [&'static str] {
567 &[ #( #relate_field_literals ),* ]
568 }
569
570 fn strip_relation_fields(row: &mut ::serde_json::Value) {
571 if let ::serde_json::Value::Object(map) = row {
572 #( #strip_relation_fields )*
573 }
574 }
575
576 fn inject_relation_values_from_model(
577 &self,
578 row: &mut ::serde_json::Value,
579 ) -> ::anyhow::Result<()> {
580 if let ::serde_json::Value::Object(map) = row {
581 #( #inject_relation_values_from_model )*
582 }
583 Ok(())
584 }
585
586 fn prepare_relation_writes(
587 &self,
588 record: ::surrealdb::types::RecordId,
589 ) -> impl ::std::future::Future<Output = ::anyhow::Result<::std::vec::Vec<::appdb::RelationWrite>>> + Send {
590 async move {
591 let mut writes = ::std::vec::Vec::new();
592 #( #prepare_relation_writes )*
593 Ok(writes)
594 }
595 }
596
597 fn inject_relation_values_from_db(
598 record: ::surrealdb::types::RecordId,
599 row: &mut ::serde_json::Value,
600 ) -> impl ::std::future::Future<Output = ::anyhow::Result<()>> + Send {
601 async move {
602 if let ::serde_json::Value::Object(map) = row {
603 #( #inject_relation_values_from_db )*
604 }
605 Ok(())
606 }
607 }
608 }
609 };
610
611 let foreign_model_impl = if foreign_fields.is_empty() {
612 quote! {
613 impl ::appdb::ForeignModel for #struct_ident {
614 async fn persist_foreign(value: Self) -> ::anyhow::Result<Self::Stored> {
615 <Self as ::appdb::StoredModel>::into_stored(value)
616 }
617
618 async fn hydrate_foreign(stored: Self::Stored) -> ::anyhow::Result<Self> {
619 <Self as ::appdb::StoredModel>::from_stored(stored)
620 }
621
622 fn decode_stored_row(
623 row: ::surrealdb::types::Value,
624 ) -> ::anyhow::Result<Self::Stored>
625 where
626 Self::Stored: ::serde::de::DeserializeOwned,
627 {
628 Ok(::serde_json::from_value(row.into_json_value())?)
629 }
630
631 #relation_methods_impl
632 }
633 }
634 } else {
635 let stored_struct_ident = format_ident!("AppdbStored{}", struct_ident);
636 quote! {
637 #[derive(
638 Debug,
639 Clone,
640 ::serde::Serialize,
641 ::serde::Deserialize,
642 ::surrealdb::types::SurrealValue,
643 )]
644 #vis struct #stored_struct_ident {
645 #( #stored_fields, )*
646 }
647
648 impl ::appdb::StoredModel for #struct_ident {
649 type Stored = #stored_struct_ident;
650
651 fn into_stored(self) -> ::anyhow::Result<Self::Stored> {
652 unreachable!("foreign fields require async persist_foreign")
653 }
654
655 fn from_stored(_stored: Self::Stored) -> ::anyhow::Result<Self> {
656 unreachable!("foreign fields require async hydrate_foreign")
657 }
658 }
659
660 impl ::appdb::ForeignModel for #struct_ident {
661 async fn persist_foreign(value: Self) -> ::anyhow::Result<Self::Stored> {
662 let value = value;
663 Ok(#stored_struct_ident {
664 #( #into_stored_assignments, )*
665 })
666 }
667
668 async fn hydrate_foreign(stored: Self::Stored) -> ::anyhow::Result<Self> {
669 Ok(Self {
670 #( #from_stored_assignments, )*
671 })
672 }
673
674 fn has_foreign_fields() -> bool {
675 true
676 }
677
678 fn foreign_field_names() -> &'static [&'static str] {
679 &[ #( #foreign_field_literals ),* ]
680 }
681
682 fn decode_stored_row(
683 row: ::surrealdb::types::Value,
684 ) -> ::anyhow::Result<Self::Stored>
685 where
686 Self::Stored: ::serde::de::DeserializeOwned,
687 {
688 let mut row = row.into_json_value();
689 if let ::serde_json::Value::Object(map) = &mut row {
690 #( #decode_foreign_fields )*
691 }
692 Ok(::serde_json::from_value(row)?)
693 }
694
695 #relation_methods_impl
696 }
697 }
698 };
699
700 let store_marker_ident = format_ident!("AppdbStoreMarker{}", struct_ident);
701
702 Ok(quote! {
703 #[doc(hidden)]
704 #vis struct #store_marker_ident;
705
706 impl ::appdb::model::meta::ModelMeta for #struct_ident {
707 fn storage_table() -> &'static str {
708 #resolved_table_name_expr
709 }
710
711 fn table_name() -> &'static str {
712 static TABLE_NAME: ::std::sync::OnceLock<&'static str> = ::std::sync::OnceLock::new();
713 TABLE_NAME.get_or_init(|| {
714 let table = #resolved_table_name_expr;
715 ::appdb::model::meta::register_table(stringify!(#struct_ident), table)
716 })
717 }
718 }
719
720 impl ::appdb::model::meta::StoreModelMarker for #struct_ident {}
721 impl ::appdb::model::meta::StoreModelMarker for #store_marker_ident {}
722 #pagination_meta_impl
723
724 impl ::appdb::model::meta::UniqueLookupMeta for #struct_ident {
725 fn lookup_fields() -> &'static [&'static str] {
726 &[ #( #lookup_field_literals ),* ]
727 }
728
729 fn foreign_fields() -> &'static [&'static str] {
730 &[ #( #foreign_field_literals ),* ]
731 }
732 }
733 #stored_model_impl
734 #foreign_model_impl
735
736 #auto_has_id_impl
737 #resolve_record_id_impl
738
739 #( #unique_schema_impls )*
740 #( #pagin_schema_impl )*
741
742 impl ::appdb::repository::Crud for #struct_ident {}
743
744 impl #struct_ident {
745 pub async fn save(self) -> ::anyhow::Result<Self> {
751 <Self as ::appdb::repository::Crud>::save(self).await
752 }
753
754 pub async fn save_many(data: ::std::vec::Vec<Self>) -> ::anyhow::Result<::std::vec::Vec<Self>> {
756 <Self as ::appdb::repository::Crud>::save_many(data).await
757 }
758
759 pub async fn get<T>(id: T) -> ::anyhow::Result<Self>
760 where
761 ::surrealdb::types::RecordIdKey: From<T>,
762 T: Send,
763 {
764 ::appdb::repository::Repo::<Self>::get(id).await
765 }
766
767 pub async fn list() -> ::anyhow::Result<::std::vec::Vec<Self>> {
768 ::appdb::repository::Repo::<Self>::list().await
769 }
770
771 pub async fn list_limit(count: i64) -> ::anyhow::Result<::std::vec::Vec<Self>> {
772 ::appdb::repository::Repo::<Self>::list_limit(count).await
773 }
774 #pagination_methods_impl
775
776 pub async fn relate_by_name<Target>(&self, target: &Target, relation: &str) -> ::anyhow::Result<()>
777 where
778 Target: ::appdb::model::meta::ResolveRecordId + Send + Sync,
779 {
780 <Self as ::appdb::graph::GraphCrud>::relate_by_name(self, target, relation).await
781 }
782
783 pub async fn back_relate_by_name<Target>(&self, target: &Target, relation: &str) -> ::anyhow::Result<()>
784 where
785 Target: ::appdb::model::meta::ResolveRecordId + Send + Sync,
786 {
787 <Self as ::appdb::graph::GraphCrud>::back_relate_by_name(self, target, relation).await
788 }
789
790 pub async fn unrelate_by_name<Target>(&self, target: &Target, relation: &str) -> ::anyhow::Result<()>
791 where
792 Target: ::appdb::model::meta::ResolveRecordId + Send + Sync,
793 {
794 <Self as ::appdb::graph::GraphCrud>::unrelate_by_name(self, target, relation).await
795 }
796
797 pub async fn outgoing_ids(&self, relation: &str) -> ::anyhow::Result<::std::vec::Vec<::surrealdb::types::RecordId>> {
798 <Self as ::appdb::repository::Crud>::outgoing_ids(self, relation).await
799 }
800
801 pub async fn outgoing<Target>(&self, relation: &str) -> ::anyhow::Result<::std::vec::Vec<Target>>
802 where
803 Target: ::appdb::model::meta::ModelMeta + ::appdb::StoredModel + ::appdb::ForeignModel,
804 <Target as ::appdb::StoredModel>::Stored: ::serde::de::DeserializeOwned,
805 {
806 <Self as ::appdb::repository::Crud>::outgoing::<Target>(self, relation).await
807 }
808
809 pub async fn outgoing_count(&self, relation: &str) -> ::anyhow::Result<i64> {
810 <Self as ::appdb::repository::Crud>::outgoing_count(self, relation).await
811 }
812
813 pub async fn outgoing_count_as<Target>(&self, relation: &str) -> ::anyhow::Result<i64>
814 where
815 Target: ::appdb::model::meta::ModelMeta + ::appdb::StoredModel + ::appdb::ForeignModel,
816 {
817 <Self as ::appdb::repository::Crud>::outgoing_count_as::<Target>(self, relation).await
818 }
819
820 pub async fn incoming_ids(&self, relation: &str) -> ::anyhow::Result<::std::vec::Vec<::surrealdb::types::RecordId>> {
821 <Self as ::appdb::repository::Crud>::incoming_ids(self, relation).await
822 }
823
824 pub async fn incoming<Target>(&self, relation: &str) -> ::anyhow::Result<::std::vec::Vec<Target>>
825 where
826 Target: ::appdb::model::meta::ModelMeta + ::appdb::StoredModel + ::appdb::ForeignModel,
827 <Target as ::appdb::StoredModel>::Stored: ::serde::de::DeserializeOwned,
828 {
829 <Self as ::appdb::repository::Crud>::incoming::<Target>(self, relation).await
830 }
831
832 pub async fn incoming_count(&self, relation: &str) -> ::anyhow::Result<i64> {
833 <Self as ::appdb::repository::Crud>::incoming_count(self, relation).await
834 }
835
836 pub async fn incoming_count_as<Target>(&self, relation: &str) -> ::anyhow::Result<i64>
837 where
838 Target: ::appdb::model::meta::ModelMeta + ::appdb::StoredModel + ::appdb::ForeignModel,
839 {
840 <Self as ::appdb::repository::Crud>::incoming_count_as::<Target>(self, relation).await
841 }
842
843 pub async fn delete_all() -> ::anyhow::Result<()> {
844 ::appdb::repository::Repo::<Self>::delete_all().await
845 }
846
847 pub async fn find_one_id(
848 k: &str,
849 v: &str,
850 ) -> ::anyhow::Result<::surrealdb::types::RecordId> {
851 ::appdb::repository::Repo::<Self>::find_one_id(k, v).await
852 }
853
854 pub async fn list_record_ids() -> ::anyhow::Result<::std::vec::Vec<::surrealdb::types::RecordId>> {
855 ::appdb::repository::Repo::<Self>::list_record_ids().await
856 }
857
858 pub async fn create_at(
859 id: ::surrealdb::types::RecordId,
860 data: Self,
861 ) -> ::anyhow::Result<Self> {
862 ::appdb::repository::Repo::<Self>::create_at(id, data).await
863 }
864
865 pub async fn upsert_at(
866 id: ::surrealdb::types::RecordId,
867 data: Self,
868 ) -> ::anyhow::Result<Self> {
869 ::appdb::repository::Repo::<Self>::upsert_at(id, data).await
870 }
871
872 pub async fn update_at(
873 self,
874 id: ::surrealdb::types::RecordId,
875 ) -> ::anyhow::Result<Self> {
876 ::appdb::repository::Repo::<Self>::update_at(id, self).await
877 }
878
879
880 pub async fn delete<T>(id: T) -> ::anyhow::Result<()>
881 where
882 ::surrealdb::types::RecordIdKey: From<T>,
883 T: Send,
884 {
885 ::appdb::repository::Repo::<Self>::delete(id).await
886 }
887 }
888 })
889}
890
891fn derive_bridge_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
892 let enum_ident = input.ident;
893
894 let variants = match input.data {
895 Data::Enum(data) => data.variants,
896 _ => {
897 return Err(Error::new_spanned(
898 enum_ident,
899 "Bridge can only be derived for enums",
900 ));
901 }
902 };
903
904 let payloads = variants
905 .iter()
906 .map(parse_bridge_variant)
907 .collect::<syn::Result<Vec<_>>>()?;
908
909 let from_impls = payloads.iter().map(|variant| {
910 let variant_ident = &variant.variant_ident;
911 let payload_ty = &variant.payload_ty;
912
913 quote! {
914 impl ::std::convert::From<#payload_ty> for #enum_ident {
915 fn from(value: #payload_ty) -> Self {
916 Self::#variant_ident(value)
917 }
918 }
919 }
920 });
921
922 let persist_match_arms = payloads.iter().map(|variant| {
923 let variant_ident = &variant.variant_ident;
924
925 quote! {
926 Self::#variant_ident(value) => <_ as ::appdb::Bridge>::persist_foreign(value).await,
927 }
928 });
929
930 let hydrate_match_arms = payloads.iter().map(|variant| {
931 let variant_ident = &variant.variant_ident;
932 let payload_ty = &variant.payload_ty;
933
934 quote! {
935 table if table == <#payload_ty as ::appdb::model::meta::ModelMeta>::storage_table() => {
936 ::std::result::Result::Ok(Self::#variant_ident(
937 <#payload_ty as ::appdb::Bridge>::hydrate_foreign(id).await?,
938 ))
939 }
940 }
941 });
942
943 Ok(quote! {
944 #( #from_impls )*
945
946 #[::async_trait::async_trait]
947 impl ::appdb::Bridge for #enum_ident {
948 async fn persist_foreign(self) -> ::anyhow::Result<::surrealdb::types::RecordId> {
949 match self {
950 #( #persist_match_arms )*
951 }
952 }
953
954 async fn hydrate_foreign(
955 id: ::surrealdb::types::RecordId,
956 ) -> ::anyhow::Result<Self> {
957 match id.table.to_string().as_str() {
958 #( #hydrate_match_arms, )*
959 table => ::anyhow::bail!(
960 "unsupported foreign table `{table}` for enum dispatcher `{}`",
961 ::std::stringify!(#enum_ident)
962 ),
963 }
964 }
965 }
966 })
967}
968
969#[derive(Clone)]
970struct BridgeVariant {
971 variant_ident: syn::Ident,
972 payload_ty: Type,
973}
974
975fn parse_bridge_variant(variant: &syn::Variant) -> syn::Result<BridgeVariant> {
976 let payload_ty = match &variant.fields {
977 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
978 fields.unnamed.first().expect("single field").ty.clone()
979 }
980 Fields::Unnamed(_) => {
981 return Err(Error::new_spanned(
982 &variant.ident,
983 "Bridge variants must be single-field tuple variants",
984 ));
985 }
986 Fields::Unit => {
987 return Err(Error::new_spanned(
988 &variant.ident,
989 "Bridge does not support unit variants",
990 ));
991 }
992 Fields::Named(_) => {
993 return Err(Error::new_spanned(
994 &variant.ident,
995 "Bridge does not support struct variants",
996 ));
997 }
998 };
999
1000 let payload_path = match &payload_ty {
1001 Type::Path(path) => path,
1002 _ => {
1003 return Err(Error::new_spanned(
1004 &payload_ty,
1005 "Bridge payload must implement appdb::Bridge",
1006 ));
1007 }
1008 };
1009
1010 let segment = payload_path.path.segments.last().ok_or_else(|| {
1011 Error::new_spanned(&payload_ty, "Bridge payload must implement appdb::Bridge")
1012 })?;
1013
1014 if !matches!(segment.arguments, PathArguments::None) {
1015 return Err(Error::new_spanned(
1016 &payload_ty,
1017 "Bridge payload must implement appdb::Bridge",
1018 ));
1019 }
1020
1021 Ok(BridgeVariant {
1022 variant_ident: variant.ident.clone(),
1023 payload_ty,
1024 })
1025}
1026
1027fn derive_relation_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1028 let struct_ident = input.ident;
1029 let relation_name = relation_name_override(&input.attrs)?
1030 .unwrap_or_else(|| to_snake_case(&struct_ident.to_string()));
1031
1032 match input.data {
1033 Data::Struct(data) => match data.fields {
1034 Fields::Unit | Fields::Named(_) => {}
1035 _ => {
1036 return Err(Error::new_spanned(
1037 struct_ident,
1038 "Relation can only be derived for unit structs or structs with named fields",
1039 ));
1040 }
1041 },
1042 _ => {
1043 return Err(Error::new_spanned(
1044 struct_ident,
1045 "Relation can only be derived for structs",
1046 ));
1047 }
1048 }
1049
1050 Ok(quote! {
1051 impl ::appdb::model::relation::RelationMeta for #struct_ident {
1052 fn relation_name() -> &'static str {
1053 static REL_NAME: ::std::sync::OnceLock<&'static str> = ::std::sync::OnceLock::new();
1054 REL_NAME.get_or_init(|| ::appdb::model::relation::register_relation(#relation_name))
1055 }
1056 }
1057
1058 impl #struct_ident {
1059 pub async fn relate<A, B>(a: &A, b: &B) -> ::anyhow::Result<()>
1060 where
1061 A: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1062 B: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1063 {
1064 ::appdb::graph::relate_at(a.resolve_record_id().await?, b.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name()).await
1065 }
1066
1067 pub async fn back_relate<A, B>(a: &A, b: &B) -> ::anyhow::Result<()>
1068 where
1069 A: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1070 B: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1071 {
1072 ::appdb::graph::back_relate_at(a.resolve_record_id().await?, b.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name()).await
1073 }
1074
1075 pub async fn unrelate<A, B>(a: &A, b: &B) -> ::anyhow::Result<()>
1076 where
1077 A: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1078 B: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1079 {
1080 ::appdb::graph::unrelate_at(a.resolve_record_id().await?, b.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name()).await
1081 }
1082
1083 pub async fn out_ids<A>(a: &A, out_table: &str) -> ::anyhow::Result<::std::vec::Vec<::surrealdb::types::RecordId>>
1084 where
1085 A: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1086 {
1087 ::appdb::graph::out_ids(a.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name(), out_table).await
1088 }
1089
1090 pub async fn in_ids<B>(b: &B, in_table: &str) -> ::anyhow::Result<::std::vec::Vec<::surrealdb::types::RecordId>>
1091 where
1092 B: ::appdb::model::meta::ResolveRecordId + Send + Sync,
1093 {
1094 ::appdb::graph::in_ids(b.resolve_record_id().await?, <Self as ::appdb::model::relation::RelationMeta>::relation_name(), in_table).await
1095 }
1096 }
1097 })
1098}
1099
1100fn derive_sensitive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1101 let struct_ident = input.ident;
1102 let encrypted_ident = format_ident!("Encrypted{}", struct_ident);
1103 let vis = input.vis;
1104 let type_crypto_config = type_crypto_config(&input.attrs)?;
1105 let named_fields = match input.data {
1106 Data::Struct(data) => match data.fields {
1107 Fields::Named(fields) => fields.named,
1108 _ => {
1109 return Err(Error::new_spanned(
1110 struct_ident,
1111 "Sensitive can only be derived for structs with named fields",
1112 ));
1113 }
1114 },
1115 _ => {
1116 return Err(Error::new_spanned(
1117 struct_ident,
1118 "Sensitive can only be derived for structs",
1119 ));
1120 }
1121 };
1122
1123 let mut secure_field_count = 0usize;
1124 let mut encrypted_fields = Vec::new();
1125 let mut encrypt_assignments = Vec::new();
1126 let mut decrypt_assignments = Vec::new();
1127 let mut runtime_encrypt_assignments = Vec::new();
1128 let mut runtime_decrypt_assignments = Vec::new();
1129 let mut field_tag_structs = Vec::new();
1130 let mut secure_field_meta_entries = Vec::new();
1131
1132 for field in named_fields.iter() {
1133 let ident = field.ident.clone().expect("named field");
1134 let field_vis = field.vis.clone();
1135 let secure = has_secure_attr(&field.attrs);
1136 let field_crypto_config = field_crypto_config(&field.attrs)?;
1137
1138 if !secure && field_crypto_config.is_present() {
1139 return Err(Error::new_spanned(
1140 ident,
1141 "#[crypto(...)] on a field requires #[secure] on the same field",
1142 ));
1143 }
1144
1145 if secure {
1146 secure_field_count += 1;
1147 let secure_kind = secure_kind(field)?;
1148 let encrypted_ty = secure_kind.encrypted_type();
1149 let field_tag_ident = format_ident!(
1150 "AppdbSensitiveFieldTag{}{}",
1151 struct_ident,
1152 to_pascal_case(&ident.to_string())
1153 );
1154 let field_tag_literal = ident.to_string();
1155 let effective_account = field_crypto_config
1156 .field_account
1157 .clone()
1158 .or_else(|| type_crypto_config.account.clone());
1159 let service_override = type_crypto_config.service.clone();
1160 let account_literal = effective_account
1161 .as_ref()
1162 .map(|value| quote! { ::std::option::Option::Some(#value) })
1163 .unwrap_or_else(|| quote! { ::std::option::Option::None });
1164 let service_literal = service_override
1165 .as_ref()
1166 .map(|value| quote! { ::std::option::Option::Some(#value) })
1167 .unwrap_or_else(|| quote! { ::std::option::Option::None });
1168 let encrypt_expr = secure_kind.encrypt_with_context_expr(&ident);
1169 let decrypt_expr = secure_kind.decrypt_with_context_expr(&ident);
1170 let runtime_encrypt_expr =
1171 secure_kind.encrypt_with_runtime_expr(&ident, &field_tag_ident);
1172 let runtime_decrypt_expr =
1173 secure_kind.decrypt_with_runtime_expr(&ident, &field_tag_ident);
1174 encrypted_fields.push(quote! { #field_vis #ident: #encrypted_ty });
1175 encrypt_assignments.push(quote! { #ident: #encrypt_expr });
1176 decrypt_assignments.push(quote! { #ident: #decrypt_expr });
1177 runtime_encrypt_assignments.push(quote! { #ident: #runtime_encrypt_expr });
1178 runtime_decrypt_assignments.push(quote! { #ident: #runtime_decrypt_expr });
1179 secure_field_meta_entries.push(quote! {
1180 ::appdb::crypto::SensitiveFieldMetadata {
1181 model_tag: ::std::concat!(::std::module_path!(), "::", ::std::stringify!(#struct_ident)),
1182 field_tag: #field_tag_literal,
1183 service: #service_literal,
1184 account: #account_literal,
1185 secure_fields: &[],
1186 }
1187 });
1188 field_tag_structs.push(quote! {
1189 #[doc(hidden)]
1190 #vis struct #field_tag_ident;
1191
1192 impl ::appdb::crypto::SensitiveFieldTag for #field_tag_ident {
1193 fn model_tag() -> &'static str {
1194 <#struct_ident as ::appdb::crypto::SensitiveModelTag>::model_tag()
1195 }
1196
1197 fn field_tag() -> &'static str {
1198 #field_tag_literal
1199 }
1200
1201 fn crypto_metadata() -> &'static ::appdb::crypto::SensitiveFieldMetadata {
1202 static FIELD_META: ::std::sync::OnceLock<::appdb::crypto::SensitiveFieldMetadata> = ::std::sync::OnceLock::new();
1203 FIELD_META.get_or_init(|| ::appdb::crypto::SensitiveFieldMetadata {
1204 model_tag: <#struct_ident as ::appdb::crypto::SensitiveModelTag>::model_tag(),
1205 field_tag: #field_tag_literal,
1206 service: #service_literal,
1207 account: #account_literal,
1208 secure_fields: &#struct_ident::SECURE_FIELDS,
1209 })
1210 }
1211 }
1212 });
1213 } else {
1214 let ty = field.ty.clone();
1215 encrypted_fields.push(quote! { #field_vis #ident: #ty });
1216 encrypt_assignments.push(quote! { #ident: self.#ident.clone() });
1217 decrypt_assignments.push(quote! { #ident: encrypted.#ident.clone() });
1218 runtime_encrypt_assignments.push(quote! { #ident: self.#ident.clone() });
1219 runtime_decrypt_assignments.push(quote! { #ident: encrypted.#ident.clone() });
1220 }
1221 }
1222
1223 if secure_field_count == 0 {
1224 return Err(Error::new_spanned(
1225 struct_ident,
1226 "Sensitive requires at least one #[secure] field",
1227 ));
1228 }
1229
1230 Ok(quote! {
1231 #[derive(
1232 Debug,
1233 Clone,
1234 ::serde::Serialize,
1235 ::serde::Deserialize,
1236 ::surrealdb::types::SurrealValue,
1237 )]
1238 #vis struct #encrypted_ident {
1239 #( #encrypted_fields, )*
1240 }
1241
1242 impl ::appdb::crypto::SensitiveModelTag for #struct_ident {
1243 fn model_tag() -> &'static str {
1244 ::std::concat!(::std::module_path!(), "::", ::std::stringify!(#struct_ident))
1245 }
1246 }
1247
1248 #( #field_tag_structs )*
1249
1250 impl ::appdb::Sensitive for #struct_ident {
1251 type Encrypted = #encrypted_ident;
1252
1253 fn encrypt(
1254 &self,
1255 context: &::appdb::crypto::CryptoContext,
1256 ) -> ::std::result::Result<Self::Encrypted, ::appdb::crypto::CryptoError> {
1257 ::std::result::Result::Ok(#encrypted_ident {
1258 #( #encrypt_assignments, )*
1259 })
1260 }
1261
1262 fn decrypt(
1263 encrypted: &Self::Encrypted,
1264 context: &::appdb::crypto::CryptoContext,
1265 ) -> ::std::result::Result<Self, ::appdb::crypto::CryptoError> {
1266 ::std::result::Result::Ok(Self {
1267 #( #decrypt_assignments, )*
1268 })
1269 }
1270
1271 fn encrypt_with_runtime_resolver(
1272 &self,
1273 ) -> ::std::result::Result<Self::Encrypted, ::appdb::crypto::CryptoError> {
1274 ::std::result::Result::Ok(#encrypted_ident {
1275 #( #runtime_encrypt_assignments, )*
1276 })
1277 }
1278
1279 fn decrypt_with_runtime_resolver(
1280 encrypted: &Self::Encrypted,
1281 ) -> ::std::result::Result<Self, ::appdb::crypto::CryptoError> {
1282 ::std::result::Result::Ok(Self {
1283 #( #runtime_decrypt_assignments, )*
1284 })
1285 }
1286
1287 fn secure_fields() -> &'static [::appdb::crypto::SensitiveFieldMetadata] {
1288 &Self::SECURE_FIELDS
1289 }
1290 }
1291
1292 impl #struct_ident {
1293 pub const SECURE_FIELDS: [::appdb::crypto::SensitiveFieldMetadata; #secure_field_count] = [
1294 #( #secure_field_meta_entries, )*
1295 ];
1296
1297 pub fn encrypt(
1298 &self,
1299 context: &::appdb::crypto::CryptoContext,
1300 ) -> ::std::result::Result<#encrypted_ident, ::appdb::crypto::CryptoError> {
1301 <Self as ::appdb::Sensitive>::encrypt(self, context)
1302 }
1303 }
1304
1305 impl #encrypted_ident {
1306 pub fn decrypt(
1307 &self,
1308 context: &::appdb::crypto::CryptoContext,
1309 ) -> ::std::result::Result<#struct_ident, ::appdb::crypto::CryptoError> {
1310 <#struct_ident as ::appdb::Sensitive>::decrypt(self, context)
1311 }
1312 }
1313 })
1314}
1315
1316fn has_secure_attr(attrs: &[Attribute]) -> bool {
1317 attrs.iter().any(|attr| attr.path().is_ident("secure"))
1318}
1319
1320fn has_unique_attr(attrs: &[Attribute]) -> bool {
1321 attrs.iter().any(|attr| attr.path().is_ident("unique"))
1322}
1323
1324#[derive(Default, Clone)]
1325struct TypeCryptoConfig {
1326 service: Option<String>,
1327 account: Option<String>,
1328}
1329
1330#[derive(Default, Clone)]
1331struct FieldCryptoConfig {
1332 field_account: Option<String>,
1333}
1334
1335impl FieldCryptoConfig {
1336 fn is_present(&self) -> bool {
1337 self.field_account.is_some()
1338 }
1339}
1340
1341fn type_crypto_config(attrs: &[Attribute]) -> syn::Result<TypeCryptoConfig> {
1342 let mut config = TypeCryptoConfig::default();
1343 let mut seen = HashSet::new();
1344
1345 for attr in attrs {
1346 if !attr.path().is_ident("crypto") {
1347 continue;
1348 }
1349
1350 attr.parse_nested_meta(|meta| {
1351 let key = meta
1352 .path
1353 .get_ident()
1354 .cloned()
1355 .ok_or_else(|| meta.error("unsupported crypto attribute"))?;
1356
1357 if !seen.insert(key.to_string()) {
1358 return Err(meta.error("duplicate crypto attribute key"));
1359 }
1360
1361 let value = meta.value()?;
1362 let literal: syn::LitStr = value.parse()?;
1363 match key.to_string().as_str() {
1364 "service" => config.service = Some(literal.value()),
1365 "account" => config.account = Some(literal.value()),
1366 _ => {
1367 return Err(
1368 meta.error("unsupported crypto attribute; expected `service` or `account`")
1369 );
1370 }
1371 }
1372 Ok(())
1373 })?;
1374 }
1375
1376 Ok(config)
1377}
1378
1379fn field_crypto_config(attrs: &[Attribute]) -> syn::Result<FieldCryptoConfig> {
1380 let mut config = FieldCryptoConfig::default();
1381 let mut seen = HashSet::new();
1382
1383 for attr in attrs {
1384 if attr.path().is_ident("crypto") {
1385 attr.parse_nested_meta(|meta| {
1386 let key = meta
1387 .path
1388 .get_ident()
1389 .cloned()
1390 .ok_or_else(|| meta.error("unsupported crypto attribute"))?;
1391
1392 if !seen.insert(key.to_string()) {
1393 return Err(meta.error("duplicate crypto attribute key"));
1394 }
1395
1396 let value = meta.value()?;
1397 let literal: syn::LitStr = value.parse()?;
1398 match key.to_string().as_str() {
1399 "field_account" => config.field_account = Some(literal.value()),
1400 _ => {
1401 return Err(meta.error(
1402 "unsupported field crypto attribute; expected `field_account`",
1403 ));
1404 }
1405 }
1406 Ok(())
1407 })?;
1408 } else if attr.path().is_ident("secure") && matches!(attr.meta, Meta::List(_)) {
1409 return Err(Error::new_spanned(
1410 attr,
1411 "#[secure] does not accept arguments; use #[crypto(field_account = \"...\")] on the field",
1412 ));
1413 }
1414 }
1415
1416 Ok(config)
1417}
1418
1419fn table_alias_target(attrs: &[Attribute]) -> syn::Result<Option<Type>> {
1420 let mut target = None;
1421
1422 for attr in attrs {
1423 if !attr.path().is_ident("table_as") {
1424 continue;
1425 }
1426
1427 if target.is_some() {
1428 return Err(Error::new_spanned(
1429 attr,
1430 "duplicate #[table_as(...)] attribute is not supported",
1431 ));
1432 }
1433
1434 let parsed: Type = attr.parse_args().map_err(|_| {
1435 Error::new_spanned(attr, "#[table_as(...)] requires exactly one target type")
1436 })?;
1437
1438 match parsed {
1439 Type::Path(TypePath { ref path, .. }) if !path.segments.is_empty() => {
1440 target = Some(parsed);
1441 }
1442 _ => {
1443 return Err(Error::new_spanned(
1444 parsed,
1445 "#[table_as(...)] target must be a type path",
1446 ));
1447 }
1448 }
1449 }
1450
1451 Ok(target)
1452}
1453
1454fn resolved_schema_table_name(struct_ident: &syn::Ident, table_alias: Option<&Type>) -> String {
1455 match table_alias {
1456 Some(Type::Path(type_path)) => type_path
1457 .path
1458 .segments
1459 .last()
1460 .map(|segment| to_snake_case(&segment.ident.to_string()))
1461 .unwrap_or_else(|| to_snake_case(&struct_ident.to_string())),
1462 Some(_) => to_snake_case(&struct_ident.to_string()),
1463 None => to_snake_case(&struct_ident.to_string()),
1464 }
1465}
1466
1467fn field_foreign_attr(field: &Field) -> syn::Result<Option<&Attribute>> {
1468 let mut foreign_attr = None;
1469
1470 for attr in &field.attrs {
1471 if !attr.path().is_ident("foreign") {
1472 continue;
1473 }
1474
1475 if foreign_attr.is_some() {
1476 return Err(Error::new_spanned(
1477 attr,
1478 "duplicate nested-ref attribute is not supported",
1479 ));
1480 }
1481
1482 foreign_attr = Some(attr);
1483 }
1484
1485 Ok(foreign_attr)
1486}
1487
1488fn field_relation_attr(field: &Field) -> syn::Result<Option<&Attribute>> {
1489 let mut relate_attr = None;
1490
1491 for attr in &field.attrs {
1492 if !attr.path().is_ident("relate") && !attr.path().is_ident("back_relate") {
1493 continue;
1494 }
1495
1496 if let Some(previous) = relate_attr {
1497 return Err(Error::new_spanned(
1498 attr,
1499 relation_attr_conflict_message(previous, attr),
1500 ));
1501 }
1502
1503 relate_attr = Some(attr);
1504 }
1505
1506 Ok(relate_attr)
1507}
1508
1509fn field_pagin_attr(field: &Field) -> syn::Result<Option<&Attribute>> {
1510 let mut pagin_attr = None;
1511
1512 for attr in &field.attrs {
1513 if !attr.path().is_ident("pagin") {
1514 continue;
1515 }
1516
1517 if pagin_attr.is_some() {
1518 return Err(Error::new_spanned(
1519 attr,
1520 "duplicate #[pagin] attribute is not supported",
1521 ));
1522 }
1523
1524 pagin_attr = Some(attr);
1525 }
1526
1527 Ok(pagin_attr)
1528}
1529
1530fn validate_foreign_field(field: &Field, attr: &Attribute) -> syn::Result<Type> {
1531 if attr.path().is_ident("foreign") {
1532 return foreign_leaf_type(&field.ty)
1533 .ok_or_else(|| Error::new_spanned(&field.ty, BINDREF_ACCEPTED_SHAPES));
1534 }
1535
1536 Err(Error::new_spanned(attr, "unsupported foreign attribute"))
1537}
1538
1539const BINDREF_ACCEPTED_SHAPES: &str = "#[foreign] supports recursive Option<_> / Vec<_> shapes whose leaf type implements appdb::Bridge";
1540
1541const BINDREF_BRIDGE_STORE_ONLY: &str =
1542 "#[foreign] leaf types must derive Store or #[derive(Bridge)] dispatcher enums";
1543
1544const RELATE_ACCEPTED_SHAPES: &str = "relation-backed fields support Child / Option<Child> / Vec<Child> / Option<Vec<Child>> shapes whose leaf type implements appdb::Bridge";
1545
1546const PAGIN_ACCEPTED_SHAPES: &str = "#[pagin] supports direct scalar fields plus Id/RecordId; Option<_> and Vec<_> wrappers are not supported";
1547
1548#[derive(Clone, Copy)]
1549enum RelationFieldDirection {
1550 Outgoing,
1551 Incoming,
1552}
1553
1554impl RelationFieldDirection {
1555 fn attr_label(self) -> &'static str {
1556 match self {
1557 Self::Outgoing => "#[relate(...)]",
1558 Self::Incoming => "#[back_relate(...)]",
1559 }
1560 }
1561
1562 fn write_direction_tokens(self) -> proc_macro2::TokenStream {
1563 match self {
1564 Self::Outgoing => quote!(::appdb::RelationWriteDirection::Outgoing),
1565 Self::Incoming => quote!(::appdb::RelationWriteDirection::Incoming),
1566 }
1567 }
1568
1569 fn write_edges_tokens(self) -> proc_macro2::TokenStream {
1570 match self {
1571 Self::Outgoing => quote! {
1572 ids
1573 .into_iter()
1574 .enumerate()
1575 .map(|(position, out)| ::appdb::graph::OrderedRelationEdge {
1576 _in: ::std::option::Option::Some(record.clone()),
1577 out,
1578 position: position as i64,
1579 })
1580 .collect()
1581 },
1582 Self::Incoming => quote! {
1583 ids
1584 .into_iter()
1585 .enumerate()
1586 .map(|(position, source)| ::appdb::graph::OrderedRelationEdge {
1587 _in: ::std::option::Option::Some(source),
1588 out: record.clone(),
1589 position: position as i64,
1590 })
1591 .collect()
1592 },
1593 }
1594 }
1595
1596 fn load_edges_tokens(self, relation_name: &str) -> proc_macro2::TokenStream {
1597 match self {
1598 Self::Outgoing => quote! {
1599 ::appdb::graph::GraphRepo::out_edges(record.clone(), #relation_name)
1600 .await?
1601 .into_iter()
1602 .map(|edge| edge.out)
1603 .collect()
1604 },
1605 Self::Incoming => quote! {
1606 {
1607 let mut ids = ::std::vec::Vec::new();
1608 for edge in ::appdb::graph::GraphRepo::in_edges(record.clone(), #relation_name).await? {
1609 let incoming = edge._in.ok_or_else(|| {
1610 ::anyhow::anyhow!("back relate field received relation edge without `in` record id")
1611 })?;
1612 ids.push(incoming);
1613 }
1614 ids
1615 }
1616 },
1617 }
1618 }
1619}
1620
1621#[derive(Clone)]
1622struct ForeignField {
1623 ident: syn::Ident,
1624 kind: ForeignFieldKind,
1625}
1626
1627#[derive(Clone)]
1628struct ForeignFieldKind {
1629 original_ty: Type,
1630 stored_ty: Type,
1631}
1632
1633#[derive(Clone)]
1634struct RelateField {
1635 ident: syn::Ident,
1636 relation_name: String,
1637 field_ty: Type,
1638 direction: RelationFieldDirection,
1639}
1640
1641#[derive(Clone)]
1642struct PaginField {
1643 ident: syn::Ident,
1644}
1645
1646fn parse_foreign_field(field: &Field, attr: &Attribute) -> syn::Result<ForeignField> {
1647 validate_foreign_field(field, attr)?;
1648 let ident = field.ident.clone().expect("named field");
1649
1650 let kind = ForeignFieldKind {
1651 original_ty: field.ty.clone(),
1652 stored_ty: foreign_stored_type(&field.ty)
1653 .ok_or_else(|| Error::new_spanned(&field.ty, BINDREF_ACCEPTED_SHAPES))?,
1654 };
1655
1656 Ok(ForeignField { ident, kind })
1657}
1658
1659fn parse_relate_field(field: &Field, attr: &Attribute) -> syn::Result<RelateField> {
1660 let direction = parse_relation_direction(attr)?;
1661 let relation_name = attr
1662 .parse_args::<syn::LitStr>()
1663 .map_err(|_| {
1664 Error::new_spanned(
1665 attr,
1666 format!(
1667 "{} requires exactly one string literal",
1668 direction.attr_label()
1669 ),
1670 )
1671 })?
1672 .value();
1673 if relation_name.is_empty() {
1674 return Err(Error::new_spanned(
1675 attr,
1676 format!("{} relation name must not be empty", direction.attr_label()),
1677 ));
1678 }
1679
1680 validate_relate_field(field, attr)?;
1681
1682 Ok(RelateField {
1683 ident: field.ident.clone().expect("named field"),
1684 relation_name,
1685 field_ty: field.ty.clone(),
1686 direction,
1687 })
1688}
1689
1690fn parse_pagin_field(field: &Field, attr: &Attribute) -> syn::Result<PaginField> {
1691 validate_pagin_field(field, attr)?;
1692 Ok(PaginField {
1693 ident: field.ident.clone().expect("named field"),
1694 })
1695}
1696
1697fn validate_relate_field(field: &Field, attr: &Attribute) -> syn::Result<Type> {
1698 if !attr.path().is_ident("relate") && !attr.path().is_ident("back_relate") {
1699 return Err(Error::new_spanned(attr, "unsupported relate attribute"));
1700 }
1701
1702 let accepted = relate_leaf_type(&field.ty).cloned().map(Type::Path);
1703
1704 accepted.ok_or_else(|| Error::new_spanned(&field.ty, RELATE_ACCEPTED_SHAPES))
1705}
1706
1707fn validate_pagin_field(field: &Field, attr: &Attribute) -> syn::Result<Type> {
1708 if !attr.path().is_ident("pagin") {
1709 return Err(Error::new_spanned(attr, "unsupported pagination attribute"));
1710 }
1711
1712 if pagination_leaf_type(&field.ty).is_none() {
1713 return Err(Error::new_spanned(&field.ty, PAGIN_ACCEPTED_SHAPES));
1714 }
1715
1716 Ok(field.ty.clone())
1717}
1718
1719fn relate_leaf_type(ty: &Type) -> Option<&TypePath> {
1720 if let Some(leaf) = direct_store_child_type(ty) {
1721 return Some(leaf);
1722 }
1723
1724 if let Some(inner) = option_inner_type(ty) {
1725 if let Some(leaf) = direct_store_child_type(inner) {
1726 return Some(leaf);
1727 }
1728
1729 if let Some(vec_inner) = vec_inner_type(inner) {
1730 return direct_store_child_type(vec_inner);
1731 }
1732
1733 return None;
1734 }
1735
1736 if let Some(inner) = vec_inner_type(ty) {
1737 return direct_store_child_type(inner);
1738 }
1739
1740 None
1741}
1742
1743fn pagination_leaf_type(ty: &Type) -> Option<Type> {
1744 if option_inner_type(ty).is_some() || vec_inner_type(ty).is_some() {
1745 return None;
1746 }
1747
1748 if is_id_type(ty)
1749 || is_record_id_type(ty)
1750 || is_string_type(ty)
1751 || is_common_non_store_leaf_type(ty)
1752 {
1753 return Some(ty.clone());
1754 }
1755
1756 None
1757}
1758
1759fn parse_relation_direction(attr: &Attribute) -> syn::Result<RelationFieldDirection> {
1760 if attr.path().is_ident("relate") {
1761 return Ok(RelationFieldDirection::Outgoing);
1762 }
1763 if attr.path().is_ident("back_relate") {
1764 return Ok(RelationFieldDirection::Incoming);
1765 }
1766
1767 Err(Error::new_spanned(attr, "unsupported relate attribute"))
1768}
1769
1770fn relation_attr_label(attr: &Attribute) -> &'static str {
1771 if attr.path().is_ident("back_relate") {
1772 "#[back_relate(...)]"
1773 } else {
1774 "#[relate(...)]"
1775 }
1776}
1777
1778fn relation_attr_conflict_message(previous: &Attribute, current: &Attribute) -> String {
1779 match (
1780 previous.path().is_ident("relate"),
1781 previous.path().is_ident("back_relate"),
1782 current.path().is_ident("relate"),
1783 current.path().is_ident("back_relate"),
1784 ) {
1785 (true, false, true, false) => {
1786 "duplicate #[relate(...)] attribute is not supported".to_owned()
1787 }
1788 (false, true, false, true) => {
1789 "duplicate #[back_relate(...)] attribute is not supported".to_owned()
1790 }
1791 _ => "#[relate(...)] cannot be combined with #[back_relate(...)]".to_owned(),
1792 }
1793}
1794
1795fn foreign_field_kind<'a>(
1796 ident: &syn::Ident,
1797 fields: &'a [ForeignField],
1798) -> Option<&'a ForeignFieldKind> {
1799 fields
1800 .iter()
1801 .find(|field| field.ident == *ident)
1802 .map(|field| &field.kind)
1803}
1804
1805fn stored_field_type(field: &Field, foreign_fields: &[ForeignField]) -> Type {
1806 let ident = field.ident.as_ref().expect("named field");
1807 match foreign_field_kind(ident, foreign_fields) {
1808 Some(ForeignFieldKind { stored_ty, .. }) => stored_ty.clone(),
1809 None => field.ty.clone(),
1810 }
1811}
1812
1813fn foreign_stored_type(ty: &Type) -> Option<Type> {
1814 if let Some(inner) = option_inner_type(ty) {
1815 let inner = foreign_stored_type(inner)?;
1816 return Some(syn::parse_quote!(::std::option::Option<#inner>));
1817 }
1818
1819 if let Some(inner) = vec_inner_type(ty) {
1820 let inner = foreign_stored_type(inner)?;
1821 return Some(syn::parse_quote!(::std::vec::Vec<#inner>));
1822 }
1823
1824 direct_store_child_type(ty)
1825 .cloned()
1826 .map(|_| syn::parse_quote!(::surrealdb::types::RecordId))
1827}
1828
1829fn foreign_leaf_type(ty: &Type) -> Option<Type> {
1830 if let Some(inner) = option_inner_type(ty) {
1831 return foreign_leaf_type(inner);
1832 }
1833
1834 if let Some(inner) = vec_inner_type(ty) {
1835 return foreign_leaf_type(inner);
1836 }
1837
1838 direct_store_child_type(ty).cloned().map(Type::Path)
1839}
1840
1841fn invalid_foreign_leaf_type(ty: &Type) -> Option<Type> {
1842 let leaf = foreign_leaf_type(ty)?;
1843 match &leaf {
1844 Type::Path(type_path) => {
1845 let segment = type_path.path.segments.last()?;
1846 if matches!(segment.arguments, PathArguments::None) {
1847 None
1848 } else {
1849 Some(leaf)
1850 }
1851 }
1852 _ => Some(leaf),
1853 }
1854}
1855
1856fn direct_store_child_type(ty: &Type) -> Option<&TypePath> {
1857 let Type::Path(type_path) = ty else {
1858 return None;
1859 };
1860
1861 let segment = type_path.path.segments.last()?;
1862 if !matches!(segment.arguments, PathArguments::None) {
1863 return None;
1864 }
1865
1866 if is_id_type(ty) || is_string_type(ty) || is_common_non_store_leaf_type(ty) {
1867 return None;
1868 }
1869
1870 Some(type_path)
1871}
1872
1873fn is_common_non_store_leaf_type(ty: &Type) -> bool {
1874 matches!(
1875 ty,
1876 Type::Path(TypePath { path, .. })
1877 if path.is_ident("bool")
1878 || path.is_ident("u8")
1879 || path.is_ident("u16")
1880 || path.is_ident("u32")
1881 || path.is_ident("u64")
1882 || path.is_ident("u128")
1883 || path.is_ident("usize")
1884 || path.is_ident("i8")
1885 || path.is_ident("i16")
1886 || path.is_ident("i32")
1887 || path.is_ident("i64")
1888 || path.is_ident("i128")
1889 || path.is_ident("isize")
1890 || path.is_ident("f32")
1891 || path.is_ident("f64")
1892 || path.is_ident("char")
1893 )
1894}
1895
1896fn secure_field_count(fields: &syn::punctuated::Punctuated<Field, syn::token::Comma>) -> usize {
1897 fields
1898 .iter()
1899 .filter(|field| has_secure_attr(&field.attrs))
1900 .count()
1901}
1902
1903fn relation_name_override(attrs: &[Attribute]) -> syn::Result<Option<String>> {
1904 for attr in attrs {
1905 if !attr.path().is_ident("relation") {
1906 continue;
1907 }
1908
1909 let mut name = None;
1910 attr.parse_nested_meta(|meta| {
1911 if meta.path.is_ident("name") {
1912 let value = meta.value()?;
1913 let literal: syn::LitStr = value.parse()?;
1914 name = Some(literal.value());
1915 Ok(())
1916 } else {
1917 Err(meta.error("unsupported relation attribute"))
1918 }
1919 })?;
1920 return Ok(name);
1921 }
1922
1923 Ok(None)
1924}
1925
1926enum SecureKind {
1927 Shape(Type),
1928}
1929
1930impl SecureKind {
1931 fn encrypted_type(&self) -> proc_macro2::TokenStream {
1932 match self {
1933 SecureKind::Shape(ty) => quote! { <#ty as ::appdb::SensitiveShape>::Encrypted },
1934 }
1935 }
1936
1937 fn encrypt_with_context_expr(&self, ident: &syn::Ident) -> proc_macro2::TokenStream {
1938 match self {
1939 SecureKind::Shape(ty) => {
1940 quote! { <#ty as ::appdb::SensitiveShape>::encrypt_with_context(&self.#ident, context)? }
1941 }
1942 }
1943 }
1944
1945 fn decrypt_with_context_expr(&self, ident: &syn::Ident) -> proc_macro2::TokenStream {
1946 match self {
1947 SecureKind::Shape(ty) => {
1948 quote! { <#ty as ::appdb::SensitiveShape>::decrypt_with_context(&encrypted.#ident, context)? }
1949 }
1950 }
1951 }
1952
1953 fn encrypt_with_runtime_expr(
1954 &self,
1955 ident: &syn::Ident,
1956 field_tag_ident: &syn::Ident,
1957 ) -> proc_macro2::TokenStream {
1958 match self {
1959 SecureKind::Shape(ty) => {
1960 quote! {{
1961 let context = ::appdb::crypto::resolve_crypto_context_for::<#field_tag_ident>()?;
1962 <#ty as ::appdb::SensitiveShape>::encrypt_with_context(&self.#ident, context.as_ref())?
1963 }}
1964 }
1965 }
1966 }
1967
1968 fn decrypt_with_runtime_expr(
1969 &self,
1970 ident: &syn::Ident,
1971 field_tag_ident: &syn::Ident,
1972 ) -> proc_macro2::TokenStream {
1973 match self {
1974 SecureKind::Shape(ty) => {
1975 quote! {{
1976 let context = ::appdb::crypto::resolve_crypto_context_for::<#field_tag_ident>()?;
1977 <#ty as ::appdb::SensitiveShape>::decrypt_with_context(&encrypted.#ident, context.as_ref())?
1978 }}
1979 }
1980 }
1981 }
1982}
1983
1984fn secure_kind(field: &Field) -> syn::Result<SecureKind> {
1985 if secure_shape_supported(&field.ty) {
1986 return Ok(SecureKind::Shape(field.ty.clone()));
1987 }
1988
1989 Err(Error::new_spanned(
1990 &field.ty,
1991 secure_shape_error_message(&field.ty),
1992 ))
1993}
1994
1995fn secure_shape_supported(ty: &Type) -> bool {
1996 if is_string_type(ty) {
1997 return true;
1998 }
1999
2000 if sensitive_value_wrapper_inner_type(ty).is_some() {
2001 return true;
2002 }
2003
2004 if let Some(inner) = option_inner_type(ty) {
2005 return secure_shape_supported(inner);
2006 }
2007
2008 if let Some(inner) = vec_inner_type(ty) {
2009 return secure_shape_supported(inner);
2010 }
2011
2012 direct_sensitive_child_type(ty).is_some()
2013}
2014
2015fn secure_shape_error_message(ty: &Type) -> &'static str {
2016 if invalid_secure_leaf_type(ty).is_some() {
2017 "#[secure] child shapes require a direct named Sensitive type leaf with only Option<_> and Vec<_> wrappers"
2018 } else {
2019 "#[secure] supports String, appdb::SensitiveValueOf<T>, and recursive Child / Option<Child> / Vec<Child> shapes where Child implements appdb::Sensitive"
2020 }
2021}
2022
2023fn direct_sensitive_child_type(ty: &Type) -> Option<&TypePath> {
2024 let Type::Path(type_path) = ty else {
2025 return None;
2026 };
2027
2028 let segment = type_path.path.segments.last()?;
2029 if !matches!(segment.arguments, PathArguments::None) {
2030 return None;
2031 }
2032
2033 if is_id_type(ty) || is_string_type(ty) || is_common_non_store_leaf_type(ty) {
2034 return None;
2035 }
2036
2037 Some(type_path)
2038}
2039
2040fn invalid_secure_leaf_type(ty: &Type) -> Option<Type> {
2041 if let Some(inner) = option_inner_type(ty) {
2042 return invalid_secure_leaf_type(inner);
2043 }
2044
2045 if let Some(inner) = vec_inner_type(ty) {
2046 return invalid_secure_leaf_type(inner);
2047 }
2048
2049 let leaf = direct_sensitive_child_type(ty)?.clone();
2050 let segment = leaf.path.segments.last()?;
2051 if matches!(segment.arguments, PathArguments::None) {
2052 None
2053 } else {
2054 Some(Type::Path(leaf))
2055 }
2056}
2057
2058fn is_string_type(ty: &Type) -> bool {
2059 match ty {
2060 Type::Path(TypePath { path, .. }) => path.is_ident("String"),
2061 _ => false,
2062 }
2063}
2064
2065fn is_id_type(ty: &Type) -> bool {
2066 match ty {
2067 Type::Path(TypePath { path, .. }) => path.segments.last().is_some_and(|segment| {
2068 let ident = segment.ident.to_string();
2069 ident == "Id"
2070 }),
2071 _ => false,
2072 }
2073}
2074
2075fn is_record_id_type(ty: &Type) -> bool {
2076 match ty {
2077 Type::Path(TypePath { path, .. }) => path.segments.last().is_some_and(|segment| {
2078 let ident = segment.ident.to_string();
2079 ident == "RecordId"
2080 }),
2081 _ => false,
2082 }
2083}
2084
2085fn option_inner_type(ty: &Type) -> Option<&Type> {
2086 let Type::Path(TypePath { path, .. }) = ty else {
2087 return None;
2088 };
2089 let segment = path.segments.last()?;
2090 if segment.ident != "Option" {
2091 return None;
2092 }
2093 let PathArguments::AngleBracketed(args) = &segment.arguments else {
2094 return None;
2095 };
2096 let GenericArgument::Type(inner) = args.args.first()? else {
2097 return None;
2098 };
2099 Some(inner)
2100}
2101
2102fn vec_inner_type(ty: &Type) -> Option<&Type> {
2103 let Type::Path(TypePath { path, .. }) = ty else {
2104 return None;
2105 };
2106 let segment = path.segments.last()?;
2107 if segment.ident != "Vec" {
2108 return None;
2109 }
2110 let PathArguments::AngleBracketed(args) = &segment.arguments else {
2111 return None;
2112 };
2113 let GenericArgument::Type(inner) = args.args.first()? else {
2114 return None;
2115 };
2116 Some(inner)
2117}
2118
2119fn sensitive_value_wrapper_inner_type(ty: &Type) -> Option<&Type> {
2120 let Type::Path(TypePath { path, .. }) = ty else {
2121 return None;
2122 };
2123 let segment = path.segments.last()?;
2124 if segment.ident != "SensitiveValueOf" {
2125 return None;
2126 }
2127 let PathArguments::AngleBracketed(args) = &segment.arguments else {
2128 return None;
2129 };
2130 let GenericArgument::Type(inner) = args.args.first()? else {
2131 return None;
2132 };
2133 Some(inner)
2134}
2135
2136fn to_snake_case(input: &str) -> String {
2137 let mut out = String::with_capacity(input.len() + 4);
2138 let mut prev_is_lower_or_digit = false;
2139
2140 for ch in input.chars() {
2141 if ch.is_ascii_uppercase() {
2142 if prev_is_lower_or_digit {
2143 out.push('_');
2144 }
2145 out.push(ch.to_ascii_lowercase());
2146 prev_is_lower_or_digit = false;
2147 } else {
2148 out.push(ch);
2149 prev_is_lower_or_digit = ch.is_ascii_lowercase() || ch.is_ascii_digit();
2150 }
2151 }
2152
2153 out
2154}
2155
2156fn to_pascal_case(input: &str) -> String {
2157 let mut out = String::with_capacity(input.len());
2158 let mut uppercase_next = true;
2159
2160 for ch in input.chars() {
2161 if ch == '_' || ch == '-' {
2162 uppercase_next = true;
2163 continue;
2164 }
2165
2166 if uppercase_next {
2167 out.push(ch.to_ascii_uppercase());
2168 uppercase_next = false;
2169 } else {
2170 out.push(ch);
2171 }
2172 }
2173
2174 out
2175}