diff --git a/src/cli.rs b/src/cli.rs index fe1e09a..93e80d2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -7,7 +7,7 @@ use std::error::Error; use clap::Parser; -use crate::{error::AppError, prompt::Prompt}; +use crate::{error::AppError, prompt::Prompt, req::Params}; use oreq::schema::read::ReadSchema; #[derive(Parser, Debug)] @@ -18,7 +18,7 @@ pub struct Cli { #[arg(long, short, help = "Base URL", value_hint = clap::ValueHint::Url)] pub base_url: Option, #[arg(long, short = 'H', value_parser = parse_key_val::)] - pub headers: Option>, + pub headers: Option>, #[arg(long, short, help = "Path to request")] pub path: Option, #[arg(long = "request", short = 'X', help = "Method to use")] @@ -74,9 +74,32 @@ impl Cli { let mut term = Term::default(); let mut theme = FancyTheme::default(); let mut init = Prompt::new(api.schema, &mut term, &mut theme) - .run() + .run( + self.path.clone(), + self.method.clone(), + self.path_param + .clone() + .map(|x| x.into_iter().collect()) + .unwrap_or_default(), + self.query_param + .clone() + .map(|x| x.into_iter().collect()) + .unwrap_or_default(), + self.headers + .clone() + .map(|x| x.into_iter().collect()) + .unwrap_or_default(), + self.field + .clone() + .map(|x| x.into_iter().collect()) + .unwrap_or_default(), + ) .map_err(AppError::PromptError)?; init.base = server; + if let Some(headers) = self.headers.clone() { + init.header + .extend(headers.into_iter().map(|(k, v)| Params::Header(k, v))); + } let args = init.to_curl_args().map_err(AppError::ParseError)?; println!("{}", args.join(" ")); diff --git a/src/prompt.rs b/src/prompt.rs index d5f151e..d20bdcb 100644 --- a/src/prompt.rs +++ b/src/prompt.rs @@ -1,4 +1,5 @@ use anyhow::anyhow; +use http::Method; use indexmap::IndexMap; use openapiv3::{OpenAPI, Operation, Parameter, ParameterData, ParameterSchemaOrContent, PathItem}; use promptuity::{prompts::SelectOption, Promptuity, Terminal, Theme}; @@ -47,14 +48,74 @@ where } } - pub fn run(&mut self) -> anyhow::Result { + pub fn run( + &mut self, + path: Option, + method: Option, + path_params: IndexMap, + query_params: IndexMap, + header: IndexMap, + fields: IndexMap, + ) -> anyhow::Result { self.provider.term().clear()?; self.provider.begin()?; - let mut path = self.path_prompt()?; - let (path, path_item) = self.provider.prompt(&mut path)?; - let mut method = self.method_prompt(&path_item)?; - let (method, operation) = self.provider.prompt(&mut method)?; + let mut path_prompt = self.path_prompt()?; + let (path, path_item) = if let Some(path) = path { + let path_item = self + .api + .paths + .paths + .get(&path) + .ok_or_else(|| anyhow!("Path not found"))?; + let path_item = path_item.item(&self.api)?; + (path, path_item.clone()) + } else { + self.provider.prompt(&mut path_prompt)? + }; + let mut method_prompt = self.method_prompt(&path_item)?; + let (method, operation) = if let Some(method) = method { + match method { + Method::GET => { + let operation = path_item + .get + .clone() + .ok_or_else(|| anyhow!("Method not found"))?; + ("GET".to_owned(), operation) + } + Method::POST => { + let operation = path_item + .post + .clone() + .ok_or_else(|| anyhow!("Method not found"))?; + ("POST".to_owned(), operation) + } + Method::PUT => { + let operation = path_item + .put + .clone() + .ok_or_else(|| anyhow!("Method not found"))?; + ("PUT".to_owned(), operation) + } + Method::DELETE => { + let operation = path_item + .delete + .clone() + .ok_or_else(|| anyhow!("Method not found"))?; + ("DELETE".to_owned(), operation) + } + Method::PATCH => { + let operation = path_item + .patch + .clone() + .ok_or_else(|| anyhow!("Method not found"))?; + ("PATCH".to_owned(), operation) + } + _ => self.provider.prompt(&mut method_prompt)?, + } + } else { + self.provider.prompt(&mut method_prompt)? + }; let mut params_data = ParamsMap::default(); for param in operation.parameters { @@ -81,7 +142,11 @@ where self.provider.step("Path Parameters")?; for param in ¶ms_data.path { let mut prompt = self.parameter_prompt(param)?; - let value = self.provider.prompt(&mut *prompt)?; + let value = if let Some(value) = path_params.get(¶m.name) { + value.clone() + } else { + self.provider.prompt(&mut *prompt)? + }; params.path.push(Params::Path(param.name.to_owned(), value)); } } @@ -90,7 +155,11 @@ where self.provider.step("Query Parameters")?; for param in ¶ms_data.query { let mut prompt = self.parameter_prompt(param)?; - let value = self.provider.prompt(&mut *prompt)?; + let value = if let Some(value) = query_params.get(¶m.name) { + value.clone() + } else { + self.provider.prompt(&mut *prompt)? + }; params .query .push(Params::Query(param.name.to_owned(), Some(value))); @@ -101,7 +170,11 @@ where self.provider.step("Header Parameters")?; for param in ¶ms_data.header { let mut prompt = self.parameter_prompt(param)?; - let value = self.provider.prompt(&mut *prompt)?; + let value = if let Some(value) = header.get(¶m.name) { + value.clone() + } else { + self.provider.prompt(&mut *prompt)? + }; params .header .push(Params::Header(param.name.to_owned(), value)); @@ -128,7 +201,8 @@ where .ok_or_else(|| anyhow!("Only supported 'application/json'"))?; let req_body = req_body.item(&self.api)?; - let mut prompt = prompt_builder(&self.api, req_body, "Request Body".to_owned()); + let mut prompt = + prompt_builder(&self.api, req_body, "Request Body".to_owned(), Some(fields)); let value = self.provider.prompt(&mut *prompt)?; Some(value) } else { @@ -202,7 +276,12 @@ where match parameter.format.clone() { ParameterSchemaOrContent::Schema(schema) => { let item = schema.item(&self.api)?; - Ok(prompt_builder(&self.api, item, parameter.name.clone())) + Ok(prompt_builder( + &self.api, + item, + parameter.name.clone(), + None, + )) } ParameterSchemaOrContent::Content(_) => Err(anyhow!("Content not supported")), } diff --git a/src/prompts/array.rs b/src/prompts/array.rs index f8c3830..2034529 100644 --- a/src/prompts/array.rs +++ b/src/prompts/array.rs @@ -108,7 +108,7 @@ impl Array { let idx = self.value.len(); let msg = format!("{}[{}]", self.message, idx); - let mut prompt = prompt_builder(&self.api, item, msg); + let mut prompt = prompt_builder(&self.api, item, msg, None); prompt.setup()?; self.current_prompt = Some(prompt); diff --git a/src/prompts/mod.rs b/src/prompts/mod.rs index aeee43b..fab3419 100644 --- a/src/prompts/mod.rs +++ b/src/prompts/mod.rs @@ -1,5 +1,6 @@ use array::Array; use boolean::Boolean; +use indexmap::IndexMap; use number::Number; use object::Object; use openapiv3::{OpenAPI, Schema, SchemaKind, Type}; @@ -20,6 +21,7 @@ pub fn prompt_builder( api: &OpenAPI, schema: &Schema, message: String, + default: Option>, ) -> Box> { match &schema.schema_kind { SchemaKind::Type(Type::Boolean(_)) => Box::new(Boolean::new(message)), @@ -33,7 +35,12 @@ pub fn prompt_builder( Box::new(Number::new(message, integer.clone().into())) } SchemaKind::Type(Type::Object(object)) => { - Box::new(Object::new(message, api, object.clone())) + let mut object = Object::new(message, api, object.clone()); + if let Some(default) = default { + object.with_value(default); + } + + Box::new(object) } SchemaKind::Type(Type::Array(array)) => Box::new(Array::new(message, api, array.clone())), _ => unimplemented!(), diff --git a/src/prompts/object.rs b/src/prompts/object.rs index 5a9de2e..d02d482 100644 --- a/src/prompts/object.rs +++ b/src/prompts/object.rs @@ -105,6 +105,14 @@ impl Object { self } + pub fn with_value(&mut self, value: IndexMap) -> &mut Self { + self.value = value + .into_iter() + .map(|(k, v)| (k, Some(v))) + .collect::>(); + self + } + fn next_prompt(&mut self) -> Result { let mut prompt = self.prompts.pop_front(); if let Some((_, prompt)) = &mut prompt { @@ -128,13 +136,18 @@ impl Prompt for Object { type Output = Value; fn setup(&mut self) -> Result<(), promptuity::Error> { - for (key, schema) in self.option.properties.clone() { + let properties = self.option.properties.clone(); + let properties = properties + .into_iter() + .filter(|(k, _)| !self.value.contains_key(k)); + + for (key, schema) in properties { let schema = schema.unbox(); let schema = schema .item(&self.api) .map_err(|x| promptuity::Error::Config(x.to_string()))?; - let prompt = prompt_builder(&self.api, schema, key.clone()); + let prompt = prompt_builder(&self.api, schema, key.clone(), None); self.prompts.push_back((key.clone(), prompt)); } diff --git a/src/req.rs b/src/req.rs index 4bc97ea..2af3f07 100644 --- a/src/req.rs +++ b/src/req.rs @@ -33,6 +33,8 @@ impl RequestInit { for header in self.header.iter() { if let Params::Header(k, v) = header { + let v: SerdeValue = v.clone().into(); + args.push(format!("-H '{}: {}'", k, v)); } } @@ -58,7 +60,7 @@ impl TryInto for RequestInit { Some( v.clone() .map::(|x| x.into()) - .and_then(|x| x.to_query_string()) + .map(|x| x.to_string()) .map(|x| format!("{}={}", k, x)) .unwrap_or(k.clone()), ) diff --git a/src/serde.rs b/src/serde.rs index b68b2fb..906c79b 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use serde_json::Value; pub struct SerdeValue(Value); @@ -8,21 +10,22 @@ impl From for SerdeValue { } } -impl SerdeValue { - pub fn to_query_string(&self) -> Option { - match &self.0 { - Value::Bool(b) => Some(b.to_string()), - Value::Number(n) => Some(n.to_string()), - Value::String(s) => Some(s.to_owned()), - Value::Array(a) => Some( - a.iter() - .map::(|x| x.clone().into()) - .filter_map(|x| x.to_query_string()) - .collect::>() - .join(","), - ), - Value::Null => None, - _ => None, - } +impl Display for SerdeValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let string = match &self.0 { + Value::Bool(b) => b.to_string(), + Value::Number(n) => n.to_string(), + Value::String(s) => s.to_owned(), + Value::Array(a) => a + .iter() + .map::(|x| x.clone().into()) + .map(|x| x.to_string()) + .collect::>() + .join(","), + Value::Null => String::new(), + _ => String::new(), + }; + + write!(f, "{}", string) } }