use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
#[serde(tag = "event", rename_all = "lowercase")]
pub(crate) enum Event {
Init(InitEvent),
Upload(UploadEvent),
Download(DownloadEvent),
Terminate,
}
#[derive(Debug, Deserialize)]
pub(crate) struct InitEvent {
pub(crate) remote: String,
}
#[derive(Debug, Deserialize)]
pub(crate) struct UploadEvent {
pub(crate) oid: String,
pub(crate) size: u64,
pub(crate) path: String,
}
#[derive(Debug, Deserialize)]
pub(crate) struct DownloadEvent {
pub(crate) oid: String,
pub(crate) size: u64,
}
#[derive(Debug, Serialize)]
pub(crate) struct InitResponse<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) error: Option<ErrorPayload<'a>>,
}
#[derive(Debug, Serialize)]
pub(crate) struct ProgressEvent<'a> {
pub(crate) event: &'static str,
pub(crate) oid: &'a str,
#[serde(rename = "bytesSoFar")]
pub(crate) bytes_so_far: u64,
#[serde(rename = "bytesSinceLast")]
pub(crate) bytes_since_last: u64,
}
#[derive(Debug, Serialize)]
pub(crate) struct CompleteEvent<'a> {
pub(crate) event: &'static str,
pub(crate) oid: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) path: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) error: Option<ErrorPayload<'a>>,
}
#[derive(Debug, Serialize)]
pub(crate) struct ErrorPayload<'a> {
pub(crate) code: u32,
pub(crate) message: &'a str,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_init_event_and_ignores_unused_fields() {
let json = r#"{"event":"init","operation":"upload","remote":"origin","concurrent":true,"concurrenttransfers":3}"#;
let evt: Event = serde_json::from_str(json).expect("parse init");
match evt {
Event::Init(init) => assert_eq!(init.remote, "origin"),
other => panic!("expected init, got {other:?}"),
}
}
#[test]
fn parses_upload_event() {
let json = r#"{"event":"upload","oid":"abc","size":42,"path":"/tmp/foo"}"#;
let evt: Event = serde_json::from_str(json).expect("parse upload");
match evt {
Event::Upload(u) => {
assert_eq!(u.oid, "abc");
assert_eq!(u.size, 42);
assert_eq!(u.path, "/tmp/foo");
}
other => panic!("expected upload, got {other:?}"),
}
}
#[test]
fn parses_download_event() {
let json = r#"{"event":"download","oid":"abc","size":42}"#;
let evt: Event = serde_json::from_str(json).expect("parse download");
assert!(matches!(evt, Event::Download(_)));
}
#[test]
fn parses_terminate_event() {
let json = r#"{"event":"terminate"}"#;
let evt: Event = serde_json::from_str(json).expect("parse terminate");
assert!(matches!(evt, Event::Terminate));
}
#[test]
fn complete_skips_optional_fields_when_absent() {
let evt = CompleteEvent {
event: "complete",
oid: "abc",
path: None,
error: None,
};
let out = serde_json::to_string(&evt).expect("serialize");
assert_eq!(out, r#"{"event":"complete","oid":"abc"}"#);
}
#[test]
fn complete_emits_path_on_download_success() {
let evt = CompleteEvent {
event: "complete",
oid: "abc",
path: Some("/tmp/abc"),
error: None,
};
let out = serde_json::to_string(&evt).expect("serialize");
assert_eq!(out, r#"{"event":"complete","oid":"abc","path":"/tmp/abc"}"#);
}
#[test]
fn complete_emits_error_on_failure() {
let evt = CompleteEvent {
event: "complete",
oid: "abc",
path: None,
error: Some(ErrorPayload {
code: 2,
message: "boom",
}),
};
let out = serde_json::to_string(&evt).expect("serialize");
assert_eq!(
out,
r#"{"event":"complete","oid":"abc","error":{"code":2,"message":"boom"}}"#
);
}
#[test]
fn init_response_empty_on_success() {
let resp = InitResponse { error: None };
let out = serde_json::to_string(&resp).expect("serialize");
assert_eq!(out, "{}");
}
}