From 1b2b1db95f8261e6185d30591980bc7343c04f65 Mon Sep 17 00:00:00 2001 From: notplants Date: Tue, 28 Dec 2021 20:35:46 -0500 Subject: [PATCH] Deserialize TypedSsbMessageValue --- examples/ssb-client.rs | 12 +++---- src/error.rs | 4 +++ src/lib.rs | 2 +- src/utils.rs | 76 ++++++++++++++++++++++++++---------------- 4 files changed, 59 insertions(+), 35 deletions(-) diff --git a/examples/ssb-client.rs b/examples/ssb-client.rs index 37c779c..11e75e8 100644 --- a/examples/ssb-client.rs +++ b/examples/ssb-client.rs @@ -4,6 +4,7 @@ use kuska_ssb::api::dto::content::{SubsetQuery, TypedMessage, Post}; use golgi::error::GolgiError; use golgi::sbot::Sbot; +use golgi::utils::{SsbMessageValue, TypedSsbMessageValue}; async fn run() -> Result<(), GolgiError> { let mut sbot_client = Sbot::init(None, None).await?; @@ -14,13 +15,12 @@ async fn run() -> Result<(), GolgiError> { let author = "@L/z54cbc8V1kL1/MiBhpEKuN3QJkSoZYNaukny3ghIs=.ed25519".to_string(); println!("Calling create_history"); - let hist_response = sbot_client.create_history_stream(author).await?; - println!("hist: {:?}", hist_response); + let messages : Vec = sbot_client.create_history_stream(author).await?; + println!("hist: {:?}", messages); - let posts: Vec = hist_response.iter().flat_map(|msg| msg.into_post()).collect(); - - for post in posts { - println!("post: {:?}", post); + for message in messages { + let t: TypedSsbMessageValue = message.into_typed_message_value()?; + println!("t: {:?}", t); } Ok(()) diff --git a/src/error.rs b/src/error.rs index fd9c85f..4dfb3eb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -35,6 +35,8 @@ pub enum GolgiError { Sbot(String), /// JSON serialization or deserialization error. SerdeJson(JsonError), + /// Error decoding typed ssb message from content. + ContentTypeDecode(String), } impl std::error::Error for GolgiError { @@ -48,6 +50,7 @@ impl std::error::Error for GolgiError { GolgiError::Rpc(ref err) => Some(err), GolgiError::Sbot(_) => None, GolgiError::SerdeJson(ref err) => Some(err), + GolgiError::ContentTypeDecode(ref err) => None, } } } @@ -67,6 +70,7 @@ impl std::fmt::Display for GolgiError { GolgiError::Sbot(ref err) => write!(f, "Sbot returned an error response: {}", err), GolgiError::SerdeJson(_) => write!(f, "Failed to serialize JSON slice"), //GolgiError::WhoAmI(ref err) => write!(f, "{}", err), + GolgiError::ContentTypeDecode(ref err) => write!(f, "Failed to decode typed message from ssb message content: {}", err), } } } diff --git a/src/lib.rs b/src/lib.rs index 6fd638d..15b1daa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,6 @@ pub mod error; pub mod sbot; -mod utils; +pub mod utils; pub use crate::error::GolgiError; diff --git a/src/utils.rs b/src/utils.rs index f8a025a..a15734d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,7 +6,6 @@ use serde::{Serialize, Deserialize}; use kuska_ssb::api::dto::WhoAmIOut; use kuska_ssb::feed::{Feed, Message}; use kuska_ssb::rpc::{RecvMsg, RequestNo, RpcReader}; -use kuska_ssb::api::dto::content::{Post, Vote}; use kuska_ssb::api::dto::content::TypedMessage; use ssb_legacy_msg_data::LegacyF64; @@ -36,37 +35,58 @@ impl SsbMessageValue { msg_type } - pub fn into_post(self) -> Result { - let p: Post = serde_json::from_value(self.content)?; - Ok(p) + pub fn into_typed_message(self) -> Result { + let t: TypedMessage = serde_json::from_value(self.content)?; + Ok(t) } - pub fn into_vote(self) -> Result { - let p: Vote = serde_json::from_value(self.content)?; - Ok(p) + pub fn into_typed_message_value(self) -> Result { + let typed_message: TypedMessage = get_typed_message_from_value(self.content.clone())?; + let typed_message_value = TypedSsbMessageValue { + previous: self.previous, + author: self.author, + sequence: self.sequence, + timestamp: self.timestamp, + hash: self.hash, + content: self.content, + typed_message, + signature: self.signature, + }; + Ok(typed_message_value) } +} - // NOTE: this doesnt work, because the arms return two different types - // but maybe there is a way to do type inheritance? - // this SO post says you dont deserialize directly into enum variants - // https://stackoverflow.com/questions/59460464/how-do-i-use-serde-to-deserialize-into-a-specific-enum-variant -// pub fn into_typed_message(self) -> Result { -// let msg_type = self.get_message_type(); -// let msg = match msg_type { -// "post" => { -// let p: Post = serde_json::from_value(self.content)?; -// p -// }, -// "vote" => { -// let p: Vote = serde_json::from_value(self.content)?; -// p -// }, -// _ => { -// Err(GolgiError::Sbot("No type included in message content".to_string())) -// } -// }; -// Ok(msg) -// } + +/// Function to parse a TypedMessage from an ssb message content field. +/// TypedMessage has a tag field, named "type", which instructs serde to choose which variant +/// to attempt to deserialize by looking at the value of the type field. +/// +/// See documentation here: https://serde.rs/enum-representations.html#internally-tagged +/// +/// # Arguments +/// +/// * `value` - A serde value to be parsed into a TypedMessage. +pub fn get_typed_message_from_value(value: Value) -> Result { + let message_type = value.get("type").ok_or(GolgiError::ContentTypeDecode("no type field in content".to_string()))?; + let message_type_str = message_type.as_str().ok_or(GolgiError::ContentTypeDecode("invalid type field in content".to_string()))?; + let to_return: TypedMessage = serde_json::from_value(value)?; + Ok(to_return) +} + +/// Data type representing the `value` of a message object (`KVT`), +/// with an additional field typed_message which contains a TypedMessage object +/// made by deserializing the content field. +#[derive(Serialize, Deserialize, Debug)] +#[serde(deny_unknown_fields)] +pub struct TypedSsbMessageValue { + pub previous: Option, + pub author: String, + pub sequence: u64, + pub timestamp: LegacyF64, + pub hash: String, + pub content: Value, + pub typed_message: TypedMessage, + pub signature: String, } /// Data type representing the `value` of a message object (`KVT`). More information concerning the