mbus_api/client/
mod.rs

1use async_trait::async_trait;
2use futures::{Stream, future, future::BoxFuture, stream, future::TryFutureExt, future::FutureExt, stream::StreamExt};
3use hyper::header::{HeaderName, HeaderValue, CONTENT_TYPE};
4use hyper::{Body, Request, Response, service::Service, Uri};
5use percent_encoding::{utf8_percent_encode, AsciiSet};
6use std::borrow::Cow;
7use std::convert::TryInto;
8use std::io::{ErrorKind, Read};
9use std::error::Error;
10use std::future::Future;
11use std::fmt;
12use std::marker::PhantomData;
13use std::path::Path;
14use std::sync::{Arc, Mutex};
15use std::str;
16use std::str::FromStr;
17use std::string::ToString;
18use std::task::{Context, Poll};
19use swagger::{ApiError, AuthData, BodyExt, Connector, DropContextService, Has, XSpanIdString};
20use url::form_urlencoded;
21
22
23use crate::models;
24use crate::header;
25
26/// https://url.spec.whatwg.org/#fragment-percent-encode-set
27#[allow(dead_code)]
28const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
29    .add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
30
31/// This encode set is used for object IDs
32///
33/// Aside from the special characters defined in the `PATH_SEGMENT_ENCODE_SET`,
34/// the vertical bar (|) is encoded.
35#[allow(dead_code)]
36const ID_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'|');
37
38use crate::{Api,
39     GetResponse,
40     GetMultiResponse,
41     HatResponse,
42     HatOffResponse,
43     HatOnResponse,
44     MbusApiResponse,
45     ScanResponse
46     };
47
48/// Convert input into a base path, e.g. "http://example:123". Also checks the scheme as it goes.
49fn into_base_path(input: impl TryInto<Uri, Error=hyper::http::uri::InvalidUri>, correct_scheme: Option<&'static str>) -> Result<String, ClientInitError> {
50    // First convert to Uri, since a base path is a subset of Uri.
51    let uri = input.try_into()?;
52
53    let scheme = uri.scheme_str().ok_or(ClientInitError::InvalidScheme)?;
54
55    // Check the scheme if necessary
56    if let Some(correct_scheme) = correct_scheme {
57        if scheme != correct_scheme {
58            return Err(ClientInitError::InvalidScheme);
59        }
60    }
61
62    let host = uri.host().ok_or_else(|| ClientInitError::MissingHost)?;
63    let port = uri.port_u16().map(|x| format!(":{}", x)).unwrap_or_default();
64    Ok(format!("{}://{}{}{}", scheme, host, port, uri.path().trim_end_matches('/')))
65}
66
67/// A client that implements the API by making HTTP calls out to a server.
68pub struct Client<S, C> where
69    S: Service<
70           (Request<Body>, C),
71           Response=Response<Body>> + Clone + Sync + Send + 'static,
72    S::Future: Send + 'static,
73    S::Error: Into<crate::ServiceError> + fmt::Display,
74    C: Clone + Send + Sync + 'static
75{
76    /// Inner service
77    client_service: S,
78
79    /// Base path of the API
80    base_path: String,
81
82    /// Marker
83    marker: PhantomData<fn(C)>,
84}
85
86impl<S, C> fmt::Debug for Client<S, C> where
87    S: Service<
88           (Request<Body>, C),
89           Response=Response<Body>> + Clone + Sync + Send + 'static,
90    S::Future: Send + 'static,
91    S::Error: Into<crate::ServiceError> + fmt::Display,
92    C: Clone + Send + Sync + 'static
93{
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(f, "Client {{ base_path: {} }}", self.base_path)
96    }
97}
98
99impl<S, C> Clone for Client<S, C> where
100    S: Service<
101           (Request<Body>, C),
102           Response=Response<Body>> + Clone + Sync + Send + 'static,
103    S::Future: Send + 'static,
104    S::Error: Into<crate::ServiceError> + fmt::Display,
105    C: Clone + Send + Sync + 'static
106{
107    fn clone(&self) -> Self {
108        Self {
109            client_service: self.client_service.clone(),
110            base_path: self.base_path.clone(),
111            marker: PhantomData,
112        }
113    }
114}
115
116impl<Connector, C> Client<DropContextService<hyper::client::Client<Connector, Body>, C>, C> where
117    Connector: hyper::client::connect::Connect + Clone + Send + Sync + 'static,
118    C: Clone + Send + Sync + 'static,
119{
120    /// Create a client with a custom implementation of hyper::client::Connect.
121    ///
122    /// Intended for use with custom implementations of connect for e.g. protocol logging
123    /// or similar functionality which requires wrapping the transport layer. When wrapping a TCP connection,
124    /// this function should be used in conjunction with `swagger::Connector::builder()`.
125    ///
126    /// For ordinary tcp connections, prefer the use of `try_new_http`, `try_new_https`
127    /// and `try_new_https_mutual`, to avoid introducing a dependency on the underlying transport layer.
128    ///
129    /// # Arguments
130    ///
131    /// * `base_path` - base path of the client API, i.e. "http://www.my-api-implementation.com"
132    /// * `protocol` - Which protocol to use when constructing the request url, e.g. `Some("http")`
133    /// * `connector` - Implementation of `hyper::client::Connect` to use for the client
134    pub fn try_new_with_connector(
135        base_path: &str,
136        protocol: Option<&'static str>,
137        connector: Connector,
138    ) -> Result<Self, ClientInitError>
139    {
140        let client_service = hyper::client::Client::builder().build(connector);
141        let client_service = DropContextService::new(client_service);
142
143        Ok(Self {
144            client_service,
145            base_path: into_base_path(base_path, protocol)?,
146            marker: PhantomData,
147        })
148    }
149}
150
151#[derive(Debug, Clone)]
152pub enum HyperClient {
153    Http(hyper::client::Client<hyper::client::HttpConnector, Body>),
154    Https(hyper::client::Client<HttpsConnector, Body>),
155}
156
157impl Service<Request<Body>> for HyperClient {
158    type Response = Response<Body>;
159    type Error = hyper::Error;
160    type Future = hyper::client::ResponseFuture;
161
162    fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
163       match self {
164          HyperClient::Http(client) => client.poll_ready(cx),
165          HyperClient::Https(client) => client.poll_ready(cx),
166       }
167    }
168
169    fn call(&mut self, req: Request<Body>) -> Self::Future {
170       match self {
171          HyperClient::Http(client) => client.call(req),
172          HyperClient::Https(client) => client.call(req)
173       }
174    }
175}
176
177impl<C> Client<DropContextService<HyperClient, C>, C> where
178    C: Clone + Send + Sync + 'static,
179{
180    /// Create an HTTP client.
181    ///
182    /// # Arguments
183    /// * `base_path` - base path of the client API, i.e. "http://www.my-api-implementation.com"
184    pub fn try_new(
185        base_path: &str,
186    ) -> Result<Self, ClientInitError> {
187        let uri = Uri::from_str(base_path)?;
188
189        let scheme = uri.scheme_str().ok_or(ClientInitError::InvalidScheme)?;
190        let scheme = scheme.to_ascii_lowercase();
191
192        let connector = Connector::builder();
193
194        let client_service = match scheme.as_str() {
195            "http" => {
196                HyperClient::Http(hyper::client::Client::builder().build(connector.build()))
197            },
198            "https" => {
199                let connector = connector.https()
200                   .build()
201                   .map_err(|e| ClientInitError::SslError(e))?;
202                HyperClient::Https(hyper::client::Client::builder().build(connector))
203            },
204            _ => {
205                return Err(ClientInitError::InvalidScheme);
206            }
207        };
208
209        let client_service = DropContextService::new(client_service);
210
211        Ok(Self {
212            client_service,
213            base_path: into_base_path(base_path, None)?,
214            marker: PhantomData,
215        })
216    }
217}
218
219impl<C> Client<DropContextService<hyper::client::Client<hyper::client::HttpConnector, Body>, C>, C> where
220    C: Clone + Send + Sync + 'static
221{
222    /// Create an HTTP client.
223    ///
224    /// # Arguments
225    /// * `base_path` - base path of the client API, i.e. "http://www.my-api-implementation.com"
226    pub fn try_new_http(
227        base_path: &str,
228    ) -> Result<Self, ClientInitError> {
229        let http_connector = Connector::builder().build();
230
231        Self::try_new_with_connector(base_path, Some("http"), http_connector)
232    }
233}
234
235#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
236type HttpsConnector = hyper_tls::HttpsConnector<hyper::client::HttpConnector>;
237
238#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
239type HttpsConnector = hyper_openssl::HttpsConnector<hyper::client::HttpConnector>;
240
241impl<C> Client<DropContextService<hyper::client::Client<HttpsConnector, Body>, C>, C> where
242    C: Clone + Send + Sync + 'static
243{
244    /// Create a client with a TLS connection to the server
245    ///
246    /// # Arguments
247    /// * `base_path` - base path of the client API, i.e. "https://www.my-api-implementation.com"
248    pub fn try_new_https(base_path: &str) -> Result<Self, ClientInitError>
249    {
250        let https_connector = Connector::builder()
251            .https()
252            .build()
253            .map_err(|e| ClientInitError::SslError(e))?;
254        Self::try_new_with_connector(base_path, Some("https"), https_connector)
255    }
256
257    /// Create a client with a TLS connection to the server using a pinned certificate
258    ///
259    /// # Arguments
260    /// * `base_path` - base path of the client API, i.e. "https://www.my-api-implementation.com"
261    /// * `ca_certificate` - Path to CA certificate used to authenticate the server
262    #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
263    pub fn try_new_https_pinned<CA>(
264        base_path: &str,
265        ca_certificate: CA,
266    ) -> Result<Self, ClientInitError>
267    where
268        CA: AsRef<Path>,
269    {
270        let https_connector = Connector::builder()
271            .https()
272            .pin_server_certificate(ca_certificate)
273            .build()
274            .map_err(|e| ClientInitError::SslError(e))?;
275        Self::try_new_with_connector(base_path, Some("https"), https_connector)
276    }
277
278    /// Create a client with a mutually authenticated TLS connection to the server.
279    ///
280    /// # Arguments
281    /// * `base_path` - base path of the client API, i.e. "https://www.my-api-implementation.com"
282    /// * `ca_certificate` - Path to CA certificate used to authenticate the server
283    /// * `client_key` - Path to the client private key
284    /// * `client_certificate` - Path to the client's public certificate associated with the private key
285    #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
286    pub fn try_new_https_mutual<CA, K, D>(
287        base_path: &str,
288        ca_certificate: CA,
289        client_key: K,
290        client_certificate: D,
291    ) -> Result<Self, ClientInitError>
292    where
293        CA: AsRef<Path>,
294        K: AsRef<Path>,
295        D: AsRef<Path>,
296    {
297        let https_connector = Connector::builder()
298            .https()
299            .pin_server_certificate(ca_certificate)
300            .client_authentication(client_key, client_certificate)
301            .build()
302            .map_err(|e| ClientInitError::SslError(e))?;
303        Self::try_new_with_connector(base_path, Some("https"), https_connector)
304    }
305}
306
307impl<S, C> Client<S, C> where
308    S: Service<
309           (Request<Body>, C),
310           Response=Response<Body>> + Clone + Sync + Send + 'static,
311    S::Future: Send + 'static,
312    S::Error: Into<crate::ServiceError> + fmt::Display,
313    C: Clone + Send + Sync + 'static
314{
315    /// Constructor for creating a `Client` by passing in a pre-made `hyper::service::Service` /
316    /// `tower::Service`
317    ///
318    /// This allows adding custom wrappers around the underlying transport, for example for logging.
319    pub fn try_new_with_client_service(
320        client_service: S,
321        base_path: &str,
322    ) -> Result<Self, ClientInitError>
323    {
324        Ok(Self {
325            client_service,
326            base_path: into_base_path(base_path, None)?,
327            marker: PhantomData,
328        })
329    }
330}
331
332/// Error type failing to create a Client
333#[derive(Debug)]
334pub enum ClientInitError {
335    /// Invalid URL Scheme
336    InvalidScheme,
337
338    /// Invalid URI
339    InvalidUri(hyper::http::uri::InvalidUri),
340
341    /// Missing Hostname
342    MissingHost,
343
344    /// SSL Connection Error
345    #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
346    SslError(native_tls::Error),
347
348    /// SSL Connection Error
349    #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
350    SslError(openssl::error::ErrorStack),
351}
352
353impl From<hyper::http::uri::InvalidUri> for ClientInitError {
354    fn from(err: hyper::http::uri::InvalidUri) -> ClientInitError {
355        ClientInitError::InvalidUri(err)
356    }
357}
358
359impl fmt::Display for ClientInitError {
360    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361        let s: &dyn fmt::Debug = self;
362        s.fmt(f)
363    }
364}
365
366impl Error for ClientInitError {
367    fn description(&self) -> &str {
368        "Failed to produce a hyper client."
369    }
370}
371
372#[async_trait]
373impl<S, C> Api<C> for Client<S, C> where
374    S: Service<
375       (Request<Body>, C),
376       Response=Response<Body>> + Clone + Sync + Send + 'static,
377    S::Future: Send + 'static,
378    S::Error: Into<crate::ServiceError> + fmt::Display,
379    C: Has<XSpanIdString>  + Clone + Send + Sync + 'static,
380{
381    fn poll_ready(&self, cx: &mut Context) -> Poll<Result<(), crate::ServiceError>> {
382        match self.client_service.clone().poll_ready(cx) {
383            Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())),
384            Poll::Ready(Ok(o)) => Poll::Ready(Ok(o)),
385            Poll::Pending => Poll::Pending,
386        }
387    }
388
389    async fn get(
390        &self,
391        param_device: String,
392        param_baudrate: models::Baudrate,
393        param_address: String,
394        context: &C) -> Result<GetResponse, ApiError>
395    {
396        let mut client_service = self.client_service.clone();
397        let mut uri = format!(
398            "{}/mbus/get/{device}/{baudrate}/{address}",
399            self.base_path
400            ,device=utf8_percent_encode(&param_device.to_string(), ID_ENCODE_SET)
401            ,baudrate=utf8_percent_encode(&param_baudrate.to_string(), ID_ENCODE_SET)
402            ,address=utf8_percent_encode(&param_address.to_string(), ID_ENCODE_SET)
403        );
404
405        // Query parameters
406        let query_string = {
407            let mut query_string = form_urlencoded::Serializer::new("".to_owned());
408            query_string.finish()
409        };
410        if !query_string.is_empty() {
411            uri += "?";
412            uri += &query_string;
413        }
414
415        let uri = match Uri::from_str(&uri) {
416            Ok(uri) => uri,
417            Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))),
418        };
419
420        let mut request = match Request::builder()
421            .method("POST")
422            .uri(uri)
423            .body(Body::empty()) {
424                Ok(req) => req,
425                Err(e) => return Err(ApiError(format!("Unable to create request: {}", e)))
426        };
427
428        let header = HeaderValue::from_str(Has::<XSpanIdString>::get(context).0.clone().to_string().as_str());
429        request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header {
430            Ok(h) => h,
431            Err(e) => return Err(ApiError(format!("Unable to create X-Span ID header value: {}", e)))
432        });
433
434        let mut response = client_service.call((request, context.clone()))
435            .map_err(|e| ApiError(format!("No response received: {}", e))).await?;
436
437        match response.status().as_u16() {
438            200 => {
439                let body = response.into_body();
440                let body = body
441                        .to_raw()
442                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
443                let body = str::from_utf8(&body)
444                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
445                // ToDo: this will move to swagger-rs and become a standard From conversion trait
446                // once https://github.com/RReverser/serde-xml-rs/pull/45 is accepted upstream
447                let body = serde_xml_rs::from_str::<String>(body)
448                    .map_err(|e| ApiError(format!("Response body did not match the schema: {}", e)))?;
449                Ok(GetResponse::OK
450                    (body)
451                )
452            }
453            400 => {
454                let body = response.into_body();
455                let body = body
456                        .to_raw()
457                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
458                let body = str::from_utf8(&body)
459                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
460                let body = body.to_string();
461                Ok(GetResponse::BadRequest
462                    (body)
463                )
464            }
465            404 => {
466                let body = response.into_body();
467                let body = body
468                        .to_raw()
469                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
470                let body = str::from_utf8(&body)
471                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
472                let body = body.to_string();
473                Ok(GetResponse::NotFound
474                    (body)
475                )
476            }
477            code => {
478                let headers = response.headers().clone();
479                let body = response.into_body()
480                       .take(100)
481                       .to_raw().await;
482                Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
483                    code,
484                    headers,
485                    match body {
486                        Ok(body) => match String::from_utf8(body) {
487                            Ok(body) => body,
488                            Err(e) => format!("<Body was not UTF8: {:?}>", e),
489                        },
490                        Err(e) => format!("<Failed to read body: {}>", e),
491                    }
492                )))
493            }
494        }
495    }
496
497    async fn get_multi(
498        &self,
499        param_device: String,
500        param_baudrate: models::Baudrate,
501        param_address: String,
502        param_maxframes: i32,
503        context: &C) -> Result<GetMultiResponse, ApiError>
504    {
505        let mut client_service = self.client_service.clone();
506        let mut uri = format!(
507            "{}/mbus/getMulti/{device}/{baudrate}/{address}/{maxframes}",
508            self.base_path
509            ,device=utf8_percent_encode(&param_device.to_string(), ID_ENCODE_SET)
510            ,baudrate=utf8_percent_encode(&param_baudrate.to_string(), ID_ENCODE_SET)
511            ,address=utf8_percent_encode(&param_address.to_string(), ID_ENCODE_SET)
512            ,maxframes=utf8_percent_encode(&param_maxframes.to_string(), ID_ENCODE_SET)
513        );
514
515        // Query parameters
516        let query_string = {
517            let mut query_string = form_urlencoded::Serializer::new("".to_owned());
518            query_string.finish()
519        };
520        if !query_string.is_empty() {
521            uri += "?";
522            uri += &query_string;
523        }
524
525        let uri = match Uri::from_str(&uri) {
526            Ok(uri) => uri,
527            Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))),
528        };
529
530        let mut request = match Request::builder()
531            .method("POST")
532            .uri(uri)
533            .body(Body::empty()) {
534                Ok(req) => req,
535                Err(e) => return Err(ApiError(format!("Unable to create request: {}", e)))
536        };
537
538        let header = HeaderValue::from_str(Has::<XSpanIdString>::get(context).0.clone().to_string().as_str());
539        request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header {
540            Ok(h) => h,
541            Err(e) => return Err(ApiError(format!("Unable to create X-Span ID header value: {}", e)))
542        });
543
544        let mut response = client_service.call((request, context.clone()))
545            .map_err(|e| ApiError(format!("No response received: {}", e))).await?;
546
547        match response.status().as_u16() {
548            200 => {
549                let body = response.into_body();
550                let body = body
551                        .to_raw()
552                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
553                let body = str::from_utf8(&body)
554                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
555                // ToDo: this will move to swagger-rs and become a standard From conversion trait
556                // once https://github.com/RReverser/serde-xml-rs/pull/45 is accepted upstream
557                let body = serde_xml_rs::from_str::<String>(body)
558                    .map_err(|e| ApiError(format!("Response body did not match the schema: {}", e)))?;
559                Ok(GetMultiResponse::OK
560                    (body)
561                )
562            }
563            400 => {
564                let body = response.into_body();
565                let body = body
566                        .to_raw()
567                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
568                let body = str::from_utf8(&body)
569                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
570                let body = body.to_string();
571                Ok(GetMultiResponse::BadRequest
572                    (body)
573                )
574            }
575            404 => {
576                let body = response.into_body();
577                let body = body
578                        .to_raw()
579                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
580                let body = str::from_utf8(&body)
581                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
582                let body = body.to_string();
583                Ok(GetMultiResponse::NotFound
584                    (body)
585                )
586            }
587            code => {
588                let headers = response.headers().clone();
589                let body = response.into_body()
590                       .take(100)
591                       .to_raw().await;
592                Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
593                    code,
594                    headers,
595                    match body {
596                        Ok(body) => match String::from_utf8(body) {
597                            Ok(body) => body,
598                            Err(e) => format!("<Body was not UTF8: {:?}>", e),
599                        },
600                        Err(e) => format!("<Failed to read body: {}>", e),
601                    }
602                )))
603            }
604        }
605    }
606
607    async fn hat(
608        &self,
609        context: &C) -> Result<HatResponse, ApiError>
610    {
611        let mut client_service = self.client_service.clone();
612        let mut uri = format!(
613            "{}/mbus/hat",
614            self.base_path
615        );
616
617        // Query parameters
618        let query_string = {
619            let mut query_string = form_urlencoded::Serializer::new("".to_owned());
620            query_string.finish()
621        };
622        if !query_string.is_empty() {
623            uri += "?";
624            uri += &query_string;
625        }
626
627        let uri = match Uri::from_str(&uri) {
628            Ok(uri) => uri,
629            Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))),
630        };
631
632        let mut request = match Request::builder()
633            .method("GET")
634            .uri(uri)
635            .body(Body::empty()) {
636                Ok(req) => req,
637                Err(e) => return Err(ApiError(format!("Unable to create request: {}", e)))
638        };
639
640        let header = HeaderValue::from_str(Has::<XSpanIdString>::get(context).0.clone().to_string().as_str());
641        request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header {
642            Ok(h) => h,
643            Err(e) => return Err(ApiError(format!("Unable to create X-Span ID header value: {}", e)))
644        });
645
646        let mut response = client_service.call((request, context.clone()))
647            .map_err(|e| ApiError(format!("No response received: {}", e))).await?;
648
649        match response.status().as_u16() {
650            200 => {
651                let body = response.into_body();
652                let body = body
653                        .to_raw()
654                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
655                let body = str::from_utf8(&body)
656                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
657                let body = serde_json::from_str::<models::Hat>(body)?;
658                Ok(HatResponse::OK
659                    (body)
660                )
661            }
662            404 => {
663                let body = response.into_body();
664                let body = body
665                        .to_raw()
666                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
667                let body = str::from_utf8(&body)
668                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
669                let body = body.to_string();
670                Ok(HatResponse::NotFound
671                    (body)
672                )
673            }
674            code => {
675                let headers = response.headers().clone();
676                let body = response.into_body()
677                       .take(100)
678                       .to_raw().await;
679                Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
680                    code,
681                    headers,
682                    match body {
683                        Ok(body) => match String::from_utf8(body) {
684                            Ok(body) => body,
685                            Err(e) => format!("<Body was not UTF8: {:?}>", e),
686                        },
687                        Err(e) => format!("<Failed to read body: {}>", e),
688                    }
689                )))
690            }
691        }
692    }
693
694    async fn hat_off(
695        &self,
696        context: &C) -> Result<HatOffResponse, ApiError>
697    {
698        let mut client_service = self.client_service.clone();
699        let mut uri = format!(
700            "{}/mbus/hat/off",
701            self.base_path
702        );
703
704        // Query parameters
705        let query_string = {
706            let mut query_string = form_urlencoded::Serializer::new("".to_owned());
707            query_string.finish()
708        };
709        if !query_string.is_empty() {
710            uri += "?";
711            uri += &query_string;
712        }
713
714        let uri = match Uri::from_str(&uri) {
715            Ok(uri) => uri,
716            Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))),
717        };
718
719        let mut request = match Request::builder()
720            .method("POST")
721            .uri(uri)
722            .body(Body::empty()) {
723                Ok(req) => req,
724                Err(e) => return Err(ApiError(format!("Unable to create request: {}", e)))
725        };
726
727        let header = HeaderValue::from_str(Has::<XSpanIdString>::get(context).0.clone().to_string().as_str());
728        request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header {
729            Ok(h) => h,
730            Err(e) => return Err(ApiError(format!("Unable to create X-Span ID header value: {}", e)))
731        });
732
733        let mut response = client_service.call((request, context.clone()))
734            .map_err(|e| ApiError(format!("No response received: {}", e))).await?;
735
736        match response.status().as_u16() {
737            200 => {
738                let body = response.into_body();
739                Ok(
740                    HatOffResponse::OK
741                )
742            }
743            404 => {
744                let body = response.into_body();
745                let body = body
746                        .to_raw()
747                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
748                let body = str::from_utf8(&body)
749                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
750                let body = body.to_string();
751                Ok(HatOffResponse::NotFound
752                    (body)
753                )
754            }
755            code => {
756                let headers = response.headers().clone();
757                let body = response.into_body()
758                       .take(100)
759                       .to_raw().await;
760                Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
761                    code,
762                    headers,
763                    match body {
764                        Ok(body) => match String::from_utf8(body) {
765                            Ok(body) => body,
766                            Err(e) => format!("<Body was not UTF8: {:?}>", e),
767                        },
768                        Err(e) => format!("<Failed to read body: {}>", e),
769                    }
770                )))
771            }
772        }
773    }
774
775    async fn hat_on(
776        &self,
777        context: &C) -> Result<HatOnResponse, ApiError>
778    {
779        let mut client_service = self.client_service.clone();
780        let mut uri = format!(
781            "{}/mbus/hat/on",
782            self.base_path
783        );
784
785        // Query parameters
786        let query_string = {
787            let mut query_string = form_urlencoded::Serializer::new("".to_owned());
788            query_string.finish()
789        };
790        if !query_string.is_empty() {
791            uri += "?";
792            uri += &query_string;
793        }
794
795        let uri = match Uri::from_str(&uri) {
796            Ok(uri) => uri,
797            Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))),
798        };
799
800        let mut request = match Request::builder()
801            .method("POST")
802            .uri(uri)
803            .body(Body::empty()) {
804                Ok(req) => req,
805                Err(e) => return Err(ApiError(format!("Unable to create request: {}", e)))
806        };
807
808        let header = HeaderValue::from_str(Has::<XSpanIdString>::get(context).0.clone().to_string().as_str());
809        request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header {
810            Ok(h) => h,
811            Err(e) => return Err(ApiError(format!("Unable to create X-Span ID header value: {}", e)))
812        });
813
814        let mut response = client_service.call((request, context.clone()))
815            .map_err(|e| ApiError(format!("No response received: {}", e))).await?;
816
817        match response.status().as_u16() {
818            200 => {
819                let body = response.into_body();
820                Ok(
821                    HatOnResponse::OK
822                )
823            }
824            404 => {
825                let body = response.into_body();
826                let body = body
827                        .to_raw()
828                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
829                let body = str::from_utf8(&body)
830                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
831                let body = body.to_string();
832                Ok(HatOnResponse::NotFound
833                    (body)
834                )
835            }
836            code => {
837                let headers = response.headers().clone();
838                let body = response.into_body()
839                       .take(100)
840                       .to_raw().await;
841                Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
842                    code,
843                    headers,
844                    match body {
845                        Ok(body) => match String::from_utf8(body) {
846                            Ok(body) => body,
847                            Err(e) => format!("<Body was not UTF8: {:?}>", e),
848                        },
849                        Err(e) => format!("<Failed to read body: {}>", e),
850                    }
851                )))
852            }
853        }
854    }
855
856    async fn mbus_api(
857        &self,
858        context: &C) -> Result<MbusApiResponse, ApiError>
859    {
860        let mut client_service = self.client_service.clone();
861        let mut uri = format!(
862            "{}/mbus/api",
863            self.base_path
864        );
865
866        // Query parameters
867        let query_string = {
868            let mut query_string = form_urlencoded::Serializer::new("".to_owned());
869            query_string.finish()
870        };
871        if !query_string.is_empty() {
872            uri += "?";
873            uri += &query_string;
874        }
875
876        let uri = match Uri::from_str(&uri) {
877            Ok(uri) => uri,
878            Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))),
879        };
880
881        let mut request = match Request::builder()
882            .method("GET")
883            .uri(uri)
884            .body(Body::empty()) {
885                Ok(req) => req,
886                Err(e) => return Err(ApiError(format!("Unable to create request: {}", e)))
887        };
888
889        let header = HeaderValue::from_str(Has::<XSpanIdString>::get(context).0.clone().to_string().as_str());
890        request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header {
891            Ok(h) => h,
892            Err(e) => return Err(ApiError(format!("Unable to create X-Span ID header value: {}", e)))
893        });
894
895        let mut response = client_service.call((request, context.clone()))
896            .map_err(|e| ApiError(format!("No response received: {}", e))).await?;
897
898        match response.status().as_u16() {
899            200 => {
900                let body = response.into_body();
901                let body = body
902                        .to_raw()
903                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
904                let body = str::from_utf8(&body)
905                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
906                let body = body.to_string();
907                Ok(MbusApiResponse::OK
908                    (body)
909                )
910            }
911            404 => {
912                let body = response.into_body();
913                let body = body
914                        .to_raw()
915                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
916                let body = str::from_utf8(&body)
917                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
918                let body = body.to_string();
919                Ok(MbusApiResponse::NotFound
920                    (body)
921                )
922            }
923            code => {
924                let headers = response.headers().clone();
925                let body = response.into_body()
926                       .take(100)
927                       .to_raw().await;
928                Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
929                    code,
930                    headers,
931                    match body {
932                        Ok(body) => match String::from_utf8(body) {
933                            Ok(body) => body,
934                            Err(e) => format!("<Body was not UTF8: {:?}>", e),
935                        },
936                        Err(e) => format!("<Failed to read body: {}>", e),
937                    }
938                )))
939            }
940        }
941    }
942
943    async fn scan(
944        &self,
945        param_device: String,
946        param_baudrate: models::Baudrate,
947        context: &C) -> Result<ScanResponse, ApiError>
948    {
949        let mut client_service = self.client_service.clone();
950        let mut uri = format!(
951            "{}/mbus/scan/{device}/{baudrate}",
952            self.base_path
953            ,device=utf8_percent_encode(&param_device.to_string(), ID_ENCODE_SET)
954            ,baudrate=utf8_percent_encode(&param_baudrate.to_string(), ID_ENCODE_SET)
955        );
956
957        // Query parameters
958        let query_string = {
959            let mut query_string = form_urlencoded::Serializer::new("".to_owned());
960            query_string.finish()
961        };
962        if !query_string.is_empty() {
963            uri += "?";
964            uri += &query_string;
965        }
966
967        let uri = match Uri::from_str(&uri) {
968            Ok(uri) => uri,
969            Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))),
970        };
971
972        let mut request = match Request::builder()
973            .method("POST")
974            .uri(uri)
975            .body(Body::empty()) {
976                Ok(req) => req,
977                Err(e) => return Err(ApiError(format!("Unable to create request: {}", e)))
978        };
979
980        let header = HeaderValue::from_str(Has::<XSpanIdString>::get(context).0.clone().to_string().as_str());
981        request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header {
982            Ok(h) => h,
983            Err(e) => return Err(ApiError(format!("Unable to create X-Span ID header value: {}", e)))
984        });
985
986        let mut response = client_service.call((request, context.clone()))
987            .map_err(|e| ApiError(format!("No response received: {}", e))).await?;
988
989        match response.status().as_u16() {
990            200 => {
991                let body = response.into_body();
992                let body = body
993                        .to_raw()
994                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
995                let body = str::from_utf8(&body)
996                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
997                let body = body.to_string();
998                Ok(ScanResponse::OK
999                    (body)
1000                )
1001            }
1002            400 => {
1003                let body = response.into_body();
1004                let body = body
1005                        .to_raw()
1006                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
1007                let body = str::from_utf8(&body)
1008                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
1009                let body = body.to_string();
1010                Ok(ScanResponse::BadRequest
1011                    (body)
1012                )
1013            }
1014            404 => {
1015                let body = response.into_body();
1016                let body = body
1017                        .to_raw()
1018                        .map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
1019                let body = str::from_utf8(&body)
1020                    .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
1021                let body = body.to_string();
1022                Ok(ScanResponse::NotFound
1023                    (body)
1024                )
1025            }
1026            code => {
1027                let headers = response.headers().clone();
1028                let body = response.into_body()
1029                       .take(100)
1030                       .to_raw().await;
1031                Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
1032                    code,
1033                    headers,
1034                    match body {
1035                        Ok(body) => match String::from_utf8(body) {
1036                            Ok(body) => body,
1037                            Err(e) => format!("<Body was not UTF8: {:?}>", e),
1038                        },
1039                        Err(e) => format!("<Failed to read body: {}>", e),
1040                    }
1041                )))
1042            }
1043        }
1044    }
1045
1046}