From 0aeed5578e01cbc1b302b5255a9d5f8791e8c27b Mon Sep 17 00:00:00 2001 From: notplants Date: Wed, 29 Dec 2021 11:48:53 -0500 Subject: [PATCH 1/5] Working on profile --- src/sbot.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/sbot.rs b/src/sbot.rs index 4ff8608..341ee03 100644 --- a/src/sbot.rs +++ b/src/sbot.rs @@ -153,6 +153,23 @@ impl Sbot { self.publish(msg).await } + + + /// Get the about messages for a particular user. + pub async fn get_about_messages(&mut self, ssb_id: String) -> Result { + let req_id = self.client.getsubset_req_send(query).await?; + + utils::get_async_until_eof(&mut self.rpc_reader, req_id, utils::kvt_res_parse).await + } + + + /// Get the latest description for a particular user from their about messages. + pub async fn get_description(&mut self, ssb_id: String) -> Result { + let req_id = self.client.getsubset_req_send(query).await?; + + utils::get_async_until_eof(&mut self.rpc_reader, req_id, utils::kvt_res_parse).await + } + /// Call the `createHistoryStream` RPC method and return a vector /// of SsbMessageValue. pub async fn create_history_stream( -- 2.49.0 From 696701e855319419c5c1efae961be335fa70b98a Mon Sep 17 00:00:00 2001 From: notplants Date: Wed, 29 Dec 2021 15:12:20 -0500 Subject: [PATCH 2/5] Get description --- src/messages.rs | 57 ++++++++++++++++++++++++++++++++++++++-------- src/sbot.rs | 60 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 96 insertions(+), 21 deletions(-) diff --git a/src/messages.rs b/src/messages.rs index ee39a07..9434e80 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -29,16 +29,55 @@ pub struct SsbMessageValue { pub signature: String, } +// Enum representing the different possible message content types +#[derive(Debug)] +pub enum SsbMessageContentType { + About, + Vote, + Post, + Contact, + None +} + impl SsbMessageValue { - /// Gets the type field of the message content if found, - /// and if not returns "none" - pub fn get_message_type(&self) -> String { - let msg_type: String = self - .content - .get("type") - .map(|msg_type| msg_type.to_string()) - .unwrap_or_else(|| "none".to_string()); - msg_type + + /// Gets the type field of the message content as an enum, if found. + /// if no type field is found, it returns a SsbMessageContentType::None + /// if a type field is found with an invalid format it returns a GolgiError::ContentTypeDecode + pub fn get_message_type(&self) -> Result { + let msg_type = self + .content + .get("type"); + let enum_type = match msg_type { + Some(mtype) => { + let mtype_str: &str = mtype.as_str().ok_or(GolgiError::ContentTypeDecode("type field with invalid format".to_string()))?; + match mtype_str { + "about" => SsbMessageContentType::About, + "post" => SsbMessageContentType::Post, + "vote" => SsbMessageContentType::Vote, + "contact" => SsbMessageContentType::Contact, + _ => return Err(GolgiError::ContentTypeDecode("type field with unknown value".to_string())) + } + }, + None => { + SsbMessageContentType::None + } + }; + Ok(enum_type) + } + + /// Helper function which returns true if this message is of the given type, + /// and false if the type does not match or is not found + pub fn is_message_type(&self, message_type: SsbMessageContentType) -> bool { + let self_message_type = self.get_message_type(); + match self_message_type { + Ok(mtype) => { + matches!(mtype, message_type) + } + Err(_err) => { + false + } + } } /// Converts the content json value into an SsbMessageContent enum, diff --git a/src/sbot.rs b/src/sbot.rs index 341ee03..56cfeda 100644 --- a/src/sbot.rs +++ b/src/sbot.rs @@ -5,7 +5,7 @@ use kuska_handshake::async_std::BoxStream; use kuska_sodiumoxide::crypto::{auth, sign::ed25519}; use kuska_ssb::{ api::{ - dto::{content::SubsetQuery, CreateHistoryStreamIn}, + dto::{CreateHistoryStreamIn}, ApiCaller, }, discovery, keystore, @@ -14,9 +14,12 @@ use kuska_ssb::{ }; use crate::error::GolgiError; -use crate::messages::{SsbKVT, SsbMessageContent, SsbMessageValue}; +use crate::messages::{SsbKVT, SsbMessageContent, SsbMessageValue, SsbMessageContentType}; use crate::utils; +// re-export types from kuska +pub use kuska_ssb::api::dto::content::SubsetQuery; + /// The Scuttlebutt identity, keys and configuration parameters for connecting to a local sbot /// instance, as well as handles for calling RPC methods and receiving responses. pub struct Sbot { @@ -154,20 +157,53 @@ impl Sbot { } - - /// Get the about messages for a particular user. - pub async fn get_about_messages(&mut self, ssb_id: String) -> Result { - let req_id = self.client.getsubset_req_send(query).await?; - - utils::get_async_until_eof(&mut self.rpc_reader, req_id, utils::kvt_res_parse).await + /// Get the about messages for a particular user in order of recency. + pub async fn get_about_messages(&mut self, ssb_id: String) -> Result, GolgiError> { + let query = SubsetQuery::Author{ + op: "author".to_string(), + feed: self.id.to_string(), + }; + let kvts: Vec = self.get_subset(query).await?; + let messages: Vec = kvts.into_iter().map(|kvt| kvt.value).collect(); + // TODO: after fixing sbot regression, + // change this subset query to filter by type about in addition to author + // and remove this filter section + // filter down to about messages + let mut about_messages: Vec = messages.into_iter().filter(|msg| { + msg.is_message_type(SsbMessageContentType::About) + }).collect(); + // TODO: use subset query to order messages instead of doing it this way + about_messages.sort_by(|a, b| { + a.timestamp.partial_cmp(&b.timestamp).unwrap() + }); + // return about messages + Ok(about_messages) } - /// Get the latest description for a particular user from their about messages. pub async fn get_description(&mut self, ssb_id: String) -> Result { - let req_id = self.client.getsubset_req_send(query).await?; - - utils::get_async_until_eof(&mut self.rpc_reader, req_id, utils::kvt_res_parse).await + // vector of about messages with most recent at the front of the vector + let about_messages = self.get_about_messages(ssb_id).await?; + // iterate through the vector looking for an about message with a description + // the first one we find is th emost recnet + for msg in about_messages { + let about_message = msg.into_ssb_message_content()?; + match about_message { + SsbMessageContent::About{description, ..} => { + match description { + Some(description_text) => { + return Ok(description_text) + }, + None => { + continue + } + } + }, + _ => {} + } + } + // if no about message with a description was found, then return the empty string + Ok("".to_string()) } /// Call the `createHistoryStream` RPC method and return a vector -- 2.49.0 From 961f817a8b8aac29d53b85e2038f9d7d5d7ed605 Mon Sep 17 00:00:00 2001 From: notplants Date: Wed, 29 Dec 2021 21:03:06 -0500 Subject: [PATCH 3/5] Replace verbose get_description with more concise version --- src/sbot.rs | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/src/sbot.rs b/src/sbot.rs index 325026d..5fbafa5 100644 --- a/src/sbot.rs +++ b/src/sbot.rs @@ -191,32 +191,6 @@ impl Sbot { Ok(about_messages) } - /// Get the latest description for a particular user from their about messages. - pub async fn get_description(&mut self, ssb_id: &str) -> Result, GolgiError> { - // vector of about messages with most recent at the front of the vector - let about_messages = self.get_about_messages(ssb_id).await?; - // iterate through the vector looking for an about message with a description - // the first one we find is th emost recnet - for msg in about_messages { - let about_message = msg.into_ssb_message_content()?; - match about_message { - SsbMessageContent::About{description, ..} => { - match description { - Some(description_text) => { - return Ok(Some(description_text)) - }, - None => { - continue - } - } - }, - _ => {} - } - } - // if no about message with a description was found, then return the empty string - Ok(None) - } - /// Get value of latest about message with given key from given user pub async fn get_latest_about_message(&mut self, ssb_id: &str, key: &str) -> Result, GolgiError> { // vector of about messages with most recent at the front of the vector @@ -252,6 +226,10 @@ impl Sbot { self.get_latest_about_message(ssb_id, "name").await } + pub async fn get_description(&mut self, ssb_id: &str) -> Result, GolgiError> { + self.get_latest_about_message(ssb_id, "description").await + } + /// Call the `createHistoryStream` RPC method and return a vector /// of SsbMessageValue. pub async fn create_history_stream( -- 2.49.0 From 65d64bbacfdc78b1f6ca2a70d4fa6eee7a48576c Mon Sep 17 00:00:00 2001 From: notplants Date: Thu, 30 Dec 2021 07:50:34 -0500 Subject: [PATCH 4/5] Revert "Remove ContentTypeDecode error variant" This reverts commit 028e3a47b554144d5be710dc4923dc284cea2f4e. --- src/error.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/error.rs b/src/error.rs index 71d503c..ac8fab5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -36,6 +36,8 @@ pub enum GolgiError { Sbot(String), /// JSON serialization or deserialization error. SerdeJson(JsonError), + /// Error decoding typed ssb message from content. + ContentTypeDecode(String), /// Error decoding UTF8 string from bytes Utf8Parse { /// The underlying parse error. @@ -54,6 +56,7 @@ impl std::error::Error for GolgiError { GolgiError::Rpc(ref err) => Some(err), GolgiError::Sbot(_) => None, GolgiError::SerdeJson(ref err) => Some(err), + GolgiError::ContentTypeDecode(_) => None, GolgiError::Utf8Parse{ ref source} => Some(source), } } @@ -74,6 +77,11 @@ 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 + ), GolgiError::Utf8Parse{ source } => write!(f, "Failed to deserialize UTF8 from bytes: {}", source), } } -- 2.49.0 From db948f1f673db68f3bf5a0147c8d0b4830e1e95c Mon Sep 17 00:00:00 2001 From: notplants Date: Thu, 30 Dec 2021 13:35:39 -0500 Subject: [PATCH 5/5] Response to code review --- src/error.rs | 6 +++--- src/messages.rs | 30 ++++++++++++------------------ src/sbot.rs | 43 ++++++++++++++++++------------------------- src/utils.rs | 2 ++ 4 files changed, 35 insertions(+), 46 deletions(-) diff --git a/src/error.rs b/src/error.rs index ac8fab5..7060222 100644 --- a/src/error.rs +++ b/src/error.rs @@ -37,7 +37,7 @@ pub enum GolgiError { /// JSON serialization or deserialization error. SerdeJson(JsonError), /// Error decoding typed ssb message from content. - ContentTypeDecode(String), + ContentType(String), /// Error decoding UTF8 string from bytes Utf8Parse { /// The underlying parse error. @@ -56,7 +56,7 @@ impl std::error::Error for GolgiError { GolgiError::Rpc(ref err) => Some(err), GolgiError::Sbot(_) => None, GolgiError::SerdeJson(ref err) => Some(err), - GolgiError::ContentTypeDecode(_) => None, + GolgiError::ContentType(_) => None, GolgiError::Utf8Parse{ ref source} => Some(source), } } @@ -77,7 +77,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!( + GolgiError::ContentType(ref err) => write!( f, "Failed to decode typed message from ssb message content: {}", err diff --git a/src/messages.rs b/src/messages.rs index d839e94..6ea9cc3 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -36,32 +36,26 @@ pub enum SsbMessageContentType { Vote, Post, Contact, - None + Unrecognized } impl SsbMessageValue { /// Gets the type field of the message content as an enum, if found. - /// if no type field is found, it returns a SsbMessageContentType::None - /// if a type field is found with an invalid format it returns a GolgiError::ContentTypeDecode + /// if no type field is found or the type field is not a string, it returns an Err(GolgiError::ContentType) + /// if a type field is found but with an unknown string it returns an Ok(SsbMessageContentType::Unrecognized) pub fn get_message_type(&self) -> Result { let msg_type = self .content - .get("type"); - let enum_type = match msg_type { - Some(mtype) => { - let mtype_str: &str = mtype.as_str().ok_or(GolgiError::ContentTypeDecode("type field with invalid format".to_string()))?; - match mtype_str { - "about" => SsbMessageContentType::About, - "post" => SsbMessageContentType::Post, - "vote" => SsbMessageContentType::Vote, - "contact" => SsbMessageContentType::Contact, - _ => return Err(GolgiError::ContentTypeDecode("type field with unknown value".to_string())) - } - }, - None => { - SsbMessageContentType::None - } + .get("type") + .ok_or(GolgiError::ContentType("type field not found".to_string()))?; + let mtype_str: &str = msg_type.as_str().ok_or(GolgiError::ContentType("type field value is not a string as expected".to_string()))?; + let enum_type = match mtype_str { + "about" => SsbMessageContentType::About, + "post" => SsbMessageContentType::Post, + "vote" => SsbMessageContentType::Vote, + "contact" => SsbMessageContentType::Contact, + _ => SsbMessageContentType::Unrecognized, }; Ok(enum_type) } diff --git a/src/sbot.rs b/src/sbot.rs index 5fbafa5..a7b2250 100644 --- a/src/sbot.rs +++ b/src/sbot.rs @@ -195,31 +195,24 @@ impl Sbot { pub async fn get_latest_about_message(&mut self, ssb_id: &str, key: &str) -> Result, GolgiError> { // vector of about messages with most recent at the front of the vector let about_messages = self.get_about_messages(ssb_id).await?; - // iterate through the vector looking for an about message with the given key - // the first one we find is the most recent - for msg in about_messages { - let value = msg.content.get(key); - match value { - Some(val) => { - let val_as_str_option = val.as_str(); - match val_as_str_option { - Some(val_as_str) => { - return Ok(Some(val_as_str.to_string())) - }, - None => { - // if it is an improperly formatted field (not a string) - // then just continue - continue - } - } - }, - None => { - continue - } - } - } - // if no about message with given key was found, then return None - Ok(None) + // iterate through the vector looking for most recent about message with the given key + let latest_about = about_messages + .iter() + // find the first msg that contains the field `key` + .find(|msg| msg.content.get(key).is_some()) + // map the found msg (`Some(SsbMessageValue)`) to a `Some(Some(&Value))` + .map(|msg| msg.content.get(key)) + // flatten `Some(Some(&Value))` into `Some(&Value)` + .flatten() + // map `Some(&Value)` to `Some(Some(&str))` + .map(|msg_val| msg_val.as_str()) + // flatten `Some(Some(&str))` to `Some(&str)` + .flatten() + // map `Some(&str))` to `Some(String)` + .map(|msg_str| msg_str.to_string()); + + // return value is either `Ok(Some(String))` or `Ok(None)` + Ok(latest_about) } pub async fn get_name(&mut self, ssb_id: &str) -> Result, GolgiError> { diff --git a/src/utils.rs b/src/utils.rs index c840610..34f5ec6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -140,6 +140,8 @@ where Ok(messages) } + + /// Takes in an rpc request number, and a handling function, /// and calls the handling function on all responses which match the request number, /// and prints out the result of the handling function. -- 2.49.0