1use clap::{Parser, ValueEnum};
18use http::Uri;
19use serde_json::Value;
20use std::io::{self, Write};
21use tracing::info;
22use url::Url;
23
24use openstack_sdk::{
25 AsyncOpenStack,
26 api::{AsyncClient, RestClient},
27 types::ServiceType,
28};
29
30use crate::Cli;
31use crate::OpenStackCliError;
32use crate::common::parse_key_val;
33use crate::output::OutputProcessor;
34
35fn url_to_http_uri(url: Url) -> Uri {
36 url.as_str()
37 .parse::<Uri>()
38 .expect("failed to parse a url::Url as an http::Uri")
39}
40
41#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, ValueEnum)]
43pub enum Method {
44 Head,
46 Get,
48 Patch,
50 Put,
52 Post,
54 Delete,
56}
57
58impl From<Method> for http::Method {
59 fn from(item: Method) -> Self {
60 match item {
61 Method::Head => http::Method::HEAD,
62 Method::Get => http::Method::GET,
63 Method::Patch => http::Method::PATCH,
64 Method::Put => http::Method::PUT,
65 Method::Post => http::Method::POST,
66 Method::Delete => http::Method::DELETE,
67 }
68 }
69}
70
71#[derive(Debug, Parser)]
84pub struct ApiCommand {
85 #[arg()]
87 service_type: String,
88
89 #[arg()]
93 url: String,
94
95 #[arg(short, long, value_enum, default_value_t=Method::Get)]
97 method: Method,
98
99 #[arg(long, short='H', value_name="key=value", value_parser = parse_key_val::<String, String>)]
101 header: Vec<(String, String)>,
102
103 #[arg(long)]
105 body: Option<String>,
106}
107
108impl ApiCommand {
109 pub async fn take_action(
111 &self,
112 parsed_args: &Cli,
113 client: &mut AsyncOpenStack,
114 ) -> Result<(), OpenStackCliError> {
115 info!("Perform REST API call {:?}", self);
116
117 let op = OutputProcessor::from_args(parsed_args);
118 op.validate_args(parsed_args)?;
119
120 let service_type = ServiceType::from(self.service_type.as_str());
121
122 client.discover_service_endpoint(&service_type).await?;
123
124 let service_endpoint = client.get_service_endpoint(&service_type, None)?;
125
126 let endpoint = service_endpoint.build_request_url(&self.url)?;
127
128 let mut req = http::Request::builder()
129 .method::<http::Method>(self.method.clone().into())
130 .uri(url_to_http_uri(endpoint))
131 .header(
132 http::header::ACCEPT,
133 http::HeaderValue::from_static("application/json"),
134 );
135
136 let headers = req.headers_mut().unwrap();
137 for (name, val) in &self.header {
138 headers.insert(
139 http::HeaderName::from_lowercase(name.to_lowercase().as_bytes()).unwrap(),
140 http::HeaderValue::from_str(val.as_str()).unwrap(),
141 );
142 }
143
144 let rsp = client
145 .rest_async(req, self.body.clone().unwrap_or_default().into_bytes())
146 .await?;
147
148 info!("Response = {:?}", rsp);
149 if let Some(content_type) = rsp.headers().get("content-type") {
150 if content_type == "application/json" {
151 if !rsp.body().is_empty() {
152 let data: Value = serde_json::from_slice(rsp.body())?;
153 op.output_machine(data)?;
154 }
155 } else {
156 io::stdout().write_all(rsp.body())?;
157 }
158 }
159
160 Ok(())
161 }
162}