1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
//! Message types and conversion methods.

use kuska_ssb::api::dto::content::TypedMessage;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt::Debug;

use crate::error::GolgiError;

/// `SsbMessageContent` is a type alias for `TypedMessage` from the `kuska_ssb` library.
/// It is aliased in golgi to fit the naming convention of the other message
/// types: `SsbMessageKVT` and `SsbMessageValue`.
///
/// See the [kuska source code](https://github.com/Kuska-ssb/ssb/blob/master/src/api/dto/content.rs#L103) for the type definition of `TypedMessage`.
pub type SsbMessageContent = TypedMessage;

/// The `value` of an SSB message (the `V` in `KVT`).
///
/// More information concerning the data model can be found in the
/// [`Metadata` documentation](https://spec.scuttlebutt.nz/feed/messages.html#metadata).
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
#[allow(missing_docs)]
pub struct SsbMessageValue {
    pub previous: Option<String>,
    pub author: String,
    pub sequence: u64,
    pub timestamp: f64,
    pub hash: String,
    pub content: Value,
    pub signature: String,
}

/// Message content types.
#[derive(Debug)]
#[allow(missing_docs)]
pub enum SsbMessageContentType {
    About,
    Vote,
    Post,
    Contact,
    Unrecognized,
}

impl SsbMessageValue {
    /// Get the type field of the message content as an enum, if found.
    ///
    /// 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<SsbMessageContentType, GolgiError> {
        let msg_type = self
            .content
            .get("type")
            .ok_or_else(|| GolgiError::ContentType("type field not found".to_string()))?;
        let mtype_str: &str = msg_type.as_str().ok_or_else(|| {
            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)
    }

    /// 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,
        }
    }

    /// Convert the content JSON value into an `SsbMessageContent` `enum`,
    /// using the `type` field as a tag to select which variant of the `enum`
    /// to deserialize into.
    ///
    /// See the [Serde docs on internally-tagged enum representations](https://serde.rs/enum-representations.html#internally-tagged) for further details.
    pub fn into_ssb_message_content(self) -> Result<SsbMessageContent, GolgiError> {
        let m: SsbMessageContent = serde_json::from_value(self.content)?;
        Ok(m)
    }
}

/// An SSB message represented as a key-value-timestamp (`KVT`).
///
/// More information concerning the data model can be found in the
/// [`Metadata` documentation](https://spec.scuttlebutt.nz/feed/messages.html#metadata).
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
#[allow(missing_docs)]
pub struct SsbMessageKVT {
    pub key: String,
    pub value: SsbMessageValue,
    pub timestamp: f64,
    pub rts: Option<f64>,
}