use clap::{Parser, ValueEnum};
use eyre::WrapErr;
use http::{HeaderName, HeaderValue, Uri};
use serde_json::Value;
use std::io::{self, Write};
use tracing::info;
use openstack_sdk::{
AsyncOpenStack,
api::{AsyncClient, RestClient},
types::ServiceType,
};
use crate::Cli;
use crate::OpenStackCliError;
use crate::common::parse_key_val;
use crate::output::OutputProcessor;
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, ValueEnum)]
pub enum Method {
Head,
Get,
Patch,
Put,
Post,
Delete,
}
impl From<Method> for http::Method {
fn from(item: Method) -> Self {
match item {
Method::Head => http::Method::HEAD,
Method::Get => http::Method::GET,
Method::Patch => http::Method::PATCH,
Method::Put => http::Method::PUT,
Method::Post => http::Method::POST,
Method::Delete => http::Method::DELETE,
}
}
}
#[derive(Debug, Parser)]
pub struct ApiCommand {
#[arg()]
service_type: String,
#[arg()]
url: String,
#[arg(short, long, value_enum, default_value_t=Method::Get)]
method: Method,
#[arg(long, short='H', value_name="key=value", value_parser = parse_key_val::<String, String>)]
header: Vec<(String, String)>,
#[arg(long)]
body: Option<String>,
}
impl ApiCommand {
pub async fn take_action(
&self,
parsed_args: &Cli,
client: &mut AsyncOpenStack,
) -> Result<(), OpenStackCliError> {
info!("Perform REST API call {:?}", self);
let op = OutputProcessor::from_args(parsed_args, Some("api"), Some("exec"));
op.validate_args(parsed_args)?;
let service_type = ServiceType::from(self.service_type.as_str());
client.discover_service_endpoint(&service_type).await?;
let service_endpoint = client.get_service_endpoint(&service_type, None)?;
let endpoint = service_endpoint.build_request_url(&self.url)?;
let mut req = http::Request::builder()
.method::<http::Method>(self.method.clone().into())
.uri(endpoint.as_str().parse::<Uri>()?)
.header(
http::header::ACCEPT,
HeaderValue::from_static("application/json"),
);
if let Some(headers) = req.headers_mut() {
headers.extend(
self.header
.iter()
.map(|(name, val)| {
Ok::<(HeaderName, HeaderValue), OpenStackCliError>((
HeaderName::from_lowercase(name.to_lowercase().as_bytes())
.wrap_err_with(|| {
format!("{} cannot be used as header name", name)
})?,
HeaderValue::from_str(val.as_str()).wrap_err_with(|| {
format!("{} cannot be used as the header value", val)
})?,
))
})
.collect::<Result<Vec<(_, _)>, _>>()?
.into_iter(),
)
}
let rsp = client
.rest_async(req, self.body.clone().unwrap_or_default().into_bytes())
.await?;
info!("Response = {:?}", rsp);
if let Some(content_type) = rsp.headers().get("content-type") {
if content_type == "application/json" {
if !rsp.body().is_empty() {
let data: Value = serde_json::from_slice(rsp.body())?;
op.output_machine(data)?;
}
} else {
io::stdout().write_all(rsp.body())?;
}
}
op.show_command_hint()?;
Ok(())
}
}