1use super::{
2 FieldOutputCapabilities, UserConfig, UserFieldConfig, UserFieldFactory, UserFieldReference,
3 UserFieldType,
4};
5use crate::AuthResult;
6use crate::store::schema::resolve_field_name;
7use crate::{FieldMap, FieldValue as Value};
8use std::sync::Arc;
9
10pub struct AdapterRecord {
12 output: FieldMap,
13 storage: FieldMap,
14 raw_storage: Option<FieldOutputCapabilities>,
15}
16
17impl AdapterRecord {
18 pub fn new(core: FieldMap, storage: FieldMap) -> Self {
20 Self {
21 output: core,
22 storage,
23 raw_storage: None,
24 }
25 }
26
27 pub fn map_storage_fields(
30 &mut self,
31 fields: &UserConfig,
32 capabilities: FieldOutputCapabilities,
33 map: impl Fn(&str, &UserFieldConfig) -> AuthResult<Option<Value>>,
34 ) -> AuthResult<()> {
35 for (name, field) in fields.fields() {
36 if name == "id" {
37 continue;
38 }
39 let storage = resolve_field_name(field.field_name.as_deref(), name);
40 if let Some(value) = map(storage, field)? {
41 let _ = self.storage.insert(storage.to_owned(), value);
42 }
43 }
44 self.raw_storage = Some(capabilities);
45 Ok(())
46 }
47
48 pub fn map_native_fields(
50 &mut self,
51 fields: &UserConfig,
52 map: impl Fn(&str) -> AuthResult<Option<Value>>,
53 ) -> AuthResult<()> {
54 for (name, value) in &mut self.output {
55 if !fields.fields().contains_key(name)
56 && let Some(raw) = map(name)?
57 {
58 *value = raw;
59 }
60 }
61 Ok(())
62 }
63}
64
65fn field(field_type: UserFieldType, required: bool) -> UserFieldConfig {
66 UserFieldConfig {
67 field_type,
68 required: Some(required),
69 ..Default::default()
70 }
71}
72
73fn timestamp(default: bool, update: bool) -> UserFieldConfig {
74 let now: UserFieldFactory = Arc::new(|| Ok(chrono::Utc::now().into()));
75 UserFieldConfig {
76 default_value_fn: default.then(|| now.clone()),
77 on_update: update.then_some(now),
78 ..field(UserFieldType::Date, true)
79 }
80}
81
82impl crate::config::AccountConfig {
83 pub fn field_schema(&self) -> UserConfig {
85 let mut fields = [
86 ("accountId".into(), field(UserFieldType::String, true)),
87 ("providerId".into(), field(UserFieldType::String, true)),
88 (
89 "userId".into(),
90 UserFieldConfig {
91 references: Some(UserFieldReference {
92 model: "user".into(),
93 field: "id".into(),
94 }),
95 ..field(UserFieldType::String, true)
96 },
97 ),
98 (
99 "accessToken".into(),
100 UserFieldConfig {
101 returned: Some(false),
102 ..field(UserFieldType::String, false)
103 },
104 ),
105 (
106 "refreshToken".into(),
107 UserFieldConfig {
108 returned: Some(false),
109 ..field(UserFieldType::String, false)
110 },
111 ),
112 (
113 "idToken".into(),
114 UserFieldConfig {
115 returned: Some(false),
116 ..field(UserFieldType::String, false)
117 },
118 ),
119 (
120 "accessTokenExpiresAt".into(),
121 UserFieldConfig {
122 returned: Some(false),
123 ..field(UserFieldType::Date, false)
124 },
125 ),
126 (
127 "refreshTokenExpiresAt".into(),
128 UserFieldConfig {
129 returned: Some(false),
130 ..field(UserFieldType::Date, false)
131 },
132 ),
133 ("scope".into(), field(UserFieldType::String, false)),
134 (
135 "password".into(),
136 UserFieldConfig {
137 returned: Some(false),
138 ..field(UserFieldType::String, false)
139 },
140 ),
141 ("createdAt".into(), timestamp(true, false)),
142 ("updatedAt".into(), timestamp(false, true)),
143 ]
144 .into_iter()
145 .collect::<indexmap::IndexMap<_, _>>();
146 fields.extend(self.additional_fields.clone());
147 UserConfig {
148 additional_fields: Some(fields),
149 }
150 }
151}
152
153impl crate::config::VerificationConfig {
154 pub fn field_schema(&self) -> UserConfig {
156 let mut fields = [
157 ("identifier".into(), field(UserFieldType::String, true)),
158 ("value".into(), field(UserFieldType::String, true)),
159 ("expiresAt".into(), field(UserFieldType::Date, true)),
160 ("createdAt".into(), timestamp(true, false)),
161 ("updatedAt".into(), timestamp(true, true)),
162 ]
163 .into_iter()
164 .collect::<indexmap::IndexMap<_, _>>();
165 fields.extend(self.additional_fields.clone());
166 UserConfig {
167 additional_fields: Some(fields),
168 }
169 }
170}
171
172impl UserConfig {
173 pub async fn organization_output_records(
175 &self,
176 records: Vec<AdapterRecord>,
177 supports_native_json: bool,
178 ) -> AuthResult<Vec<FieldMap>> {
179 self.organization_output_records_then(records, supports_native_json, |_, output| {
180 std::future::ready(Ok(output))
181 })
182 .await
183 }
184
185 pub async fn organization_output_records_then<R: Send, F>(
187 &self,
188 records: Vec<AdapterRecord>,
189 supports_native_json: bool,
190 complete: impl Fn(usize, FieldMap) -> F + Sync,
191 ) -> AuthResult<Vec<R>>
192 where
193 F: std::future::Future<Output = AuthResult<R>> + Send,
194 {
195 self.organization_output_records_with_json(records, |_| supports_native_json, complete)
196 .await
197 }
198
199 pub(crate) async fn organization_output_memory_records(
200 &self,
201 records: Vec<AdapterRecord>,
202 ) -> AuthResult<Vec<FieldMap>> {
203 self.organization_output_records_with_json(
204 records,
205 UserFieldConfig::references_id,
206 |_, output| std::future::ready(Ok(output)),
207 )
208 .await
209 }
210
211 async fn organization_output_records_with_json<R: Send, F>(
212 &self,
213 records: Vec<AdapterRecord>,
214 supports_native_json: impl Fn(&UserFieldConfig) -> bool + Sync,
215 complete: impl Fn(usize, FieldMap) -> F + Sync,
216 ) -> AuthResult<Vec<R>>
217 where
218 F: std::future::Future<Output = AuthResult<R>> + Send,
219 {
220 let mut rows = self.organization_records(records)?;
221 super::batch::project_fields_then(
222 &mut rows,
223 self.fields(),
224 |row, name, field| {
225 Box::pin(project_organization_field(
226 row,
227 name,
228 field,
229 supports_native_json(field),
230 ))
231 },
232 |index, record| complete(index, std::mem::take(&mut record.output)),
233 )
234 .await
235 }
236
237 pub async fn organization_output_records_batches_then<V: Send, R: Send, F>(
239 &self,
240 records: Vec<AdapterRecord>,
241 supports_native_json: bool,
242 decode: impl Fn(usize, FieldMap) -> AuthResult<V> + Sync,
243 complete: impl Fn(Vec<(usize, V)>) -> F + Sync,
244 ) -> AuthResult<Vec<R>>
245 where
246 F: std::future::Future<Output = AuthResult<Vec<(usize, R)>>> + Send,
247 {
248 self.organization_output_records_batches_with_json(
249 records,
250 |_| supports_native_json,
251 decode,
252 complete,
253 )
254 .await
255 }
256
257 pub(crate) async fn organization_output_memory_records_batches_then<V: Send, R: Send, F>(
258 &self,
259 records: Vec<AdapterRecord>,
260 decode: impl Fn(usize, FieldMap) -> AuthResult<V> + Sync,
261 complete: impl Fn(Vec<(usize, V)>) -> F + Sync,
262 ) -> AuthResult<Vec<R>>
263 where
264 F: std::future::Future<Output = AuthResult<Vec<(usize, R)>>> + Send,
265 {
266 self.organization_output_records_batches_with_json(
267 records,
268 UserFieldConfig::references_id,
269 decode,
270 complete,
271 )
272 .await
273 }
274
275 async fn organization_output_records_batches_with_json<V: Send, R: Send, F>(
276 &self,
277 records: Vec<AdapterRecord>,
278 supports_native_json: impl Fn(&UserFieldConfig) -> bool + Sync,
279 decode: impl Fn(usize, FieldMap) -> AuthResult<V> + Sync,
280 complete: impl Fn(Vec<(usize, V)>) -> F + Sync,
281 ) -> AuthResult<Vec<R>>
282 where
283 F: std::future::Future<Output = AuthResult<Vec<(usize, R)>>> + Send,
284 {
285 let mut rows = self.organization_records(records)?;
286 super::batch::project_fields_batches_then(
287 &mut rows,
288 self.fields(),
289 |row, name, field| {
290 Box::pin(project_organization_field(
291 row,
292 name,
293 field,
294 supports_native_json(field),
295 ))
296 },
297 |index, record| decode(index, std::mem::take(&mut record.output)),
298 complete,
299 )
300 .await
301 }
302
303 fn organization_records(
304 &self,
305 records: Vec<AdapterRecord>,
306 ) -> AuthResult<Vec<OrganizationRecord>> {
307 records
308 .into_iter()
309 .map(|mut record| {
310 record
311 .output
312 .retain(|name, _| name == "id" || !self.fields().contains_key(name));
313 Ok(record)
314 })
315 .collect()
316 }
317
318 pub fn record_storage_key<'a>(&'a self, name: &'a str) -> &'a str {
320 if name == "id" {
321 return name;
322 }
323 resolve_field_name(
324 self.fields()
325 .get(name)
326 .and_then(|field| field.field_name.as_deref()),
327 name,
328 )
329 }
330
331 pub async fn project_record(
333 &self,
334 storage: &FieldMap,
335 supports_native_json: bool,
336 supports_native_dates: bool,
337 ) -> AuthResult<FieldMap> {
338 Ok(self
340 .project_records(
341 std::slice::from_ref(storage),
342 supports_native_json,
343 supports_native_dates,
344 )
345 .await?
346 .remove(0))
347 }
348
349 pub async fn project_records(
351 &self,
352 storage: &[FieldMap],
353 supports_native_json: bool,
354 supports_native_dates: bool,
355 ) -> AuthResult<Vec<FieldMap>> {
356 self.project_records_then(
357 storage,
358 supports_native_json,
359 supports_native_dates,
360 |_, fields| std::future::ready(Ok(fields)),
361 )
362 .await
363 }
364
365 pub(crate) async fn project_memory_records(
367 &self,
368 storage: &[FieldMap],
369 ) -> AuthResult<Vec<FieldMap>> {
370 self.project_memory_adapter_records(adapter_records(storage)?)
371 .await
372 }
373
374 pub(crate) async fn project_memory_records_batches_then<R: Send, F>(
376 &self,
377 storage: &[FieldMap],
378 complete: impl Fn(Vec<(usize, FieldMap)>) -> F + Sync,
379 ) -> AuthResult<Vec<R>>
380 where
381 F: std::future::Future<Output = AuthResult<Vec<(usize, R)>>> + Send,
382 {
383 let mut records = adapter_records(storage)?;
384 super::batch::project_fields_batches_then(
385 &mut records,
386 self.fields(),
387 |record, name, field| {
388 Box::pin(project_adapter_field(
389 record,
390 name,
391 field,
392 field.references_id(),
393 true,
394 ))
395 },
396 |_, record| Ok(std::mem::take(&mut record.output)),
397 complete,
398 )
399 .await
400 }
401
402 pub async fn project_records_then<R: Send, F>(
405 &self,
406 storage: &[FieldMap],
407 supports_native_json: bool,
408 supports_native_dates: bool,
409 complete: impl Fn(usize, FieldMap) -> F + Sync,
410 ) -> AuthResult<Vec<R>>
411 where
412 F: std::future::Future<Output = AuthResult<R>> + Send,
413 {
414 let records = adapter_records(storage)?;
415 self.project_adapter_records_then(
416 records,
417 supports_native_json,
418 supports_native_dates,
419 complete,
420 )
421 .await
422 }
423
424 pub async fn project_records_batches_then<R: Send, F>(
426 &self,
427 storage: &[FieldMap],
428 supports_native_json: bool,
429 supports_native_dates: bool,
430 complete: impl Fn(Vec<(usize, FieldMap)>) -> F + Sync,
431 ) -> AuthResult<Vec<R>>
432 where
433 F: std::future::Future<Output = AuthResult<Vec<(usize, R)>>> + Send,
434 {
435 self.project_adapter_records_batches_then(
436 adapter_records(storage)?,
437 supports_native_json,
438 supports_native_dates,
439 complete,
440 )
441 .await
442 }
443
444 pub async fn record_storage_fields_for_adapter(
447 &self,
448 input: FieldMap,
449 create: bool,
450 supports_native_json: bool,
451 native_json_field: impl Fn(&str) -> bool,
452 ) -> AuthResult<FieldMap> {
453 self.record_storage_fields_with_binding(input, create, |name, field, value| {
454 field.adapter_input(value, supports_native_json, native_json_field(name))
455 })
456 .await
457 }
458
459 pub async fn record_storage_fields_with_binding(
461 &self,
462 input: FieldMap,
463 create: bool,
464 bind: impl Fn(&str, &super::UserFieldConfig, Value) -> AuthResult<Value>,
465 ) -> AuthResult<FieldMap> {
466 let mut output = FieldMap::new();
467 if let Some(id) = input.get("id") {
468 let _ = output.insert("id".into(), id.clone());
469 }
470 output.extend(self.storage_fields_async(input, create, true, bind).await?);
471 Ok(output)
472 }
473
474 pub async fn record_output_fields(
477 &self,
478 core: FieldMap,
479 storage: &FieldMap,
480 supports_native_json: bool,
481 supports_native_dates: bool,
482 ) -> AuthResult<FieldMap> {
483 Ok(self
485 .project_adapter_records(
486 vec![AdapterRecord::new(core, storage.clone())],
487 supports_native_json,
488 supports_native_dates,
489 )
490 .await?
491 .remove(0))
492 }
493
494 pub async fn project_adapter_records(
496 &self,
497 records: Vec<AdapterRecord>,
498 supports_native_json: bool,
499 supports_native_dates: bool,
500 ) -> AuthResult<Vec<FieldMap>> {
501 self.project_adapter_records_then(
502 records,
503 supports_native_json,
504 supports_native_dates,
505 |_, fields| std::future::ready(Ok(fields)),
506 )
507 .await
508 }
509
510 pub async fn project_adapter_records_with_capabilities(
512 &self,
513 mut records: Vec<AdapterRecord>,
514 capabilities: FieldOutputCapabilities,
515 supports_native_dates: bool,
516 ) -> AuthResult<Vec<FieldMap>> {
517 super::batch::project_fields(&mut records, self.fields(), |record, name, field| {
518 Box::pin(project_adapter_field_with_capabilities(
519 record,
520 name,
521 field,
522 capabilities,
523 supports_native_dates,
524 ))
525 })
526 .await?;
527 Ok(records.into_iter().map(|record| record.output).collect())
528 }
529
530 pub(crate) async fn project_memory_adapter_records(
532 &self,
533 mut records: Vec<AdapterRecord>,
534 ) -> AuthResult<Vec<FieldMap>> {
535 super::batch::project_fields(&mut records, self.fields(), |record, name, field| {
536 Box::pin(project_adapter_field(
537 record,
538 name,
539 field,
540 field.references_id(),
541 true,
542 ))
543 })
544 .await?;
545 Ok(records.into_iter().map(|record| record.output).collect())
546 }
547
548 pub async fn project_adapter_records_then<R: Send, F>(
551 &self,
552 mut records: Vec<AdapterRecord>,
553 supports_native_json: bool,
554 supports_native_dates: bool,
555 complete: impl Fn(usize, FieldMap) -> F + Sync,
556 ) -> AuthResult<Vec<R>>
557 where
558 F: std::future::Future<Output = AuthResult<R>> + Send,
559 {
560 super::batch::project_fields_then(
561 &mut records,
562 self.fields(),
563 |record, name, field| {
564 Box::pin(project_adapter_field(
565 record,
566 name,
567 field,
568 supports_native_json,
569 supports_native_dates,
570 ))
571 },
572 |index, record| complete(index, std::mem::take(&mut record.output)),
573 )
574 .await
575 }
576
577 pub async fn project_adapter_records_batches_then<R: Send, F>(
579 &self,
580 mut records: Vec<AdapterRecord>,
581 supports_native_json: bool,
582 supports_native_dates: bool,
583 complete: impl Fn(Vec<(usize, FieldMap)>) -> F + Sync,
584 ) -> AuthResult<Vec<R>>
585 where
586 F: std::future::Future<Output = AuthResult<Vec<(usize, R)>>> + Send,
587 {
588 super::batch::project_fields_batches_then(
589 &mut records,
590 self.fields(),
591 |record, name, field| {
592 Box::pin(project_adapter_field(
593 record,
594 name,
595 field,
596 supports_native_json,
597 supports_native_dates,
598 ))
599 },
600 |_, record| Ok(std::mem::take(&mut record.output)),
601 complete,
602 )
603 .await
604 }
605}
606
607type OrganizationRecord = AdapterRecord;
608
609async fn project_organization_field(
610 record: &mut OrganizationRecord,
611 name: &str,
612 field: &UserFieldConfig,
613 supports_native_json: bool,
614) -> AuthResult<()> {
615 if name == "id" {
616 return Ok(());
617 }
618 let value = record
619 .storage
620 .get(resolve_field_name(field.field_name.as_deref(), name))
621 .cloned()
622 .unwrap_or_default();
623 let value = if let Some(capabilities) = record.raw_storage {
624 field.adapter_output_from_raw(value, capabilities).await?
625 } else {
626 field.adapter_output(value, supports_native_json).await?
627 };
628 super::organization::assign_output(&mut record.output, name, field, value)
629}
630
631fn adapter_records(storage: &[FieldMap]) -> AuthResult<Vec<AdapterRecord>> {
632 storage
633 .iter()
634 .map(|storage| {
635 let mut core = FieldMap::new();
636 if let Some(id) = storage.get("id") {
637 let _ = core.insert(
638 "id".into(),
639 if id.is_null() || id.is_undefined() {
640 id.clone()
641 } else {
642 Value::String(
643 crate::SchemaValue::<String>::from_field(id.clone())
644 .display_string()?,
645 )
646 },
647 );
648 }
649 Ok(AdapterRecord::new(core, storage.clone()))
650 })
651 .collect()
652}
653
654async fn project_adapter_field(
655 record: &mut AdapterRecord,
656 name: &str,
657 field: &UserFieldConfig,
658 supports_native_json: bool,
659 supports_native_dates: bool,
660) -> AuthResult<()> {
661 project_adapter_field_with_capabilities(
662 record,
663 name,
664 field,
665 FieldOutputCapabilities::json_only(supports_native_json),
666 supports_native_dates,
667 )
668 .await
669}
670
671async fn project_adapter_field_with_capabilities(
672 record: &mut AdapterRecord,
673 name: &str,
674 field: &UserFieldConfig,
675 capabilities: FieldOutputCapabilities,
676 supports_native_dates: bool,
677) -> AuthResult<()> {
678 if name == "id" {
679 return Ok(());
680 }
681 let value = record
682 .storage
683 .get(resolve_field_name(field.field_name.as_deref(), name))
684 .cloned()
685 .unwrap_or_default();
686 let value = if let Some(capabilities) = record.raw_storage {
687 field.adapter_output_from_raw(value, capabilities).await?
688 } else {
689 field
690 .adapter_output_with_capabilities(value, capabilities)
691 .await?
692 };
693 let value = project_output_value(value, field, supports_native_dates);
694 let _ = record.output.insert(name.to_owned(), value);
695 Ok(())
696}
697
698pub(crate) async fn project_adapter_value(
699 value: Value,
700 field: &UserFieldConfig,
701 supports_native_json: bool,
702 supports_native_dates: bool,
703) -> AuthResult<Value> {
704 let value = field.adapter_output(value, supports_native_json).await?;
705 Ok(project_output_value(value, field, supports_native_dates))
706}
707
708fn project_output_value(
709 value: Value,
710 field: &UserFieldConfig,
711 supports_native_dates: bool,
712) -> Value {
713 if !supports_native_dates
714 && !field.uses_id_output()
715 && matches!(field.field_type, UserFieldType::Date)
716 {
717 match value {
718 Value::String(text) => Value::Date(
719 crate::utils::date::parse_adapter_date(&text)
720 .map(crate::FieldDate::from)
721 .unwrap_or_else(crate::FieldDate::invalid),
722 ),
723 value => value,
724 }
725 } else {
726 value
727 }
728}
729
730#[cfg(test)]
731mod tests {
732 use super::*;
733 use crate::user_fields::{FieldTransforms, UserFieldTransform};
734 macro_rules! json {
735 ($($token:tt)*) => { Value::from_json(serde_json::json!($($token)*)).expect("valid JSON field") };
736 }
737
738 #[tokio::test]
739 async fn raw_adapter_json_reaches_callbacks_before_output_decoding() -> AuthResult<()> {
740 for raw in [Value::Null, json!({"owner": "Alpha"}), json!(["Alpha"])] {
741 let expected = raw.clone();
742 let fields = UserConfig {
743 additional_fields: Some(
744 [(
745 "payload".into(),
746 UserFieldConfig {
747 field_type: UserFieldType::Json,
748 transform: Some(FieldTransforms {
749 output: Some(UserFieldTransform::new(move |value| {
750 assert_eq!(value, expected.clone());
751 Ok(json!("[3,4]"))
752 })),
753 ..Default::default()
754 }),
755 ..Default::default()
756 },
757 )]
758 .into(),
759 ),
760 };
761 let mut record = AdapterRecord::new(FieldMap::new(), FieldMap::new());
762 record.map_storage_fields(
763 &fields,
764 FieldOutputCapabilities::json_only(false),
765 |_, _| Ok(Some(raw.clone())),
766 )?;
767 let projected = fields
768 .project_adapter_records_with_capabilities(
769 vec![record],
770 FieldOutputCapabilities::json_only(false),
771 true,
772 )
773 .await?;
774 assert_eq!(
775 projected.first().and_then(|row| row.get("payload")),
776 Some(&json!([3, 4]))
777 );
778 }
779 Ok(())
780 }
781}