[fractal] API: Implement (de)serializers for Url and Host types



commit 40b7ef9f0e11472dc31c42124c1cf46a1fa9362f
Author: Alejandro Domínguez <adomu net-c com>
Date:   Wed Oct 2 10:14:26 2019 +0200

    API: Implement (de)serializers for Url and Host types
    
    This guarantees that the (de)serialized structs which
    contain URLs in any of their fields will always have
    a valid URL string and not just a generic one.

 fractal-gtk/src/widgets/login.rs                   | 13 ++--
 fractal-matrix-api/src/backend/mod.rs              |  9 ++-
 fractal-matrix-api/src/backend/user.rs             | 34 ++++++++--
 fractal-matrix-api/src/de.rs                       | 76 ++++++++++++++++++++++
 fractal-matrix-api/src/lib.rs                      |  1 +
 fractal-matrix-api/src/model/member.rs             |  4 +-
 fractal-matrix-api/src/model/room.rs               |  4 +-
 fractal-matrix-api/src/r0.rs                       | 48 +++++++++++++-
 fractal-matrix-api/src/r0/account.rs               |  7 +-
 .../r0/contact/request_verification_token_email.rs |  7 +-
 .../contact/request_verification_token_msisdn.rs   |  7 +-
 .../src/r0/directory/post_public_rooms.rs          |  5 +-
 fractal-matrix-api/src/r0/media/create.rs          |  4 +-
 fractal-matrix-api/src/r0/profile/get_profile.rs   |  5 +-
 .../src/r0/profile/set_avatar_url.rs               |  4 +-
 fractal-matrix-api/src/r0/search/user.rs           |  4 +-
 fractal-matrix-api/src/r0/server/domain_info.rs    |  7 +-
 .../src/r0/thirdparty/get_supported_protocols.rs   | 11 +++-
 fractal-matrix-api/src/ser.rs                      | 18 +++++
 fractal-matrix-api/src/util.rs                     |  7 +-
 20 files changed, 237 insertions(+), 38 deletions(-)
