siderite_validation/
change.rs1use crate::context::ValidationContext;
5use crate::schema::{Schema, SchemaObject, SchemaRegistry};
6use crate::validate::Validate;
7use serde::{Deserialize, Deserializer};
8use serde_json::Value;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
35pub enum Change<T> {
36 #[default]
38 Unchanged,
39 Set(T),
41}
42
43impl<T> Change<T> {
44 pub fn apply(self, target: &mut T) {
49 if let Self::Set(value) = self {
50 *target = value;
51 }
52 }
53
54 pub fn into_option(self) -> Option<T> {
60 match self {
61 Self::Unchanged => None,
62 Self::Set(value) => Some(value),
63 }
64 }
65
66 pub const fn is_set(&self) -> bool {
68 matches!(self, Self::Set(_))
69 }
70}
71
72impl<'de, T: Deserialize<'de>> Deserialize<'de> for Change<T> {
73 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
74 T::deserialize(d).map(Self::Set)
75 }
76}
77
78impl<T: Validate> Validate for Change<T> {
79 fn prepare(input: &mut Value, ctx: &mut ValidationContext) {
80 T::prepare(input, ctx);
82 }
83
84 fn validate(&self, ctx: &mut ValidationContext) {
85 if let Self::Set(value) = self {
86 value.validate(ctx);
87 }
88 }
89}
90
91impl<T: Schema + 'static> Schema for Change<T> {
92 fn schema(registry: &mut SchemaRegistry) -> SchemaObject {
93 registry.subschema::<T>()
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 #![allow(clippy::unwrap_used)]
100
101 use super::*;
102
103 #[derive(Debug, Deserialize)]
104 struct Body {
105 #[serde(default)]
106 name: Change<String>,
107 #[serde(default)]
108 note: Change<Option<i32>>,
109 }
110
111 fn parse(text: &str) -> Result<Body, serde_json::Error> {
112 serde_json::from_str(text)
113 }
114
115 #[test]
116 fn absent_null_and_values() {
117 let absent = parse("{}").unwrap();
118 assert_eq!(absent.name, Change::Unchanged);
119 assert_eq!(absent.note, Change::Unchanged);
120 let set = parse(r#"{"name":"x","note":3}"#).unwrap();
121 assert_eq!(set.name, Change::Set("x".to_owned()));
122 assert_eq!(set.note, Change::Set(Some(3)));
123 let cleared = parse(r#"{"note":null}"#).unwrap();
124 assert_eq!(cleared.note, Change::Set(None));
125 assert!(parse(r#"{"name":null}"#).is_err());
126 }
127
128 #[test]
129 fn apply_and_accessors() {
130 let mut current = 1;
131 Change::Unchanged.apply(&mut current);
132 assert_eq!(current, 1);
133 Change::Set(2).apply(&mut current);
134 assert_eq!(current, 2);
135 assert_eq!(Change::Set(5).into_option(), Some(5));
136 assert_eq!(Change::<i32>::Unchanged.into_option(), None);
137 assert!(Change::Set(()).is_set());
138 }
139
140 #[test]
141 fn the_schema_is_the_inner_type() {
142 let mut registry = SchemaRegistry::default();
143 let inner = registry.subschema::<i64>();
144 let changed = Change::<i64>::schema(&mut registry);
145 assert_eq!(changed, inner);
146 }
147}