use crate::protocol::Message;
use serde::{Deserialize, Serialize};
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant, SystemTime};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecordedSession {
pub name: String,
pub started_at: SystemTime,
#[serde(with = "duration_serde")]
pub duration: Duration,
pub events: Vec<SessionEvent>,
pub metadata: SessionMetadata,
}
impl RecordedSession {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
started_at: SystemTime::now(),
duration: Duration::ZERO,
events: Vec::new(),
metadata: SessionMetadata::default(),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.events.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
#[must_use]
pub fn messages(&self) -> Vec<&Message> {
self.events
.iter()
.filter_map(|e| match e {
SessionEvent::MessageSent { message, .. }
| SessionEvent::MessageReceived { message, .. } => Some(message),
_ => None,
})
.collect()
}
#[must_use]
pub fn events_in_range(&self, start: Duration, end: Duration) -> Vec<&SessionEvent> {
self.events
.iter()
.filter(|e| {
let offset = e.offset();
offset >= start && offset <= end
})
.collect()
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SessionMetadata {
pub client_name: Option<String>,
pub client_version: Option<String>,
pub server_name: Option<String>,
pub server_version: Option<String>,
pub transport: Option<String>,
pub protocol_version: Option<String>,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum SessionEvent {
SessionStarted {
#[serde(with = "duration_serde")]
offset: Duration,
},
MessageSent {
#[serde(with = "duration_serde")]
offset: Duration,
message: Message,
},
MessageReceived {
#[serde(with = "duration_serde")]
offset: Duration,
message: Message,
},
Error {
#[serde(with = "duration_serde")]
offset: Duration,
error: String,
},
SessionEnded {
#[serde(with = "duration_serde")]
offset: Duration,
reason: Option<String>,
},
Custom {
#[serde(with = "duration_serde")]
offset: Duration,
name: String,
data: serde_json::Value,
},
}
impl SessionEvent {
#[must_use]
pub fn offset(&self) -> Duration {
match self {
Self::SessionStarted { offset }
| Self::MessageSent { offset, .. }
| Self::MessageReceived { offset, .. }
| Self::Error { offset, .. }
| Self::SessionEnded { offset, .. }
| Self::Custom { offset, .. } => *offset,
}
}
}
pub struct SessionRecorder {
name: String,
started: Instant,
state: Arc<RwLock<RecorderState>>,
}
struct RecorderState {
events: Vec<SessionEvent>,
recording: bool,
metadata: SessionMetadata,
}
impl SessionRecorder {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
started: Instant::now(),
state: Arc::new(RwLock::new(RecorderState {
events: vec![SessionEvent::SessionStarted {
offset: Duration::ZERO,
}],
recording: true,
metadata: SessionMetadata::default(),
})),
}
}
pub fn set_metadata(&self, metadata: SessionMetadata) {
if let Ok(mut state) = self.state.write() {
state.metadata = metadata;
}
}
#[must_use]
pub fn is_recording(&self) -> bool {
self.state.read().is_ok_and(|s| s.recording)
}
pub fn stop(&self, reason: Option<String>) {
if let Ok(mut state) = self.state.write() {
if state.recording {
state.events.push(SessionEvent::SessionEnded {
offset: self.started.elapsed(),
reason,
});
state.recording = false;
}
}
}
pub fn record_sent(&self, message: Message) {
if let Ok(mut state) = self.state.write() {
if state.recording {
state.events.push(SessionEvent::MessageSent {
offset: self.started.elapsed(),
message,
});
}
}
}
pub fn record_received(&self, message: Message) {
if let Ok(mut state) = self.state.write() {
if state.recording {
state.events.push(SessionEvent::MessageReceived {
offset: self.started.elapsed(),
message,
});
}
}
}
pub fn record_error(&self, error: impl Into<String>) {
if let Ok(mut state) = self.state.write() {
if state.recording {
state.events.push(SessionEvent::Error {
offset: self.started.elapsed(),
error: error.into(),
});
}
}
}
pub fn record_custom(&self, name: impl Into<String>, data: serde_json::Value) {
if let Ok(mut state) = self.state.write() {
if state.recording {
state.events.push(SessionEvent::Custom {
offset: self.started.elapsed(),
name: name.into(),
data,
});
}
}
}
#[must_use]
pub fn event_count(&self) -> usize {
self.state.read().map_or(0, |s| s.events.len())
}
#[must_use]
pub fn finalize(self) -> RecordedSession {
self.stop(Some("finalized".to_string()));
let (events, metadata) = self
.state
.read()
.map(|s| (s.events.clone(), s.metadata.clone()))
.unwrap_or_default();
RecordedSession {
name: self.name,
started_at: SystemTime::now() - self.started.elapsed(),
duration: self.started.elapsed(),
events,
metadata,
}
}
}
impl Clone for SessionRecorder {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
started: self.started,
state: Arc::clone(&self.state),
}
}
}
mod duration_serde {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::time::Duration;
pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
duration.as_millis().serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let ms = u64::deserialize(deserializer)?;
Ok(Duration::from_millis(ms))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::{Request, RequestId, Response};
#[test]
fn test_session_recorder() {
let recorder = SessionRecorder::new("test-session");
recorder.record_sent(Message::Request(Request::new("test/method", 1)));
recorder.record_received(Message::Response(Response::success(
RequestId::from(1),
serde_json::json!({}),
)));
assert_eq!(recorder.event_count(), 3); assert!(recorder.is_recording());
let session = recorder.finalize();
assert_eq!(session.name, "test-session");
assert_eq!(session.events.len(), 4); }
#[test]
fn test_session_serialization() {
let recorder = SessionRecorder::new("test");
recorder.record_sent(Message::Request(Request::new("ping", 1)));
let session = recorder.finalize();
let json = session.to_json().expect("Failed to serialize");
let restored = RecordedSession::from_json(&json).expect("Failed to deserialize");
assert_eq!(restored.name, "test");
assert_eq!(restored.events.len(), session.events.len());
}
#[test]
fn test_session_metadata() {
let recorder = SessionRecorder::new("test");
let metadata = SessionMetadata {
client_name: Some("test-client".to_string()),
client_version: Some("1.0.0".to_string()),
..Default::default()
};
recorder.set_metadata(metadata);
let session = recorder.finalize();
assert_eq!(
session.metadata.client_name,
Some("test-client".to_string())
);
}
}