use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::protocol::JsonMessage;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SerializedTimestamp {
pub sec: u32,
pub nsec: u32,
}
impl SerializedTimestamp {
fn from_nsecs(timestamp: u64) -> Self {
SerializedTimestamp {
sec: u32::try_from(timestamp / 1e9 as u64).unwrap_or(u32::MAX),
nsec: (timestamp % 1e9 as u64) as u32,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "op", rename = "serverInfo", rename_all = "camelCase")]
pub struct ServerInfo {
pub name: String,
pub capabilities: Vec<Capability>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub supported_encodings: Vec<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub metadata: HashMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data_start_time: Option<SerializedTimestamp>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data_end_time: Option<SerializedTimestamp>,
}
impl ServerInfo {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
capabilities: vec![],
supported_encodings: vec![],
metadata: HashMap::default(),
session_id: None,
data_start_time: None,
data_end_time: None,
}
}
#[must_use]
pub fn with_capabilities(mut self, capabilities: impl IntoIterator<Item = Capability>) -> Self {
self.capabilities = capabilities.into_iter().collect();
self
}
#[must_use]
pub fn with_supported_encodings(
mut self,
encodings: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
self.supported_encodings = encodings.into_iter().map(|s| s.into()).collect();
self
}
#[must_use]
pub fn with_metadata(mut self, metadata: HashMap<String, String>) -> Self {
self.metadata = metadata;
self
}
#[must_use]
pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
self.session_id = Some(session_id.into());
self
}
#[must_use]
pub fn with_playback_time_range(mut self, time_range: Option<(u64, u64)>) -> Self {
if let Some((start_time, end_time)) = time_range {
self.data_start_time = Some(SerializedTimestamp::from_nsecs(start_time));
self.data_end_time = Some(SerializedTimestamp::from_nsecs(end_time));
} else {
self.data_start_time = None;
self.data_end_time = None;
}
self
}
}
impl JsonMessage for ServerInfo {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Capability {
ClientPublish,
Parameters,
ParametersSubscribe,
Time,
Services,
ConnectionGraph,
Assets,
PlaybackControl,
}
#[cfg(test)]
mod tests {
use super::*;
fn message() -> ServerInfo {
ServerInfo::new("example server")
}
fn message_full() -> ServerInfo {
message()
.with_capabilities([
Capability::ClientPublish,
Capability::Time,
Capability::PlaybackControl,
])
.with_supported_encodings(["json"])
.with_metadata(maplit::hashmap! {
"key".into() => "value".into(),
})
.with_session_id("1675789422160")
.with_playback_time_range(Some((1000000, 1000005)))
}
#[test]
fn test_encode() {
insta::assert_json_snapshot!(message());
}
#[test]
fn test_encode_full() {
insta::assert_json_snapshot!(message_full());
}
fn test_roundtrip_inner(orig: ServerInfo) {
let buf = orig.to_string();
let parsed: ServerInfo = serde_json::from_str(&buf).unwrap();
assert_eq!(parsed, orig);
}
#[test]
fn test_roundtrip() {
test_roundtrip_inner(message());
}
#[test]
fn test_roundtrip_full() {
test_roundtrip_inner(message_full());
}
}