pub mod args;
use crate::core::env::Environment;
use crate::core::import::import_collection;
use crate::core::parser::parse_project;
use crate::core::persistence::PersistenceManager;
use crate::engine::http::RequestEngine;
use args::{CollectionsCommands, Commands, EnvCommands, Method};
use crossterm::style::{Color, Stylize};
use std::collections::HashMap;
pub async fn run_cli(command: Commands) -> Result<(), Box<dyn std::error::Error>> {
let persistence = PersistenceManager::new();
match command {
Commands::Send {
method,
url,
body,
header,
env,
silent,
json: json_flag,
headers_only,
offline,
} => {
let environment = if let Some(path) = env {
Environment::from_file(path)?
} else {
Environment::default()
};
send_request(
method,
&url,
body,
header,
&environment,
silent,
json_flag,
headers_only,
offline,
)
.await?;
}
Commands::Import { path, base_url } => {
let mut collection = import_collection(path)?;
process_base_url(&mut collection, base_url);
let mut existing = persistence.load_collections()?;
existing.push(collection.clone());
persistence.save_collections(&existing)?;
println!(
"{} imported collection: {}",
"Successfully".with(Color::Green),
collection.name.with(Color::Cyan)
);
}
Commands::Parse { path, base_url } => {
let mut collection = parse_project(path)?;
process_base_url(&mut collection, base_url);
let mut existing = persistence.load_collections()?;
existing.push(collection.clone());
persistence.save_collections(&existing)?;
println!(
"{} parsed project and created collection: {}",
"Successfully".with(Color::Green),
collection.name.with(Color::Cyan)
);
}
Commands::Collections { command } => match command {
CollectionsCommands::List => {
let collections = persistence.load_collections()?;
if collections.is_empty() {
println!("No collections found. Use 'import' or 'parse' to add some.");
return Ok(());
}
println!("{}", "Your Collections:".with(Color::Yellow).bold());
for (i, col) in collections.iter().enumerate() {
println!(
" {}. {}",
(i + 1).to_string().with(Color::DarkGrey),
col.name
);
}
}
CollectionsCommands::Show { name } => {
let collections = persistence.load_collections()?;
let col = collections
.iter()
.find(|c| c.name == name)
.ok_or_else(|| format!("Collection '{}' not found", name))?;
println!(
"{} {}",
"Collection:".with(Color::Yellow).bold(),
col.name.clone().with(Color::Cyan)
);
print_collection_items(&col.items, 0);
}
},
Commands::Run {
collection,
request,
env,
silent,
json: json_flag,
} => {
let collections = persistence.load_collections()?;
let col = collections
.iter()
.find(|c| c.name == collection)
.ok_or_else(|| format!("Collection '{}' not found", collection))?;
let req = col.find_request_by_name(&request).ok_or_else(|| {
format!(
"Request '{}' not found in collection '{}'",
request, collection
)
})?;
let mut environment = Environment::default();
for v in &col.env_vars {
if v.enabled {
environment.variables.insert(v.key.clone(), v.value.clone());
}
}
if let Some(env_path) = env {
let file_env = Environment::from_file(env_path)?;
for (k, v) in file_env.variables {
environment.variables.insert(k, v);
}
}
let headers = req
.headers
.iter()
.filter(|h| h.enabled)
.map(|h| format!("{}:{}", h.key, h.value))
.collect();
let body = match req.body.selected {
crate::core::collection::BodyType::Raw => Some(req.body.raw.content.clone()),
_ => None,
};
send_request(
req.method,
&req.url,
body,
headers,
&environment,
silent,
json_flag,
false,
false,
)
.await?;
}
Commands::Env { command } => match command {
EnvCommands::List => {
let collections = persistence.load_collections()?;
println!(
"{}",
"Collections with Environments:".with(Color::Yellow).bold()
);
for col in collections {
if !col.env_vars.is_empty() {
println!(" - {} ({} variables)", col.name, col.env_vars.len());
}
}
}
EnvCommands::Show { collection } => {
let collections = persistence.load_collections()?;
let col = collections
.iter()
.find(|c| c.name == collection)
.ok_or_else(|| format!("Collection '{}' not found", collection))?;
println!(
"{} {}",
"Environment for:".with(Color::Yellow).bold(),
col.name.clone().with(Color::Cyan)
);
for v in &col.env_vars {
let status = if v.enabled {
"[x]".with(Color::Green)
} else {
"[ ]".with(Color::DarkGrey)
};
println!(
" {} {}: {}",
status,
v.key.clone().with(Color::Yellow),
v.value
);
}
}
},
}
Ok(())
}
async fn send_request(
method: Method,
url: &str,
body: Option<String>,
headers_list: Vec<String>,
environment: &Environment,
silent: bool,
json_flag: bool,
headers_only: bool,
offline: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let final_url = environment.replace_vars(url);
let final_body = body.map(|b| environment.replace_vars(&b));
let mut final_headers = HashMap::new();
for h in headers_list {
if let Some((key, value)) = h.split_once(':') {
final_headers.insert(
environment.replace_vars(key.trim()),
environment.replace_vars(value.trim()),
);
}
}
if offline {
println!("{}", "--- OFFLINE MODE ---".with(Color::Yellow).bold());
println!("{}: {:?}", "Method".bold(), method);
println!("{}: {}", "URL".bold(), final_url);
println!("{}: {:#?}", "Headers".bold(), final_headers);
if let Some(b) = final_body {
println!("{}:\n{}", "Body".bold(), b);
}
return Ok(());
}
let body_type = if let Some(b) = final_body {
crate::core::collection::RequestBody::raw(b, "application/json".to_string())
} else {
crate::core::collection::RequestBody::default()
};
let engine = RequestEngine::new();
let response = engine
.send(
method.into(),
&final_url,
final_headers,
Vec::new(),
body_type,
crate::core::collection::Auth::default(),
)
.await?;
let status = response.status();
let status_color = if status.is_success() {
Color::Green
} else if status.is_client_error() || status.is_server_error() {
Color::Red
} else {
Color::Yellow
};
if !silent && !headers_only {
println!(
"{}: {}",
"Status".bold(),
status.to_string().with(status_color)
);
}
if headers_only {
println!("{:#?}", response.headers());
return Ok(());
}
if !silent {
println!("{}: {:#?}", "Headers".bold(), response.headers());
}
let body_text = response.text().await?;
if json_flag {
println!("{}", body_text);
} else if !silent {
println!("{}:\n{}", "Body".bold(), body_text);
} else {
print!("{}", body_text);
}
Ok(())
}
fn print_collection_items(items: &[crate::core::collection::CollectionItem], depth: usize) {
use crate::core::collection::CollectionItem;
let indent = " ".repeat(depth);
for item in items {
match item {
CollectionItem::Folder(f) => {
println!("{}📁 {}", indent, f.name.clone().bold());
print_collection_items(&f.items, depth + 1);
}
CollectionItem::Request(r) => {
let method_color = match r.method {
Method::Get => Color::Green,
Method::Post => Color::Yellow,
Method::Put => Color::Blue,
Method::Patch => Color::Magenta,
Method::Delete => Color::Red,
};
println!(
"{} {} {}",
indent,
format!("{:?}", r.method).with(method_color).bold(),
r.name
);
}
}
}
}
fn process_base_url(
collection: &mut crate::core::collection::Collection,
base_url_arg: Option<String>,
) {
let base_url = if let Some(url) = base_url_arg {
if url == "interactive" {
get_interactive_base_url()
} else {
url
}
} else if let Some(detected) = collection.detect_base_url() {
detected
} else {
"http://localhost:8080".to_string()
};
collection.apply_base_url(&base_url);
println!(
"{} Base URL applied: {}",
"ℹ".with(Color::Blue),
base_url.with(Color::Cyan)
);
}
fn get_interactive_base_url() -> String {
use std::io::{self, Write};
println!(
"{}",
"\n--- Interactive Base URL Builder ---"
.with(Color::Yellow)
.bold()
);
let mut protocol = String::new();
print!(
"{} Choose protocol (1: http, 2: https) [1]: ",
"?".with(Color::Green)
);
io::stdout().flush().unwrap();
io::stdin().read_line(&mut protocol).unwrap();
let protocol = match protocol.trim() {
"2" | "https" => "https",
_ => "http",
};
let mut host_type = String::new();
print!(
"{} Choose host type (1: local, 2: remote) [1]: ",
"?".with(Color::Green)
);
io::stdout().flush().unwrap();
io::stdin().read_line(&mut host_type).unwrap();
let host = match host_type.trim() {
"2" | "remote" => {
let mut remote_host = String::new();
print!(
"{} Enter remote host (e.g. example.com): ",
"?".with(Color::Green)
);
io::stdout().flush().unwrap();
io::stdin().read_line(&mut remote_host).unwrap();
let h = remote_host.trim().to_string();
if h.is_empty() {
"example.com".to_string()
} else {
h
}
}
_ => "localhost".to_string(),
};
let mut port = String::new();
if host == "localhost" {
print!("{} Enter port [8080]: ", "?".with(Color::Green));
io::stdout().flush().unwrap();
io::stdin().read_line(&mut port).unwrap();
let port = port.trim();
if port.is_empty() {
format!("{}://{}:8080", protocol, host)
} else {
format!("{}://{}:{}", protocol, host, port)
}
} else {
format!("{}://{}", protocol, host)
}
}