1#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
10#![cfg_attr(
11 test,
12 allow(
13 unused,
14 clippy::all,
15 clippy::pedantic,
16 clippy::nursery,
17 clippy::dbg_macro,
18 clippy::unwrap_used,
19 clippy::missing_docs_in_private_items,
20 )
21)]
22
23pub use bytes;
24pub use prost;
25
26pub use ipc::Instruction;
27pub use payload::{gistit::Inner, Gistit};
28
29pub mod payload {
30 use super::prost::Message;
31 use super::Result;
32 use sha2::{Digest, Sha256};
33
34 include!(concat!(env!("OUT_DIR"), "/gistit.payload.rs"));
35
36 pub fn hash(author: &str, description: Option<&str>, data: impl AsRef<[u8]>) -> String {
37 let mut hasher = Sha256::new();
38 hasher.update(data);
39 hasher.update(author);
40 hasher.update(description.unwrap_or(""));
41
42 format!("{:x}", hasher.finalize())
43 }
44
45 impl Gistit {
46 #[must_use]
47 pub fn new(
48 hash: String,
49 author: String,
50 description: Option<String>,
51 timestamp: String,
52 inner: Vec<gistit::Inner>,
53 ) -> Self {
54 Self {
55 hash,
56 author,
57 description,
58 timestamp,
59 inner,
60 }
61 }
62
63 #[must_use]
64 pub const fn new_inner(
65 name: String,
66 lang: String,
67 size: u32,
68 data: String,
69 ) -> gistit::Inner {
70 gistit::Inner {
71 name,
72 lang,
73 size,
74 data,
75 }
76 }
77
78 pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
84 Ok(Self::decode(bytes.as_ref())?)
85 }
86 }
87}
88
89pub mod ipc {
90 use super::Gistit;
91 use super::{Error, Result};
92
93 include!(concat!(env!("OUT_DIR"), "/gistit.ipc.rs"));
94
95 impl Instruction {
96 #[must_use]
97 pub const fn request_status() -> Self {
98 Self {
99 kind: Some(instruction::Kind::StatusRequest(
100 instruction::StatusRequest {},
101 )),
102 }
103 }
104
105 #[must_use]
106 pub const fn request_fetch(hash: String) -> Self {
107 Self {
108 kind: Some(instruction::Kind::FetchRequest(instruction::FetchRequest {
109 hash,
110 })),
111 }
112 }
113
114 #[must_use]
115 pub const fn request_provide(gistit: Gistit) -> Self {
116 Self {
117 kind: Some(instruction::Kind::ProvideRequest(
118 instruction::ProvideRequest {
119 gistit: Some(gistit),
120 },
121 )),
122 }
123 }
124
125 #[must_use]
126 pub const fn request_shutdown() -> Self {
127 Self {
128 kind: Some(instruction::Kind::ShutdownRequest(
129 instruction::ShutdownRequest {},
130 )),
131 }
132 }
133
134 #[must_use]
135 pub const fn request_dial(address: String) -> Self {
136 Self {
137 kind: Some(instruction::Kind::DialRequest(instruction::DialRequest {
138 address,
139 })),
140 }
141 }
142
143 #[must_use]
144 pub const fn respond_status(
145 peer_id: String,
146 peer_count: u32,
147 pending_connections: u32,
148 hosting: u32,
149 ) -> Self {
150 Self {
151 kind: Some(instruction::Kind::StatusResponse(
152 instruction::StatusResponse {
153 peer_id,
154 peer_count,
155 pending_connections,
156 hosting,
157 },
158 )),
159 }
160 }
161
162 #[must_use]
163 pub const fn respond_fetch(gistit: Option<Gistit>) -> Self {
164 Self {
165 kind: Some(instruction::Kind::FetchResponse(
166 instruction::FetchResponse { gistit },
167 )),
168 }
169 }
170
171 #[must_use]
172 pub const fn respond_provide(maybe_hash: Option<String>) -> Self {
173 Self {
174 kind: Some(instruction::Kind::ProvideResponse(
175 instruction::ProvideResponse { hash: maybe_hash },
176 )),
177 }
178 }
179
180 #[allow(clippy::missing_const_for_fn)]
186 pub fn expect_request(self) -> Result<instruction::Kind> {
187 match self {
188 Self {
189 kind:
190 Some(
191 instruction::Kind::FetchResponse(_)
192 | instruction::Kind::ProvideResponse(_)
193 | instruction::Kind::StatusResponse(_),
194 )
195 | None,
196 } => Err(Error::Other("instruction is not a request")),
197 Self {
198 kind: Some(request),
199 } => Ok(request),
200 }
201 }
202
203 #[allow(clippy::missing_const_for_fn)]
209 pub fn expect_response(self) -> Result<instruction::Kind> {
210 match self {
211 Self {
212 kind:
213 Some(
214 instruction::Kind::FetchRequest(_)
215 | instruction::Kind::StatusRequest(_)
216 | instruction::Kind::ShutdownRequest(_)
217 | instruction::Kind::ProvideRequest(_),
218 )
219 | None,
220 } => Err(Error::Other("instruction is not a response")),
221 Self {
222 kind: Some(response),
223 } => Ok(response),
224 }
225 }
226 }
227}
228
229pub type Result<T> = std::result::Result<T, Error>;
230
231#[derive(thiserror::Error, Debug)]
232pub enum Error {
233 #[error("decode error {0}")]
234 Decode(#[from] prost::DecodeError),
235
236 #[error("other error {0}")]
237 Other(&'static str),
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use prost::Message;
244
245 #[test]
246 fn test_payload_encode_decode() {
247 let mut payload = Gistit::default();
248 payload.description = Some("foo".to_owned());
249 payload.author = "Matthew McCaunaghey".to_owned();
250
251 let bytes = payload.encode_to_vec();
252 assert_eq!(Gistit::decode(&*bytes).unwrap(), payload);
253 }
254
255 #[test]
256 fn test_ipc_encode_decode() {
257 let instruction = Instruction::request_shutdown();
258 let bytes = instruction.encode_to_vec();
259 assert_eq!(Instruction::decode(&*bytes).unwrap(), instruction);
260 }
261
262 #[test]
263 fn test_ipc_unwrap_methods() {
264 let req1 = Instruction::request_shutdown().expect_request().unwrap();
265 let req2 = Instruction::request_provide(Gistit::default())
266 .expect_request()
267 .unwrap();
268 let req3 = Instruction::request_status().expect_request().unwrap();
269 let req4 = Instruction::request_fetch(String::new())
270 .expect_request()
271 .unwrap();
272
273 let res1 = Instruction::respond_fetch(Some(Gistit::default()))
274 .expect_response()
275 .unwrap();
276 let res2 = Instruction::respond_provide(None)
277 .expect_response()
278 .unwrap();
279 let res3 = Instruction::respond_status(String::new(), 0, 0, 0)
280 .expect_response()
281 .unwrap();
282
283 assert!(true);
284 }
285}