From 742e731b952967f595bad1b870918fe5424ed6a2 Mon Sep 17 00:00:00 2001 From: silverpill Date: Thu, 25 Aug 2022 00:09:37 +0000 Subject: [PATCH] Convert PaymentOption type into enum --- src/activitypub/actors/attachments.rs | 33 ++++---- src/mastodon_api/accounts/types.rs | 7 +- src/mastodon_api/accounts/views.rs | 2 +- src/models/profiles/types.rs | 106 +++++++++++++++++--------- src/models/profiles/validators.rs | 22 +----- 5 files changed, 93 insertions(+), 77 deletions(-) diff --git a/src/activitypub/actors/attachments.rs b/src/activitypub/actors/attachments.rs index bd13413..9310a44 100644 --- a/src/activitypub/actors/attachments.rs +++ b/src/activitypub/actors/attachments.rs @@ -15,8 +15,8 @@ use crate::frontend::get_subscription_page_url; use crate::models::profiles::types::{ ExtraField, IdentityProof, + PaymentLink, PaymentOption, - PaymentType, }; use super::types::ActorAttachment; @@ -67,10 +67,10 @@ pub fn attach_payment_option( user_id: &Uuid, payment_option: PaymentOption, ) -> ActorAttachment { - match payment_option.payment_type { - PaymentType::Link => unimplemented!(), - PaymentType::EthereumSubscription => { - let name = format!("{:?}", payment_option.payment_type); + match payment_option { + PaymentOption::Link(_) => unimplemented!(), + PaymentOption::EthereumSubscription => { + let name = "EthereumSubscription".to_string(); let subscription_page_url = get_subscription_page_url(instance_url, user_id); ActorAttachment { @@ -91,11 +91,13 @@ pub fn parse_payment_option( if attachment.object_type != LINK { return Err(ValidationError("invalid attachment type")); }; - let payment_option = PaymentOption { - payment_type: PaymentType::Link, - name: Some(attachment.name.clone()), - href: attachment.href.clone(), - }; + let href = attachment.href.as_ref() + .ok_or(ValidationError("href attribute is required"))? + .to_string(); + let payment_option = PaymentOption::Link(PaymentLink { + name: attachment.name.clone(), + href: href, + }); Ok(payment_option) } @@ -153,7 +155,7 @@ mod tests { #[test] fn test_payment_option() { let user_id = new_uuid(); - let payment_option = PaymentOption::subscription(); + let payment_option = PaymentOption::EthereumSubscription; let subscription_page_url = format!("https://example.com/profile/{}/subscription", user_id); let attachment = attach_payment_option( @@ -166,8 +168,11 @@ mod tests { assert_eq!(attachment.href.as_deref().unwrap(), subscription_page_url); let parsed_option = parse_payment_option(&attachment).unwrap(); - assert!(matches!(parsed_option.payment_type, PaymentType::Link)); - assert_eq!(parsed_option.name.unwrap(), "EthereumSubscription"); - assert_eq!(parsed_option.href.unwrap(), subscription_page_url); + let link = match parsed_option { + PaymentOption::Link(link) => link, + _ => panic!("wrong option"), + }; + assert_eq!(link.name, "EthereumSubscription"); + assert_eq!(link.href, subscription_page_url); } } diff --git a/src/mastodon_api/accounts/types.rs b/src/mastodon_api/accounts/types.rs index aca8cc4..ddb82ae 100644 --- a/src/mastodon_api/accounts/types.rs +++ b/src/mastodon_api/accounts/types.rs @@ -12,7 +12,6 @@ use crate::models::profiles::types::{ ExtraField, IdentityProof, PaymentOption, - PaymentType, ProfileUpdateData, }; use crate::models::profiles::validators::validate_username; @@ -97,9 +96,9 @@ impl Account { let subscription_page_url = profile.payment_options.clone() .into_inner().into_iter() .map(|option| { - match option.payment_type { - PaymentType::Link => option.href.unwrap_or_default(), - PaymentType::EthereumSubscription => { + match option { + PaymentOption::Link(link) => link.href, + PaymentOption::EthereumSubscription => { get_subscription_page_url(instance_url, &profile.id) }, } diff --git a/src/mastodon_api/accounts/views.rs b/src/mastodon_api/accounts/views.rs index b837ab2..483198f 100644 --- a/src/mastodon_api/accounts/views.rs +++ b/src/mastodon_api/accounts/views.rs @@ -345,7 +345,7 @@ async fn subscriptions_enabled( if current_user.profile.payment_options.is_empty() { // Add payment option to profile let mut profile_data = ProfileUpdateData::from(¤t_user.profile); - profile_data.payment_options = vec![PaymentOption::subscription()]; + profile_data.payment_options = vec![PaymentOption::EthereumSubscription]; current_user.profile = update_profile( db_client, ¤t_user.id, diff --git a/src/models/profiles/types.rs b/src/models/profiles/types.rs index 39697a2..41f884e 100644 --- a/src/models/profiles/types.rs +++ b/src/models/profiles/types.rs @@ -5,6 +5,8 @@ use postgres_types::FromSql; use serde::{ Deserialize, Deserializer, Serialize, Serializer, de::Error as DeserializerError, + ser::SerializeMap, + __private::ser::FlatMapSerializer, }; use uuid::Uuid; @@ -16,7 +18,6 @@ use crate::ethereum::identity::DidPkh; use super::validators::{ validate_username, validate_display_name, - validate_payment_options, clean_bio, clean_extra_fields, }; @@ -41,7 +42,6 @@ impl IdentityProofs { json_from_sql!(IdentityProofs); json_to_sql!(IdentityProofs); -#[derive(Clone, Debug)] pub enum PaymentType { Link, EthereumSubscription, @@ -69,38 +69,58 @@ impl TryFrom for PaymentType { } } -impl Serialize for PaymentType { - fn serialize(&self, serializer: S) -> Result - where S: Serializer - { - let value: i16 = self.into(); - serializer.serialize_i16(value) - } -} - -impl<'de> Deserialize<'de> for PaymentType { - fn deserialize(deserializer: D) -> Result - where D: Deserializer<'de> - { - let value: i16 = Deserialize::deserialize(deserializer)?; - Self::try_from(value).map_err(DeserializerError::custom) - } -} - #[derive(Clone, Debug, Deserialize, Serialize)] -pub struct PaymentOption { - pub payment_type: PaymentType, - pub name: Option, - pub href: Option, +pub struct PaymentLink { + pub name: String, + pub href: String, } -impl PaymentOption { - pub fn subscription() -> Self { - Self { - payment_type: PaymentType::EthereumSubscription, - name: None, - href: None, - } +#[derive(Clone, Debug)] +pub enum PaymentOption { + Link(PaymentLink), + EthereumSubscription, +} + +// Integer tags are not supported https://github.com/serde-rs/serde/issues/745 +// Workaround: https://stackoverflow.com/a/65576570 +impl<'de> Deserialize<'de> for PaymentOption { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> + { + let value = serde_json::Value::deserialize(deserializer)?; + let payment_type = value.get("payment_type") + .and_then(serde_json::Value::as_u64) + .and_then(|val| i16::try_from(val).ok()) + .and_then(|val| PaymentType::try_from(val).ok()) + .ok_or(DeserializerError::custom("invalid payment type"))?; + let payment_option = match payment_type { + PaymentType::Link => { + let link = PaymentLink::deserialize(value) + .map_err(DeserializerError::custom)?; + Self::Link(link) + }, + PaymentType::EthereumSubscription => Self::EthereumSubscription, + }; + Ok(payment_option) + } +} + +impl Serialize for PaymentOption { + fn serialize(&self, serializer: S) -> Result + where S: Serializer, + { + let mut map = serializer.serialize_map(None)?; + let payment_type = match self { + Self::Link(_) => PaymentType::Link, + Self::EthereumSubscription => PaymentType::EthereumSubscription, + }; + map.serialize_entry("payment_type", &i16::from(&payment_type))?; + + match self { + Self::Link(link) => link.serialize(FlatMapSerializer(&mut map))?, + Self::EthereumSubscription => (), + }; + map.end() } } @@ -268,7 +288,6 @@ impl ProfileCreateData { let cleaned_bio = clean_bio(bio, self.actor_json.is_some())?; self.bio = Some(cleaned_bio); }; - validate_payment_options(&self.payment_options)?; self.extra_fields = clean_extra_fields(&self.extra_fields)?; Ok(()) } @@ -296,7 +315,6 @@ impl ProfileUpdateData { let cleaned_bio = clean_bio(bio, self.actor_json.is_some())?; self.bio = Some(cleaned_bio); }; - validate_payment_options(&self.payment_options)?; self.extra_fields = clean_extra_fields(&self.extra_fields)?; Ok(()) } @@ -336,15 +354,29 @@ mod tests { } #[test] - fn test_payment_option_serialization() { + fn test_payment_option_link_serialization() { + let json_data = r#"{"payment_type":1,"name":"test","href":"https://test.com"}"#; + let payment_option: PaymentOption = serde_json::from_str(json_data).unwrap(); + let link = match payment_option { + PaymentOption::Link(ref link) => link, + _ => panic!("wrong option"), + }; + assert_eq!(link.name, "test"); + assert_eq!(link.href, "https://test.com"); + let serialized = serde_json::to_string(&payment_option).unwrap(); + assert_eq!(serialized, json_data); + } + + #[test] + fn test_payment_option_ethereum_subscription_serialization() { let json_data = r#"{"payment_type":2,"name":null,"href":null}"#; let payment_option: PaymentOption = serde_json::from_str(json_data).unwrap(); assert!(matches!( - payment_option.payment_type, - PaymentType::EthereumSubscription, + payment_option, + PaymentOption::EthereumSubscription, )); let serialized = serde_json::to_string(&payment_option).unwrap(); - assert_eq!(serialized, json_data); + assert_eq!(serialized, r#"{"payment_type":2}"#); } #[test] diff --git a/src/models/profiles/validators.rs b/src/models/profiles/validators.rs index 8975956..e36b4f2 100644 --- a/src/models/profiles/validators.rs +++ b/src/models/profiles/validators.rs @@ -1,7 +1,7 @@ use regex::Regex; use crate::errors::ValidationError; use crate::utils::html::{clean_html, clean_html_strict}; -use super::types::{ExtraField, PaymentOption, PaymentType}; +use super::types::ExtraField; const USERNAME_RE: &str = r"^[a-zA-Z0-9_\.-]+$"; @@ -46,26 +46,6 @@ pub fn clean_bio(bio: &str, is_remote: bool) -> Result Ok(cleaned_bio) } -pub fn validate_payment_options(payment_options: &[PaymentOption]) - -> Result<(), ValidationError> -{ - for option in payment_options { - match option.payment_type { - PaymentType::Link => { - if option.name.is_none() || option.href.is_none() { - return Err(ValidationError("invalid payment option")); - }; - }, - PaymentType::EthereumSubscription => { - if option.name.is_some() || option.href.is_some() { - return Err(ValidationError("invalid payment option")); - }; - }, - }; - }; - Ok(()) -} - const FIELD_NAME_MAX_SIZE: usize = 500; const FIELD_VALUE_MAX_SIZE: usize = 5000;