Repository: ruma/ruma (rust)

Issue:
Remove RawExt
`Raw` is now defined in the same crate as `RawExt`, so the latter can now simply be an extra `impl` block.

Pull request:
Some refactorings around the Raw type
Closes #975.

Patch:
diff --git a/crates/ruma-client-api/src/config/get_global_account_data.rs b/crates/ruma-client-api/src/config/get_global_account_data.rs
index 033decfc..123bf4ea 100644
--- a/crates/ruma-client-api/src/config/get_global_account_data.rs
+++ b/crates/ruma-client-api/src/config/get_global_account_data.rs
@@ -34,7 +34,7 @@ pub mod v3 {
         response: {
             /// Account data content for the given type.
             ///
-            /// Use `ruma_common::events::RawExt` for deserialization.
+            /// Use [`Raw::deserialize_content`] for deserialization.
             #[ruma_api(body)]
             pub account_data: Raw<AnyGlobalAccountDataEventContent>,
         }
diff --git a/crates/ruma-client-api/src/config/get_room_account_data.rs b/crates/ruma-client-api/src/config/get_room_account_data.rs
index 3df2974d..2f61140a 100644
--- a/crates/ruma-client-api/src/config/get_room_account_data.rs
+++ b/crates/ruma-client-api/src/config/get_room_account_data.rs
@@ -38,7 +38,7 @@ pub mod v3 {
         response: {
             /// Account data content for the given type.
             ///
-            /// Use `ruma_common::events::RawExt` for deserialization.
+            /// Use [`Raw::deserialize_content`] for deserialization.
             #[ruma_api(body)]
             pub account_data: Raw<AnyRoomAccountDataEventContent>,
         }
diff --git a/crates/ruma-client-api/src/state/get_state_events_for_key.rs b/crates/ruma-client-api/src/state/get_state_events_for_key.rs
index 84021958..10c1da86 100644
--- a/crates/ruma-client-api/src/state/get_state_events_for_key.rs
+++ b/crates/ruma-client-api/src/state/get_state_events_for_key.rs
@@ -28,7 +28,7 @@ pub mod v3 {
             /// The content of the state event.
             ///
             /// Since the inner type of the `Raw` does not implement `Deserialize`, you need to use
-            /// `ruma_common::events::RawExt` to deserialize it.
+            /// [`Raw::deserialize_content`] to deserialize it.
             #[ruma_api(body)]
             pub content: Raw<AnyStateEventContent>,
         }
diff --git a/crates/ruma-common/src/events.rs b/crates/ruma-common/src/events.rs
index 56c71190..57586848 100644
--- a/crates/ruma-common/src/events.rs
+++ b/crates/ruma-common/src/events.rs
@@ -230,14 +230,9 @@ pub trait RedactContent {
     fn redact(self, version: &RoomVersionId) -> Self::Redacted;
 }
 
-/// Extension trait for [`Raw<_>`][crate::serde::Raw].
-pub trait RawExt<T: EventContent> {
+impl<T: EventContent> Raw<T> {
     /// Try to deserialize the JSON as an event's content.
-    fn deserialize_content(&self, event_type: &str) -> serde_json::Result<T>;
-}
-
-impl<T: EventContent> RawExt<T> for Raw<T> {
-    fn deserialize_content(&self, event_type: &str) -> serde_json::Result<T> {
+    pub fn deserialize_content(&self, event_type: &str) -> serde_json::Result<T> {
         T::from_parts(event_type, self.json())
     }
 }
diff --git a/crates/ruma-common/src/events/room/join_rules.rs b/crates/ruma-common/src/events/room/join_rules.rs
index c549d845..fefe7f3b 100644
--- a/crates/ruma-common/src/events/room/join_rules.rs
+++ b/crates/ruma-common/src/events/room/join_rules.rs
@@ -109,6 +109,7 @@ impl<'de> Deserialize<'de> for JoinRule {
 
         #[derive(Deserialize)]
         struct ExtractType<'a> {
+            #[serde(borrow)]
             join_rule: Option<Cow<'a, str>>,
         }
 
diff --git a/crates/ruma-common/src/serde/cow.rs b/crates/ruma-common/src/serde/cow.rs
index f4fba913..eb6a70d3 100644
--- a/crates/ruma-common/src/serde/cow.rs
+++ b/crates/ruma-common/src/serde/cow.rs
@@ -1,23 +1,6 @@
 use std::{borrow::Cow, str};
 
-use serde::de::{self, Deserialize, Deserializer, Unexpected, Visitor};
-
-pub(crate) struct MyCowStr<'a>(Cow<'a, str>);
-
-impl<'a> MyCowStr<'a> {
-    pub(crate) fn get(self) -> Cow<'a, str> {
-        self.0
-    }
-}
-
-impl<'de> Deserialize<'de> for MyCowStr<'de> {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        deserialize_cow_str(deserializer).map(Self)
-    }
-}
+use serde::de::{self, Deserializer, Unexpected, Visitor};
 
 /// Deserialize a `Cow<'de, str>`.
 ///
diff --git a/crates/ruma-common/src/serde/raw.rs b/crates/ruma-common/src/serde/raw.rs
index 13cfa57f..332a73bf 100644
--- a/crates/ruma-common/src/serde/raw.rs
+++ b/crates/ruma-common/src/serde/raw.rs
@@ -1,18 +1,16 @@
 use std::{
     clone::Clone,
-    fmt::{self, Debug, Formatter},
+    fmt::{self, Debug},
     marker::PhantomData,
     mem,
 };
 
 use serde::{
-    de::{Deserialize, Deserializer, IgnoredAny, MapAccess, Visitor},
+    de::{self, Deserialize, DeserializeSeed, Deserializer, IgnoredAny, MapAccess, Visitor},
     ser::{Serialize, Serializer},
 };
 use serde_json::value::{to_raw_value as to_raw_json_value, RawValue as RawJsonValue};
 
