1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_with::skip_serializing_none;
5
6use crate::{EqIdentity, Identity};
7
8#[derive(Clone, Copy, Debug, Default)]
10#[non_exhaustive]
11pub struct UpsertOptions {
12 pub ignore_nulls: bool,
15 pub merge_maps: bool,
18 pub merge_lists: bool,
21}
22
23impl UpsertOptions {
24 pub fn ignore_nulls(mut self, ignore_nulls: bool) -> Self {
27 self.ignore_nulls = ignore_nulls;
28 self
29 }
30
31 pub fn merge_metadata(mut self, merge_metadata: bool) -> Self {
34 self.merge_maps = merge_metadata;
35 self
36 }
37
38 pub fn merge_lists(mut self, merge_lists: bool) -> Self {
41 self.merge_lists = merge_lists;
42 self
43 }
44}
45
46pub trait IntoPatchItem<TPatch> {
48 fn patch(self, options: &UpsertOptions) -> Option<TPatch>;
50}
51
52pub trait IntoPatch<TPatch> {
54 fn patch(self, options: &UpsertOptions) -> TPatch;
56}
57
58#[skip_serializing_none]
59#[derive(Serialize, Deserialize, Debug, Clone)]
60#[serde(untagged, rename_all_fields = "camelCase")]
61pub enum UpdateSetNull<T> {
63 Set {
65 set: T,
67 },
68 SetNull {
70 set_null: bool,
72 },
73}
74
75impl<T> Default for UpdateSetNull<T> {
76 fn default() -> Self {
77 Self::SetNull { set_null: false }
78 }
79}
80
81impl<T> IntoPatchItem<UpdateSetNull<T>> for Option<T> {
82 fn patch(self, options: &UpsertOptions) -> Option<UpdateSetNull<T>> {
83 match (self, options.ignore_nulls) {
84 (None, true) => None,
85 (None, false) => Some(UpdateSetNull::SetNull { set_null: true }),
86 (Some(x), _) => Some(UpdateSetNull::Set { set: x }),
87 }
88 }
89}
90
91impl<T> UpdateSetNull<T> {
92 pub fn set(value: T) -> Self {
98 Self::Set { set: value }
99 }
100
101 pub fn set_null(set_null: bool) -> Self {
107 Self::SetNull { set_null }
108 }
109}
110
111#[skip_serializing_none]
112#[derive(Serialize, Deserialize, Debug, Clone)]
113#[serde(rename_all = "camelCase")]
114pub struct UpdateSet<T> {
116 pub set: T,
118}
119
120impl<T> UpdateSet<T> {
121 pub fn set(value: T) -> Self {
127 Self { set: value }
128 }
129}
130
131impl<T> IntoPatchItem<UpdateSet<T>> for T {
132 fn patch(self, _options: &UpsertOptions) -> Option<UpdateSet<T>> {
133 Some(UpdateSet { set: self })
134 }
135}
136
137#[skip_serializing_none]
138#[derive(Serialize, Deserialize, Debug, Clone)]
139#[serde(untagged, rename_all_fields = "camelCase")]
140pub enum UpdateList<TAdd, TRemove> {
142 AddRemove {
144 add: Option<Vec<TAdd>>,
146 remove: Option<Vec<TRemove>>,
148 },
149 Set {
151 set: Vec<TAdd>,
153 },
154}
155
156impl<TAdd, TRemove> UpdateList<TAdd, TRemove> {
157 pub fn add_remove(add: Vec<TAdd>, remove: Vec<TRemove>) -> Self {
164 Self::AddRemove {
165 add: Some(add),
166 remove: Some(remove),
167 }
168 }
169
170 pub fn add(add: Vec<TAdd>) -> Self {
176 Self::AddRemove {
177 add: Some(add),
178 remove: None,
179 }
180 }
181
182 pub fn remove(remove: Vec<TRemove>) -> Self {
188 Self::AddRemove {
189 add: None,
190 remove: Some(remove),
191 }
192 }
193
194 pub fn set(set: Vec<TAdd>) -> Self {
200 Self::Set { set }
201 }
202}
203
204impl<TAdd, TRemove> IntoPatchItem<UpdateList<TAdd, TRemove>> for Vec<TAdd> {
205 fn patch(self, options: &UpsertOptions) -> Option<UpdateList<TAdd, TRemove>> {
206 if options.merge_lists {
207 Some(UpdateList::add(self))
208 } else {
209 Some(UpdateList::set(self))
210 }
211 }
212}
213
214impl<TAdd, TRemove> IntoPatchItem<UpdateList<TAdd, TRemove>> for Option<Vec<TAdd>> {
215 fn patch(self, options: &UpsertOptions) -> Option<UpdateList<TAdd, TRemove>> {
216 match (self, options.ignore_nulls, options.merge_lists) {
217 (Some(x), _, true) => Some(UpdateList::add(x)),
218 (Some(x), _, false) => Some(UpdateList::set(x)),
219 (None, false, false) => Some(UpdateList::set(vec![])),
220 (None, _, _) => None,
221 }
222 }
223}
224
225#[skip_serializing_none]
226#[derive(Serialize, Deserialize, Debug, Clone)]
227#[serde(untagged, rename_all_fields = "camelCase")]
228pub enum UpdateMap<TKey, TValue>
230where
231 TKey: std::hash::Hash + std::cmp::Eq,
232{
233 AddRemove {
235 add: Option<HashMap<TKey, TValue>>,
237 remove: Option<Vec<TKey>>,
239 },
240 Set {
242 set: HashMap<TKey, TValue>,
244 },
245}
246
247impl<TKey, TValue> UpdateMap<TKey, TValue>
248where
249 TKey: std::hash::Hash + std::cmp::Eq,
250{
251 pub fn add_remove(add: HashMap<TKey, TValue>, remove: Vec<TKey>) -> Self {
258 Self::AddRemove {
259 add: Some(add),
260 remove: Some(remove),
261 }
262 }
263
264 pub fn add(add: HashMap<TKey, TValue>) -> Self {
270 Self::AddRemove {
271 add: Some(add),
272 remove: None,
273 }
274 }
275
276 pub fn remove(remove: Vec<TKey>) -> Self {
282 Self::AddRemove {
283 add: None,
284 remove: Some(remove),
285 }
286 }
287
288 pub fn set(set: HashMap<TKey, TValue>) -> Self {
294 Self::Set { set }
295 }
296}
297
298impl<TKey: std::hash::Hash + std::cmp::Eq, TValue> IntoPatchItem<UpdateMap<TKey, TValue>>
299 for HashMap<TKey, TValue>
300{
301 fn patch(self, options: &UpsertOptions) -> Option<UpdateMap<TKey, TValue>> {
302 if options.merge_maps {
303 Some(UpdateMap::add(self))
304 } else {
305 Some(UpdateMap::set(self))
306 }
307 }
308}
309
310impl<TKey: std::hash::Hash + std::cmp::Eq, TValue> IntoPatchItem<UpdateMap<TKey, TValue>>
311 for Option<HashMap<TKey, TValue>>
312{
313 fn patch(self, options: &UpsertOptions) -> Option<UpdateMap<TKey, TValue>> {
314 match (self, options.ignore_nulls, options.merge_lists) {
315 (Some(x), _, true) => Some(UpdateMap::add(x)),
316 (Some(x), _, false) => Some(UpdateMap::set(x)),
317 (None, false, false) => Some(UpdateMap::set(HashMap::new())),
318 (None, _, _) => None,
319 }
320 }
321}
322
323#[derive(Serialize, Deserialize, Debug, Default, Clone)]
324#[serde(rename_all = "camelCase")]
325pub struct Patch<T>
327where
328 T: Default,
329{
330 #[serde(flatten)]
331 pub id: Identity,
333 pub update: T,
335}
336
337impl<T> Patch<T>
338where
339 T: Default,
340{
341 pub fn new(id: Identity) -> Self {
347 Patch::<T> {
348 id,
349 update: T::default(),
350 }
351 }
352}
353
354impl<T> EqIdentity for Patch<T>
355where
356 T: Default,
357{
358 fn eq(&self, id: &Identity) -> bool {
359 &self.id == id
360 }
361}
362
363macro_rules! to_idt {
365 ($it:ident) => {
366 if $it.id > 0 {
367 Identity::Id { id: $it.id }
368 } else {
369 $it.external_id
370 .as_ref()
371 .map(|e| Identity::ExternalId {
372 external_id: e.clone(),
373 })
374 .unwrap_or_else(|| Identity::Id { id: $it.id })
375 }
376 };
377}