gistit_proto/
lib.rs

1//
2//   ________.__          __  .__  __
3//  /  _____/|__| _______/  |_|__|/  |_
4// /   \  ___|  |/  ___/\   __\  \   __\
5// \    \_\  \  |\___ \  |  | |  ||  |
6//  \______  /__/____  > |__| |__||__|
7//         \/        \/
8//
9#![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        /// Decodes a buffer into [`Self`]
79        ///
80        /// # Errors
81        ///
82        /// Fails if buffer doesn't contain protobuf encoded data
83        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        /// Unwraps [`Self`] expecting a request kind
181        ///
182        /// # Errors
183        ///
184        /// Fails if instruction is not a request or is none
185        #[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        /// Unwraps [`Self`] expecting a response kind
204        ///
205        /// # Errors
206        ///
207        /// Fails if instruction is not a response or is none
208        #[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}