1use serde::de::Deserializer;
54use serde::{Deserialize, Serialize};
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60#[non_exhaustive]
61pub enum MutationOp {
62 Create,
64 Update,
66 Delete,
68}
69
70impl MutationOp {
71 #[must_use]
73 pub const fn as_str(self) -> &'static str {
74 match self {
75 Self::Create => "create",
76 Self::Update => "update",
77 Self::Delete => "delete",
78 }
79 }
80}
81
82impl std::fmt::Display for MutationOp {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 f.write_str(self.as_str())
85 }
86}
87
88#[derive(Debug, Clone)]
93pub struct MutationContext {
94 pub op: MutationOp,
96 pub actor: Option<String>,
98 pub request_id: Option<String>,
100 pub now: chrono::DateTime<chrono::Utc>,
102 pub invalidate_keys: Vec<String>,
104}
105
106impl MutationContext {
107 #[must_use]
112 pub fn new(op: MutationOp) -> Self {
113 Self {
114 op,
115 actor: None,
116 request_id: Some(uuid::Uuid::new_v4().to_string()),
117 now: chrono::Utc::now(),
118 invalidate_keys: Vec::new(),
119 }
120 }
121
122 pub fn invalidate(&mut self, key: impl Into<String>) {
124 self.invalidate_keys.push(key.into());
125 }
126}
127
128use crate::AutumnResult;
131use std::future::Future;
132
133pub trait MutationHooks: Send + Sync + 'static {
146 type Model: Send + Sync;
148 type NewModel: Send + Sync;
150 type UpdateModel: Send + Sync;
152
153 fn before_create(
155 &self,
156 _ctx: &mut MutationContext,
157 _new: &mut Self::NewModel,
158 ) -> impl Future<Output = AutumnResult<()>> + Send {
159 async { Ok(()) }
160 }
161
162 fn before_update(
173 &self,
174 _ctx: &mut MutationContext,
175 _draft: &mut UpdateDraft<Self::Model>,
176 ) -> impl Future<Output = AutumnResult<()>> + Send
177 where
178 Self::Model: Clone,
179 {
180 async { Ok(()) }
181 }
182
183 fn before_delete(
185 &self,
186 _ctx: &mut MutationContext,
187 _record: &Self::Model,
188 ) -> impl Future<Output = AutumnResult<()>> + Send {
189 async { Ok(()) }
190 }
191
192 fn after_create(
194 &self,
195 _ctx: &mut MutationContext,
196 _record: &Self::Model,
197 ) -> impl Future<Output = AutumnResult<()>> + Send {
198 async { Ok(()) }
199 }
200
201 fn after_update(
203 &self,
204 _ctx: &mut MutationContext,
205 _record: &Self::Model,
206 ) -> impl Future<Output = AutumnResult<()>> + Send {
207 async { Ok(()) }
208 }
209}
210
211pub trait RepositoryHooksDefault: Sized {
217 fn autumn_default() -> Self;
219}
220
221impl<T> RepositoryHooksDefault for T
222where
223 T: Default,
224{
225 fn autumn_default() -> Self {
226 Self::default()
227 }
228}
229
230pub trait RepositoryHooksClone: Sized {
235 #[must_use]
237 fn autumn_clone(&self) -> Self;
238}
239
240impl<T> RepositoryHooksClone for T
241where
242 T: Clone,
243{
244 fn autumn_clone(&self) -> Self {
245 self.clone()
246 }
247}
248
249pub struct NoHooks<M, N, U> {
256 _phantom: std::marker::PhantomData<(M, N, U)>,
257}
258
259impl<M, N, U> Default for NoHooks<M, N, U> {
260 fn default() -> Self {
261 Self {
262 _phantom: std::marker::PhantomData,
263 }
264 }
265}
266
267impl<M, N, U> MutationHooks for NoHooks<M, N, U>
268where
269 M: Send + Sync + Clone + 'static,
270 N: Send + Sync + 'static,
271 U: Send + Sync + 'static,
272{
273 type Model = M;
274 type NewModel = N;
275 type UpdateModel = U;
276}
277
278#[derive(Debug, Clone, Default, PartialEq, Eq)]
288pub enum Patch<T> {
289 #[default]
291 Unchanged,
292 Set(T),
294 Clear,
296}
297
298impl<T> Patch<T> {
299 #[must_use]
301 pub const fn is_unchanged(&self) -> bool {
302 matches!(self, Self::Unchanged)
303 }
304
305 #[must_use]
307 pub const fn is_set(&self) -> bool {
308 matches!(self, Self::Set(_))
309 }
310
311 #[must_use]
313 pub const fn is_clear(&self) -> bool {
314 matches!(self, Self::Clear)
315 }
316
317 #[must_use]
319 pub const fn as_set(&self) -> Option<&T> {
320 match self {
321 Self::Set(v) => Some(v),
322 _ => None,
323 }
324 }
325
326 #[must_use]
332 pub fn into_option(self) -> Option<Option<T>> {
333 match self {
334 Self::Set(v) => Some(Some(v)),
335 Self::Clear => Some(None),
336 Self::Unchanged => None,
337 }
338 }
339}
340
341impl<T: Serialize> Serialize for Patch<T> {
342 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
343 match self {
344 Self::Unchanged | Self::Clear => serializer.serialize_none(),
345 Self::Set(v) => v.serialize(serializer),
346 }
347 }
348}
349
350impl<'de, T: Deserialize<'de>> Deserialize<'de> for Patch<T> {
351 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
352 let opt: Option<T> = Option::deserialize(deserializer)?;
355 Ok(opt.map_or_else(|| Self::Clear, Self::Set))
356 }
357}
358
359#[derive(Debug, Clone, PartialEq, Eq)]
365pub struct FieldDiff<T> {
366 before: T,
367 after: T,
368}
369
370impl<T: PartialEq> FieldDiff<T> {
371 #[must_use]
373 pub const fn new(before: T, after: T) -> Self {
374 Self { before, after }
375 }
376
377 #[must_use]
379 pub const fn before(&self) -> &T {
380 &self.before
381 }
382
383 #[must_use]
385 pub const fn after(&self) -> &T {
386 &self.after
387 }
388
389 #[must_use]
391 pub fn changed(&self) -> bool {
392 self.before != self.after
393 }
394
395 #[must_use]
397 pub fn unchanged(&self) -> bool {
398 self.before == self.after
399 }
400
401 #[must_use]
403 pub fn changed_to(&self, value: &T) -> bool {
404 self.changed() && self.after == *value
405 }
406
407 #[must_use]
409 pub fn changed_from(&self, value: &T) -> bool {
410 self.changed() && self.before == *value
411 }
412
413 pub fn set(&mut self, value: T) {
415 self.after = value;
416 }
417}
418
419impl<T: PartialEq> FieldDiff<Option<T>> {
420 #[must_use]
422 pub const fn was_set(&self) -> bool {
423 self.before.is_none() && self.after.is_some()
424 }
425
426 #[must_use]
428 pub const fn was_cleared(&self) -> bool {
429 self.before.is_some() && self.after.is_none()
430 }
431}
432
433#[derive(Debug, Clone)]
454pub struct UpdateDraft<T: Clone> {
455 pub before: T,
460 pub after: T,
465}
466
467impl<T: Clone> UpdateDraft<T> {
468 #[must_use]
473 pub fn new(before: T) -> Self {
474 let after = before.clone();
475 Self { before, after }
476 }
477
478 #[must_use]
482 pub const fn new_with_changes(before: T, after: T) -> Self {
483 Self { before, after }
484 }
485
486 #[must_use]
488 pub const fn before(&self) -> &T {
489 &self.before
490 }
491
492 #[must_use]
494 pub const fn after(&self) -> &T {
495 &self.after
496 }
497
498 #[must_use]
502 pub const fn after_mut(&mut self) -> &mut T {
503 &mut self.after
504 }
505
506 #[must_use]
508 pub fn into_after(self) -> T {
509 self.after
510 }
511}
512
513#[derive(Debug)]
531pub struct DraftField<'a, T> {
532 before: &'a T,
533 after: &'a mut T,
534}
535
536impl<'a, T> DraftField<'a, T> {
537 #[must_use]
539 pub const fn new(before: &'a T, after: &'a mut T) -> Self {
540 Self { before, after }
541 }
542
543 #[must_use]
545 pub const fn before(&self) -> &T {
546 self.before
547 }
548
549 #[must_use]
551 pub const fn after(&self) -> &T {
552 self.after
553 }
554
555 pub fn set(&mut self, value: T) {
557 *self.after = value;
558 }
559}
560
561impl<T: PartialEq> DraftField<'_, T> {
562 #[must_use]
564 pub fn changed(&self) -> bool {
565 self.before != self.after
566 }
567
568 #[must_use]
570 pub fn unchanged(&self) -> bool {
571 self.before == self.after
572 }
573
574 #[must_use]
576 pub fn changed_to(&self, value: &T) -> bool {
577 self.changed() && *self.after == *value
578 }
579
580 #[must_use]
582 pub fn changed_from(&self, value: &T) -> bool {
583 self.changed() && *self.before == *value
584 }
585}
586
587impl<T: PartialEq> DraftField<'_, Option<T>> {
588 #[must_use]
590 pub const fn was_set(&self) -> bool {
591 self.before.is_none() && self.after.is_some()
592 }
593
594 #[must_use]
596 pub const fn was_cleared(&self) -> bool {
597 self.before.is_some() && self.after.is_none()
598 }
599}
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604
605 #[test]
608 fn patch_unchanged_is_default() {
609 let p: Patch<String> = Patch::default();
610 assert!(p.is_unchanged());
611 assert!(!p.is_set());
612 assert!(!p.is_clear());
613 }
614
615 #[test]
616 fn patch_set_holds_value() {
617 let p = Patch::Set("hello");
618 assert!(p.is_set());
619 assert!(!p.is_unchanged());
620 assert!(!p.is_clear());
621 assert_eq!(p.as_set(), Some(&"hello"));
622 }
623
624 #[test]
625 fn patch_clear_is_clear() {
626 let p: Patch<i32> = Patch::Clear;
627 assert!(p.is_clear());
628 assert!(!p.is_set());
629 assert!(!p.is_unchanged());
630 }
631
632 #[test]
633 fn patch_into_option_set() {
634 assert_eq!(Patch::Set(42).into_option(), Some(Some(42)));
635 }
636
637 #[test]
638 fn patch_into_option_clear() {
639 assert_eq!(Patch::<i32>::Clear.into_option(), Some(None));
640 }
641
642 #[test]
643 fn patch_into_option_unchanged() {
644 assert_eq!(Patch::<i32>::Unchanged.into_option(), None);
645 }
646
647 #[test]
650 fn field_diff_unchanged() {
651 let diff = FieldDiff::new(1, 1);
652 assert!(diff.unchanged());
653 assert!(!diff.changed());
654 }
655
656 #[test]
657 fn field_diff_changed() {
658 let diff = FieldDiff::new(1, 2);
659 assert!(diff.changed());
660 }
661
662 #[test]
663 fn field_diff_changed_to() {
664 let diff = FieldDiff::new(1, 2);
665 assert!(diff.changed_to(&2));
666 }
667
668 #[test]
669 fn field_diff_changed_from() {
670 let diff = FieldDiff::new(1, 2);
671 assert!(diff.changed_from(&1));
672 }
673
674 #[test]
675 fn field_diff_set_updates_after() {
676 let mut diff = FieldDiff::new(1, 1);
677 assert!(diff.unchanged());
678 diff.set(5);
679 assert!(diff.changed());
680 assert_eq!(diff.after(), &5);
681 assert_eq!(diff.before(), &1);
682 }
683
684 #[test]
685 fn field_diff_option_was_set() {
686 let diff = FieldDiff::new(None, Some(42));
687 assert!(diff.was_set());
688 }
689
690 #[test]
691 fn field_diff_option_was_cleared() {
692 let diff = FieldDiff::new(Some(42), None);
693 assert!(diff.was_cleared());
694 }
695
696 #[test]
699 fn mutation_op_as_str() {
700 assert_eq!(MutationOp::Create.as_str(), "create");
701 assert_eq!(MutationOp::Update.as_str(), "update");
702 assert_eq!(MutationOp::Delete.as_str(), "delete");
703 }
704
705 #[test]
706 fn mutation_op_display() {
707 assert_eq!(format!("{}", MutationOp::Create), "create");
708 }
709
710 #[test]
713 fn mutation_context_auto_populates() {
714 let ctx = MutationContext::new(MutationOp::Create);
715 assert!(ctx.actor.is_none());
716 assert!(ctx.request_id.is_some());
717 assert_eq!(ctx.request_id.as_ref().unwrap().len(), 36);
719 assert!(matches!(ctx.op, MutationOp::Create));
720 assert!(ctx.invalidate_keys.is_empty());
721 }
722
723 #[test]
724 fn mutation_context_invalidate_pushes_key() {
725 let mut ctx = MutationContext::new(MutationOp::Create);
726 assert!(ctx.invalidate_keys.is_empty());
727 ctx.invalidate("cache:key");
728 assert_eq!(ctx.invalidate_keys, vec!["cache:key".to_string()]);
729 }
730
731 #[test]
732 fn mutation_context_with_actor() {
733 let mut ctx = MutationContext::new(MutationOp::Update);
734 ctx.actor = Some("user-123".into());
735 assert_eq!(ctx.actor.as_deref(), Some("user-123"));
736 }
737
738 #[tokio::test]
741 async fn no_hooks_all_methods_are_noop() {
742 let hooks: NoHooks<(), (), ()> = NoHooks::default();
743 let mut ctx = MutationContext::new(MutationOp::Create);
744 let mut new_model = ();
745 let model = ();
746 let mut draft = UpdateDraft::new(());
747
748 assert!(hooks.before_create(&mut ctx, &mut new_model).await.is_ok());
749 assert!(hooks.before_update(&mut ctx, &mut draft).await.is_ok());
750 assert!(hooks.before_delete(&mut ctx, &model).await.is_ok());
751 assert!(hooks.after_create(&mut ctx, &model).await.is_ok());
752 assert!(hooks.after_update(&mut ctx, &model).await.is_ok());
753 }
754
755 #[test]
758 fn patch_serde_set_roundtrip() {
759 let p = Patch::Set(42);
760 let json = serde_json::to_string(&p).unwrap();
761 assert_eq!(json, "42");
762 let back: Patch<i32> = serde_json::from_str(&json).unwrap();
763 assert_eq!(back, Patch::Set(42));
764 }
765
766 #[test]
767 fn patch_serde_clear_serializes_as_null() {
768 let p: Patch<i32> = Patch::Clear;
769 let json = serde_json::to_string(&p).unwrap();
770 assert_eq!(json, "null");
771 }
772
773 #[test]
774 fn patch_serde_null_deserializes_as_clear() {
775 let p: Patch<i32> = serde_json::from_str("null").unwrap();
776 assert_eq!(p, Patch::Clear);
777 }
778
779 #[test]
780 fn patch_serde_absent_field_is_unchanged() {
781 #[derive(Deserialize, PartialEq, Debug)]
782 struct Payload {
783 #[serde(default)]
784 name: Patch<String>,
785 #[serde(default)]
786 age: Patch<i32>,
787 }
788 let p: Payload = serde_json::from_str(r#"{"name": "Alice"}"#).unwrap();
789 assert_eq!(p.name, Patch::Set("Alice".to_string()));
790 assert_eq!(p.age, Patch::Unchanged);
791 }
792
793 #[test]
794 fn patch_serde_explicit_null_is_clear() {
795 #[derive(Deserialize, PartialEq, Debug)]
796 struct Payload {
797 #[serde(default)]
798 name: Patch<String>,
799 }
800 let p: Payload = serde_json::from_str(r#"{"name": null}"#).unwrap();
801 assert_eq!(p.name, Patch::Clear);
802 }
803
804 #[test]
807 fn update_draft_before_after() {
808 let draft = UpdateDraft::new_with_changes("old".to_string(), "new".to_string());
809 assert_eq!(draft.before(), "old");
810 assert_eq!(draft.after(), "new");
811 }
812
813 #[test]
814 fn update_draft_into_after() {
815 let draft = UpdateDraft::new_with_changes(1, 2);
816 assert_eq!(draft.into_after(), 2);
817 }
818
819 #[test]
820 fn update_draft_new_clones() {
821 let draft = UpdateDraft::new(42);
822 assert_eq!(draft.before(), &42);
823 assert_eq!(draft.after(), &42);
824 }
825
826 #[test]
827 fn update_draft_after_mut() {
828 let mut draft = UpdateDraft::new_with_changes(1, 2);
829 *draft.after_mut() = 3;
830 assert_eq!(draft.after(), &3);
831 }
832
833 #[test]
836 fn draft_field_before_after() {
837 let before = 1;
838 let mut after = 2;
839 let field = DraftField::new(&before, &mut after);
840 assert_eq!(field.before(), &1);
841 assert_eq!(field.after(), &2);
842 }
843
844 #[test]
845 fn draft_field_changed() {
846 let before = 1;
847 let mut after = 2;
848 let field = DraftField::new(&before, &mut after);
849 assert!(field.changed());
850 assert!(!field.unchanged());
851 }
852
853 #[test]
854 fn draft_field_unchanged() {
855 let before = 1;
856 let mut after = 1;
857 let field = DraftField::new(&before, &mut after);
858 assert!(field.unchanged());
859 assert!(!field.changed());
860 }
861
862 #[test]
863 fn draft_field_changed_to() {
864 let before = "draft".to_string();
865 let mut after = "published".to_string();
866 let field = DraftField::new(&before, &mut after);
867 assert!(field.changed_to(&"published".to_string()));
868 assert!(!field.changed_to(&"draft".to_string()));
869 }
870
871 #[test]
872 fn draft_field_changed_from() {
873 let before = "draft".to_string();
874 let mut after = "published".to_string();
875 let field = DraftField::new(&before, &mut after);
876 assert!(field.changed_from(&"draft".to_string()));
877 assert!(!field.changed_from(&"published".to_string()));
878 }
879
880 #[test]
881 fn draft_field_set_mutates_after() {
882 let before = 10;
883 let mut after = 10;
884 {
885 let mut field = DraftField::new(&before, &mut after);
886 assert!(field.unchanged());
887 field.set(20);
888 assert!(field.changed());
889 assert_eq!(field.after(), &20);
890 }
891 assert_eq!(after, 20);
893 }
894
895 #[test]
896 fn draft_field_option_was_set() {
897 let before: Option<i32> = None;
898 let mut after: Option<i32> = Some(42);
899 let field = DraftField::new(&before, &mut after);
900 assert!(field.was_set());
901 assert!(!field.was_cleared());
902 }
903
904 #[test]
905 fn draft_field_option_was_cleared() {
906 let before: Option<i32> = Some(42);
907 let mut after: Option<i32> = None;
908 let field = DraftField::new(&before, &mut after);
909 assert!(field.was_cleared());
910 assert!(!field.was_set());
911 }
912}