1#[allow(clippy::large_enum_variant)]
8#[path = "generated/geode.rs"]
9mod generated;
10
11pub use generated::*;
13
14use prost::Message;
15
16use crate::error::Result;
19
20pub fn encode_with_length_prefix(msg: &QuicClientMessage) -> Vec<u8> {
27 let data = msg.encode_to_vec();
28 let length = data.len() as u32;
29 let mut result = Vec::with_capacity(4 + data.len());
30 result.extend(&length.to_be_bytes());
31 result.extend(data);
32 result
33}
34
35pub fn decode_length_prefix(data: &[u8]) -> Result<u32> {
37 if data.len() < 4 {
38 return Err(crate::error::Error::protocol(
39 "Insufficient data for length prefix",
40 ));
41 }
42 Ok(u32::from_be_bytes([data[0], data[1], data[2], data[3]]))
43}
44
45pub fn decode_quic_server_message(data: &[u8]) -> Result<QuicServerMessage> {
47 QuicServerMessage::decode(data)
48 .map_err(|e| crate::error::Error::protocol(format!("Protobuf decode error: {}", e)))
49}
50
51#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn test_encode_decode_hello_roundtrip() {
61 let req = HelloRequest {
62 username: "admin".to_string(),
63 password: "secret".to_string(),
64 tenant_id: Some("tenant1".to_string()),
65 client_name: String::new(),
66 client_version: String::new(),
67 wanted_conformance: String::new(),
68 graph: None,
69 role: None,
70 };
71 let msg = QuicClientMessage {
72 msg: Some(quic_client_message::Msg::Hello(req)),
73 };
74 let encoded = msg.encode_to_vec();
75 assert!(!encoded.is_empty());
76
77 let decoded = QuicClientMessage::decode(encoded.as_slice()).unwrap();
79 match decoded.msg {
80 Some(quic_client_message::Msg::Hello(hello)) => {
81 assert_eq!(hello.username, "admin");
82 assert_eq!(hello.password, "secret");
83 assert_eq!(hello.tenant_id, Some("tenant1".to_string()));
84 }
85 _ => panic!("Expected Hello variant"),
86 }
87 }
88
89 #[test]
94 fn test_hello_carries_tenant_and_role_gap0841() {
95 let req = HelloRequest {
96 username: "analyst".to_string(),
97 password: "secret".to_string(),
98 tenant_id: Some("acme-corp".to_string()),
99 client_name: "geode-rust".to_string(),
100 client_version: "0.2.0".to_string(),
101 wanted_conformance: "min".to_string(),
102 graph: Some("mygraph".to_string()),
103 role: Some("analyst-role".to_string()),
104 };
105 let msg = QuicClientMessage {
106 msg: Some(quic_client_message::Msg::Hello(req)),
107 };
108 let encoded = msg.encode_to_vec();
109
110 let decoded = QuicClientMessage::decode(encoded.as_slice()).unwrap();
111 match decoded.msg {
112 Some(quic_client_message::Msg::Hello(hello)) => {
113 assert_eq!(hello.tenant_id, Some("acme-corp".to_string()));
114 assert_eq!(hello.role, Some("analyst-role".to_string()));
115 assert_eq!(hello.graph, Some("mygraph".to_string()));
116 }
117 _ => panic!("Expected Hello variant"),
118 }
119 }
120
121 #[test]
125 fn test_hello_omits_tenant_and_role_when_unset_gap0841() {
126 let req = HelloRequest {
127 username: "admin".to_string(),
128 password: "secret".to_string(),
129 tenant_id: None,
130 client_name: "geode-rust".to_string(),
131 client_version: "0.2.0".to_string(),
132 wanted_conformance: "min".to_string(),
133 graph: None,
134 role: None,
135 };
136 let encoded = req.encode_to_vec();
137 let decoded = HelloRequest::decode(encoded.as_slice()).unwrap();
138 assert_eq!(decoded.tenant_id, None);
139 assert_eq!(decoded.role, None);
140 }
141
142 #[test]
143 fn test_encode_decode_execute_roundtrip() {
144 let params = vec![
145 Param {
146 name: "name".to_string(),
147 value: Some(Value {
148 kind: Some(value::Kind::StringVal(StringValue {
149 value: "Alice".to_string(),
150 kind: 0,
151 })),
152 }),
153 },
154 Param {
155 name: "age".to_string(),
156 value: Some(Value {
157 kind: Some(value::Kind::IntVal(IntValue { value: 30, kind: 0 })),
158 }),
159 },
160 ];
161
162 let req = ExecuteRequest {
163 session_id: "session123".to_string(),
164 query: "MATCH (n) RETURN n".to_string(),
165 params,
166 };
167 let msg = QuicClientMessage {
168 msg: Some(quic_client_message::Msg::Execute(req)),
169 };
170 let encoded = msg.encode_to_vec();
171 assert!(!encoded.is_empty());
172
173 let decoded = QuicClientMessage::decode(encoded.as_slice()).unwrap();
174 match decoded.msg {
175 Some(quic_client_message::Msg::Execute(exec)) => {
176 assert_eq!(exec.session_id, "session123");
177 assert_eq!(exec.query, "MATCH (n) RETURN n");
178 assert_eq!(exec.params.len(), 2);
179 }
180 _ => panic!("Expected Execute variant"),
181 }
182 }
183
184 #[test]
185 fn test_encode_with_length_prefix() {
186 let msg = QuicClientMessage {
187 msg: Some(quic_client_message::Msg::Ping(PingRequest {})),
188 };
189 let encoded = encode_with_length_prefix(&msg);
190 assert!(encoded.len() >= 4);
192 let length = u32::from_be_bytes([encoded[0], encoded[1], encoded[2], encoded[3]]);
193 assert_eq!(length as usize, encoded.len() - 4);
194 }
195
196 #[test]
197 fn test_decode_length_prefix() {
198 let data = [0x00, 0x00, 0x00, 0x10];
199 let length = decode_length_prefix(&data).unwrap();
200 assert_eq!(length, 16);
201 }
202
203 #[test]
204 fn test_decode_length_prefix_insufficient_data() {
205 let data = [0x00, 0x00];
206 let result = decode_length_prefix(&data);
207 assert!(result.is_err());
208 }
209
210 #[test]
211 fn test_decode_hello_response() {
212 let resp = HelloResponse {
214 success: true,
215 session_id: "sess123".to_string(),
216 error_message: String::new(),
217 capabilities: vec![],
218 password_reset_required: false,
219 graph: None,
220 };
221 let encoded = resp.encode_to_vec();
222 let decoded = HelloResponse::decode(encoded.as_slice()).unwrap();
223 assert!(decoded.success);
224 assert_eq!(decoded.session_id, "sess123");
225 }
226
227 #[test]
228 fn test_decode_ping_response() {
229 let resp = PingResponse { ok: true };
230 let encoded = resp.encode_to_vec();
231 let decoded = PingResponse::decode(encoded.as_slice()).unwrap();
232 assert!(decoded.ok);
233 }
234
235 #[test]
236 fn test_value_null() {
237 let val = Value {
238 kind: Some(value::Kind::NullVal(NullValue {})),
239 };
240 assert!(matches!(val.kind, Some(value::Kind::NullVal(_))));
241 }
242
243 #[test]
244 fn test_value_default() {
245 let val = Value::default();
246 assert!(val.kind.is_none());
247 }
248
249 #[test]
250 fn test_message_defaults() {
251 let client_msg = QuicClientMessage::default();
252 assert!(client_msg.msg.is_none());
253
254 let server_msg = QuicServerMessage::default();
255 assert!(server_msg.msg.is_none());
256 }
257}