Add REST and RPC clients to lightning-block-sync
[rust-lightning] / lightning-block-sync / src / rest.rs
1 use crate::http::{HttpEndpoint, HttpClient};
2
3 use std::convert::TryFrom;
4 use std::convert::TryInto;
5
6 /// A simple REST client for requesting resources using HTTP `GET`.
7 pub struct RestClient {
8         endpoint: HttpEndpoint,
9         client: HttpClient,
10 }
11
12 impl RestClient {
13         /// Creates a new REST client connected to the given endpoint.
14         pub fn new(endpoint: HttpEndpoint) -> std::io::Result<Self> {
15                 let client = HttpClient::connect(&endpoint)?;
16                 Ok(Self { endpoint, client })
17         }
18
19         /// Requests a resource encoded in `F` format and interpreted as type `T`.
20         async fn request_resource<F, T>(&mut self, resource_path: &str) -> std::io::Result<T>
21         where F: TryFrom<Vec<u8>, Error = std::io::Error> + TryInto<T, Error = std::io::Error> {
22                 let host = format!("{}:{}", self.endpoint.host(), self.endpoint.port());
23                 let uri = format!("{}/{}", self.endpoint.path().trim_end_matches("/"), resource_path);
24                 self.client.get::<F>(&uri, &host).await?.try_into()
25         }
26 }
27
28 #[cfg(test)]
29 mod tests {
30         use super::*;
31         use crate::http::BinaryResponse;
32         use crate::http::client_tests::{HttpServer, MessageBody};
33
34         /// Parses binary data as a string-encoded `u32`.
35         impl TryInto<u32> for BinaryResponse {
36                 type Error = std::io::Error;
37
38                 fn try_into(self) -> std::io::Result<u32> {
39                         match std::str::from_utf8(&self.0) {
40                                 Err(e) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e)),
41                                 Ok(s) => match u32::from_str_radix(s, 10) {
42                                         Err(e) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e)),
43                                         Ok(n) => Ok(n),
44                                 }
45                         }
46                 }
47         }
48
49         #[tokio::test]
50         async fn request_unknown_resource() {
51                 let server = HttpServer::responding_with_not_found();
52                 let mut client = RestClient::new(server.endpoint()).unwrap();
53
54                 match client.request_resource::<BinaryResponse, u32>("/").await {
55                         Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::NotFound),
56                         Ok(_) => panic!("Expected error"),
57                 }
58         }
59
60         #[tokio::test]
61         async fn request_malformed_resource() {
62                 let server = HttpServer::responding_with_ok(MessageBody::Content("foo"));
63                 let mut client = RestClient::new(server.endpoint()).unwrap();
64
65                 match client.request_resource::<BinaryResponse, u32>("/").await {
66                         Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::InvalidData),
67                         Ok(_) => panic!("Expected error"),
68                 }
69         }
70
71         #[tokio::test]
72         async fn request_valid_resource() {
73                 let server = HttpServer::responding_with_ok(MessageBody::Content(42));
74                 let mut client = RestClient::new(server.endpoint()).unwrap();
75
76                 match client.request_resource::<BinaryResponse, u32>("/").await {
77                         Err(e) => panic!("Unexpected error: {:?}", e),
78                         Ok(n) => assert_eq!(n, 42),
79                 }
80         }
81 }