Add REST and RPC clients to lightning-block-sync
[rust-lightning] / lightning-block-sync / src / rest.rs
diff --git a/lightning-block-sync/src/rest.rs b/lightning-block-sync/src/rest.rs
new file mode 100644 (file)
index 0000000..3ccd5d4
--- /dev/null
@@ -0,0 +1,81 @@
+use crate::http::{HttpEndpoint, HttpClient};
+
+use std::convert::TryFrom;
+use std::convert::TryInto;
+
+/// A simple REST client for requesting resources using HTTP `GET`.
+pub struct RestClient {
+       endpoint: HttpEndpoint,
+       client: HttpClient,
+}
+
+impl RestClient {
+       /// Creates a new REST client connected to the given endpoint.
+       pub fn new(endpoint: HttpEndpoint) -> std::io::Result<Self> {
+               let client = HttpClient::connect(&endpoint)?;
+               Ok(Self { endpoint, client })
+       }
+
+       /// Requests a resource encoded in `F` format and interpreted as type `T`.
+       async fn request_resource<F, T>(&mut self, resource_path: &str) -> std::io::Result<T>
+       where F: TryFrom<Vec<u8>, Error = std::io::Error> + TryInto<T, Error = std::io::Error> {
+               let host = format!("{}:{}", self.endpoint.host(), self.endpoint.port());
+               let uri = format!("{}/{}", self.endpoint.path().trim_end_matches("/"), resource_path);
+               self.client.get::<F>(&uri, &host).await?.try_into()
+       }
+}
+
+#[cfg(test)]
+mod tests {
+       use super::*;
+       use crate::http::BinaryResponse;
+       use crate::http::client_tests::{HttpServer, MessageBody};
+
+       /// Parses binary data as a string-encoded `u32`.
+       impl TryInto<u32> for BinaryResponse {
+               type Error = std::io::Error;
+
+               fn try_into(self) -> std::io::Result<u32> {
+                       match std::str::from_utf8(&self.0) {
+                               Err(e) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e)),
+                               Ok(s) => match u32::from_str_radix(s, 10) {
+                                       Err(e) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e)),
+                                       Ok(n) => Ok(n),
+                               }
+                       }
+               }
+       }
+
+       #[tokio::test]
+       async fn request_unknown_resource() {
+               let server = HttpServer::responding_with_not_found();
+               let mut client = RestClient::new(server.endpoint()).unwrap();
+
+               match client.request_resource::<BinaryResponse, u32>("/").await {
+                       Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::NotFound),
+                       Ok(_) => panic!("Expected error"),
+               }
+       }
+
+       #[tokio::test]
+       async fn request_malformed_resource() {
+               let server = HttpServer::responding_with_ok(MessageBody::Content("foo"));
+               let mut client = RestClient::new(server.endpoint()).unwrap();
+
+               match client.request_resource::<BinaryResponse, u32>("/").await {
+                       Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::InvalidData),
+                       Ok(_) => panic!("Expected error"),
+               }
+       }
+
+       #[tokio::test]
+       async fn request_valid_resource() {
+               let server = HttpServer::responding_with_ok(MessageBody::Content(42));
+               let mut client = RestClient::new(server.endpoint()).unwrap();
+
+               match client.request_resource::<BinaryResponse, u32>("/").await {
+                       Err(e) => panic!("Unexpected error: {:?}", e),
+                       Ok(n) => assert_eq!(n, 42),
+               }
+       }
+}