You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
yuurei/src/post/mod.rs

157 lines
3.5 KiB

//! Structure for updating posts in-place
use super::*;
use std::{
fmt,
hash::{
Hash,Hasher,
},
};
use chrono::prelude::*;
/// Represents all valid timestamps for a post.
///
/// Usually in UTC, pls keep it in Utc...
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Serialize, Deserialize)]
pub struct PostTimestamp
{
pub opened: DateTime<Utc>,
pub closed: Option<DateTime<Utc>>,
pub last_edit: DateTime<Utc>,
}
impl PostTimestamp
{
/// Create a new timestamp for a new post
pub fn new() -> Self
{
let now = Utc::now();
Self {
opened: now.clone(),
closed: None,
last_edit: now,
}
}
}
impl Hash for PostTimestamp {
fn hash<H: Hasher>(&self, state: &mut H) {
self.opened.hash(state);
self.closed.hash(state);
self.last_edit.hash(state);
}
}
impl fmt::Display for PostTimestamp
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
writeln!(f, " Opened - {}", self.opened)?;
writeln!(f, " Edited - {}", self.last_edit)?;
write!(f, " Closed - ")?;
if let Some(closed) = &self.closed {
writeln!(f, "{}", closed)?;
} else {
writeln!(f, "Stil open")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
/// A closed and finished post. The inverse of `Imouto`.
pub struct Static
{
id: identity::PostID,
user: identity::User,
title: Option<String>,
karada: String,
timestamp: PostTimestamp,
/// Hash of the rest of the post data. . . .
hash: crypto::sha256::Sha256Hash,
}
impl Static
{
/// Compute the hash for this instance
pub fn into_hashed(mut self) -> Self
{
self.hash = Default::default();
let bytes = serde_cbor::to_vec(&self).expect("Failed to serialise static post to CBOR");
Self {
hash: crypto::sha256::compute_slice(&bytes),
..self
}
}
/// Validate the internal hash of this instance
pub fn validate_hash(&self) -> bool
{
self.clone().into_hashed().hash == self.hash
}
}
use suspend::{
Suspendable,
SuspendStream,
};
#[async_trait]
impl Suspendable for Static
{
async fn suspend<S: SuspendStream +Send+Sync + ?Sized>(self, into: &mut S) -> Result<(), suspend::Error>
{
let mut output = suspend::Object::new();
output.insert("post-static", self);
into.set_object(output).await
}
async fn load<S: SuspendStream +Send+Sync+ ?Sized>(from: &mut S) -> Result<Self, suspend::Error>
{
let mut input = from.get_object().await?.ok_or(suspend::Error::BadObject)?;
input.try_get("post-static").ok_or(suspend::Error::BadObject)
}
}
#[cfg(test)]
mod tests
{
use super::*;
#[tokio::test]
async fn static_post_ser()
{
let mut output = suspend::MemorySuspendStream::new();
let post1 = Static {
id: identity::PostID::new(),
user: Default::default(),
title: None,
karada: "hello world".to_owned(),
timestamp: PostTimestamp::new(),
hash: Default::default(),
}.into_hashed();
let post1c = post1.clone();
assert!(post1.validate_hash());
post1.suspend(&mut output).await.expect("Suspend failed");
let buffer = output.buffer().clone();
let post2 = Static::load(&mut output).await.expect("Suspend load failed");
assert_eq!(post1c, post2);
assert!(post1c.validate_hash());
assert!(post2.validate_hash());
let buffer2 = suspend::oneshot(post2.clone()).await.expect("Oneshot failed");
assert_eq!(&buffer2[..], &buffer[..]);
let post3: Static = suspend::single(buffer2).await.expect("Single failed");
assert!(post3.validate_hash());
assert_eq!(post3, post2);
}
}