-use super::cow::MyCowStr;
-
 /// A wrapper around `Box<RawValue>`, to be used in place of any type in the Matrix endpoint
 /// definition to allow request and response types to contain that said type represented by
 /// the generic argument `Ev`.
@@ -97,6 +95,36 @@ impl<T> Raw<T> {
     where
         U: Deserialize<'a>,
     {
+        struct FieldVisitor<'b>(&'b str);
+
+        impl<'b, 'de> Visitor<'de> for FieldVisitor<'b> {
+            type Value = bool;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+                write!(formatter, "`{}`", self.0)
+            }
+
+            fn visit_str<E>(self, value: &str) -> Result<bool, E>
+            where
+                E: de::Error,
+            {
+                Ok(value == self.0)
+            }
+        }
+
+        struct Field<'b>(&'b str);
+
+        impl<'b, 'de> DeserializeSeed<'de> for Field<'b> {
+            type Value = bool;
+
+            fn deserialize<D>(self, deserializer: D) -> Result<bool, D::Error>
+            where
+                D: Deserializer<'de>,
+            {
+                deserializer.deserialize_identifier(FieldVisitor(self.0))
+            }
+        }
+
         struct SingleFieldVisitor<'b, T> {
             field_name: &'b str,
             _phantom: PhantomData<T>,
@@ -114,7 +142,7 @@ impl<T> Raw<T> {
         {
             type Value = Option<T>;
 
-            fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
+            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> std::fmt::Result {
                 formatter.write_str("a string")
             }
 
@@ -123,8 +151,8 @@ impl<T> Raw<T> {
                 A: MapAccess<'de>,
             {
                 let mut res = None;
-                while let Some(key) = map.next_key::<MyCowStr<'_>>()? {
-                    if key.get() == self.field_name {
+                while let Some(is_right_field) = map.next_key_seed(Field(self.field_name))? {
+                    if is_right_field {
                         res = Some(map.next_value()?);
                     } else {
                         map.next_value::<IgnoredAny>()?;
@@ -177,7 +205,7 @@ impl<T> Clone for Raw<T> {
 }
 
 impl<T> Debug for Raw<T> {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         use std::any::type_name;
         f.debug_struct(&format!("Raw::<{}>", type_name::<T>())).field("json", &self.json).finish()
     }
@@ -200,3 +228,32 @@ impl<T> Serialize for Raw<T> {
         self.json.serialize(serializer)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use serde::Deserialize;
+    use serde_json::{from_str as from_json_str, value::RawValue as RawJsonValue};
+
+    use super::Raw;
+
+    #[test]
+    fn get_field() -> serde_json::Result<()> {
+        #[derive(Debug, PartialEq, Deserialize)]
+        struct A<'a> {
+            #[serde(borrow)]
+            b: Vec<&'a str>,
+        }
+
+        const OBJ: &str = r#"{ "a": { "b": [  "c"] }, "z": 5 }"#;
+        let raw: Raw<()> = from_json_str(OBJ)?;
+
+        assert_eq!(raw.get_field::<u8>("z")?, Some(5));
+        assert_eq!(raw.get_field::<&RawJsonValue>("a")?.unwrap().get(), r#"{ "b": [  "c"] }"#);
+        assert_eq!(raw.get_field::<A<'_>>("a")?, Some(A { b: vec!["c"] }));
+
+        assert!(raw.get_field::<u8>("b")?.is_none());
+        assert!(raw.get_field::<u8>("a").is_err());
+
+        Ok(())
+    }
+}


Test patch:
diff --git a/crates/ruma-common/tests/events/message_event.rs b/crates/ruma-common/tests/events/message_event.rs
index 20536d9e..f9b3c5e6 100644
--- a/crates/ruma-common/tests/events/message_event.rs
+++ b/crates/ruma-common/tests/events/message_event.rs
@@ -8,7 +8,7 @@ use ruma_common::{
         room::{ImageInfo, ThumbnailInfo},
         sticker::StickerEventContent,
         AnyMessageLikeEvent, AnyMessageLikeEventContent, AnySyncMessageLikeEvent, MessageLikeEvent,
-        RawExt, Unsigned,
+        Unsigned,
     },
     mxc_uri, room_id,
     serde::Raw,
diff --git a/crates/ruma-common/tests/events/state_event.rs b/crates/ruma-common/tests/events/state_event.rs
index 8a60a020..29ee7c32 100644
--- a/crates/ruma-common/tests/events/state_event.rs
+++ b/crates/ruma-common/tests/events/state_event.rs
@@ -8,7 +8,7 @@ use ruma_common::{
             avatar::{ImageInfo, RoomAvatarEventContent},
             ThumbnailInfo,
         },
-        AnyRoomEvent, AnyStateEvent, AnyStateEventContent, AnySyncStateEvent, RawExt, StateEvent,
+        AnyRoomEvent, AnyStateEvent, AnyStateEventContent, AnySyncStateEvent, StateEvent,
         SyncStateEvent, Unsigned,
     },
     mxc_uri, room_alias_id, room_id,