Skip to content

Commit

Permalink
Fix deserialisation of Option<_> fields
Browse files Browse the repository at this point in the history
Workaround for <serde-rs/serde#723>.
  • Loading branch information
tesaguri committed Feb 25, 2024
1 parent a208ee5 commit 3e83761
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 19 deletions.
30 changes: 20 additions & 10 deletions crates/kitsune-type/src/ap/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,40 +32,48 @@ pub struct Actor {
#[serde(deserialize_with = "jsonld::serde::FirstOk::deserialize")]
pub r#type: ActorType,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
#[serde(deserialize_with = "jsonld::serde::Optional::<jsonld::serde::First<_>>::deserialize")]
pub name: Option<String>,
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
pub preferred_username: String,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
#[serde(deserialize_with = "jsonld::serde::Optional::<jsonld::serde::First<_>>::deserialize")]
pub subject: Option<String>,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
#[serde(deserialize_with = "jsonld::serde::Optional::<jsonld::serde::First<_>>::deserialize")]
pub icon: Option<MediaAttachment>,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
#[serde(deserialize_with = "jsonld::serde::Optional::<jsonld::serde::First<_>>::deserialize")]
pub image: Option<MediaAttachment>,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
pub manually_approves_followers: bool,
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
pub public_key: PublicKey,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
#[serde(deserialize_with = "jsonld::serde::Optional::<jsonld::serde::First<_>>::deserialize")]
pub endpoints: Option<Endpoints>,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::FirstId::deserialize")]
#[serde(
deserialize_with = "jsonld::serde::Optional::<jsonld::serde::FirstId<_>>::deserialize"
)]
pub featured: Option<String>,
#[serde(deserialize_with = "jsonld::serde::FirstId::deserialize")]
pub inbox: String,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::FirstId::deserialize")]
#[serde(
deserialize_with = "jsonld::serde::Optional::<jsonld::serde::FirstId<_>>::deserialize"
)]
pub outbox: Option<String>,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::FirstId::deserialize")]
#[serde(
deserialize_with = "jsonld::serde::Optional::<jsonld::serde::FirstId<_>>::deserialize"
)]
pub followers: Option<String>,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::FirstId::deserialize")]
#[serde(
deserialize_with = "jsonld::serde::Optional::<jsonld::serde::FirstId<_>>::deserialize"
)]
pub following: Option<String>,
#[serde(default = "Timestamp::now_utc")]
pub published: Timestamp,
Expand All @@ -81,7 +89,9 @@ impl RdfNode for Actor {
#[serde(rename_all = "camelCase")]
pub struct Endpoints {
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::FirstId::deserialize")]
#[serde(
deserialize_with = "jsonld::serde::Optional::<jsonld::serde::FirstId<_>>::deserialize"
)]
pub shared_inbox: Option<String>,
}

Expand Down
10 changes: 6 additions & 4 deletions crates/kitsune-type/src/ap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,15 @@ pub struct Object {
#[serde(deserialize_with = "jsonld::serde::FirstId::deserialize")]
pub attributed_to: String,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::FirstId::deserialize")]
#[serde(
deserialize_with = "jsonld::serde::Optional::<jsonld::serde::FirstId<_>>::deserialize"
)]
pub in_reply_to: Option<String>,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
#[serde(deserialize_with = "jsonld::serde::Optional::<jsonld::serde::First<_>>::deserialize")]
pub name: Option<String>,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
#[serde(deserialize_with = "jsonld::serde::Optional::<jsonld::serde::First<_>>::deserialize")]
pub summary: Option<String>,
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
pub content: String,
Expand Down Expand Up @@ -215,6 +217,6 @@ pub struct Tag {
pub name: String,
pub href: Option<String>,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
#[serde(deserialize_with = "jsonld::serde::Optional::<jsonld::serde::First<_>>::deserialize")]
pub icon: Option<MediaAttachment>,
}
4 changes: 2 additions & 2 deletions crates/kitsune-type/src/ap/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ pub struct MediaAttachment {
#[serde(deserialize_with = "jsonld::serde::FirstOk::deserialize")]
pub r#type: MediaAttachmentType,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
#[serde(deserialize_with = "jsonld::serde::Optional::<jsonld::serde::First<_>>::deserialize")]
pub name: Option<String>,
pub media_type: Option<String>,
#[serde(default)]
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
#[serde(deserialize_with = "jsonld::serde::Optional::<jsonld::serde::First<_>>::deserialize")]
pub blurhash: Option<String>,
#[serde(deserialize_with = "jsonld::serde::First::deserialize")]
pub url: String,
Expand Down
11 changes: 11 additions & 0 deletions crates/kitsune-type/src/jsonld/serde/first.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ where
}
}

// XXX: Intentionally limiting to `First<PhantomData<_>>` rather than `First<_>` to help inference
// of the type parameter of `Optional::<First<_>>::deserialize`.
impl<'de, T> Default for First<PhantomData<T>>
where
T: Deserialize<'de>,
{
fn default() -> Self {
Self::new()
}
}

impl<'de, T> DeserializeSeed<'de> for First<T>
where
T: DeserializeSeed<'de>,
Expand Down
9 changes: 9 additions & 0 deletions crates/kitsune-type/src/jsonld/serde/first_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ where
}
}

