use crate::identity::IdentityAssurance;
use crate::ids::{AttachmentId, ParticipantId, StreamId};
use bytes::Bytes;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum VconRef {
Local { uuid: Uuid },
Url { url: String },
}
#[derive(Clone, Debug)]
pub struct VconParty {
pub participant_id: ParticipantId,
pub display_name: Option<String>,
pub did_or_stir: Option<String>,
pub validation: IdentityAssurance,
}
#[derive(Clone, Debug)]
pub struct VconDialog {
pub kind: VconDialogKind,
pub stream_id: Option<StreamId>,
pub started: DateTime<Utc>,
pub ended: Option<DateTime<Utc>>,
pub parties: Vec<ParticipantId>,
pub mimetype: Option<String>,
}
#[derive(Clone, Debug)]
pub enum VconDialogKind {
Audio,
Video,
Text,
Transfer,
Other(String),
}
#[derive(Clone, Debug)]
pub struct VconAnalysis {
pub kind: VconAnalysisKind,
pub vendor: Option<String>,
pub product: Option<String>,
pub body: Bytes,
pub mimetype: String,
}
#[derive(Clone, Debug)]
pub enum VconAnalysisKind {
Transcript,
Sentiment,
Summary,
Other(String),
}
#[derive(Clone, Debug)]
pub struct VconAttachment {
pub id: AttachmentId,
pub mimetype: String,
pub body: Bytes,
pub note: Option<String>,
}
#[derive(Clone, Debug)]
pub struct VconSnapshot {
pub parties: Vec<VconParty>,
pub dialogs: Vec<VconDialog>,
pub analyses: Vec<VconAnalysis>,
pub attachments: Vec<VconAttachment>,
}
pub trait VconBuilderHandle: Send + Sync {
fn add_party(&self, party: VconParty);
fn add_dialog(&self, dialog: VconDialog);
fn add_analysis(&self, analysis: VconAnalysis);
fn add_attachment(&self, attachment: VconAttachment);
fn snapshot(&self) -> VconSnapshot;
}
pub struct DefaultVconBuilder {
inner: std::sync::Mutex<VconSnapshot>,
}
impl DefaultVconBuilder {
pub fn new() -> Self {
Self {
inner: std::sync::Mutex::new(VconSnapshot {
parties: Vec::new(),
dialogs: Vec::new(),
analyses: Vec::new(),
attachments: Vec::new(),
}),
}
}
}
impl Default for DefaultVconBuilder {
fn default() -> Self {
Self::new()
}
}
impl VconBuilderHandle for DefaultVconBuilder {
fn add_party(&self, party: VconParty) {
self.inner
.lock()
.expect("vcon builder lock poisoned")
.parties
.push(party);
}
fn add_dialog(&self, dialog: VconDialog) {
self.inner
.lock()
.expect("vcon builder lock poisoned")
.dialogs
.push(dialog);
}
fn add_analysis(&self, analysis: VconAnalysis) {
self.inner
.lock()
.expect("vcon builder lock poisoned")
.analyses
.push(analysis);
}
fn add_attachment(&self, attachment: VconAttachment) {
self.inner
.lock()
.expect("vcon builder lock poisoned")
.attachments
.push(attachment);
}
fn snapshot(&self) -> VconSnapshot {
let g = self.inner.lock().expect("vcon builder lock poisoned");
VconSnapshot {
parties: g.parties.clone(),
dialogs: g.dialogs.clone(),
analyses: g.analyses.clone(),
attachments: g.attachments.clone(),
}
}
}
pub fn encode_snapshot(snapshot: &VconSnapshot) -> bytes::Bytes {
let mut s = String::from("{\"version\":\"1\",\"parties\":[");
for (i, p) in snapshot.parties.iter().enumerate() {
if i > 0 {
s.push(',');
}
s.push_str(&format!(
"{{\"participant_id\":\"{}\",\"display_name\":\"{}\"}}",
p.participant_id,
p.display_name.clone().unwrap_or_default(),
));
}
s.push_str("],\"dialogs\":[");
for (i, d) in snapshot.dialogs.iter().enumerate() {
if i > 0 {
s.push(',');
}
s.push_str(&format!(
"{{\"kind\":\"{:?}\",\"started\":\"{}\"}}",
d.kind, d.started,
));
}
s.push_str("],\"analyses\":[");
for (i, a) in snapshot.analyses.iter().enumerate() {
if i > 0 {
s.push(',');
}
s.push_str(&format!(
"{{\"kind\":\"{:?}\",\"body_len\":{}}}",
a.kind,
a.body.len(),
));
}
s.push_str("],\"attachments\":[");
for (i, a) in snapshot.attachments.iter().enumerate() {
if i > 0 {
s.push(',');
}
s.push_str(&format!(
"{{\"id\":\"{}\",\"mimetype\":\"{}\",\"body_len\":{}}}",
a.id,
a.mimetype,
a.body.len(),
));
}
s.push_str("]}");
bytes::Bytes::from(s.into_bytes())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vcon_ref_local_roundtrips_through_json() {
let uuid = Uuid::nil();
let v = VconRef::Local { uuid };
let json = serde_json::to_value(&v).expect("encode");
assert_eq!(json["kind"], "local");
assert_eq!(json["uuid"], uuid.to_string());
let back: VconRef = serde_json::from_value(json).expect("decode");
assert_eq!(v, back);
}
#[test]
fn vcon_ref_url_roundtrips_through_json() {
let v = VconRef::Url {
url: "https://vcons.example/abc123".into(),
};
let json = serde_json::to_value(&v).expect("encode");
assert_eq!(json["kind"], "url");
let back: VconRef = serde_json::from_value(json).expect("decode");
assert_eq!(v, back);
}
}