client/src/lib.rs@main
raw
1use std::path::PathBuf;
2
3use http_body_util::{BodyExt as _, Full};
4use hyper::{Method, Request, StatusCode, Uri, body::Bytes, client::conn};
5use hyper_util::rt::TokioIo;
6use tokio::net::UnixStream;
7
8#[derive(Debug, thiserror::Error)]
9pub enum GetError {
10 #[error("failed to connect to unix socket")]
11 SocketConnect(#[source] std::io::Error),
12 #[error("failed to complete http handshake")]
13 HttpHandshake(#[source] hyper::Error),
14 #[error("given key-value key was invalid")]
15 InvalidKey(#[source] serde_urlencoded::ser::Error),
16 #[error("failed to create request url")]
17 InvalidUrl(#[source] hyper::http::Error),
18 #[error("failed to build http request")]
19 BuildRequest(#[source] hyper::http::Error),
20 #[error("failed to send http request")]
21 SendRequest(#[source] hyper::Error),
22 #[error("http request returned with a failed response: {0}")]
23 FailedResponse(StatusCode),
24 #[error("failed to collect response body")]
25 CollectBody(#[source] hyper::Error),
26}
27
28#[derive(Debug, thiserror::Error)]
29pub enum SetError {
30 #[error("failed to connect to unix socket")]
31 SocketConnect(#[source] std::io::Error),
32 #[error("failed to complete http handshake")]
33 HttpHandshake(#[source] hyper::Error),
34 #[error("given key-value key was invalid")]
35 InvalidKey(#[source] serde_urlencoded::ser::Error),
36 #[error("failed to create request url")]
37 InvalidUrl(#[source] hyper::http::Error),
38 #[error("failed to build http request")]
39 BuildRequest(#[source] hyper::http::Error),
40 #[error("failed to send http request")]
41 SendRequest(#[source] hyper::Error),
42 #[error("http request returned with a failed response: {0}")]
43 FailedResponse(StatusCode),
44}
45
46pub struct Client {
47 path: PathBuf,
48}
49
50impl Client {
51 pub async fn get(&self, key: &str) -> Result<Option<Vec<u8>>, GetError> {
52 let stream = UnixStream::connect(&self.path)
53 .await
54 .map_err(GetError::SocketConnect)?;
55 let io = TokioIo::new(stream);
56
57 let (mut sender, conn) = conn::http1::handshake(io)
58 .await
59 .map_err(GetError::HttpHandshake)?;
60
61 tokio::task::spawn(async move {
62 if let Err(err) = conn.await {
63 tracing::error!("Connection failed: {:?}", err);
64 }
65 });
66
67 let query = write_query(key).map_err(GetError::InvalidKey)?;
68
69 let uri = Uri::builder()
70 .scheme("http")
71 .authority("localhost.invalid-tld")
72 .path_and_query(query)
73 .build()
74 .map_err(GetError::InvalidUrl)?;
75
76 let req = Request::builder()
77 .method(Method::GET)
78 .uri(uri)
79 .body(Full::new(Bytes::from_static(&[])))
80 .map_err(GetError::BuildRequest)?;
81
82 let res = sender
83 .send_request(req)
84 .await
85 .map_err(GetError::SendRequest)?;
86
87 if matches!(res.status(), StatusCode::BAD_REQUEST) {
88 return Err(GetError::FailedResponse(res.status()));
89 }
90 if matches!(res.status(), StatusCode::NOT_FOUND) {
91 return Ok(None);
92 }
93
94 let body = res
95 .into_body()
96 .collect()
97 .await
98 .map_err(GetError::CollectBody)?
99 .to_bytes();
100
101 Ok(Some(body.to_vec()))
102 }
103
104 pub async fn set(&self, key: &str, value: &[u8]) -> Result<(), SetError> {
105 let stream = UnixStream::connect(&self.path)
106 .await
107 .map_err(SetError::SocketConnect)?;
108 let io = TokioIo::new(stream);
109
110 let (mut sender, conn) = conn::http1::handshake(io)
111 .await
112 .map_err(SetError::HttpHandshake)?;
113
114 tokio::task::spawn(async move {
115 if let Err(err) = conn.await {
116 tracing::error!("Connection failed: {:?}", err);
117 }
118 });
119
120 let query = write_query(key).map_err(SetError::InvalidKey)?;
121
122 let uri = Uri::builder()
123 .scheme("http")
124 .authority("localhost.invalid-tld")
125 .path_and_query(query)
126 .build()
127 .map_err(SetError::BuildRequest)?;
128
129 let body = Full::new(Bytes::from(value.to_vec()));
130
131 let req = Request::builder()
132 .method(Method::POST)
133 .uri(uri)
134 .body(body)
135 .map_err(SetError::BuildRequest)?;
136
137 let res = sender
138 .send_request(req)
139 .await
140 .map_err(SetError::SendRequest)?;
141
142 if matches!(res.status(), StatusCode::BAD_REQUEST) {
143 return Err(SetError::FailedResponse(res.status()));
144 }
145
146 Ok(())
147 }
148}
149
150impl Default for Client {
151 fn default() -> Self {
152 Self {
153 path: PathBuf::from("/tmp/last-sky.sock"),
154 }
155 }
156}
157
158#[inline]
159fn write_query(key: &str) -> Result<String, serde_urlencoded::ser::Error> {
160 #[derive(serde::Serialize)]
161 struct Key<'k> {
162 key: &'k str,
163 }
164
165 let mut output = "/?".to_string();
166
167 let mut encoder = form_urlencoded::Serializer::new(&mut output);
168 let serializer = serde_urlencoded::Serializer::new(&mut encoder);
169
170 <Key<'_> as serde::Serialize>::serialize(&Key { key }, serializer)?;
171
172 Ok(output)
173}
174