1use core::unreachable;
2use std::collections::HashMap;
3
4use crate::client::Client;
5
6use super::metadata::{Command, Operation, Schema};
7use anyhow::{bail, Result};
8use clap::ArgMatches;
9
10pub struct CommandInvocation {
11 command: Command,
12 matches: ArgMatches,
13}
14
15impl CommandInvocation {
16 pub fn new(command: &Command, matches: &ArgMatches) -> Self {
17 Self {
18 command: command.clone(),
19 matches: matches.clone(),
20 }
21 }
22
23 pub async fn invoke(&self, client: &Client) -> Result<String> {
24 if self.command.operations.is_empty() {
25 bail!("No operation found for command {}", self.command.name);
26 }
27 let operation = self.command.operations.first().unwrap();
28 let operation_ionvocation = OperationInvocation::new(operation, &self.matches);
29 operation_ionvocation.invoke(client).await
30 }
31}
32
33struct OperationInvocation {
34 operation: Operation,
35 matches: ArgMatches,
36}
37
38impl OperationInvocation {
39 pub fn new(operation: &Operation, matches: &ArgMatches) -> Self {
40 Self {
41 operation: operation.clone(),
42 matches: matches.clone(),
43 }
44 }
45
46 pub async fn invoke(&self, client: &crate::client::Client) -> Result<String> {
47 if self.operation.http.is_none() {
48 bail!(
49 r#"HTTP information not found for operation "{}""#,
50 self.operation
51 .operation_id
52 .clone()
53 .unwrap_or("".to_string()),
54 );
55 }
56
57 let http = self.operation.http.as_ref().unwrap();
58 let mut path = http.path.clone();
59 for param in &http.request.path.params {
60 if let Some(value) = self.matches.get_one::<String>(¶m.arg) {
61 path = path.replace(&format!("{{{}}}", param.name), value);
62 } else if let Some(true) = param.required {
63 bail!("missing required path parameter: {}", param.name);
64 } else {
65 unreachable!(
66 r#"optional path parameter "{}" not supported yet!"#,
67 param.name
68 )
69 }
70 }
71 let mut query_pairs = HashMap::new();
72 for param in &http.request.query.consts {
74 query_pairs.insert(param.name.clone(), param.default.value.clone());
75 }
76 let body: Option<bytes::Bytes> = if let Some(body_meta) = &http.request.body {
77 if let Some(schema) = &body_meta.json.schema {
78 self.build_body(schema.clone())?
79 .map(|v| bytes::Bytes::from(v.to_string()))
80 } else {
81 None
82 }
83 } else {
84 None
85 };
86 let response = client
87 .run(
88 http.request.method.into(),
89 path.as_str(),
90 &query_pairs["api-version"],
91 body,
92 None,
93 )
94 .await?;
95 for response_meta in &http.responses {
96 if let Some(status_codes) = &response_meta.status_code {
97 if status_codes.contains(&(u16::from(response.status_code) as i64)) {
98 return Ok(String::from_utf8(response.body.to_vec())?);
99 }
100 }
101 }
102 bail!(
103 "error response: {}\n\n{}",
104 response.status_code,
105 String::from_utf8_lossy(&response.body)
106 );
107 }
108
109 fn build_body(&self, schema: Schema) -> Result<Option<serde_json::Value>> {
110 if let Some(props) = &schema.props {
111 let mut map = serde_json::Map::new();
112 for prop in props {
113 if let Some(prop_name) = &prop.name {
114 let value = self.build_value(prop.clone())?;
115 if let Some(value) = value {
116 map.insert(prop_name.clone(), value);
117 }
118 } else {
119 bail!(r#"property lacks the "name" in the schema"#,);
120 }
121 }
122 return Ok(Some(serde_json::Value::Object(map)));
123 }
124 bail!(r#"schema lacks the "props" in the schema"#);
125 }
126
127 fn build_value(&self, schema: Schema) -> Result<Option<serde_json::Value>> {
128 match schema.type_.as_str() {
129 "object" => {
130 if let Some(arg) = &schema.arg {
131 if let Some(value) = self.matches.get_one::<String>(arg) {
132 Ok(Some(serde_json::from_str(value)?))
133 } else {
134 Ok(None)
135 }
136 } else if let Some(props) = &schema.props {
137 let mut map = serde_json::Map::new();
138 for prop in props {
139 if let Some(prop_name) = &prop.name {
140 let value = self.build_value(prop.clone())?;
141 if let Some(value) = value {
142 map.insert(prop_name.clone(), value);
143 }
144 } else {
145 bail!(r#"property lacks the "name" in the schema"#,);
146 }
147 }
148 if map.is_empty() {
149 Ok(None)
150 } else {
151 Ok(Some(serde_json::Value::Object(map)))
152 }
153 } else {
154 bail!(r#"object schema lacks both the "arg" and "props" in the schema"#);
155 }
156 }
157 s if s.starts_with("array") || s == "string" => {
158 if let Some(arg) = &schema.arg {
159 if let Some(value) = self.matches.get_one::<String>(arg) {
160 Ok(Some(serde_json::from_str(value)?))
161 } else {
162 Ok(None)
163 }
164 } else {
165 bail!(
166 r#"schema "{}" lacks the "arg" in the schema"#,
167 schema.name.unwrap_or("".to_string())
168 );
169 }
170 }
171 s if s.starts_with("integer") => {
172 if let Some(arg) = &schema.arg {
173 if let Some(value) = self.matches.get_one::<i32>(arg) {
174 Ok(Some((*value).into()))
175 } else {
176 Ok(None)
177 }
178 } else {
179 bail!(
180 r#"schema "{}" lacks the "arg" in the schema"#,
181 schema.name.unwrap_or("".to_string())
182 );
183 }
184 }
185 "boolean" => {
186 if let Some(arg) = &schema.arg {
187 if let Some(value) = self.matches.get_one::<bool>(arg) {
188 Ok(Some((*value).into()))
189 } else {
190 Ok(None)
191 }
192 } else {
193 bail!(
194 r#"schema "{}" lacks the "arg" in the schema"#,
195 schema.name.unwrap_or("".to_string())
196 );
197 }
198 }
199 _ => {
202 if let Some(arg) = &schema.arg {
204 if let Some(value) = self.matches.get_one::<String>(arg) {
205 match serde_json::from_str(value) {
206 Ok(v) => Ok(Some(v)),
207 Err(_) => Ok(Some(serde_json::Value::String(value.clone()))),
208 }
209 } else {
210 Ok(None)
211 }
212 } else {
213 bail!(
214 r#"schema "{}" lacks the "arg" in the schema"#,
215 schema.name.unwrap_or("".to_string())
216 );
217 }
218 }
219 }
220 }
221}