use chrono::DateTime;
use diesel::prelude::*;
use miden_protocol::utils::serde::{Deserializable, Serializable};
use super::schema::notes;
use crate::database::DatabaseError;
use crate::types::{NoteHeader, StoredNote};
#[derive(Queryable, Selectable, Debug, Clone)]
#[diesel(table_name = notes)]
pub struct Note {
pub seq: i64,
pub id: Vec<u8>,
pub tag: i64,
pub header: Vec<u8>,
pub details: Vec<u8>,
pub created_at: i64,
pub after_block_num: Option<i64>,
}
#[derive(Insertable)]
#[diesel(table_name = notes)]
pub struct NewNote {
pub id: Vec<u8>,
pub tag: i64,
pub header: Vec<u8>,
pub details: Vec<u8>,
pub created_at: i64,
pub after_block_num: Option<i64>,
}
impl From<&StoredNote> for NewNote {
fn from(note: &StoredNote) -> Self {
Self {
id: note.header.id().as_bytes().to_vec(),
tag: i64::from(note.header.metadata().tag().as_u32()),
header: note.header.to_bytes(),
details: note.details.clone(),
created_at: note.created_at.timestamp_micros(),
after_block_num: note.after_block_num.map(i64::from),
}
}
}
impl TryFrom<Note> for StoredNote {
type Error = DatabaseError;
fn try_from(note: Note) -> std::result::Result<Self, Self::Error> {
let created_at = DateTime::from_timestamp_micros(note.created_at).ok_or_else(|| {
DatabaseError::Deserialization(format!(
"Invalid timestamp microseconds: {}",
note.created_at
))
})?;
let header = NoteHeader::read_from_bytes(¬e.header).map_err(|e| {
DatabaseError::Deserialization(format!("Failed to deserialize header: {e}"))
})?;
Ok(StoredNote {
header,
details: note.details,
created_at,
seq: note.seq,
after_block_num: note
.after_block_num
.map(|n| {
u32::try_from(n).map_err(|_| {
DatabaseError::Deserialization(format!("Invalid after_block_num: {n}"))
})
})
.transpose()?,
})
}
}
#[cfg(test)]
mod tests {
use chrono::Utc;
use miden_protocol::utils::serde::Serializable;
use super::*;
use crate::database::DatabaseError;
use crate::test_utils::test_note_header;
#[test]
fn test_note_converts_to_stored_note() {
let header = test_note_header();
let created_at = Utc::now().timestamp_micros();
let row = Note {
seq: 7,
id: header.id().as_bytes().to_vec(),
tag: 0,
header: header.to_bytes(),
details: vec![1, 2, 3],
created_at,
after_block_num: Some(100),
};
let stored = StoredNote::try_from(row).expect("valid row must convert");
assert_eq!(stored.seq, 7);
assert_eq!(stored.header.id().as_bytes(), header.id().as_bytes());
assert_eq!(stored.details, vec![1, 2, 3]);
assert_eq!(stored.created_at.timestamp_micros(), created_at);
assert_eq!(stored.after_block_num, Some(100));
let bare = Note {
seq: 8,
id: header.id().as_bytes().to_vec(),
tag: 0,
header: header.to_bytes(),
details: vec![],
created_at,
after_block_num: None,
};
let stored_bare = StoredNote::try_from(bare).expect("valid row must convert");
assert_eq!(stored_bare.after_block_num, None);
}
#[test]
fn test_out_of_range_after_block_num_is_rejected() {
let header = test_note_header();
let row = Note {
seq: 1,
id: header.id().as_bytes().to_vec(),
tag: 0,
header: header.to_bytes(),
details: vec![],
created_at: Utc::now().timestamp_micros(),
after_block_num: Some(i64::from(u32::MAX) + 1),
};
match StoredNote::try_from(row) {
Err(DatabaseError::Deserialization(msg)) => {
assert!(msg.contains("Invalid after_block_num"), "unexpected message: {msg}");
},
other => panic!("expected Deserialization error, got: {other:?}"),
}
}
}