---
diff --git a/fractal-gtk/src/widgets/login.rs b/fractal-gtk/src/widgets/login.rs
index 71a1cf72..baef5d61 100644
--- a/fractal-gtk/src/widgets/login.rs
+++ b/fractal-gtk/src/widgets/login.rs
@@ -94,26 +94,23 @@ impl LoginWidget {
                 if !password.is_empty() && !username.is_empty() {
                     // take the user's homeserver value if the
                     // well-known request fails
-                    let hs_url = Url::parse(&txt);
-
-                    if hs_url.is_err() {
+                    let mut homeserver_url = if let Ok(hs_url) = Url::parse(&txt) {
+                        hs_url
+                    } else {
                         let msg = i18n("Malformed server URL");
                         ErrorDialog::new(false, &msg);
                         return;
                     };
 
-                    let mut homeserver_url =
-                        hs_url.expect("hs_url must return earlier if it's Err");
                     let mut idserver = globals::DEFAULT_IDENTITYSERVER.clone();
                     match get_well_known(&txt) {
                         // TODO: Use Url everywhere
                         Ok(response) => {
                             info!("Got well-known response from {}: {:#?}", &txt, response);
-                            homeserver_url =
-                                Url::parse(&response.homeserver.base_url).unwrap_or(homeserver_url);
+                            homeserver_url = response.homeserver.base_url;
                             idserver = response
                                 .identity_server
-                                .and_then(|ids| Url::parse(&ids.base_url).ok())
+                                .map(|ids| ids.base_url)
                                 .unwrap_or(idserver);
                         }
                         Err(e) => info!("Failed to .well-known request: {:#?}", e),
diff --git a/fractal-matrix-api/src/backend/mod.rs b/fractal-matrix-api/src/backend/mod.rs
index d79fdf63..6c863dd2 100644
--- a/fractal-matrix-api/src/backend/mod.rs
+++ b/fractal-matrix-api/src/backend/mod.rs
@@ -110,16 +110,19 @@ impl Backend {
             Ok(BKCommand::SetUserName(name)) => user::set_username(self, name),
             Ok(BKCommand::GetThreePID) => user::get_threepid(self),
             Ok(BKCommand::GetTokenEmail(identity, email, client_secret)) => {
-                user::get_email_token(self, identity, email, client_secret)
+                let r = user::get_email_token(self, identity, email, client_secret);
+                bkerror2!(r, tx, BKResponse::GetTokenEmail);
             }
             Ok(BKCommand::GetTokenPhone(identity, phone, client_secret)) => {
-                user::get_phone_token(self, identity, phone, client_secret)
+                let r = user::get_phone_token(self, identity, phone, client_secret);
+                bkerror2!(r, tx, BKResponse::GetTokenPhone);
             }
             Ok(BKCommand::SubmitPhoneToken(_, client_secret, sid, token)) => {
                 user::submit_phone_token(self, client_secret, sid, token)
             }
             Ok(BKCommand::AddThreePID(identity, client_secret, sid)) => {
-                user::add_threepid(self, identity, client_secret, sid)
+                let r = user::add_threepid(self, identity, client_secret, sid);
+                bkerror2!(r, tx, BKResponse::AddThreePID);
             }
             Ok(BKCommand::DeleteThreePID(medium, address)) => {
                 user::delete_three_pid(self, medium, address)
diff --git a/fractal-matrix-api/src/backend/user.rs b/fractal-matrix-api/src/backend/user.rs
index e990f782..f8e50d9d 100644
--- a/fractal-matrix-api/src/backend/user.rs
+++ b/fractal-matrix-api/src/backend/user.rs
@@ -12,6 +12,7 @@ use crate::util::ContentType;
 use crate::util::ResultExpectLog;
 use crate::util::HTTP_CLIENT;
 use reqwest::header::HeaderValue;
+use std::convert::TryInto;
 use std::sync::mpsc::Sender;
 use std::sync::{Arc, Mutex};
 use std::thread;
@@ -161,14 +162,19 @@ pub fn get_threepid(bk: &Backend) {
     });
 }
 
-pub fn get_email_token(bk: &Backend, identity: String, email: String, client_secret: String) {
+pub fn get_email_token(
+    bk: &Backend,
+    identity: String,
+    email: String,
+    client_secret: String,
+) -> Result<(), Error> {
     let tx = bk.tx.clone();
 
     let base = bk.get_base_url();
     let access_token = bk.data.lock().unwrap().access_token.clone();
     let params = EmailTokenParameters { access_token };
     let body = EmailTokenBody {
-        id_server: identity[8..].into(),
+        id_server: Url::parse(&identity)?.try_into()?,
         client_secret: client_secret.clone(),
         email,
         send_attempt: 1,
@@ -200,16 +206,23 @@ pub fn get_email_token(bk: &Backend, identity: String, email: String, client_sec
         tx.send(BKResponse::GetTokenEmail(query))
             .expect_log("Connection closed");
     });
+
+    Ok(())
 }
 
-pub fn get_phone_token(bk: &Backend, identity: String, phone: String, client_secret: String) {
+pub fn get_phone_token(
+    bk: &Backend,
+    identity: String,
+    phone: String,
+    client_secret: String,
+) -> Result<(), Error> {
     let tx = bk.tx.clone();
 
     let base = bk.get_base_url();
     let access_token = bk.data.lock().unwrap().access_token.clone();
     let params = PhoneTokenParameters { access_token };
     let body = PhoneTokenBody {
-        id_server: identity[8..].into(),
+        id_server: Url::parse(&identity)?.try_into()?,
         client_secret: client_secret.clone(),
         phone_number: phone,
         country: String::new(),
@@ -242,9 +255,16 @@ pub fn get_phone_token(bk: &Backend, identity: String, phone: String, client_sec
         tx.send(BKResponse::GetTokenPhone(query))
             .expect_log("Connection closed");
     });
+
+    Ok(())
 }
 
-pub fn add_threepid(bk: &Backend, identity: String, client_secret: String, sid: String) {
+pub fn add_threepid(
+    bk: &Backend,
+    identity: String,
+    client_secret: String,
+    sid: String,
+) -> Result<(), Error> {
     let tx = bk.tx.clone();
 
     let base = bk.get_base_url();
@@ -252,7 +272,7 @@ pub fn add_threepid(bk: &Backend, identity: String, client_secret: String, sid:
     let params = AddThreePIDParameters { access_token };
     let body = AddThreePIDBody {
         three_pid_creds: ThreePIDCredentials {
-            id_server: identity[8..].into(),
+            id_server: Url::parse(&identity)?.try_into()?,
             sid: sid.clone(),
             client_secret,
         },
@@ -273,6 +293,8 @@ pub fn add_threepid(bk: &Backend, identity: String, client_secret: String, sid:
         tx.send(BKResponse::AddThreePID(query))
             .expect_log("Connection closed");
     });
+
+    Ok(())
 }
 
 pub fn submit_phone_token(bk: &Backend, client_secret: String, sid: String, token: String) {
diff --git a/fractal-matrix-api/src/de.rs b/fractal-matrix-api/src/de.rs
new file mode 100644
index 00000000..e2b213d1
--- /dev/null
+++ b/fractal-matrix-api/src/de.rs
@@ -0,0 +1,76 @@
+pub mod url {
+    use serde::de::{Error, Visitor};
+    use serde::Deserializer;
+    use std::fmt::{self, Formatter};
+    use url::Url;
+
+    pub(super) struct UrlVisitor;
+
+    impl<'de> Visitor<'de> for UrlVisitor {
+        type Value = Url;
+
+        fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
+            write!(formatter, "a valid URL")
+        }
+
+        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+        where
+            E: Error,
+        {
+            Url::parse(v).map_err(E::custom)
+        }
+    }
+
+    pub fn deserialize<'de, D>(de: D) -> Result<Url, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        de.deserialize_str(UrlVisitor)
+    }
+}
+
+pub mod option_url {
+    use super::url as serde_url;
+    use serde::de::{Error, Visitor};
+    use serde::Deserializer;
+    use std::fmt::{self, Formatter};
+    use url::Url;
+
+    struct OptionUrlVisitor;
+
+    impl<'de> Visitor<'de> for OptionUrlVisitor {
+        type Value = Option<Url>;
+
+        fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
+            serde_url::UrlVisitor.expecting(formatter)
+        }
+
+        fn visit_unit<E>(self) -> Result<Self::Value, E>
+        where
+            E: Error,
+        {
+            Ok(None)
+        }
+
+        fn visit_none<E>(self) -> Result<Self::Value, E>
+        where
+            E: Error,
+        {
+            Ok(None)
+        }
+
+        fn visit_some<D>(self, de: D) -> Result<Self::Value, D::Error>
+        where
+            D: Deserializer<'de>,
+        {
+            serde_url::deserialize(de).map(Some)
+        }
+    }
+
+    pub fn deserialize<'de, D>(de: D) -> Result<Option<Url>, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        de.deserialize_option(OptionUrlVisitor)
+    }
+}
diff --git a/fractal-matrix-api/src/lib.rs b/fractal-matrix-api/src/lib.rs
index bc71a688..5b633847 100644
--- a/fractal-matrix-api/src/lib.rs
+++ b/fractal-matrix-api/src/lib.rs
@@ -6,6 +6,7 @@ pub mod globals;
 pub mod backend;
 pub mod cache;
 mod client;
+mod de;
 pub mod identity;
 mod model;
 pub mod r0;
diff --git a/fractal-matrix-api/src/model/member.rs b/fractal-matrix-api/src/model/member.rs
index fd1fbfd5..3f9ae5cd 100644
--- a/fractal-matrix-api/src/model/member.rs
+++ b/fractal-matrix-api/src/model/member.rs
@@ -1,7 +1,9 @@
 use crate::r0::search::user::User;
 use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
+use url::Url;
 
+// TODO: Remove this and use only crate::r0::search::user::User
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct Member {
     // The mxid is either inside the json object, or outside of it.
@@ -36,7 +38,7 @@ impl From<User> for Member {
         Self {
             uid: user.user_id,
             alias: user.display_name,
-            avatar: user.avatar_url,
+            avatar: user.avatar_url.map(Url::into_string),
         }
     }
 }
diff --git a/fractal-matrix-api/src/model/room.rs b/fractal-matrix-api/src/model/room.rs
index a7f9fe1a..c433080e 100644
--- a/fractal-matrix-api/src/model/room.rs
+++ b/fractal-matrix-api/src/model/room.rs
@@ -79,7 +79,7 @@ pub enum RoomTag {
 #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 pub struct Room {
     pub id: String,
-    pub avatar: Option<String>,
+    pub avatar: Option<String>, // TODO: Use Option<Url>
     pub name: Option<String>,
     pub topic: Option<String>,
     pub alias: Option<String>,
@@ -282,7 +282,7 @@ impl From<PublicRoomsChunk> for Room {
         Self {
             alias: input.canonical_alias,
             name: input.name,
-            avatar: input.avatar_url,
+            avatar: input.avatar_url.map(Url::into_string),
             topic: input.topic,
             n_members: input.num_joined_members,
             world_readable: input.world_readable,
diff --git a/fractal-matrix-api/src/r0.rs b/fractal-matrix-api/src/r0.rs
index 9b1f1bf2..7acfc49e 100644
--- a/fractal-matrix-api/src/r0.rs
+++ b/fractal-matrix-api/src/r0.rs
@@ -9,7 +9,12 @@ pub mod server;
 pub mod sync;
 pub mod thirdparty;
 
-use serde::{Deserialize, Serialize};
+use serde::{Deserialize, Serialize, Serializer};
+use std::convert::TryFrom;
+use std::fmt::{self, Display, Formatter};
+use url::Host;
+use url::ParseError;
+use url::Url;
 
 #[derive(Clone, Debug, Deserialize, Serialize)]
 #[serde(rename_all = "lowercase")]
@@ -21,6 +26,45 @@ pub enum Medium {
 #[derive(Clone, Debug, Serialize)]
 pub struct ThreePIDCredentials {
     pub client_secret: String,
-    pub id_server: String,
+    pub id_server: HostAndPort<String>,
     pub sid: String,
 }
+
+#[derive(Clone, Debug)]
+pub struct HostAndPort<T> {
+    pub host: Host<T>,
+    pub port: Option<u16>,
+}
+
+impl TryFrom<Url> for HostAndPort<String> {
+    type Error = ParseError;
+
+    fn try_from(url: Url) -> Result<Self, Self::Error> {
+        Ok(Self {
+            host: url
+                .host()
+                .ok_or(ParseError::SetHostOnCannotBeABaseUrl)?
+                .to_owned(),
+            port: url.port(),
+        })
+    }
+}
+
+impl<T: AsRef<str>> Display for HostAndPort<T> {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        if let Some(port) = self.port {
+            write!(f, "{}:{}", self.host, port)
+        } else {
+            write!(f, "{}", self.host)
+        }
+    }
+}
+
+impl<T: AsRef<str>> Serialize for HostAndPort<T> {
+    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        ser.serialize_str(&self.to_string())
+    }
+}
diff --git a/fractal-matrix-api/src/r0/account.rs b/fractal-matrix-api/src/r0/account.rs
index 9e4b667d..1718d71b 100644
--- a/fractal-matrix-api/src/r0/account.rs
+++ b/fractal-matrix-api/src/r0/account.rs
@@ -5,7 +5,9 @@ pub mod logout;
 pub mod register;
 
 use crate::r0::{Medium, ThreePIDCredentials};
+use crate::ser::serialize_url;
 use serde::Serialize;
+use url::Url;
 
 #[derive(Clone, Debug, Serialize)]
 #[serde(tag = "type")]
@@ -86,7 +88,10 @@ pub enum AuthenticationData {
         session: Option<String>,
     },
     #[serde(rename = "m.login.oauth2")]
-    OAuth2 { uri: String },
+    OAuth2 {
+        #[serde(serialize_with = "serialize_url")]
+        uri: Url,
+    },
     #[serde(rename = "m.login.email.identity")]
     Email {
         threepid_creds: ThreePIDCredentials,
diff --git a/fractal-matrix-api/src/r0/contact/request_verification_token_email.rs 
b/fractal-matrix-api/src/r0/contact/request_verification_token_email.rs
index 7776950a..62482512 100644
--- a/fractal-matrix-api/src/r0/contact/request_verification_token_email.rs
+++ b/fractal-matrix-api/src/r0/contact/request_verification_token_email.rs
@@ -1,3 +1,5 @@
+use crate::r0::HostAndPort;
+use crate::ser::serialize_option_url;
 use reqwest::Client;
 use reqwest::Error;
 use reqwest::Request;
@@ -13,10 +15,11 @@ pub struct Parameters {
 pub struct Body {
     pub client_secret: String,
     pub email: String,
-    pub id_server: String,
+    pub id_server: HostAndPort<String>,
     pub send_attempt: u64,
+    #[serde(serialize_with = "serialize_option_url")]
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub next_link: Option<String>,
+    pub next_link: Option<Url>,
 }
 
 #[derive(Clone, Debug, Deserialize)]
diff --git a/fractal-matrix-api/src/r0/contact/request_verification_token_msisdn.rs 
b/fractal-matrix-api/src/r0/contact/request_verification_token_msisdn.rs
index b95bbea0..22ce283a 100644
--- a/fractal-matrix-api/src/r0/contact/request_verification_token_msisdn.rs
+++ b/fractal-matrix-api/src/r0/contact/request_verification_token_msisdn.rs
@@ -1,3 +1,5 @@
+use crate::r0::HostAndPort;
+use crate::ser::serialize_option_url;
 use reqwest::Client;
 use reqwest::Error;
 use reqwest::Request;
@@ -14,10 +16,11 @@ pub struct Body {
     pub client_secret: String,
     pub phone_number: String,
     pub country: String,
-    pub id_server: String,
+    pub id_server: HostAndPort<String>,
     pub send_attempt: u64,
+    #[serde(serialize_with = "serialize_option_url")]
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub next_link: Option<String>,
+    pub next_link: Option<Url>,
 }
 
 #[derive(Clone, Debug, Deserialize)]
diff --git a/fractal-matrix-api/src/r0/directory/post_public_rooms.rs 
b/fractal-matrix-api/src/r0/directory/post_public_rooms.rs
index 145b7acb..5f341a7a 100644
--- a/fractal-matrix-api/src/r0/directory/post_public_rooms.rs
+++ b/fractal-matrix-api/src/r0/directory/post_public_rooms.rs
@@ -1,3 +1,4 @@
+use crate::de::option_url;
 use crate::ser::serialize_option_host;
 use reqwest::Client;
 use reqwest::Error;
@@ -61,7 +62,9 @@ pub struct Response {
 #[derive(Clone, Debug, Deserialize)]
 pub struct Chunk {
     pub aliases: Option<Vec<String>>,
-    pub avatar_url: Option<String>,
+    #[serde(deserialize_with = "option_url::deserialize")]
+    #[serde(default)]
+    pub avatar_url: Option<Url>,
     pub canonical_alias: Option<String>,
     pub guest_can_join: bool,
     pub name: Option<String>,
diff --git a/fractal-matrix-api/src/r0/media/create.rs b/fractal-matrix-api/src/r0/media/create.rs
index 8e8b0fc8..8410e541 100644
--- a/fractal-matrix-api/src/r0/media/create.rs
+++ b/fractal-matrix-api/src/r0/media/create.rs
@@ -1,3 +1,4 @@
+use crate::de::url as serde_url;
 use reqwest::header::{HeaderValue, CONTENT_TYPE};
 use reqwest::Client;
 use reqwest::Error;
@@ -13,7 +14,8 @@ pub struct Parameters {
 
 #[derive(Clone, Debug, Deserialize)]
 pub struct Response {
-    pub content_uri: String,
+    #[serde(deserialize_with = "serde_url::deserialize")]
+    pub content_uri: Url,
 }
 
 pub fn request(
diff --git a/fractal-matrix-api/src/r0/profile/get_profile.rs 
b/fractal-matrix-api/src/r0/profile/get_profile.rs
index 50afd968..d27f7358 100644
--- a/fractal-matrix-api/src/r0/profile/get_profile.rs
+++ b/fractal-matrix-api/src/r0/profile/get_profile.rs
@@ -1,3 +1,4 @@
+use crate::de::option_url;
 use reqwest::Client;
 use reqwest::Error;
 use reqwest::Request;
@@ -6,7 +7,9 @@ use url::Url;
 
 #[derive(Clone, Debug, Deserialize)]
 pub struct Response {
-    pub avatar_url: Option<String>,
+    #[serde(deserialize_with = "option_url::deserialize")]
+    #[serde(default)]
+    pub avatar_url: Option<Url>,
     pub displayname: Option<String>,
 }
 
diff --git a/fractal-matrix-api/src/r0/profile/set_avatar_url.rs 
b/fractal-matrix-api/src/r0/profile/set_avatar_url.rs
index 0d2a87ce..05ed46b5 100644
--- a/fractal-matrix-api/src/r0/profile/set_avatar_url.rs
+++ b/fractal-matrix-api/src/r0/profile/set_avatar_url.rs
@@ -1,3 +1,4 @@
+use crate::ser::serialize_option_url;
 use reqwest::Client;
 use reqwest::Error;
 use reqwest::Request;
@@ -11,7 +12,8 @@ pub struct Parameters {
 
 #[derive(Clone, Debug, Serialize)]
 pub struct Body {
-    pub avatar_url: Option<String>,
+    #[serde(serialize_with = "serialize_option_url")]
+    pub avatar_url: Option<Url>,
 }
 
 pub fn request(
diff --git a/fractal-matrix-api/src/r0/search/user.rs b/fractal-matrix-api/src/r0/search/user.rs
index 65ad510c..0703f51a 100644
--- a/fractal-matrix-api/src/r0/search/user.rs
+++ b/fractal-matrix-api/src/r0/search/user.rs
@@ -1,3 +1,4 @@
+use crate::de::option_url;
 use reqwest::Client;
 use reqwest::Error;
 use reqwest::Request;
@@ -36,8 +37,9 @@ pub struct User {
     pub user_id: String,
     #[serde(default)]
     pub display_name: Option<String>,
+    #[serde(deserialize_with = "option_url::deserialize")]
     #[serde(default)]
-    pub avatar_url: Option<String>,
+    pub avatar_url: Option<Url>,
 }
 
 fn u64_is_10(number: &u64) -> bool {
diff --git a/fractal-matrix-api/src/r0/server/domain_info.rs b/fractal-matrix-api/src/r0/server/domain_info.rs
index d9e0c361..1c298366 100644
--- a/fractal-matrix-api/src/r0/server/domain_info.rs
+++ b/fractal-matrix-api/src/r0/server/domain_info.rs
@@ -1,3 +1,4 @@
+use crate::de::url as serde_url;
 use reqwest::Client;
 use reqwest::Error;
 use reqwest::Request;
@@ -14,12 +15,14 @@ pub struct Response {
 
 #[derive(Clone, Debug, Deserialize)]
 pub struct HomeserverInfo {
-    pub base_url: String,
+    #[serde(deserialize_with = "serde_url::deserialize")]
+    pub base_url: Url,
 }
 
 #[derive(Clone, Debug, Deserialize)]
 pub struct IDServerInfo {
-    pub base_url: String,
+    #[serde(deserialize_with = "serde_url::deserialize")]
+    pub base_url: Url,
 }
 
 pub fn request(base: Url) -> Result<Request, Error> {
diff --git a/fractal-matrix-api/src/r0/thirdparty/get_supported_protocols.rs 
b/fractal-matrix-api/src/r0/thirdparty/get_supported_protocols.rs
index 65100d34..07df1978 100644
--- a/fractal-matrix-api/src/r0/thirdparty/get_supported_protocols.rs
+++ b/fractal-matrix-api/src/r0/thirdparty/get_supported_protocols.rs
@@ -1,3 +1,4 @@
+use crate::de::option_url;
 use reqwest::Client;
 use reqwest::Error;
 use reqwest::Request;
@@ -19,14 +20,16 @@ pub struct Protocol {
     pub location_fields: Vec<String>,
     // This field is documented as "required",
     // but for some reason matrix.org does not send this
-    pub icon: Option<String>,
+    #[serde(deserialize_with = "option_url::deserialize")]
+    #[serde(default)]
+    pub icon: Option<Url>,
     pub field_types: BTreeMap<String, FieldType>,
     pub instances: Vec<ProtocolInstance>,
 }
 
 #[derive(Debug, Clone, Deserialize)]
 pub struct FieldType {
-    pub regexp: String,
+    pub regexp: String, // TODO: Change type to Regex
     pub placeholder: String,
 }
 
@@ -36,7 +39,9 @@ pub struct ProtocolInstance {
     #[serde(rename = "network_id")]
     pub id: String,
     pub desc: String,
-    pub icon: Option<String>,
+    #[serde(deserialize_with = "option_url::deserialize")]
+    #[serde(default)]
+    pub icon: Option<Url>,
     pub fields: JsonValue,
 }
 
diff --git a/fractal-matrix-api/src/ser.rs b/fractal-matrix-api/src/ser.rs
index 0bb56e88..2c69fd20 100644
--- a/fractal-matrix-api/src/ser.rs
+++ b/fractal-matrix-api/src/ser.rs
@@ -1,6 +1,7 @@
 use serde::Serializer;
 use std::time::Duration;
 use url::Host;
+use url::Url;
 
 pub fn serialize_option_host<S>(host: &Option<Host>, ser: S) -> Result<S::Ok, S::Error>
 where
@@ -19,3 +20,20 @@ where
 {
     ser.serialize_u64(duration.as_secs() * 1000 + (duration.subsec_millis() as u64))
 }
+
+pub fn serialize_url<S>(url: &Url, ser: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    ser.serialize_str(url.as_str())
+}
+
+pub fn serialize_option_url<S>(url: &Option<Url>, ser: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    match url {
+        Some(u) => ser.serialize_str(u.as_str()),
+        None => ser.serialize_none(),
+    }
+}
diff --git a/fractal-matrix-api/src/util.rs b/fractal-matrix-api/src/util.rs
index 0b798b91..c293acd6 100644
--- a/fractal-matrix-api/src/util.rs
+++ b/fractal-matrix-api/src/util.rs
@@ -349,7 +349,12 @@ pub fn get_user_avatar(base: &Url, userid: &str) -> Result<(String, String), Err
         .avatar_url
         .map(|url| {
             let dest = cache_dir_path(None, userid)?;
-            dw_media(base, &url, ContentType::default_thumbnail(), Some(&dest))
+            dw_media(
+                base,
+                url.as_str(),
+                ContentType::default_thumbnail(),
+                Some(&dest),
+            )
         })
         .unwrap_or(Ok(Default::default()))?;
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]