use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PayloadMode {
Text,
SemanticFrame,
EmbeddingHints,
SemanticGraph,
}
impl PayloadMode {
pub fn mode_number(&self) -> u8 {
match self {
PayloadMode::Text => 0,
PayloadMode::SemanticFrame => 1,
PayloadMode::EmbeddingHints => 2,
PayloadMode::SemanticGraph => 3,
}
}
pub fn is_implemented(&self) -> bool {
matches!(self, PayloadMode::Text | PayloadMode::SemanticFrame)
}
}
impl fmt::Display for PayloadMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PayloadMode::Text => write!(f, "text"),
PayloadMode::SemanticFrame => write!(f, "semantic_frame"),
PayloadMode::EmbeddingHints => write!(f, "embedding_hints"),
PayloadMode::SemanticGraph => write!(f, "semantic_graph"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NegotiatedPayload {
pub mode: PayloadMode,
pub fallback_chain: Vec<PayloadMode>,
}
impl Default for NegotiatedPayload {
fn default() -> Self {
Self {
mode: PayloadMode::SemanticFrame,
fallback_chain: vec![PayloadMode::Text],
}
}
}
pub fn negotiate_payload_mode(
initiator_prefs: &[PayloadMode],
responder_prefs: &[PayloadMode],
) -> NegotiatedPayload {
let agreed = initiator_prefs
.iter()
.find(|mode| mode.is_implemented() && responder_prefs.contains(mode))
.copied()
.unwrap_or(PayloadMode::Text);
let fallback_chain: Vec<PayloadMode> = initiator_prefs
.iter()
.filter(|mode| {
mode.is_implemented()
&& **mode != agreed
&& responder_prefs.contains(mode)
&& mode.mode_number() < agreed.mode_number()
})
.copied()
.collect();
NegotiatedPayload {
mode: agreed,
fallback_chain,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn negotiate_both_support_semantic_frame() {
let initiator = vec![PayloadMode::SemanticFrame, PayloadMode::Text];
let responder = vec![PayloadMode::SemanticFrame, PayloadMode::Text];
let result = negotiate_payload_mode(&initiator, &responder);
assert_eq!(result.mode, PayloadMode::SemanticFrame);
assert_eq!(result.fallback_chain, vec![PayloadMode::Text]);
}
#[test]
fn negotiate_falls_back_to_text() {
let initiator = vec![PayloadMode::SemanticFrame, PayloadMode::Text];
let responder = vec![PayloadMode::Text];
let result = negotiate_payload_mode(&initiator, &responder);
assert_eq!(result.mode, PayloadMode::Text);
assert!(result.fallback_chain.is_empty());
}
#[test]
fn negotiate_empty_prefs_default_to_text() {
let result = negotiate_payload_mode(&[], &[]);
assert_eq!(result.mode, PayloadMode::Text);
}
#[test]
fn negotiate_skips_unimplemented_modes() {
let initiator = vec![
PayloadMode::SemanticGraph,
PayloadMode::SemanticFrame,
PayloadMode::Text,
];
let responder = vec![PayloadMode::SemanticGraph, PayloadMode::Text];
let result = negotiate_payload_mode(&initiator, &responder);
assert_eq!(result.mode, PayloadMode::Text);
}
}