Skip to content

Instantly share code, notes, and snippets.

@vdebergue
Last active August 23, 2024 09:18
Show Gist options
  • Save vdebergue/afb1dd526538f2f8664a9a5339990684 to your computer and use it in GitHub Desktop.
Save vdebergue/afb1dd526538f2f8664a9a5339990684 to your computer and use it in GitHub Desktop.
use async_graphql::{InputType, InputValueError};
use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer};
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum MaybeUpdateOrDelete<T> {
Update(T),
Delete,
Empty,
}
impl<T> MaybeUpdateOrDelete<T> {
fn is_empty(&self) -> bool {
match self {
MaybeUpdateOrDelete::Update(_) => false,
MaybeUpdateOrDelete::Delete => false,
MaybeUpdateOrDelete::Empty => true,
}
}
}
impl<T> Default for MaybeUpdateOrDelete<T> {
fn default() -> Self {
Self::Empty
}
}
impl<'de, T> Deserialize<'de> for MaybeUpdateOrDelete<T>
where
T: DeserializeOwned,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let json = serde_json::Value::deserialize(deserializer)?;
match json {
serde_json::Value::Null => Ok(MaybeUpdateOrDelete::Delete),
other => serde_json::from_value::<T>(other)
.map(|t| MaybeUpdateOrDelete::Update(t))
.map_err(|e| serde::de::Error::custom(e)),
}
}
}
impl<T> Serialize for MaybeUpdateOrDelete<T>
where
T: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match &self {
MaybeUpdateOrDelete::Update(t) => t.serialize(serializer),
MaybeUpdateOrDelete::Delete => serde_json::Value::Null.serialize(serializer),
MaybeUpdateOrDelete::Empty => serializer.serialize_unit(),
}
}
}
// Similar to Option<T>
// https://github.com/async-graphql/async-graphql/blob/880dac90f1516bbb590be63965dfcfa4973b4edd/src/types/external/optional.rs#L8
impl<T> InputType for MaybeUpdateOrDelete<T>
where
T: InputType,
{
type RawValueType = T::RawValueType;
fn type_name() -> std::borrow::Cow<'static, str> {
T::type_name()
}
fn qualified_type_name() -> String {
T::type_name().to_string()
}
fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
T::create_type_info(registry)
}
fn parse(value: Option<async_graphql::Value>) -> async_graphql::InputValueResult<Self> {
match value {
None => async_graphql::InputValueResult::Ok(MaybeUpdateOrDelete::Empty),
Some(async_graphql::Value::Null) => Ok(MaybeUpdateOrDelete::Delete),
Some(v) => T::parse(Some(v))
.map(|t| MaybeUpdateOrDelete::Update(t))
.map_err(InputValueError::propagate),
}
}
fn to_value(&self) -> async_graphql::Value {
match self {
MaybeUpdateOrDelete::Update(t) => T::to_value(t),
MaybeUpdateOrDelete::Delete => async_graphql::Value::Null,
MaybeUpdateOrDelete::Empty => async_graphql::Value::Null,
}
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
match self {
MaybeUpdateOrDelete::Update(t) => t.as_raw_value(),
MaybeUpdateOrDelete::Delete => None,
MaybeUpdateOrDelete::Empty => None,
}
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, async_graphql::InputObject)]
struct Foo {
#[serde(default, skip_serializing_if = "MaybeUpdateOrDelete::is_empty")]
a: MaybeUpdateOrDelete<u32>,
}
use super::MaybeUpdateOrDelete;
#[test]
fn read() {
let input = json!({"a": null});
let res = serde_json::from_value::<Foo>(input);
assert_eq!(
res.unwrap(),
Foo {
a: MaybeUpdateOrDelete::Delete
}
);
let input = json!({"a": 42});
let res = serde_json::from_value::<Foo>(input);
assert_eq!(
res.unwrap(),
Foo {
a: MaybeUpdateOrDelete::Update(42)
}
);
let input = json!({"b": "other"});
let res = serde_json::from_value::<Foo>(input);
assert_eq!(
res.unwrap(),
Foo {
a: MaybeUpdateOrDelete::Empty
}
);
let input = json!({"a": "error"});
let res = serde_json::from_value::<Foo>(input);
assert_eq!(res.is_err(), true);
}
#[test]
fn read_graphql() {
let input = async_graphql::Value::from_json(json!({"a": null})).unwrap();
let res = async_graphql::from_value::<Foo>(input);
assert_eq!(
res.unwrap(),
Foo {
a: MaybeUpdateOrDelete::Delete
}
);
let input = async_graphql::Value::from_json(json!({"a": 42})).unwrap();
let res = async_graphql::from_value::<Foo>(input);
assert_eq!(
res.unwrap(),
Foo {
a: MaybeUpdateOrDelete::Update(42)
}
);
let input = async_graphql::Value::from_json(json!({"b": "other"})).unwrap();
let res = async_graphql::from_value::<Foo>(input);
assert_eq!(
res.unwrap(),
Foo {
a: MaybeUpdateOrDelete::Empty
}
);
let input = async_graphql::Value::from_json(json!({"a": "error"})).unwrap();
let res = async_graphql::from_value::<Foo>(input);
assert_eq!(res.is_err(), true);
}
#[test]
fn write() {
let js = serde_json::to_value(Foo {
a: MaybeUpdateOrDelete::Update(42),
})
.unwrap();
assert_eq!(js, json!({"a": 42}));
let js = serde_json::to_value(Foo {
a: MaybeUpdateOrDelete::Delete,
})
.unwrap();
assert_eq!(js, json!({"a": null}));
let js = serde_json::to_value(Foo {
a: MaybeUpdateOrDelete::Empty,
})
.unwrap();
assert_eq!(js, json!({}));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment