use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::error::ProtocolError;
pub const PROTOCOL_VERSION: u8 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientMessage {
pub v: u8,
#[serde(flatten)]
pub payload: ClientPayload,
}
impl ClientMessage {
pub fn validate(&self) -> Result<(), ProtocolError> {
if self.v != PROTOCOL_VERSION {
return Err(ProtocolError::InvalidVersion(self.v));
}
match &self.payload {
ClientPayload::Event(event) => {
validate_session(&event.session)?;
if event.component.is_empty() {
return Err(ProtocolError::MissingField("component".into()));
}
if event.event.is_empty() {
return Err(ProtocolError::MissingField("event".into()));
}
if event.handler.is_empty() {
return Err(ProtocolError::MissingField("handler".into()));
}
}
ClientPayload::FormSubmit(form) => {
validate_session(&form.session)?;
if form.component.is_empty() {
return Err(ProtocolError::MissingField("component".into()));
}
if form.handler.is_empty() {
return Err(ProtocolError::MissingField("handler".into()));
}
}
ClientPayload::Navigate(nav) => {
validate_session(&nav.session)?;
if nav.path.is_empty() {
return Err(ProtocolError::MissingField("path".into()));
}
if !nav.path.starts_with('/') {
return Err(ProtocolError::InvalidEventType(format!(
"navigation path must start with '/', got: {}",
nav.path
)));
}
}
ClientPayload::Heartbeat(hb) => {
validate_session(&hb.session)?;
}
}
Ok(())
}
}
fn validate_session(session: &str) -> Result<(), ProtocolError> {
if session.is_empty() {
return Err(ProtocolError::InvalidSession);
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ClientPayload {
#[serde(rename = "event")]
Event(ClientEvent),
#[serde(rename = "form_submit")]
FormSubmit(FormSubmitEvent),
#[serde(rename = "navigate")]
Navigate(NavigateEvent),
#[serde(rename = "heartbeat")]
Heartbeat(HeartbeatEvent),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientEvent {
pub session: String,
pub component: String,
pub event: String,
pub handler: String,
pub payload: HashMap<String, serde_json::Value>,
pub seq: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FormSubmitEvent {
pub session: String,
pub component: String,
pub handler: String,
pub form: HashMap<String, serde_json::Value>,
pub seq: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NavigateEvent {
pub session: String,
pub path: String,
pub seq: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeartbeatEvent {
pub session: String,
pub seq: u64,
}