ldp_protocol/types/
payload.rs1use serde::{Deserialize, Serialize};
12use std::fmt;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
18#[serde(rename_all = "snake_case")]
19pub enum PayloadMode {
20 Text,
22
23 SemanticFrame,
26
27 EmbeddingHints,
30
31 SemanticGraph,
34}
35
36impl PayloadMode {
37 pub fn mode_number(&self) -> u8 {
39 match self {
40 PayloadMode::Text => 0,
41 PayloadMode::SemanticFrame => 1,
42 PayloadMode::EmbeddingHints => 2,
43 PayloadMode::SemanticGraph => 3,
44 }
45 }
46
47 pub fn is_implemented(&self) -> bool {
49 matches!(self, PayloadMode::Text | PayloadMode::SemanticFrame)
50 }
51}
52
53impl fmt::Display for PayloadMode {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 PayloadMode::Text => write!(f, "text"),
57 PayloadMode::SemanticFrame => write!(f, "semantic_frame"),
58 PayloadMode::EmbeddingHints => write!(f, "embedding_hints"),
59 PayloadMode::SemanticGraph => write!(f, "semantic_graph"),
60 }
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct NegotiatedPayload {
67 pub mode: PayloadMode,
69
70 pub fallback_chain: Vec<PayloadMode>,
72}
73
74impl Default for NegotiatedPayload {
75 fn default() -> Self {
76 Self {
77 mode: PayloadMode::SemanticFrame,
78 fallback_chain: vec![PayloadMode::Text],
79 }
80 }
81}
82
83pub fn negotiate_payload_mode(
88 initiator_prefs: &[PayloadMode],
89 responder_prefs: &[PayloadMode],
90) -> NegotiatedPayload {
91 let agreed = initiator_prefs
93 .iter()
94 .find(|mode| mode.is_implemented() && responder_prefs.contains(mode))
95 .copied()
96 .unwrap_or(PayloadMode::Text);
97
98 let fallback_chain: Vec<PayloadMode> = initiator_prefs
100 .iter()
101 .filter(|mode| {
102 mode.is_implemented()
103 && **mode != agreed
104 && responder_prefs.contains(mode)
105 && mode.mode_number() < agreed.mode_number()
106 })
107 .copied()
108 .collect();
109
110 NegotiatedPayload {
111 mode: agreed,
112 fallback_chain,
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn negotiate_both_support_semantic_frame() {
122 let initiator = vec![PayloadMode::SemanticFrame, PayloadMode::Text];
123 let responder = vec![PayloadMode::SemanticFrame, PayloadMode::Text];
124 let result = negotiate_payload_mode(&initiator, &responder);
125 assert_eq!(result.mode, PayloadMode::SemanticFrame);
126 assert_eq!(result.fallback_chain, vec![PayloadMode::Text]);
127 }
128
129 #[test]
130 fn negotiate_falls_back_to_text() {
131 let initiator = vec![PayloadMode::SemanticFrame, PayloadMode::Text];
132 let responder = vec![PayloadMode::Text];
133 let result = negotiate_payload_mode(&initiator, &responder);
134 assert_eq!(result.mode, PayloadMode::Text);
135 assert!(result.fallback_chain.is_empty());
136 }
137
138 #[test]
139 fn negotiate_empty_prefs_default_to_text() {
140 let result = negotiate_payload_mode(&[], &[]);
141 assert_eq!(result.mode, PayloadMode::Text);
142 }
143
144 #[test]
145 fn negotiate_skips_unimplemented_modes() {
146 let initiator = vec![
147 PayloadMode::SemanticGraph,
148 PayloadMode::SemanticFrame,
149 PayloadMode::Text,
150 ];
151 let responder = vec![PayloadMode::SemanticGraph, PayloadMode::Text];
152 let result = negotiate_payload_mode(&initiator, &responder);
153 assert_eq!(result.mode, PayloadMode::Text);
155 }
156}