1use std::borrow::Cow;
58
59use serde::de::Deserializer;
60use serde::{Deserialize, Serialize};
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66#[non_exhaustive]
67pub enum MutationOp {
68 Create,
70 Update,
72 Delete,
74}
75
76impl MutationOp {
77 #[must_use]
79 pub const fn as_str(self) -> &'static str {
80 match self {
81 Self::Create => "create",
82 Self::Update => "update",
83 Self::Delete => "delete",
84 }
85 }
86}
87
88impl std::fmt::Display for MutationOp {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 f.write_str(self.as_str())
91 }
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct MutationContext {
100 pub op: MutationOp,
102 pub actor: Option<String>,
104 pub request_id: Option<String>,
106 pub now: chrono::DateTime<chrono::Utc>,
108 pub invalidate_keys: Vec<String>,
110 #[serde(default, skip_serializing_if = "Option::is_none")]
113 pub idempotency_key: Option<String>,
114}
115
116impl MutationContext {
117 #[must_use]
124 pub fn new(op: MutationOp) -> Self {
125 Self {
126 op,
127 actor: crate::current::Current::actor(),
133 request_id: Some(uuid::Uuid::new_v4().to_string()),
134 now: chrono::Utc::now(),
135 invalidate_keys: Vec::new(),
136 idempotency_key: None,
137 }
138 }
139
140 pub fn invalidate(&mut self, key: impl Into<String>) {
142 self.invalidate_keys.push(key.into());
143 }
144
145 pub fn set_idempotency_key(&mut self, key: impl Into<String>) {
147 self.idempotency_key = Some(key.into());
148 }
149}
150
151use crate::AutumnResult;
154use std::future::Future;
155
156pub trait MutationHooks: Send + Sync + 'static {
169 type Model: Send + Sync;
171 type NewModel: Send + Sync;
173 type UpdateModel: Send + Sync;
175
176 fn before_create(
178 &self,
179 _ctx: &mut MutationContext,
180 _new: &mut Self::NewModel,
181 ) -> impl Future<Output = AutumnResult<()>> + Send {
182 async { Ok(()) }
183 }
184
185 fn before_update(
196 &self,
197 _ctx: &mut MutationContext,
198 _draft: &mut UpdateDraft<Self::Model>,
199 ) -> impl Future<Output = AutumnResult<()>> + Send
200 where
201 Self::Model: Clone,
202 {
203 async { Ok(()) }
204 }
205
206 fn before_delete(
208 &self,
209 _ctx: &mut MutationContext,
210 _record: &Self::Model,
211 ) -> impl Future<Output = AutumnResult<()>> + Send {
212 async { Ok(()) }
213 }
214
215 fn after_create(
217 &self,
218 _ctx: &mut MutationContext,
219 _record: &Self::Model,
220 ) -> impl Future<Output = AutumnResult<()>> + Send {
221 async { Ok(()) }
222 }
223
224 fn after_update(
226 &self,
227 _ctx: &mut MutationContext,
228 _record: &Self::Model,
229 ) -> impl Future<Output = AutumnResult<()>> + Send {
230 async { Ok(()) }
231 }
232
233 fn after_create_commit(
247 &self,
248 _ctx: &mut MutationContext,
249 _record: &Self::Model,
250 ) -> impl Future<Output = AutumnResult<()>> + Send {
251 async { Ok(()) }
252 }
253
254 fn after_update_commit(
259 &self,
260 _ctx: &mut MutationContext,
261 _record: &Self::Model,
262 ) -> impl Future<Output = AutumnResult<()>> + Send {
263 async { Ok(()) }
264 }
265
266 fn after_delete_commit(
271 &self,
272 _ctx: &mut MutationContext,
273 _record: &Self::Model,
274 ) -> impl Future<Output = AutumnResult<()>> + Send {
275 async { Ok(()) }
276 }
277}
278
279pub trait RepositoryHooksDefault: Sized {
285 fn autumn_default() -> Self;
287}
288
289impl<T> RepositoryHooksDefault for T
290where
291 T: Default,
292{
293 fn autumn_default() -> Self {
294 Self::default()
295 }
296}
297
298pub trait RepositoryHooksClone: Sized {
303 #[must_use]
305 fn autumn_clone(&self) -> Self;
306}
307
308impl<T> RepositoryHooksClone for T
309where
310 T: Clone,
311{
312 fn autumn_clone(&self) -> Self {
313 self.clone()
314 }
315}
316
317pub struct NoHooks<M, N, U> {
324 _phantom: std::marker::PhantomData<(M, N, U)>,
325}
326
327impl<M, N, U> Default for NoHooks<M, N, U> {
328 fn default() -> Self {
329 Self {
330 _phantom: std::marker::PhantomData,
331 }
332 }
333}
334
335impl<M, N, U> MutationHooks for NoHooks<M, N, U>
336where
337 M: Send + Sync + Clone + 'static,
338 N: Send + Sync + 'static,
339 U: Send + Sync + 'static,
340{
341 type Model = M;
342 type NewModel = N;
343 type UpdateModel = U;
344}
345
346#[derive(Debug, Clone, Default, PartialEq, Eq)]
356pub enum Patch<T> {
357 #[default]
359 Unchanged,
360 Set(T),
362 Clear,
364}
365
366impl<T> Patch<T> {
367 #[must_use]
369 pub const fn is_unchanged(&self) -> bool {
370 matches!(self, Self::Unchanged)
371 }
372
373 #[must_use]
375 pub const fn is_set(&self) -> bool {
376 matches!(self, Self::Set(_))
377 }
378
379 #[must_use]
381 pub const fn is_clear(&self) -> bool {
382 matches!(self, Self::Clear)
383 }
384
385 #[must_use]
387 pub const fn as_set(&self) -> Option<&T> {
388 match self {
389 Self::Set(v) => Some(v),
390 _ => None,
391 }
392 }
393
394 #[must_use]
400 pub fn into_option(self) -> Option<Option<T>> {
401 match self {
402 Self::Set(v) => Some(Some(v)),
403 Self::Clear => Some(None),
404 Self::Unchanged => None,
405 }
406 }
407}
408
409impl<T: Serialize> Serialize for Patch<T> {
410 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
411 match self {
412 Self::Unchanged | Self::Clear => serializer.serialize_none(),
413 Self::Set(v) => v.serialize(serializer),
414 }
415 }
416}
417
418impl<'de, T: Deserialize<'de>> Deserialize<'de> for Patch<T> {
419 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
420 let opt: Option<T> = Option::deserialize(deserializer)?;
423 Ok(opt.map_or_else(|| Self::Clear, Self::Set))
424 }
425}
426
427impl<T: validator::ValidateLength<u64>> validator::ValidateLength<u64> for Patch<T> {
457 fn length(&self) -> Option<u64> {
458 match self {
459 Self::Set(v) => validator::ValidateLength::length(v),
460 _ => None,
461 }
462 }
463}
464
465impl<N, T: validator::ValidateRange<N>> validator::ValidateRange<N> for Patch<T> {
466 fn greater_than(&self, max: N) -> Option<bool> {
467 match self {
468 Self::Set(v) => validator::ValidateRange::greater_than(v, max),
469 _ => None,
470 }
471 }
472
473 fn less_than(&self, min: N) -> Option<bool> {
474 match self {
475 Self::Set(v) => validator::ValidateRange::less_than(v, min),
476 _ => None,
477 }
478 }
479}
480
481impl<T: validator::ValidateEmail> validator::ValidateEmail for Patch<T> {
482 fn as_email_string(&self) -> Option<Cow<'_, str>> {
483 match self {
484 Self::Set(v) => validator::ValidateEmail::as_email_string(v),
485 _ => None,
486 }
487 }
488}
489
490impl<T: validator::ValidateUrl> validator::ValidateUrl for Patch<T> {
491 fn as_url_string(&self) -> Option<Cow<'_, str>> {
492 match self {
493 Self::Set(v) => validator::ValidateUrl::as_url_string(v),
494 _ => None,
495 }
496 }
497}
498
499impl<T: validator::ValidateContains> validator::ValidateContains for Patch<T> {
500 fn validate_contains(&self, needle: &str) -> bool {
501 match self {
502 Self::Set(v) => validator::ValidateContains::validate_contains(v, needle),
503 _ => true,
504 }
505 }
506}
507
508impl<T: validator::ValidateIp> validator::ValidateIp for Patch<T> {
509 fn validate_ipv4(&self) -> bool {
510 match self {
511 Self::Set(v) => validator::ValidateIp::validate_ipv4(v),
512 _ => true,
513 }
514 }
515
516 fn validate_ipv6(&self) -> bool {
517 match self {
518 Self::Set(v) => validator::ValidateIp::validate_ipv6(v),
519 _ => true,
520 }
521 }
522
523 fn validate_ip(&self) -> bool {
524 match self {
525 Self::Set(v) => validator::ValidateIp::validate_ip(v),
526 _ => true,
527 }
528 }
529}
530
531impl<T: validator::ValidateRegex> validator::ValidateRegex for Patch<T> {
532 fn validate_regex(&self, regex: impl validator::AsRegex) -> bool {
533 match self {
534 Self::Set(v) => validator::ValidateRegex::validate_regex(v, regex),
535 _ => true,
536 }
537 }
538}
539
540impl<T: validator::ValidateRequired> validator::ValidateRequired for Patch<T> {
552 fn validate_required(&self) -> bool {
553 match self {
554 Self::Unchanged => true,
555 Self::Clear => false,
556 Self::Set(v) => validator::ValidateRequired::validate_required(v),
557 }
558 }
559
560 fn is_some(&self) -> bool {
561 match self {
562 Self::Set(v) => validator::ValidateRequired::is_some(v),
563 _ => false,
564 }
565 }
566}
567
568#[derive(Debug, Clone, PartialEq, Eq)]
574pub struct FieldDiff<T> {
575 before: T,
576 after: T,
577}
578
579impl<T: PartialEq> FieldDiff<T> {
580 #[must_use]
582 pub const fn new(before: T, after: T) -> Self {
583 Self { before, after }
584 }
585
586 #[must_use]
588 pub const fn before(&self) -> &T {
589 &self.before
590 }
591
592 #[must_use]
594 pub const fn after(&self) -> &T {
595 &self.after
596 }
597
598 #[must_use]
600 pub fn changed(&self) -> bool {
601 self.before != self.after
602 }
603
604 #[must_use]
606 pub fn unchanged(&self) -> bool {
607 self.before == self.after
608 }
609
610 #[must_use]
612 pub fn changed_to(&self, value: &T) -> bool {
613 self.changed() && self.after == *value
614 }
615
616 #[must_use]
618 pub fn changed_from(&self, value: &T) -> bool {
619 self.changed() && self.before == *value
620 }
621
622 pub fn set(&mut self, value: T) {
624 self.after = value;
625 }
626}
627
628impl<T: PartialEq> FieldDiff<Option<T>> {
629 #[must_use]
631 pub const fn was_set(&self) -> bool {
632 self.before.is_none() && self.after.is_some()
633 }
634
635 #[must_use]
637 pub const fn was_cleared(&self) -> bool {
638 self.before.is_some() && self.after.is_none()
639 }
640}
641
642#[derive(Debug, Clone)]
663pub struct UpdateDraft<T: Clone> {
664 pub before: T,
669 pub after: T,
674}
675
676impl<T: Clone> UpdateDraft<T> {
677 #[must_use]
682 pub fn new(before: T) -> Self {
683 let after = before.clone();
684 Self { before, after }
685 }
686
687 #[must_use]
691 pub const fn new_with_changes(before: T, after: T) -> Self {
692 Self { before, after }
693 }
694
695 #[must_use]
697 pub const fn before(&self) -> &T {
698 &self.before
699 }
700
701 #[must_use]
703 pub const fn after(&self) -> &T {
704 &self.after
705 }
706
707 #[must_use]
711 pub const fn after_mut(&mut self) -> &mut T {
712 &mut self.after
713 }
714
715 #[must_use]
717 pub fn into_after(self) -> T {
718 self.after
719 }
720}
721
722#[derive(Debug)]
740pub struct DraftField<'a, T> {
741 before: &'a T,
742 after: &'a mut T,
743}
744
745impl<'a, T> DraftField<'a, T> {
746 #[must_use]
748 pub const fn new(before: &'a T, after: &'a mut T) -> Self {
749 Self { before, after }
750 }
751
752 #[must_use]
754 pub const fn before(&self) -> &T {
755 self.before
756 }
757
758 #[must_use]
760 pub const fn after(&self) -> &T {
761 self.after
762 }
763
764 pub fn set(&mut self, value: T) {
766 *self.after = value;
767 }
768}
769
770impl<T: PartialEq> DraftField<'_, T> {
771 #[must_use]
773 pub fn changed(&self) -> bool {
774 self.before != self.after
775 }
776
777 #[must_use]
779 pub fn unchanged(&self) -> bool {
780 self.before == self.after
781 }
782
783 #[must_use]
785 pub fn changed_to(&self, value: &T) -> bool {
786 self.changed() && *self.after == *value
787 }
788
789 #[must_use]
791 pub fn changed_from(&self, value: &T) -> bool {
792 self.changed() && *self.before == *value
793 }
794}
795
796impl<T: PartialEq> DraftField<'_, Option<T>> {
797 #[must_use]
799 pub const fn was_set(&self) -> bool {
800 self.before.is_none() && self.after.is_some()
801 }
802
803 #[must_use]
805 pub const fn was_cleared(&self) -> bool {
806 self.before.is_some() && self.after.is_none()
807 }
808}
809
810#[cfg(test)]
811mod tests {
812 use super::*;
813
814 #[test]
817 fn patch_unchanged_is_default() {
818 let p: Patch<String> = Patch::default();
819 assert!(p.is_unchanged());
820 assert!(!p.is_set());
821 assert!(!p.is_clear());
822 }
823
824 #[test]
825 fn patch_set_holds_value() {
826 let p = Patch::Set("hello");
827 assert!(p.is_set());
828 assert!(!p.is_unchanged());
829 assert!(!p.is_clear());
830 assert_eq!(p.as_set(), Some(&"hello"));
831 }
832
833 #[test]
834 fn patch_clear_is_clear() {
835 let p: Patch<i32> = Patch::Clear;
836 assert!(p.is_clear());
837 assert!(!p.is_set());
838 assert!(!p.is_unchanged());
839 }
840
841 #[test]
842 fn patch_into_option_set() {
843 assert_eq!(Patch::Set(42).into_option(), Some(Some(42)));
844 }
845
846 #[test]
847 fn patch_into_option_clear() {
848 assert_eq!(Patch::<i32>::Clear.into_option(), Some(None));
849 }
850
851 #[test]
852 fn patch_into_option_unchanged() {
853 assert_eq!(Patch::<i32>::Unchanged.into_option(), None);
854 }
855
856 #[test]
864 fn patch_validate_length_set_delegates_to_inner() {
865 use validator::ValidateLength;
866 assert!(!Patch::Set(String::new()).validate_length(Some(1), None, None));
868 assert!(Patch::Set(String::from("ok")).validate_length(Some(1), None, None));
870 }
871
872 #[test]
873 fn patch_validate_length_absent_passes() {
874 use validator::ValidateLength;
875 assert!(Patch::<String>::Unchanged.validate_length(Some(1), None, None));
877 assert!(Patch::<String>::Clear.validate_length(Some(1), None, None));
878 }
879
880 #[test]
884 fn patch_validate_required_unchanged_passes() {
885 use validator::ValidateRequired;
886 assert!(Patch::<Option<String>>::Unchanged.validate_required());
887 }
888
889 #[test]
890 fn patch_validate_required_clear_fails() {
891 use validator::ValidateRequired;
892 assert!(!Patch::<Option<String>>::Clear.validate_required());
893 }
894
895 #[test]
896 fn patch_validate_required_set_some_passes() {
897 use validator::ValidateRequired;
898 assert!(Patch::Set(Some(String::from("x"))).validate_required());
899 }
900
901 #[test]
902 fn patch_validate_required_set_none_fails() {
903 use validator::ValidateRequired;
904 assert!(!Patch::<Option<String>>::Set(None).validate_required());
905 }
906
907 #[test]
910 fn field_diff_unchanged() {
911 let diff = FieldDiff::new(1, 1);
912 assert!(diff.unchanged());
913 assert!(!diff.changed());
914 }
915
916 #[test]
917 fn field_diff_changed() {
918 let diff = FieldDiff::new(1, 2);
919 assert!(diff.changed());
920 }
921
922 #[test]
923 fn field_diff_changed_to() {
924 let diff = FieldDiff::new(1, 2);
925 assert!(diff.changed_to(&2));
926 }
927
928 #[test]
929 fn field_diff_changed_from() {
930 let diff = FieldDiff::new(1, 2);
931 assert!(diff.changed_from(&1));
932 }
933
934 #[test]
935 fn field_diff_set_updates_after() {
936 let mut diff = FieldDiff::new(1, 1);
937 assert!(diff.unchanged());
938 diff.set(5);
939 assert!(diff.changed());
940 assert_eq!(diff.after(), &5);
941 assert_eq!(diff.before(), &1);
942 }
943
944 #[test]
945 fn field_diff_option_was_set() {
946 let diff = FieldDiff::new(None, Some(42));
947 assert!(diff.was_set());
948 let diff2 = FieldDiff::new(Some(42), Some(42));
949 assert!(!diff2.was_set());
950 let diff3 = FieldDiff::new(None::<i32>, None);
951 assert!(!diff3.was_set());
952 let diff4 = FieldDiff::new(Some(42), None);
953 assert!(!diff4.was_set());
954 }
955
956 #[test]
957 fn field_diff_option_was_cleared() {
958 let diff = FieldDiff::new(Some(42), None);
959 assert!(diff.was_cleared());
960 let diff2 = FieldDiff::new(Some(42), Some(42));
961 assert!(!diff2.was_cleared());
962 let diff3 = FieldDiff::new(None::<i32>, None);
963 assert!(!diff3.was_cleared());
964 let diff4 = FieldDiff::new(None, Some(42));
965 assert!(!diff4.was_cleared());
966 }
967
968 #[test]
971 fn mutation_op_as_str() {
972 assert_eq!(MutationOp::Create.as_str(), "create");
973 assert_eq!(MutationOp::Update.as_str(), "update");
974 assert_eq!(MutationOp::Delete.as_str(), "delete");
975 }
976
977 #[test]
978 fn mutation_op_display() {
979 assert_eq!(format!("{}", MutationOp::Create), "create");
980 }
981
982 #[tokio::test]
985 async fn mutation_context_auto_populates() {
986 crate::current::scope_request(async {
991 let ctx = MutationContext::new(MutationOp::Create);
992 assert!(ctx.actor.is_none());
993 assert!(ctx.request_id.is_some());
994 assert_eq!(ctx.request_id.as_ref().unwrap().len(), 36);
996 assert!(matches!(ctx.op, MutationOp::Create));
997 assert!(ctx.invalidate_keys.is_empty());
998 })
999 .await;
1000 }
1001
1002 #[test]
1003 fn mutation_context_invalidate_pushes_key() {
1004 let mut ctx = MutationContext::new(MutationOp::Create);
1005 assert!(ctx.invalidate_keys.is_empty());
1006 ctx.invalidate("cache:key");
1007 assert_eq!(ctx.invalidate_keys, vec!["cache:key".to_string()]);
1008 }
1009
1010 #[test]
1011 fn mutation_context_with_actor() {
1012 let mut ctx = MutationContext::new(MutationOp::Update);
1013 ctx.actor = Some("user-123".into());
1014 assert_eq!(ctx.actor.as_deref(), Some("user-123"));
1015 }
1016
1017 #[tokio::test]
1018 async fn mutation_context_seeds_actor_from_ambient_scope() {
1019 crate::current::scope_request(async {
1023 assert!(MutationContext::new(MutationOp::Create).actor.is_none());
1024 })
1025 .await;
1026
1027 crate::current::with_actor("u1", async {
1029 let ctx = MutationContext::new(MutationOp::Create);
1030 assert_eq!(ctx.actor.as_deref(), Some("u1"));
1031 })
1032 .await;
1033 }
1034
1035 #[test]
1036 fn mutation_context_carries_scoped_idempotency_key() {
1037 let mut ctx = MutationContext::new(MutationOp::Create);
1038 assert!(ctx.idempotency_key.is_none());
1039
1040 ctx.set_idempotency_key("v2:scoped-http-key");
1041
1042 assert_eq!(ctx.idempotency_key.as_deref(), Some("v2:scoped-http-key"));
1043 }
1044
1045 #[test]
1046 fn mutation_context_deserializes_without_idempotency_key() {
1047 let ctx: MutationContext = serde_json::from_value(serde_json::json!({
1048 "op": "Create",
1049 "actor": null,
1050 "request_id": "request-1",
1051 "now": "2026-05-17T00:00:00Z",
1052 "invalidate_keys": []
1053 }))
1054 .expect("old durable hook payloads should deserialize");
1055
1056 assert!(ctx.idempotency_key.is_none());
1057 }
1058
1059 #[tokio::test]
1062 async fn no_hooks_all_methods_are_noop() {
1063 let hooks: NoHooks<(), (), ()> = NoHooks::default();
1064 let mut ctx = MutationContext::new(MutationOp::Create);
1065 let mut new_model = ();
1066 let model = ();
1067 let mut draft = UpdateDraft::new(());
1068
1069 assert!(hooks.before_create(&mut ctx, &mut new_model).await.is_ok());
1070 assert!(hooks.before_update(&mut ctx, &mut draft).await.is_ok());
1071 assert!(hooks.before_delete(&mut ctx, &model).await.is_ok());
1072 assert!(hooks.after_create(&mut ctx, &model).await.is_ok());
1073 assert!(hooks.after_update(&mut ctx, &model).await.is_ok());
1074 }
1075
1076 #[tokio::test]
1077 async fn no_hooks_commit_variants_are_noop() {
1078 let hooks: NoHooks<(), (), ()> = NoHooks::default();
1080 let mut ctx = MutationContext::new(MutationOp::Create);
1081 let model = ();
1082
1083 assert!(
1084 hooks.after_create_commit(&mut ctx, &model).await.is_ok(),
1085 "after_create_commit must default to Ok(())"
1086 );
1087 assert!(
1088 hooks.after_update_commit(&mut ctx, &model).await.is_ok(),
1089 "after_update_commit must default to Ok(())"
1090 );
1091 assert!(
1092 hooks.after_delete_commit(&mut ctx, &model).await.is_ok(),
1093 "after_delete_commit must default to Ok(())"
1094 );
1095 }
1096
1097 #[tokio::test]
1098 async fn custom_hooks_can_override_commit_variants() {
1099 use std::sync::Arc;
1100 use std::sync::atomic::{AtomicU32, Ordering};
1101
1102 static CALLS: AtomicU32 = AtomicU32::new(0);
1103
1104 #[derive(Clone, Default)]
1105 struct CountingHooks;
1106
1107 impl MutationHooks for CountingHooks {
1108 type Model = ();
1109 type NewModel = ();
1110 type UpdateModel = ();
1111
1112 async fn after_create_commit(
1113 &self,
1114 _ctx: &mut MutationContext,
1115 _record: &Self::Model,
1116 ) -> AutumnResult<()> {
1117 CALLS.fetch_add(1, Ordering::SeqCst);
1118 Ok(())
1119 }
1120
1121 async fn after_update_commit(
1122 &self,
1123 _ctx: &mut MutationContext,
1124 _record: &Self::Model,
1125 ) -> AutumnResult<()> {
1126 CALLS.fetch_add(1, Ordering::SeqCst);
1127 Ok(())
1128 }
1129
1130 async fn after_delete_commit(
1131 &self,
1132 _ctx: &mut MutationContext,
1133 _record: &Self::Model,
1134 ) -> AutumnResult<()> {
1135 CALLS.fetch_add(1, Ordering::SeqCst);
1136 Ok(())
1137 }
1138 }
1139
1140 CALLS.store(0, Ordering::SeqCst);
1141 let hooks = CountingHooks;
1142 let mut ctx = MutationContext::new(MutationOp::Create);
1143 let model = ();
1144
1145 hooks.after_create_commit(&mut ctx, &model).await.unwrap();
1146 hooks.after_update_commit(&mut ctx, &model).await.unwrap();
1147 hooks.after_delete_commit(&mut ctx, &model).await.unwrap();
1148
1149 assert_eq!(CALLS.load(Ordering::SeqCst), 3);
1150 let _ = Arc::new(CountingHooks); }
1152
1153 #[test]
1156 fn patch_serde_set_roundtrip() {
1157 let p = Patch::Set(42);
1158 let json = serde_json::to_string(&p).unwrap();
1159 assert_eq!(json, "42");
1160 let back: Patch<i32> = serde_json::from_str(&json).unwrap();
1161 assert_eq!(back, Patch::Set(42));
1162 }
1163
1164 #[test]
1165 fn patch_serde_clear_serializes_as_null() {
1166 let p: Patch<i32> = Patch::Clear;
1167 let json = serde_json::to_string(&p).unwrap();
1168 assert_eq!(json, "null");
1169 }
1170
1171 #[test]
1172 fn patch_serde_null_deserializes_as_clear() {
1173 let p: Patch<i32> = serde_json::from_str("null").unwrap();
1174 assert_eq!(p, Patch::Clear);
1175 }
1176
1177 #[test]
1178 fn patch_serde_absent_field_is_unchanged() {
1179 #[derive(Deserialize, PartialEq, Debug)]
1180 struct Payload {
1181 #[serde(default)]
1182 name: Patch<String>,
1183 #[serde(default)]
1184 age: Patch<i32>,
1185 }
1186 let p: Payload = serde_json::from_str(r#"{"name": "Alice"}"#).unwrap();
1187 assert_eq!(p.name, Patch::Set("Alice".to_string()));
1188 assert_eq!(p.age, Patch::Unchanged);
1189 }
1190
1191 #[test]
1192 fn patch_serde_explicit_null_is_clear() {
1193 #[derive(Deserialize, PartialEq, Debug)]
1194 struct Payload {
1195 #[serde(default)]
1196 name: Patch<String>,
1197 }
1198 let p: Payload = serde_json::from_str(r#"{"name": null}"#).unwrap();
1199 assert_eq!(p.name, Patch::Clear);
1200 }
1201
1202 #[test]
1205 fn update_draft_before_after() {
1206 let draft = UpdateDraft::new_with_changes("old".to_string(), "new".to_string());
1207 assert_eq!(draft.before(), "old");
1208 assert_eq!(draft.after(), "new");
1209 }
1210
1211 #[test]
1212 fn update_draft_into_after() {
1213 let draft = UpdateDraft::new_with_changes(1, 2);
1214 assert_eq!(draft.into_after(), 2);
1215 }
1216
1217 #[test]
1218 fn update_draft_new_clones() {
1219 let draft = UpdateDraft::new(42);
1220 assert_eq!(draft.before(), &42);
1221 assert_eq!(draft.after(), &42);
1222 }
1223
1224 #[test]
1225 fn update_draft_after_mut() {
1226 let mut draft = UpdateDraft::new_with_changes(1, 2);
1227 *draft.after_mut() = 3;
1228 assert_eq!(draft.after(), &3);
1229 }
1230
1231 #[test]
1234 fn draft_field_before_after() {
1235 let before = 1;
1236 let mut after = 2;
1237 let field = DraftField::new(&before, &mut after);
1238 assert_eq!(field.before(), &1);
1239 assert_eq!(field.after(), &2);
1240 }
1241
1242 #[test]
1243 fn draft_field_changed() {
1244 let before = 1;
1245 let mut after = 2;
1246 let field = DraftField::new(&before, &mut after);
1247 assert!(field.changed());
1248 assert!(!field.unchanged());
1249 }
1250
1251 #[test]
1252 fn draft_field_unchanged() {
1253 let before = 1;
1254 let mut after = 1;
1255 let field = DraftField::new(&before, &mut after);
1256 assert!(field.unchanged());
1257 assert!(!field.changed());
1258 }
1259
1260 #[test]
1261 fn draft_field_changed_to() {
1262 let before = "draft".to_string();
1263 let mut after = "published".to_string();
1264 let field = DraftField::new(&before, &mut after);
1265 assert!(field.changed_to(&"published".to_string()));
1266 assert!(!field.changed_to(&"draft".to_string()));
1267 }
1268
1269 #[test]
1270 fn draft_field_changed_from() {
1271 let before = "draft".to_string();
1272 let mut after = "published".to_string();
1273 let field = DraftField::new(&before, &mut after);
1274 assert!(field.changed_from(&"draft".to_string()));
1275 assert!(!field.changed_from(&"published".to_string()));
1276 }
1277
1278 #[test]
1279 fn draft_field_set_mutates_after() {
1280 let before = 10;
1281 let mut after = 10;
1282 {
1283 let mut field = DraftField::new(&before, &mut after);
1284 assert!(field.unchanged());
1285 field.set(20);
1286 assert!(field.changed());
1287 assert_eq!(field.after(), &20);
1288 }
1289 assert_eq!(after, 20);
1291 }
1292
1293 #[test]
1294 fn draft_field_option_was_set() {
1295 let before: Option<i32> = None;
1296 let mut after: Option<i32> = Some(42);
1297 let field = DraftField::new(&before, &mut after);
1298 assert!(field.was_set());
1299 assert!(!field.was_cleared());
1300
1301 let before2: Option<i32> = Some(42);
1302 let mut after2: Option<i32> = Some(42);
1303 let field2 = DraftField::new(&before2, &mut after2);
1304 assert!(!field2.was_set());
1305
1306 let before3: Option<i32> = None;
1307 let mut after3: Option<i32> = None;
1308 let field3 = DraftField::new(&before3, &mut after3);
1309 assert!(!field3.was_set());
1310
1311 let before4: Option<i32> = Some(42);
1312 let mut after4: Option<i32> = None;
1313 let field4 = DraftField::new(&before4, &mut after4);
1314 assert!(!field4.was_set());
1315 }
1316
1317 #[test]
1318 fn draft_field_option_was_cleared() {
1319 let before: Option<i32> = Some(42);
1320 let mut after: Option<i32> = None;
1321 let field = DraftField::new(&before, &mut after);
1322 assert!(field.was_cleared());
1323 assert!(!field.was_set());
1324
1325 let before2: Option<i32> = Some(42);
1326 let mut after2: Option<i32> = Some(42);
1327 let field2 = DraftField::new(&before2, &mut after2);
1328 assert!(!field2.was_cleared());
1329
1330 let before3: Option<i32> = None;
1331 let mut after3: Option<i32> = None;
1332 let field3 = DraftField::new(&before3, &mut after3);
1333 assert!(!field3.was_cleared());
1334
1335 let before4: Option<i32> = None;
1336 let mut after4: Option<i32> = Some(42);
1337 let field4 = DraftField::new(&before4, &mut after4);
1338 assert!(!field4.was_cleared());
1339 }
1340}