mneme/sync/transport/
http.rs1use std::time::Duration;
2
3use crate::error::{MnemeError, Result};
4use crate::sync::protocol::{SyncHello, SyncRequest, SyncResponse};
5
6pub struct HttpTransport {
8 client: reqwest::Client,
9 base_url: String,
10}
11
12impl HttpTransport {
13 pub fn new(base_url: String) -> Result<Self> {
15 let client = reqwest::Client::builder()
16 .timeout(Duration::from_secs(30))
17 .build()
18 .map_err(|e| MnemeError::Http(e.to_string()))?;
19 Ok(Self { client, base_url })
20 }
21
22 pub async fn hello(&self, hello: &SyncHello) -> Result<SyncHello> {
24 let url = format!("{}/api/v1/sync/hello", self.base_url);
25 let res = self
26 .client
27 .post(&url)
28 .json(hello)
29 .send()
30 .await
31 .map_err(|e| MnemeError::Http(e.to_string()))?;
32 if !res.status().is_success() {
33 return Err(MnemeError::Http(format!("hello failed: {}", res.status())));
34 }
35 res.json()
36 .await
37 .map_err(|e| MnemeError::Http(e.to_string()))
38 }
39
40 pub async fn pull(&self, request: &SyncRequest) -> Result<SyncResponse> {
42 let url = format!("{}/api/v1/sync/pull", self.base_url);
43 let res = self
44 .client
45 .post(&url)
46 .json(request)
47 .send()
48 .await
49 .map_err(|e| MnemeError::Http(e.to_string()))?;
50 if !res.status().is_success() {
51 return Err(MnemeError::Http(format!("pull failed: {}", res.status())));
52 }
53 res.json()
54 .await
55 .map_err(|e| MnemeError::Http(e.to_string()))
56 }
57
58 pub async fn push(&self, response: &SyncResponse) -> Result<()> {
60 let url = format!("{}/api/v1/sync/push", self.base_url);
61 let res = self
62 .client
63 .post(&url)
64 .json(response)
65 .send()
66 .await
67 .map_err(|e| MnemeError::Http(e.to_string()))?;
68 if !res.status().is_success() {
69 return Err(MnemeError::Http(format!("push failed: {}", res.status())));
70 }
71 Ok(())
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[tokio::test]
80 async fn test_http_transport_hello_unreachable() {
81 let transport = HttpTransport::new("http://255.255.255.255:1".to_string()).unwrap();
83 let hello = SyncHello {
84 project: "test".to_string(),
85 peer_id: uuid::Uuid::nil(),
86 peer_name: "test".to_string(),
87 mneme_version: "0.1.0".to_string(),
88 memory_count: 0,
89 heads: std::collections::HashMap::new(),
90 };
91 let result = tokio::time::timeout(
92 std::time::Duration::from_secs(3),
93 transport.hello(&hello),
94 )
95 .await;
96 assert!(result.is_ok(), "hello should complete within timeout");
97 assert!(result.unwrap().is_err(), "hello to unreachable should error");
98 }
99
100 #[tokio::test]
101 async fn test_http_transport_pull_unreachable() {
102 let transport = HttpTransport::new("http://255.255.255.255:1".to_string()).unwrap();
103 let request = SyncRequest {
104 project: "test".to_string(),
105 have: std::collections::HashMap::new(),
106 };
107 let result = tokio::time::timeout(
108 std::time::Duration::from_secs(3),
109 transport.pull(&request),
110 )
111 .await;
112 assert!(result.is_ok(), "pull should complete within timeout");
113 assert!(result.unwrap().is_err(), "pull from unreachable should error");
114 }
115
116 #[tokio::test]
117 async fn test_http_transport_push_unreachable() {
118 let transport = HttpTransport::new("http://255.255.255.255:1".to_string()).unwrap();
119 let response = SyncResponse {
120 project: "test".to_string(),
121 changes: vec![],
122 tombstones: vec![],
123 };
124 let result = tokio::time::timeout(
125 std::time::Duration::from_secs(3),
126 transport.push(&response),
127 )
128 .await;
129 assert!(result.is_ok(), "push should complete within timeout");
130 assert!(result.unwrap().is_err(), "push to unreachable should error");
131 }
132
133 #[test]
134 fn test_http_transport_invalid_base_url() {
135 let result = HttpTransport::new(String::new());
136 assert!(result.is_ok());
138 }
139}