From b2af0b8c4fdbe0a5f5d7ae6e6a0a8b8f2ed8c7bc Mon Sep 17 00:00:00 2001 From: alemi Date: Wed, 21 Jun 2023 18:24:20 +0200 Subject: [PATCH] feat: filter while collecting --- src/model/collector.rs | 102 +++++++++++++++++++++-------------------- src/model/mod.rs | 8 ++-- src/model/request.rs | 55 ++++++++++++++++------ 3 files changed, 98 insertions(+), 67 deletions(-) diff --git a/src/model/collector.rs b/src/model/collector.rs index 3e6c07f..f34da22 100644 --- a/src/model/collector.rs +++ b/src/model/collector.rs @@ -1,50 +1,57 @@ use postman_collection::{v1_0_0, v2_0_0, v2_1_0}; +use regex::Regex; -use super::request::IntoRequest; +use super::{request::IntoRequest, description::IntoOptionalString}; pub enum RequestNode { - Leaf(reqwest::RequestBuilder), + Leaf(reqwest::Request), Branch(Vec), } pub struct RequestTree { pub name: String, + pub description: Option, pub request: RequestNode, } pub trait CollectRequests { - fn collect_requests(&self) -> RequestTree; + fn collect_requests(&self, filter: Option<&Regex>) -> Option; } impl CollectRequests for v1_0_0::Spec { - fn collect_requests(&self) -> RequestTree { + fn collect_requests(&self, _filter: Option<&Regex>) -> Option { todo!() } } impl CollectRequests for v2_0_0::Spec { - fn collect_requests(&self) -> RequestTree { - let mut requests = Vec::new(); - for item in &self.item { - requests.push(item.collect_requests()); - } - RequestTree { - name: self.info.name.clone(), - request: RequestNode::Branch(requests), - } + fn collect_requests(&self, filter: Option<&Regex>) -> Option { + let requests = self.item.iter() + .filter_map(|x| x.collect_requests(filter)) + .collect::>(); + (!requests.is_empty()) + .then(|| RequestTree { + name: self.info.name.clone(), + description: self.info.description.as_ref().map_or(None, |x| x.as_string()), + request: RequestNode::Branch(requests), + } + ) } } impl CollectRequests for v2_1_0::Spec { - fn collect_requests(&self) -> RequestTree { - let mut requests = Vec::new(); - for item in &self.item { - requests.push(item.collect_requests()); - } - RequestTree { - name: self.info.name.clone(), - request: RequestNode::Branch(requests), - } + fn collect_requests(&self, filter: Option<&Regex>) -> Option { + let requests = self.item.iter() + .filter_map(|x| x.collect_requests(filter)) + .collect::>(); + (!requests.is_empty()) + .then(|| + RequestTree { + name: self.info.name.clone(), + description: self.info.description.as_ref().map_or(None, |x| x.as_string()), + request: RequestNode::Branch(requests), + } + ) } } @@ -55,11 +62,12 @@ impl CollectRequests for v2_1_0::Spec { // } impl CollectRequests for v2_0_0::Items { - fn collect_requests(&self) -> RequestTree { - let request : RequestNode; + fn collect_requests(&self, filter: Option<&Regex>) -> Option { if self.request.is_some() && self.item.is_some() { - panic!("some node has both a single request and child requests!"); + panic!("node has both a single request and child requests!"); } + let name = self.name.as_ref().unwrap_or(&"".to_string()).to_string(); + let description = self.description.as_ref().map_or(None, |x| x.as_string()); if let Some(r) = &self.request { let clazz = match r { v2_0_0::RequestUnion::String(url) => v2_0_0::RequestClass { @@ -68,26 +76,26 @@ impl CollectRequests for v2_0_0::Items { }, v2_0_0::RequestUnion::RequestClass(r) => r.clone(), }; - request = RequestNode::Leaf(clazz.make_request()); + Some(RequestTree { name, description, request: RequestNode::Leaf(clazz.make_request(filter)?) }) } else if let Some(sub) = &self.item { - let mut requests = Vec::new(); - for item in sub { - requests.push(item.collect_requests()); - } - request = RequestNode::Branch(requests); + let requests = sub.iter() + .filter_map(|x| x.collect_requests(filter)) + .collect::>(); + (!requests.is_empty()) + .then(|| RequestTree { name, description, request: RequestNode::Branch(requests) }) } else { - request = RequestNode::Branch(Vec::new()); // TODO make if/elseif/else nicer? - } - RequestTree { - name: self.name.as_ref().unwrap_or(&"".to_string()).to_string(), // TODO meme - request, + None } } } impl CollectRequests for v2_1_0::Items { - fn collect_requests(&self) -> RequestTree { - let request : RequestNode; + fn collect_requests(&self, filter: Option<&Regex>) -> Option { + if self.request.is_some() && self.item.is_some() { + panic!("node has both a single request and child requests!"); + } + let name = self.name.as_ref().unwrap_or(&"".to_string()).to_string(); + let description = self.description.as_ref().map_or(None, |x| x.as_string()); if let Some(r) = &self.request { let clazz = match r { v2_1_0::RequestUnion::String(url) => v2_1_0::RequestClass { @@ -102,19 +110,15 @@ impl CollectRequests for v2_1_0::Items { }, v2_1_0::RequestUnion::RequestClass(r) => r.clone(), }; - request = RequestNode::Leaf(clazz.make_request()); + Some(RequestTree { name, description, request: RequestNode::Leaf(clazz.make_request(filter)?) }) } else if let Some(sub) = &self.item { - let mut requests = Vec::new(); - for item in sub { - requests.push(item.collect_requests()); - } - request = RequestNode::Branch(requests); + let requests = sub.iter() + .filter_map(|x| x.collect_requests(filter)) + .collect::>(); + (!requests.is_empty()) + .then(|| RequestTree { name, description, request: RequestNode::Branch(requests) }) } else { - request = RequestNode::Branch(Vec::new()); // TODO make if/elseif/else nicer? - } - RequestTree { - name: self.name.as_ref().unwrap_or(&"".to_string()).to_string(), // TODO meme - request, + None } } } diff --git a/src/model/mod.rs b/src/model/mod.rs index d3de766..118d277 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,7 +1,9 @@ pub mod request; pub mod collector; +pub mod description; use postman_collection::{PostmanCollection, v2_0_0, v2_1_0}; +use regex::Regex; use self::collector::{CollectRequests, RequestTree}; @@ -52,11 +54,11 @@ impl PostWomanCollection { } } - pub fn requests(&self) -> RequestTree { + pub fn requests(&self, filter: Option<&Regex>) -> Option { match &self.collection { PostmanCollection::V1_0_0(_) => todo!(), - PostmanCollection::V2_0_0(spec) => spec.collect_requests(), - PostmanCollection::V2_1_0(spec) => spec.collect_requests(), + PostmanCollection::V2_0_0(spec) => spec.collect_requests(filter), + PostmanCollection::V2_1_0(spec) => spec.collect_requests(filter), } } } diff --git a/src/model/request.rs b/src/model/request.rs index 6590244..ee8add2 100644 --- a/src/model/request.rs +++ b/src/model/request.rs @@ -1,10 +1,11 @@ use std::str::FromStr; use postman_collection::{v1_0_0, v2_0_0, v2_1_0}; +use regex::Regex; fn fill_from_env(mut txt: String) -> String { for (k, v) in std::env::vars() { - let key = format!("{{{{{}}}}}", k); + let key = format!("{{{{{}}}}}", k); // escaping of { is done by repeating them if txt.contains(&key) { txt = txt.replace(&key, &v); } @@ -13,11 +14,11 @@ fn fill_from_env(mut txt: String) -> String { } pub trait IntoRequest { - fn make_request(&self) -> reqwest::RequestBuilder; + fn make_request(&self, filter: Option<&Regex>) -> Option; } impl IntoRequest for v2_0_0::RequestClass { - fn make_request(&self) -> reqwest::RequestBuilder { + fn make_request(&self, filter: Option<&Regex>) -> Option { let method = reqwest::Method::from_bytes( self.method.as_ref().unwrap_or(&"GET".into()).as_bytes() // TODO lol? ).unwrap_or(reqwest::Method::GET); // TODO throw an error rather than replacing it silently @@ -31,19 +32,27 @@ impl IntoRequest for v2_0_0::RequestClass { url_str = fill_from_env(url_str); + if filter.is_some() && !filter.unwrap().is_match(&url_str) { + return None; + } + let url = reqwest::Url::from_str(&url_str).unwrap_or_else(|e| { eprintln!("error creating url ({}), falling back to localhost", e); reqwest::Url::from_str("http://localhost/").unwrap() }); - let mut out = reqwest::Client::new().request(method, url); + let mut request = reqwest::Request::new(method, url); // TODO handle more auth types than just bearer if let Some(auth) = &self.auth { if let Some(bearers) = &auth.bearer { + let headers = request.headers_mut(); for v in bearers.values() { if let Some(value) = &v { - out = out.header("Authorization", format!("Bearer {}", value.as_str().unwrap_or(&value.to_string()))) + headers.insert( + "Authorization", + reqwest::header::HeaderValue::from_str(&format!("Bearer {}", value.as_str().unwrap_or(&value.to_string()))).unwrap() // TODO lmao meme + ); } } } @@ -51,10 +60,14 @@ impl IntoRequest for v2_0_0::RequestClass { match &self.header { Some(v2_0_0::HeaderUnion::HeaderArray(x)) => { + let headers = request.headers_mut(); for h in x { let k = fill_from_env(h.key.clone()); let v = fill_from_env(h.value.clone()); - out = out.header(k, v); // TODO avoid cloning + headers.insert( + reqwest::header::HeaderName::from_str(&k).unwrap(), + reqwest::header::HeaderValue::from_str(&v).unwrap() + ); // TODO avoid cloning } }, _ => {}, @@ -63,17 +76,17 @@ impl IntoRequest for v2_0_0::RequestClass { match &self.body { Some(v2_0_0::Body { raw: Some(x), .. }) => { - out = out.body(fill_from_env(x.clone())) // TODO try to avoid cloning? + *request.body_mut() = Some(reqwest::Body::from(fill_from_env(x.clone()))) // TODO try to avoid cloning? }, _ => {}, } - out + Some(request) } } impl IntoRequest for v2_1_0::RequestClass { - fn make_request(&self) -> reqwest::RequestBuilder { + fn make_request(&self, filter: Option<&Regex>) -> Option { let method = reqwest::Method::from_bytes( self.method.as_ref().unwrap_or(&"GET".into()).as_bytes() // TODO lol? ).unwrap_or(reqwest::Method::GET); // TODO throw an error rather than replacing it silently @@ -87,19 +100,27 @@ impl IntoRequest for v2_1_0::RequestClass { url_str = fill_from_env(url_str); + if filter.is_some() && !filter.unwrap().is_match(&url_str) { + return None; + } + let url = reqwest::Url::from_str(&url_str).unwrap_or_else(|e| { eprintln!("error creating url ({}), falling back to localhost", e); reqwest::Url::from_str("http://localhost/").unwrap() }); - let mut out = reqwest::Client::new().request(method, url); + let mut request = reqwest::Request::new(method, url); // TODO handle more auth types than just bearer if let Some(auth) = &self.auth { if let Some(bearers) = &auth.bearer { + let headers = request.headers_mut(); for bearer in bearers { if let Some(value) = &bearer.value { - out = out.header("Authorization", format!("Bearer {}", value.as_str().unwrap_or(&value.to_string()))) + headers.insert( + "Authorization", + reqwest::header::HeaderValue::from_str(&format!("Bearer {}", value.as_str().unwrap_or(&value.to_string()))).unwrap() // TODO lmao meme + ); } } } @@ -107,10 +128,14 @@ impl IntoRequest for v2_1_0::RequestClass { match &self.header { Some(v2_1_0::HeaderUnion::HeaderArray(x)) => { + let headers = request.headers_mut(); for h in x { let k = fill_from_env(h.key.clone()); let v = fill_from_env(h.value.clone()); - out = out.header(k, v); // TODO avoid cloning + headers.insert( + reqwest::header::HeaderName::from_str(&k).unwrap(), + reqwest::header::HeaderValue::from_str(&v).unwrap() + ); // TODO avoid cloning } }, _ => {}, @@ -119,17 +144,17 @@ impl IntoRequest for v2_1_0::RequestClass { match &self.body { Some(v2_1_0::Body { raw: Some(x), .. }) => { - out = out.body(fill_from_env(x.clone())) // TODO try to avoid cloning? + *request.body_mut() = Some(reqwest::Body::from(fill_from_env(x.clone()))) // TODO try to avoid cloning? }, _ => {}, } - out + Some(request) } } impl IntoRequest for v1_0_0::Request { - fn make_request(&self) -> reqwest::RequestBuilder { + fn make_request(&self, _filter: Option<&Regex>) -> Option { todo!() } }