impl<'de, T> Default for FirstId<PhantomData<T>>
where
T: Deserialize<'de>,
{
fn default() -> Self {
Self::new()
}
}

impl<'de, T> DeserializeSeed<'de> for FirstId<T>
where
T: DeserializeSeed<'de>,
Expand Down
9 changes: 9 additions & 0 deletions crates/kitsune-type/src/jsonld/serde/first_ok.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ where
}
}

impl<'de, T> Default for FirstOk<PhantomData<T>>
where
T: Deserialize<'de>,
{
fn default() -> Self {
Self::new()
}
}

impl<'de, T> DeserializeSeed<'de> for FirstOk<T>
where
T: DeserializeSeed<'de> + Clone,
Expand Down
9 changes: 9 additions & 0 deletions crates/kitsune-type/src/jsonld/serde/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ where
}
}

impl<'de, T> Default for Id<PhantomData<T>>
where
T: Deserialize<'de>,
{
fn default() -> Self {
Self::new()
}
}

impl<'de, T> DeserializeSeed<'de> for Id<T>
where
T: DeserializeSeed<'de>,
Expand Down
9 changes: 9 additions & 0 deletions crates/kitsune-type/src/jsonld/serde/id_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ where
}
}

impl<'de, T> Default for IdSet<PhantomData<T>>
where
T: Deserialize<'de>,
{
fn default() -> Self {
Self::new()
}
}

impl<'de, T> DeserializeSeed<'de> for IdSet<T>
where
T: DeserializeSeed<'de>,
Expand Down
8 changes: 5 additions & 3 deletions crates/kitsune-type/src/jsonld/serde/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,15 @@ mod first_id;
mod first_ok;
mod id;
mod id_set;
mod optional;
mod set;

pub use self::first::First;
pub use self::first_id::FirstId;
pub use self::first_ok::FirstOk;
pub use self::id::Id;
pub use self::id_set::IdSet;
pub use self::optional::Optional;
pub use self::set::Set;

use core::{
Expand Down Expand Up @@ -375,7 +377,7 @@ where

#[cfg(test)]
mod tests {
use super::{First, FirstId, FirstOk, IdSet};
use super::{First, FirstId, FirstOk, IdSet, Optional};
use serde::Deserialize;

/// Checks that the types work for some random real-world-ish use cases.
Expand All @@ -395,10 +397,10 @@ mod tests {
#[serde(deserialize_with = "FirstId::deserialize")]
attributed_to: String,
#[serde(default)]
#[serde(deserialize_with = "First::deserialize")]
#[serde(deserialize_with = "Optional::<First<_>>::deserialize")]
summary: Option<String>,
#[serde(default)]
#[serde(deserialize_with = "First::deserialize")]
#[serde(deserialize_with = "Optional::<First<_>>::deserialize")]
content: Option<String>,
#[serde(default)]
#[serde(deserialize_with = "IdSet::deserialize")]
Expand Down
79 changes: 79 additions & 0 deletions crates/kitsune-type/src/jsonld/serde/optional.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use core::fmt::{self, Formatter};
use serde::de::{self, DeserializeSeed, Deserializer};

/// Deserialises an `Option<T::Value>` value.
///
/// Workaround until Serde introduces a native mechanism for applying the
/// `#[serde(deserialize_with)]` attribute to the type inside an `Option<_>`.
///
/// cf. <https://github.com/serde-rs/serde/issues/723>.
pub struct Optional<T> {
seed: T,
}

struct Visitor<T>(T);

impl<'de, T> Optional<T>
where
T: DeserializeSeed<'de> + Default,
{
pub fn new() -> Self {
Self::with_seed(T::default())
}

pub fn deserialize<D>(deserializer: D) -> Result<Option<T::Value>, D::Error>
where
D: Deserializer<'de>,
{
Self::new().deserialize(deserializer)
}
}

impl<'de, T> Optional<T>
where
T: DeserializeSeed<'de>,
{
pub fn with_seed(seed: T) -> Self {
Self { seed }
}
}

impl<'de, T> DeserializeSeed<'de> for Optional<T>
where
T: DeserializeSeed<'de>,
{
type Value = Option<T::Value>;

fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_option(Visitor(self.seed))
}
}

impl<'de, T> de::Visitor<'de> for Visitor<T>
where
T: DeserializeSeed<'de>,
{
type Value = Option<T::Value>;

fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("option")
}

fn visit_unit<E>(self) -> Result<Self::Value, E> {
Ok(None)
}

fn visit_none<E>(self) -> Result<Self::Value, E> {
Ok(None)
}

fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
self.0.deserialize(deserializer).map(Some)
}
}
9 changes: 9 additions & 0 deletions crates/kitsune-type/src/jsonld/serde/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ where
}
}

impl<'de, T> Default for Set<PhantomData<T>>
where
T: Deserialize<'de>,
{
fn default() -> Self {
Self::new()
}
}

impl<'de, T> DeserializeSeed<'de> for Set<T>
where
T: DeserializeSeed<'de>,
Expand Down

0 comments on commit 3e83761

Please sign in to comment.