| 1 | use std::{ |
| 2 | fmt::Display, |
| 3 | ops::{Deref, DerefMut}, |
| 4 | }; |
| 5 | |
| 6 | use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; |
| 7 | |
| 8 | use crate::Type; |
| 9 | |
| 10 | /// Type that uses a special value to be used as none. |
| 11 | /// |
| 12 | /// See [`Optional`] documentation for the rationale for this trait's existence. |
| 13 | /// |
| 14 | /// # Caveats |
| 15 | /// |
| 16 | /// Since use of default values as none is typical, this trait is implemented for all types that |
| 17 | /// implement [`Default`] for convenience. Unfortunately, this means you can not implement this |
| 18 | /// trait manually for types that implement [`Default`]. |
| 19 | /// |
| 20 | /// Moreoever, since `bool` implements [`Default`], `NoneValue` gets implemented for `bool` as well. |
| 21 | /// However, this is unsound since its not possible to distinguish between `false` and `None` in |
| 22 | /// this case. This is why you'll get a panic on trying to serialize or deserialize an |
| 23 | /// `Optionanl<bool>`. |
| 24 | pub trait NoneValue { |
| 25 | type NoneType; |
| 26 | |
| 27 | /// The none-equivalent value. |
| 28 | fn null_value() -> Self::NoneType; |
| 29 | } |
| 30 | |
| 31 | impl<T> NoneValue for T |
| 32 | where |
| 33 | T: Default, |
| 34 | { |
| 35 | type NoneType = Self; |
| 36 | |
| 37 | fn null_value() -> Self { |
| 38 | Default::default() |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | /// An optional value. |
| 43 | /// |
| 44 | /// Since D-Bus doesn't have the concept of nullability, it uses a special value (typically the |
| 45 | /// default value) as the null value. For example [this signal][ts] uses empty strings for null |
| 46 | /// values. Serde has built-in support for `Option` but unfortunately that doesn't work for us. |
| 47 | /// Hence the need for this type. |
| 48 | /// |
| 49 | /// The serialization and deserialization of `Optional` relies on [`NoneValue`] implementation of |
| 50 | /// the underlying type. |
| 51 | /// |
| 52 | /// # Examples |
| 53 | /// |
| 54 | /// ``` |
| 55 | /// use zvariant::{serialized::Context, Optional, to_bytes, LE}; |
| 56 | /// |
| 57 | /// // `Null` case. |
| 58 | /// let ctxt = Context::new_dbus(LE, 0); |
| 59 | /// let s = Optional::<&str>::default(); |
| 60 | /// let encoded = to_bytes(ctxt, &s).unwrap(); |
| 61 | /// assert_eq!(encoded.bytes(), &[0, 0, 0, 0, 0]); |
| 62 | /// let s: Optional<&str> = encoded.deserialize().unwrap().0; |
| 63 | /// assert_eq!(*s, None); |
| 64 | /// |
| 65 | /// // `Some` case. |
| 66 | /// let s = Optional::from(Some("hello" )); |
| 67 | /// let encoded = to_bytes(ctxt, &s).unwrap(); |
| 68 | /// assert_eq!(encoded.len(), 10); |
| 69 | /// // The first byte is the length of the string in Little-Endian format. |
| 70 | /// assert_eq!(encoded[0], 5); |
| 71 | /// let s: Optional<&str> = encoded.deserialize().unwrap().0; |
| 72 | /// assert_eq!(*s, Some("hello" )); |
| 73 | /// ``` |
| 74 | /// |
| 75 | /// [ts]: https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-name-owner-changed |
| 76 | #[derive (Clone, Debug, PartialEq, Eq, Hash)] |
| 77 | pub struct Optional<T>(Option<T>); |
| 78 | |
| 79 | impl<T> Type for Optional<T> |
| 80 | where |
| 81 | T: Type, |
| 82 | { |
| 83 | fn signature() -> crate::Signature<'static> { |
| 84 | T::signature() |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | impl<T> Serialize for Optional<T> |
| 89 | where |
| 90 | T: Type + NoneValue + Serialize, |
| 91 | <T as NoneValue>::NoneType: Serialize, |
| 92 | { |
| 93 | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
| 94 | where |
| 95 | S: Serializer, |
| 96 | { |
| 97 | if T::signature() == bool::signature() { |
| 98 | panic!("`Optional<bool>` type is not supported" ); |
| 99 | } |
| 100 | |
| 101 | match &self.0 { |
| 102 | Some(value: &T) => value.serialize(serializer), |
| 103 | None => T::null_value().serialize(serializer), |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | impl<'de, T, E> Deserialize<'de> for Optional<T> |
| 109 | where |
| 110 | T: Type + NoneValue + Deserialize<'de>, |
| 111 | <T as NoneValue>::NoneType: Deserialize<'de> + TryInto<T, Error = E> + PartialEq, |
| 112 | E: Display, |
| 113 | { |
| 114 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| 115 | where |
| 116 | D: Deserializer<'de>, |
| 117 | { |
| 118 | if T::signature() == bool::signature() { |
| 119 | panic!("`Optional<bool>` type is not supported" ); |
| 120 | } |
| 121 | |
| 122 | let value: impl Deserialize<'de> + TryInto<…> + PartialEq = <<T as NoneValue>::NoneType>::deserialize(deserializer)?; |
| 123 | if value == T::null_value() { |
| 124 | Ok(Optional(None)) |
| 125 | } else { |
| 126 | Ok(Optional(Some(value.try_into().map_err(op:de::Error::custom)?))) |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | impl<T> From<Option<T>> for Optional<T> { |
| 132 | fn from(value: Option<T>) -> Self { |
| 133 | Optional(value) |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | impl<T> From<Optional<T>> for Option<T> { |
| 138 | fn from(value: Optional<T>) -> Self { |
| 139 | value.0 |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | impl<T> Deref for Optional<T> { |
| 144 | type Target = Option<T>; |
| 145 | |
| 146 | fn deref(&self) -> &Self::Target { |
| 147 | &self.0 |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | impl<T> DerefMut for Optional<T> { |
| 152 | fn deref_mut(&mut self) -> &mut Self::Target { |
| 153 | &mut self.0 |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | impl<T> Default for Optional<T> { |
| 158 | fn default() -> Self { |
| 159 | Self(None) |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | #[cfg (test)] |
| 164 | mod tests { |
| 165 | use std::panic::catch_unwind; |
| 166 | |
| 167 | #[test ] |
| 168 | fn bool_in_optional() { |
| 169 | // Ensure trying to encode/decode `bool` in `Optional` fails. |
| 170 | use crate::{to_bytes, Optional, LE}; |
| 171 | |
| 172 | let ctxt = crate::serialized::Context::new_dbus(LE, 0); |
| 173 | let res = catch_unwind(|| to_bytes(ctxt, &Optional::<bool>::default())); |
| 174 | assert!(res.is_err()); |
| 175 | |
| 176 | let data = crate::serialized::Data::new([0, 0, 0, 0].as_slice(), ctxt); |
| 177 | let res = catch_unwind(|| data.deserialize::<Optional<bool>>()); |
| 178 | assert!(res.is_err()); |
| 179 | } |
| 180 | } |
| 181 | |