Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabled input from Option #14

Merged
merged 2 commits into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -18,7 +18,7 @@ pub struct Cli {
#[arg(long, short, help = "Base URL", value_hint = clap::ValueHint::Url)]
pub base_url: Option<String>,
#[arg(long, short = 'H', value_parser = parse_key_val::<String, String>)]
pub headers: Option<Vec<(String, String)>>,
pub headers: Option<Vec<(String, serde_json::Value)>>,
#[arg(long, short, help = "Path to request")]
pub path: Option<String>,
#[arg(long = "request", short = 'X', help = "Method to use")]
Expand Down Expand Up @@ -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(" "));
Expand Down
99 changes: 89 additions & 10 deletions src/prompt.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -47,14 +48,74 @@ where
}
}

pub fn run(&mut self) -> anyhow::Result<RequestInit> {
pub fn run(
&mut self,
path: Option<String>,
method: Option<Method>,
path_params: IndexMap<String, Value>,
query_params: IndexMap<String, Value>,
header: IndexMap<String, Value>,
fields: IndexMap<String, Value>,
) -> anyhow::Result<RequestInit> {
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 {
Expand All @@ -81,7 +142,11 @@ where
self.provider.step("Path Parameters")?;
for param in &params_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(&param.name) {
value.clone()
} else {
self.provider.prompt(&mut *prompt)?
};
params.path.push(Params::Path(param.name.to_owned(), value));
}
}
Expand All @@ -90,7 +155,11 @@ where
self.provider.step("Query Parameters")?;
for param in &params_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(&param.name) {
value.clone()
} else {
self.provider.prompt(&mut *prompt)?
};
params
.query
.push(Params::Query(param.name.to_owned(), Some(value)));
Expand All @@ -101,7 +170,11 @@ where
self.provider.step("Header Parameters")?;
for param in &params_data.header {
let mut prompt = self.parameter_prompt(param)?;
let value = self.provider.prompt(&mut *prompt)?;
let value = if let Some(value) = header.get(&param.name) {
value.clone()
} else {
self.provider.prompt(&mut *prompt)?
};
params
.header
.push(Params::Header(param.name.to_owned(), value));
Expand All @@ -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 {
Expand Down Expand Up @@ -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")),
}
Expand Down
2 changes: 1 addition & 1 deletion src/prompts/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
9 changes: 8 additions & 1 deletion src/prompts/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -20,6 +21,7 @@ pub fn prompt_builder(
api: &OpenAPI,
schema: &Schema,
message: String,
default: Option<IndexMap<String, Value>>,
) -> Box<dyn Prompt<Output = Value>> {
match &schema.schema_kind {
SchemaKind::Type(Type::Boolean(_)) => Box::new(Boolean::new(message)),
Expand All @@ -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!(),
Expand Down
17 changes: 15 additions & 2 deletions src/prompts/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ impl Object {
self
}

pub fn with_value(&mut self, value: IndexMap<String, Value>) -> &mut Self {
self.value = value
.into_iter()
.map(|(k, v)| (k, Some(v)))
.collect::<IndexMap<_, _>>();
self
}

fn next_prompt(&mut self) -> Result<bool, promptuity::Error> {
let mut prompt = self.prompts.pop_front();
if let Some((_, prompt)) = &mut prompt {
Expand All @@ -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));
}

Expand Down
4 changes: 3 additions & 1 deletion src/req.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Expand All @@ -58,7 +60,7 @@ impl TryInto<Url> for RequestInit {
Some(
v.clone()
.map::<SerdeValue, _>(|x| x.into())
.and_then(|x| x.to_query_string())
.map(|x| x.to_string())
.map(|x| format!("{}={}", k, x))
.unwrap_or(k.clone()),
)
Expand Down
35 changes: 19 additions & 16 deletions src/serde.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::Display;

use serde_json::Value;

pub struct SerdeValue(Value);
Expand All @@ -8,21 +10,22 @@ impl From<Value> for SerdeValue {
}
}

impl SerdeValue {
pub fn to_query_string(&self) -> Option<String> {
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::<SerdeValue, _>(|x| x.clone().into())
.filter_map(|x| x.to_query_string())
.collect::<Vec<_>>()
.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::<SerdeValue, _>(|x| x.clone().into())
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(","),
Value::Null => String::new(),
_ => String::new(),
};

write!(f, "{}", string)
}
}
Loading