diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..4c75a70 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[build] +rustflags = ["-D", "warnings"] +rustdocflags = ["-D", "warnings"] \ No newline at end of file diff --git a/crates/crabapi/examples/multiple_requests.rs b/crates/crabapi/examples/multiple_requests.rs index 38cbc13..4a64df4 100644 --- a/crates/crabapi/examples/multiple_requests.rs +++ b/crates/crabapi/examples/multiple_requests.rs @@ -1,4 +1,4 @@ -use crabapi::core::requests::{build_request, send_requests}; +use crabapi::core::requests::{Url, build_request, send_requests}; use http::{HeaderMap, Method}; use reqwest::{Body, Client}; use std::collections::HashMap; @@ -16,7 +16,7 @@ async fn main() -> Result<(), Box> { headers.insert("key", i.to_string().parse().unwrap()); reqs.push(build_request( &client, - "http://localhost:7878", + Url::parse("http://localhost:7878").unwrap(), query, Method::POST, headers, diff --git a/crates/crabapi/examples/one_request.rs b/crates/crabapi/examples/one_request.rs index 8f79c6c..68cb808 100644 --- a/crates/crabapi/examples/one_request.rs +++ b/crates/crabapi/examples/one_request.rs @@ -1,4 +1,4 @@ -use crabapi::core::requests::{build_request, send_requests}; +use crabapi::core::requests::{Url, build_request, send_requests}; use http::{HeaderMap, Method}; use reqwest::{Body, Client}; use std::collections::HashMap; @@ -11,7 +11,7 @@ async fn main() -> Result<(), Box> { // send 1 request let req = build_request( &client, - "http://localhost:7878", + Url::parse("http://localhost:7878").unwrap(), HashMap::new(), Method::GET, HeaderMap::new(), diff --git a/crates/crabapi/src/cli/mod.rs b/crates/crabapi/src/cli/mod.rs index 8f05b6c..0c5f31e 100644 --- a/crates/crabapi/src/cli/mod.rs +++ b/crates/crabapi/src/cli/mod.rs @@ -1,5 +1,7 @@ use crate::core::app::constants; -use crate::core::requests::{build_request, print_response, send_requests}; +use crate::core::requests::{ + Url, build_request, constants as requests_constants, print_response, send_requests, +}; use clap::{Arg, ArgAction, Command}; use const_format::formatcp; use http::{HeaderMap, HeaderName, HeaderValue, Method}; @@ -40,7 +42,10 @@ impl Cli { .short('X') .long("method") .value_name("METHOD") - .help("HTTP method(GET, POST, PUT, DELETE, OPTIONS)") + .help(formatcp!( + "HTTP method({})", + requests_constants::ALL_METHODS_AS_STRING + )) .default_value("GET"), headers_arg: Arg::new("headers") .short('H') @@ -98,6 +103,7 @@ impl Cli { .unwrap() .parse::()?; let url = matches.get_one::("url").unwrap(); + let url = Url::parse(url).unwrap(); let mut headers = HeaderMap::new(); if let Some(header_values) = matches.get_many::("headers") { diff --git a/crates/crabapi/src/core/requests.rs b/crates/crabapi/src/core/requests.rs index d534b67..a90aed7 100644 --- a/crates/crabapi/src/core/requests.rs +++ b/crates/crabapi/src/core/requests.rs @@ -1,22 +1,44 @@ -use http::{HeaderMap, Method}; use reqwest::{Body, Client, Error, RequestBuilder, Response}; use std::collections::HashMap; use tokio::task::JoinHandle; +pub use http::{HeaderMap, Method}; +pub use reqwest::Url; + pub mod constants { use const_format::formatcp; + use http::Method; pub const USER_AGENT: &str = formatcp!( "{} v{}", crate::core::app::constants::APP_NAME, crate::core::app::constants::APP_VERSION ); + + pub const METHODS: [Method; 9] = [ + Method::GET, + Method::POST, + Method::PUT, + Method::DELETE, + Method::HEAD, + Method::OPTIONS, + Method::CONNECT, + Method::PATCH, + Method::TRACE, + ]; + + pub const METHODS_STRING: [&str; 9] = [ + "GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "CONNECT", "PATCH", "TRACE", + ]; + + pub const ALL_METHODS_AS_STRING: &str = + "GET, POST, PUT, DELETE, HEAD, OPTIONS, CONNECT, PATCH, TRACE"; } // TODO: Implement params too pub fn build_request( client: &Client, - url: &str, + url: Url, query: HashMap, method: Method, headers: HeaderMap, @@ -25,7 +47,7 @@ pub fn build_request( let mut default_headers = HeaderMap::new(); default_headers.insert("User-Agent", constants::USER_AGENT.parse().unwrap()); - let request = reqwest::Request::new(method, url.parse().unwrap()); + let request = reqwest::Request::new(method, url); RequestBuilder::from_parts(client.clone(), request) .query(&query) .headers(default_headers) diff --git a/crates/crabapi/src/gui/iced/mod.rs b/crates/crabapi/src/gui/iced/mod.rs index 59cc543..8d9d386 100644 --- a/crates/crabapi/src/gui/iced/mod.rs +++ b/crates/crabapi/src/gui/iced/mod.rs @@ -1,9 +1,9 @@ use iced; -use iced::widget::{Button, Column, Text, TextInput, column, row, text}; +use iced::widget::{Button, Text, TextInput, column, row}; use iced::widget::{button, combo_box, container}; use iced::{Element, Length}; -use http::Method; +use crate::core::requests::{Method, Url, constants}; pub fn init() { iced::run(GUI::title, GUI::update, GUI::view).unwrap() @@ -18,6 +18,7 @@ enum Message { SendRequest, HeaderKeyChanged(usize, String), HeaderValueChanged(usize, String), + #[allow(dead_code)] // TODO: Remove this out-out warning RemoveHeader(usize), AddHeader, } @@ -27,23 +28,18 @@ enum Message { pub struct GUI { methods: combo_box::State, method_selected: Option, - url_input: String, //http::Uri, - header_input: Vec<(String, String)>, //http::HeaderMap, - // body_input: String, //http::Body + url_input: String, + url_input_valid: bool, + header_input: Vec<(String, String)>, } impl GUI { fn new() -> Self { Self { - methods: combo_box::State::new(vec![ - Method::GET, - Method::POST, - Method::PUT, - Method::DELETE, - Method::PATCH, - ]), + methods: combo_box::State::new(constants::METHODS.into()), method_selected: None, url_input: String::new(), + url_input_valid: false, header_input: vec![(String::new(), String::new())], // body_input: String::new(), } @@ -59,7 +55,8 @@ impl GUI { self.method_selected = Some(method); } Message::UrlInputChanged(url) => { - self.url_input = url; //http::Uri::from_static("http://localhost:7878"); + self.url_input = url; + self.url_input_valid = GUI::is_valid_url(&self.url_input); } // Message::HeaderInputChanged(header) => { // self.header_input = header; // ttp::HeaderMap::new(); // } @@ -88,8 +85,18 @@ impl GUI { fn view(&self) -> Element { // ROW: Method, URI, Send Button + let url_input_icon = iced::widget::text_input::Icon { + font: iced::Font::default(), + code_point: if self.url_input_valid { '✅' } else { '❌' }, + size: Some(Self::input_size()), + spacing: 0.0, + side: iced::widget::text_input::Side::Right, + }; + let url_input = TextInput::new("Enter URI", &self.url_input) .on_input(Message::UrlInputChanged) + .size(Self::input_size()) + .icon(url_input_icon) .width(Length::Fill); let method_combo_box = combo_box( &self.methods, @@ -97,7 +104,9 @@ impl GUI { self.method_selected.as_ref(), Message::MethodChanged, ) - .width(75); + .width(75) + .size(Self::input_size_as_f32()); + let send_button = Button::new(Text::new("Send").size(20)).on_press(Message::SendRequest); let request_row = row![method_combo_box, url_input, send_button] .spacing(10) @@ -136,8 +145,16 @@ impl GUI { .into() } - fn label(label: &str) -> Column<'_, Message> { - column![text(label)].spacing(10) + fn is_valid_url(url: &str) -> bool { + Url::parse(url).is_ok() + } + + const fn input_size_as_f32() -> f32 { + 20.0 + } + + const fn input_size() -> iced::Pixels { + iced::Pixels(Self::input_size_as_f32()) } }