podman-client 0.0.2

A native Rust client for the Podman REST API over Unix sockets
Documentation
use std::any::TypeId;

use http_body_util::BodyExt;
use hyper::{
    Request,
    body::Body,
    client::conn::http1::{self, SendRequest},
    header::HOST,
};
use hyper_util::rt::TokioIo;
use serde::de::DeserializeOwned;
use serde_json::{Map, Value};
use tokio::net::UnixStream;

use crate::{
    client::Client,
    models::{connection::SendRequestOptions, lib::Error, podman::error::Error as PodmanError},
};

impl Client {
    pub(crate) async fn build_connection<B>(&self) -> Result<SendRequest<B>, Error>
    where
        B: Body + Send + 'static,
        B::Data: Send,
        B::Error: Into<Error>,
    {
        let stream = UnixStream::connect(self.socket_path()).await?;
        let stream_compat = TokioIo::new(stream);

        let (sender, connection) = http1::handshake::<_, B>(stream_compat).await?;

        tokio::spawn(async move {
            if let Err(err) = connection.await {
                eprintln!("Connection error: {:?}", err);
            }
        });

        Ok(sender)
    }

    pub(crate) async fn send_request<RequestBody, ResponseHeader, ResponseBody>(
        &self,
        options: SendRequestOptions<'_, RequestBody>,
    ) -> Result<(ResponseHeader, ResponseBody), Error>
    where
        RequestBody: Body + Send + 'static,
        RequestBody::Data: Send,
        RequestBody::Error: Into<Error>,
        ResponseHeader: DeserializeOwned + 'static,
        ResponseBody: DeserializeOwned + 'static,
    {
        let mut sender = self.build_connection().await?;

        let uri = [self.podman_base_url, options.path].concat();
        let req = Request::builder()
            .method(options.method)
            .uri(uri)
            .header(HOST, "d")
            .body(options.body)?;

        let res = sender.send_request(req).await?;
        let (res_parts, body) = res.into_parts();
        let res_status = res_parts.status;
        let body_bytes = body.collect().await?.to_bytes();

        if !res_status.is_success() {
            let podman_err = serde_json::from_slice::<PodmanError>(&body_bytes);

            return match podman_err {
                Ok(e) => Err(Box::new(e)),
                Err(_) => Err(format!(
                    "Got error status: {}. Error: {}",
                    res_status,
                    String::from_utf8(body_bytes.to_vec())?,
                )
                .into()),
            };
        }

        let header = if TypeId::of::<ResponseHeader>() == TypeId::of::<()>() {
            serde_json::from_slice(b"null")?
        } else {
            let header: Map<String, Value> = res_parts
                .headers
                .iter()
                .filter_map(|(k, v)| {
                    Some((k.to_string(), Value::String(v.to_str().ok()?.to_string())))
                })
                .collect();
            serde_json::from_value(Value::Object(header))?
        };

        let data = if TypeId::of::<ResponseBody>() == TypeId::of::<()>() || body_bytes.is_empty() {
            serde_json::from_slice(b"null")?
        } else {
            serde_json::from_slice(&body_bytes)?
        };

        Ok((header, data))
    }
}