use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use crate::{EqIdentity, Identity};
#[derive(Clone, Copy, Debug, Default)]
#[non_exhaustive]
pub struct UpsertOptions {
pub ignore_nulls: bool,
pub merge_maps: bool,
pub merge_lists: bool,
}
impl UpsertOptions {
pub fn ignore_nulls(mut self, ignore_nulls: bool) -> Self {
self.ignore_nulls = ignore_nulls;
self
}
pub fn merge_metadata(mut self, merge_metadata: bool) -> Self {
self.merge_maps = merge_metadata;
self
}
pub fn merge_lists(mut self, merge_lists: bool) -> Self {
self.merge_lists = merge_lists;
self
}
}
pub trait IntoPatchItem<TPatch> {
fn patch(self, options: &UpsertOptions) -> Option<TPatch>;
}
pub trait IntoPatch<TPatch> {
fn patch(self, options: &UpsertOptions) -> TPatch;
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged, rename_all_fields = "camelCase")]
pub enum UpdateSetNull<T> {
Set {
set: T,
},
SetNull {
set_null: bool,
},
}
impl<T> Default for UpdateSetNull<T> {
fn default() -> Self {
Self::SetNull { set_null: false }
}
}
impl<T> IntoPatchItem<UpdateSetNull<T>> for Option<T> {
fn patch(self, options: &UpsertOptions) -> Option<UpdateSetNull<T>> {
match (self, options.ignore_nulls) {
(None, true) => None,
(None, false) => Some(UpdateSetNull::SetNull { set_null: true }),
(Some(x), _) => Some(UpdateSetNull::Set { set: x }),
}
}
}
impl<T> UpdateSetNull<T> {
pub fn set(value: T) -> Self {
Self::Set { set: value }
}
pub fn set_null(set_null: bool) -> Self {
Self::SetNull { set_null }
}
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct UpdateSet<T> {
pub set: T,
}
impl<T> UpdateSet<T> {
pub fn set(value: T) -> Self {
Self { set: value }
}
}
impl<T> IntoPatchItem<UpdateSet<T>> for T {
fn patch(self, _options: &UpsertOptions) -> Option<UpdateSet<T>> {
Some(UpdateSet { set: self })
}
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged, rename_all_fields = "camelCase")]
pub enum UpdateList<TAdd, TRemove> {
AddRemove {
add: Option<Vec<TAdd>>,
remove: Option<Vec<TRemove>>,
},
Set {
set: Vec<TAdd>,
},
}
impl<TAdd, TRemove> UpdateList<TAdd, TRemove> {
pub fn add_remove(add: Vec<TAdd>, remove: Vec<TRemove>) -> Self {
Self::AddRemove {
add: Some(add),
remove: Some(remove),
}
}
pub fn add(add: Vec<TAdd>) -> Self {
Self::AddRemove {
add: Some(add),
remove: None,
}
}
pub fn remove(remove: Vec<TRemove>) -> Self {
Self::AddRemove {
add: None,
remove: Some(remove),
}
}
pub fn set(set: Vec<TAdd>) -> Self {
Self::Set { set }
}
}
impl<TAdd, TRemove> IntoPatchItem<UpdateList<TAdd, TRemove>> for Vec<TAdd> {
fn patch(self, options: &UpsertOptions) -> Option<UpdateList<TAdd, TRemove>> {
if options.merge_lists {
Some(UpdateList::add(self))
} else {
Some(UpdateList::set(self))
}
}
}
impl<TAdd, TRemove> IntoPatchItem<UpdateList<TAdd, TRemove>> for Option<Vec<TAdd>> {
fn patch(self, options: &UpsertOptions) -> Option<UpdateList<TAdd, TRemove>> {
match (self, options.ignore_nulls, options.merge_lists) {
(Some(x), _, true) => Some(UpdateList::add(x)),
(Some(x), _, false) => Some(UpdateList::set(x)),
(None, false, false) => Some(UpdateList::set(vec![])),
(None, _, _) => None,
}
}
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged, rename_all_fields = "camelCase")]
pub enum UpdateMap<TKey, TValue>
where
TKey: std::hash::Hash + std::cmp::Eq,
{
AddRemove {
add: Option<HashMap<TKey, TValue>>,
remove: Option<Vec<TKey>>,
},
Set {
set: HashMap<TKey, TValue>,
},
}
impl<TKey, TValue> UpdateMap<TKey, TValue>
where
TKey: std::hash::Hash + std::cmp::Eq,
{
pub fn add_remove(add: HashMap<TKey, TValue>, remove: Vec<TKey>) -> Self {
Self::AddRemove {
add: Some(add),
remove: Some(remove),
}
}
pub fn add(add: HashMap<TKey, TValue>) -> Self {
Self::AddRemove {
add: Some(add),
remove: None,
}
}
pub fn remove(remove: Vec<TKey>) -> Self {
Self::AddRemove {
add: None,
remove: Some(remove),
}
}
pub fn set(set: HashMap<TKey, TValue>) -> Self {
Self::Set { set }
}
}
impl<TKey: std::hash::Hash + std::cmp::Eq, TValue> IntoPatchItem<UpdateMap<TKey, TValue>>
for HashMap<TKey, TValue>
{
fn patch(self, options: &UpsertOptions) -> Option<UpdateMap<TKey, TValue>> {
if options.merge_maps {
Some(UpdateMap::add(self))
} else {
Some(UpdateMap::set(self))
}
}
}
impl<TKey: std::hash::Hash + std::cmp::Eq, TValue> IntoPatchItem<UpdateMap<TKey, TValue>>
for Option<HashMap<TKey, TValue>>
{
fn patch(self, options: &UpsertOptions) -> Option<UpdateMap<TKey, TValue>> {
match (self, options.ignore_nulls, options.merge_lists) {
(Some(x), _, true) => Some(UpdateMap::add(x)),
(Some(x), _, false) => Some(UpdateMap::set(x)),
(None, false, false) => Some(UpdateMap::set(HashMap::new())),
(None, _, _) => None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Patch<T>
where
T: Default,
{
#[serde(flatten)]
pub id: Identity,
pub update: T,
}
impl<T> Patch<T>
where
T: Default,
{
pub fn new(id: Identity) -> Self {
Patch::<T> {
id,
update: T::default(),
}
}
}
impl<T> EqIdentity for Patch<T>
where
T: Default,
{
fn eq(&self, id: &Identity) -> bool {
&self.id == id
}
}
macro_rules! to_idt {
($it:ident) => {
if $it.id > 0 {
Identity::Id { id: $it.id }
} else {
$it.external_id
.as_ref()
.map(|e| Identity::ExternalId {
external_id: e.clone(),
})
.unwrap_or_else(|| Identity::Id { id: $it.id })
}
};
}