cloud-hypervisor-client 0.4.0+api-spec-0.3.0-2026-05-04

Unofficial Rust crate for accessing the cloud-hypervisor REST API.
Documentation
use std::collections::HashMap;
use std::pin::Pin;

use futures;
use futures::Future;
use futures::future::*;
use http_body_util::BodyExt;
use hyper;
use hyper::header::{CONTENT_LENGTH, CONTENT_TYPE, HeaderValue, USER_AGENT};
use hyper_util::client::legacy::connect::Connect;
use serde;
use serde_json;

use super::{Error, configuration};

pub(crate) struct Request {
    method: hyper::Method,
    path: String,
    query_params: HashMap<String, String>,
    no_return_type: bool,
    path_params: HashMap<String, String>,
    form_params: HashMap<String, String>,
    header_params: HashMap<String, String>,
    // TODO: multiple body params are possible technically, but not supported here.
    serialized_body: Option<String>,
}

#[allow(dead_code)]
impl Request {
    pub fn new(method: hyper::Method, path: String) -> Self {
        Request {
            method,
            path,
            query_params: HashMap::new(),
            path_params: HashMap::new(),
            form_params: HashMap::new(),
            header_params: HashMap::new(),
            serialized_body: None,
            no_return_type: false,
        }
    }

    pub fn with_body_param<T: serde::Serialize>(mut self, param: T) -> Self {
        self.serialized_body = Some(serde_json::to_string(&param).unwrap());
        self
    }

    pub fn with_header_param(mut self, basename: String, param: String) -> Self {
        self.header_params.insert(basename, param);
        self
    }

    #[allow(unused)]
    pub fn with_query_param(mut self, basename: String, param: String) -> Self {
        self.query_params.insert(basename, param);
        self
    }

    #[allow(unused)]
    pub fn with_path_param(mut self, basename: String, param: String) -> Self {
        self.path_params.insert(basename, param);
        self
    }

    #[allow(unused)]
    pub fn with_form_param(mut self, basename: String, param: String) -> Self {
        self.form_params.insert(basename, param);
        self
    }

    pub fn returns_nothing(mut self) -> Self {
        self.no_return_type = true;
        self
    }

    pub fn execute<'a, C, U>(
        self,
        conf: &configuration::Configuration<C>,
    ) -> Pin<Box<dyn Future<Output = Result<U, Error>> + 'a + Send>>
    where
        C: Connect + Clone + std::marker::Send + Sync,
        U: Sized + std::marker::Send + 'a,
        for<'de> U: serde::Deserialize<'de>,
    {
        let mut query_string = ::url::form_urlencoded::Serializer::new("".to_owned());

        let mut path = self.path;
        for (k, v) in self.path_params {
            // replace {id} with the value of the id path param
            path = path.replace(&format!("{{{}}}", k), &v);
        }

        for (key, val) in self.query_params {
            query_string.append_pair(&key, &val);
        }

        let mut uri_str = format!("{}{}", conf.base_path, path);

        let query_string_str = query_string.finish();
        if !query_string_str.is_empty() {
            uri_str += "?";
            uri_str += &query_string_str;
        }
        let uri: hyper::Uri = match uri_str.parse() {
            Err(e) => return Box::pin(futures::future::err(Error::UriError(e))),
            Ok(u) => u,
        };

        let mut req_builder = hyper::Request::builder().uri(uri).method(self.method);

        if let Some(ref user_agent) = conf.user_agent {
            req_builder = req_builder.header(
                USER_AGENT,
                match HeaderValue::from_str(user_agent) {
                    Ok(header_value) => header_value,
                    Err(e) => return Box::pin(futures::future::err(super::Error::Header(e))),
                },
            );
        }

        for (k, v) in self.header_params {
            req_builder = req_builder.header(&k, v);
        }

        let req_headers = req_builder.headers_mut().unwrap();
        let request_result = if self.form_params.len() > 0 {
            req_headers.insert(
                CONTENT_TYPE,
                HeaderValue::from_static("application/x-www-form-urlencoded"),
            );
            let mut enc = ::url::form_urlencoded::Serializer::new("".to_owned());
            for (k, v) in self.form_params {
                enc.append_pair(&k, &v);
            }
            req_builder.body(enc.finish())
        } else if let Some(body) = self.serialized_body {
            req_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
            req_headers.insert(CONTENT_LENGTH, body.len().into());
            req_builder.body(body)
        } else {
            req_builder.body(String::new())
        };
        let request = match request_result {
            Ok(request) => request,
            Err(e) => return Box::pin(futures::future::err(Error::from(e))),
        };

        let no_return_type = self.no_return_type;
        Box::pin(
            conf.client
                .request(request)
                .map_err(|e| Error::from(e))
                .and_then(move |response| {
                    let status = response.status();
                    if !status.is_success() {
                        Box::pin(async move {
                            let body_bytes = response
                                .into_body()
                                .collect()
                                .await
                                .map_err(Error::from)?
                                .to_bytes();
                            let body = String::from_utf8_lossy(&body_bytes).to_string();
                            Err(Error::Api(crate::apis::ApiError { code: status, body }))
                        })
                    } else if no_return_type {
                        // This is a hack; if there's no_ret_type, U is (), but serde_json gives an
                        // error when deserializing "" into (), so deserialize 'null' into it
                        // instead.
                        // An alternate option would be to require U: Default, and then return
                        // U::default() here instead since () implements that, but then we'd
                        // need to impl default for all models.
                        futures::future::ok::<U, Error>(
                            serde_json::from_str("null").expect("serde null value"),
                        )
                        .boxed()
                    } else {
                        let collect = response.into_body().collect().map_err(Error::from);
                        collect
                            .map(|collected| {
                                collected.and_then(|collected| {
                                    serde_json::from_slice(&collected.to_bytes())
                                        .map_err(Error::from)
                                })
                            })
                            .boxed()
                    }
                }),
        )
    }
}