libhydrz/
lib.rs

1/* libhydrz, an implementation of a hydrozoa client client-server client library client library in Rust
2Copyright 2023  Eric S. Londres
3
4This program is free software: you can redistribute it and/or modify
5it under the terms of the GNU Affero General Public License as published by
6the Free Software Foundation, version 3.0.
7
8This program is distributed in the hope that it will be useful,
9but WITHOUT ANY WARRANTY; without even the implied warranty of
10MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11GNU Affero General Public License for more details.
12
13You should have received a copy of the GNU Affero General Public License
14along with this program.  If not, see <https://www.gnu.org/licenses/>.
15 */
16
17
18use std::{error::Error, net::TcpStream, io::{Write, Read}};
19
20// TODO: add documentation
21
22#[derive(Default)]
23pub struct Document {
24		pub status: u16,
25		pub body: String
26}
27
28impl From<Vec<u8>> for Document {
29		// convert a byte vector containing a hydrozoa-spec-compliant "document" CBOR object into a Document
30		fn from(value: Vec<u8>) -> Self {
31				let mut decoder = minicbor::Decoder::new(&value);
32				// open up the top-level map
33				match decoder.map() {
34						Ok(ok) => ok,
35						Err(_) => return Self::default()
36				};
37				// We don't know what order the map is in, so first we allocate for each type and then cycle through
38				let mut status = 0u16;
39				let mut body = String::new();
40				
41				for _i in 0..2 {
42						let next = decoder.bytes().unwrap();
43						if next == "body".as_bytes() {
44								match decoder.bytes() {
45										Err(_) => return Self::default(),
46										Ok(bytes) => {
47												match std::str::from_utf8(bytes) {
48														Err(_) => return Self::default(),
49														Ok(utf) => {
50																body = utf.to_string();
51														}
52												}
53										}
54								}
55						} else if next == "status".as_bytes() {
56								match decoder.u16() {
57										Err(_) => return Self { status: 0, body: String::from("") },
58										Ok(number) => status = number
59								}
60						}
61				}
62
63				Document { status, body }
64		}
65}
66
67
68pub fn uri_to_document_request(uri: String) -> Result<Vec<u8>, Box<dyn Error>> {
69		let mut buffer = [0u8; 4096]; // it's reasonable to expect URIs to be less than 4 KB
70		let  uri_str = uri.as_str();
71		let mut encoder = minicbor::Encoder::new(&mut buffer[..]);
72		encoder.array(2)?
73				.str("document_request")?
74				.map(1)?
75				.str("uri")?.str(uri_str)?
76				.end()?;
77
78		Ok(buffer.to_vec())
79}
80
81pub fn get_document_with_server(uri: String, server_authority: &str) -> Result<Document, Box<dyn Error>> {
82		let document_request = uri_to_document_request(uri)?;
83		let mut stream: TcpStream = TcpStream::connect(server_authority)?;
84		stream.write_all(&document_request)?;
85		let mut buffer: Vec<u8> = Vec::new();
86		stream.read_to_end(&mut buffer)?;
87		Ok(Document::from(buffer))
88}
89
90pub fn get_document(uri: String) -> Result<Document, Box<dyn Error>> {
91		get_document_with_server(uri, "127.0.0.1:25938")
92}
93
94// TODO: add tests