Skip to content

Commit

Permalink
feat: synchronization of requests to file system
Browse files Browse the repository at this point in the history
  • Loading branch information
wllfaria committed May 22, 2024
1 parent a64a2ec commit 86b02d7
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 16 deletions.
59 changes: 59 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion reqtui/src/net/request_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub enum ReqtuiNetRequest {
Error(String),
}

#[tracing::instrument(skip(response_tx))]
#[tracing::instrument(skip_all)]
pub fn handle_request(request: Request, response_tx: UnboundedSender<ReqtuiNetRequest>) {
tracing::debug!("starting to handle user request");
tokio::spawn(async move {
Expand Down
1 change: 1 addition & 0 deletions reqtui/src/schema/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ impl RequestMethod {

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Request {
pub id: String,
pub method: RequestMethod,
pub name: String,
pub uri: String,
Expand Down
1 change: 1 addition & 0 deletions tui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ futures = "0.3.30"
tui-big-text = { version = "0.4.3" }
tracing-subscriber = { version = "0.3.18" }
tracing-appender = "0.2.3"
uuid = { version = "1.8.0", features = ["v4", "fast-rng", "macro-diagnostics"] }

[dev-dependencies]
tempfile = "3.10.1"
Expand Down
2 changes: 2 additions & 0 deletions tui/benches/api_explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ fn create_sample_schema() -> Schema {
path: "any_path".into(),
requests: Some(vec![
RequestKind::Single(Request {
id: "any id".to_string(),
name: "testing".to_string(),
uri: "https://jsonplaceholder.typicode.com/users".to_string(),
method: RequestMethod::Get,
body: Some("[\r\n {\r\n \"id\": 1,\r\n \"name\": \"Leanne Graham\",\r\n \"username\": \"Bret\",\r\n \"email\": \"[email protected]\",\r\n \"address\": {\r\n \"street\": \"Kulas Light\",\r\n \"suite\": \"Apt. 556\",\r\n \"city\": \"Gwenborough\",\r\n \"zipcode\": \"92998-3874\",\r\n \"geo\": {\r\n \"lat\": \"-37.3159\",\r\n \"lng\": \"81.1496\"\r\n }\r\n },\r\n \"phone\": \"1-770-736-8031 x56442\",\r\n \"website\": \"hildegard.org\",\r\n \"company\": {\r\n \"name\": \"Romaguera-Crona\",\r\n \"catchPhrase\": \"Multi-layered client-server neural-net\",\r\n \"bs\": \"harness real-time e-markets\"\r\n }\r\n },\r\n {\r\n \"id\": 2,\r\n \"name\": \"Ervin Howell\",\r\n \"username\": \"Antonette\",\r\n \"email\": \"[email protected]\",\r\n \"address\": {\r\n \"street\": \"Victor Plains\",\r\n \"suite\": \"Suite 879\",\r\n \"city\": \"Wisokyburgh\",\r\n \"zipcode\": \"90566-7771\",\r\n \"geo\": {\r\n \"lat\": \"-43.9509\",\r\n \"lng\": \"-34.4618\"\r\n }\r\n },\r\n \"phone\": \"010-692-6593 x09125\",\r\n \"website\": \"anastasia.net\",\r\n \"company\": {\r\n \"name\": \"Deckow-Crist\",\r\n \"catchPhrase\": \"Proactive didactic contingency\",\r\n \"bs\": \"synergize scalable supply-chains\"\r\n }\r\n },\r\n {\r\n \"id\": 3,\r\n \"name\": \"Clementine Bauch\",\r\n \"username\": \"Samantha\",\r\n \"email\": \"[email protected]\",\r\n \"address\": {\r\n \"street\": \"Douglas Extension\",\r\n \"suite\": \"Suite 847\",\r\n \"city\": \"McKenziehaven\",\r\n \"zipcode\": \"59590-4157\",\r\n \"geo\": {\r\n \"lat\": \"-68.6102\",\r\n \"lng\": \"-47.0653\"\r\n }\r\n },\r\n \"phone\": \"1-463-123-4447\",\r\n \"website\": \"ramiro.info\",\r\n \"company\": {\r\n \"name\": \"Romaguera-Jacobson\",\r\n \"catchPhrase\": \"Face to face bifurcated interface\",\r\n \"bs\": \"e-enable strategic applications\"\r\n }\r\n },\r\n {\r\n \"id\": 4,\r\n \"name\": \"Patricia Lebsack\",\r\n \"username\": \"Karianne\",\r\n \"email\": \"[email protected]\",\r\n \"address\": {\r\n \"street\": \"Hoeger Mall\",\r\n \"suite\": \"Apt. 692\",\r\n \"city\": \"South Elvis\",\r\n \"zipcode\": \"53919-4257\",\r\n \"geo\": {\r\n \"lat\": \"29.4572\",\r\n \"lng\": \"-164.2990\"\r\n }\r\n },\r\n \"phone\": \"493-170-9623 x156\",\r\n \"website\": \"kale.biz\",\r\n \"company\": {\r\n \"name\": \"Robel-Corkery\",\r\n \"catchPhrase\": \"Multi-tiered zero tolerance productivity\",\r\n \"bs\": \"transition cutting-edge web services\"\r\n }\r\n },\r\n {\r\n \"id\": 5,\r\n \"name\": \"Chelsey Dietrich\",\r\n \"username\": \"Kamren\",\r\n \"email\": \"[email protected]\",\r\n \"address\": {\r\n \"street\": \"Skiles Walks\",\r\n \"suite\": \"Suite 351\",\r\n \"city\": \"Roscoeview\",\r\n \"zipcode\": \"33263\",\r\n \"geo\": {\r\n \"lat\": \"-31.8129\",\r\n \"lng\": \"62.5342\"\r\n }\r\n },\r\n \"phone\": \"(254)954-1289\",\r\n \"website\": \"demarco.info\",\r\n \"company\": {\r\n \"name\": \"Keebler LLC\",\r\n \"catchPhrase\": \"User-centric fault-tolerant solution\",\r\n \"bs\": \"revolutionize end-to-end systems\"\r\n }\r\n },\r\n {\r\n \"id\": 6,\r\n \"name\": \"Mrs. Dennis Schulist\",\r\n \"username\": \"Leopoldo_Corkery\",\r\n \"email\": \"[email protected]\",\r\n \"address\": {\r\n \"street\": \"Norberto Crossing\",\r\n \"suite\": \"Apt. 950\",\r\n \"city\": \"South Christy\",\r\n \"zipcode\": \"23505-1337\",\r\n \"geo\": {\r\n \"lat\": \"-71.4197\",\r\n \"lng\": \"71.7478\"\r\n }\r\n },\r\n \"phone\": \"1-477-935-8478 x6430\",\r\n \"website\": \"ola.org\",\r\n \"company\": {\r\n \"name\": \"Considine-Lockman\",\r\n \"catchPhrase\": \"Synchronised bottom-line interface\",\r\n \"bs\": \"e-enable innovative applications\"\r\n }\r\n },\r\n {\r\n \"id\": 7,\r\n \"name\": \"Kurtis Weissnat\",\r\n \"username\": \"Elwyn.Skiles\",\r\n \"email\": \"[email protected]\",\r\n \"address\": {\r\n \"street\": \"Rex Trail\",\r\n \"suite\": \"Suite 280\",\r\n \"city\": \"Howemouth\",\r\n \"zipcode\": \"58804-1099\",\r\n \"geo\": {\r\n \"lat\": \"24.8918\",\r\n \"lng\": \"21.8984\"\r\n }\r\n },\r\n \"phone\": \"210.067.6132\",\r\n \"website\": \"elvis.io\",\r\n \"company\": {\r\n \"name\": \"Johns Group\",\r\n \"catchPhrase\": \"Configurable multimedia task-force\",\r\n \"bs\": \"generate enterprise e-tailers\"\r\n }\r\n },\r\n {\r\n \"id\": 8,\r\n \"name\": \"Nicholas Runolfsdottir V\",\r\n \"username\": \"Maxime_Nienow\",\r\n \"email\": \"[email protected]\",\r\n \"address\": {\r\n \"street\": \"Ellsworth Summit\",\r\n \"suite\": \"Suite 729\",\r\n \"city\": \"Aliyaview\",\r\n \"zipcode\": \"45169\",\r\n \"geo\": {\r\n \"lat\": \"-14.3990\",\r\n \"lng\": \"-120.7677\"\r\n }\r\n },\r\n \"phone\": \"586.493.6943 x140\",\r\n \"website\": \"jacynthe.com\",\r\n \"company\": {\r\n \"name\": \"Abernathy Group\",\r\n \"catchPhrase\": \"Implemented secondary concept\",\r\n \"bs\": \"e-enable extensible e-tailers\"\r\n }\r\n },\r\n {\r\n \"id\": 9,\r\n \"name\": \"Glenna Reichert\",\r\n \"username\": \"Delphine\",\r\n \"email\": \"[email protected]\",\r\n \"address\": {\r\n \"street\": \"Dayna Park\",\r\n \"suite\": \"Suite 449\",\r\n \"city\": \"Bartholomebury\",\r\n \"zipcode\": \"76495-3109\",\r\n \"geo\": {\r\n \"lat\": \"24.6463\",\r\n \"lng\": \"-168.8889\"\r\n }\r\n },\r\n \"phone\": \"(775)976-6794 x41206\",\r\n \"website\": \"conrad.com\",\r\n \"company\": {\r\n \"name\": \"Yost and Sons\",\r\n \"catchPhrase\": \"Switchable contextually-based project\",\r\n \"bs\": \"aggregate real-time technologies\"\r\n }\r\n },\r\n {\r\n \"id\": 10,\r\n \"name\": \"Clementina DuBuque\",\r\n \"username\": \"Moriah.Stanton\",\r\n \"email\": \"[email protected]\",\r\n \"address\": {\r\n \"street\": \"Kattie Turnpike\",\r\n \"suite\": \"Suite 198\",\r\n \"city\": \"Lebsackbury\",\r\n \"zipcode\": \"31428-2261\",\r\n \"geo\": {\r\n \"lat\": \"-38.2386\",\r\n \"lng\": \"57.2232\"\r\n }\r\n },\r\n \"phone\": \"024-648-3804\",\r\n \"website\": \"ambrose.net\",\r\n \"company\": {\r\n \"name\": \"Hoeger LLC\",\r\n \"catchPhrase\": \"Centralized empowering task-force\",\r\n \"bs\": \"target end-to-end models\"\r\n }\r\n }\r\n]".to_string()),
body_type: Some("application/json".to_string()),
}),
RequestKind::Single(Request {
id: "any_other_id".to_string(),
name: "testing".to_string(),
uri: "https://jsonplaceholder.typicode.com/users".to_string(),
method: RequestMethod::Get,
Expand Down
4 changes: 2 additions & 2 deletions tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl<'app> App<'app> {
let terminal = Terminal::new(CrosstermBackend::new(std::io::stdout()))?;
Ok(Self {
screen_manager: ScreenManager::new(terminal.size()?, colors, schemas, config)?,
event_pool: EventPool::new(60f64),
event_pool: EventPool::new(60f64, 30f64),
should_quit: false,
terminal,
})
Expand All @@ -42,7 +42,7 @@ impl<'app> App<'app> {
loop {
if let Some(event) = self.event_pool.next().await {
match event {
Event::Tick => {}
Event::Tick => self.screen_manager.handle_tick()?,
Event::Resize(new_size) => self.screen_manager.resize(new_size),
Event::Render => {
self.terminal.draw(|f| {
Expand Down
4 changes: 4 additions & 0 deletions tui/src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ pub trait Component {
fn register_command_handler(&mut self, sender: UnboundedSender<Command>) -> anyhow::Result<()> {
Ok(())
}

fn handle_tick(&mut self) -> anyhow::Result<()> {
Ok(())
}
}
72 changes: 63 additions & 9 deletions tui/src/components/api_explorer/api_explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ pub struct ApiExplorer<'ae> {
curr_overlay: Overlays,
create_req_form_state: CreateReqFormState,

sync_interval: std::time::Instant,
editor: ReqEditor<'ae>,
editor_tab: ReqEditorTabs,

Expand All @@ -161,7 +162,7 @@ impl<'ae> ApiExplorer<'ae> {
) -> Self {
let layout = build_layout(size);

let selected_request = schema.requests.as_ref().and_then(|requests| {
let mut selected_request = schema.requests.as_ref().and_then(|requests| {
requests.first().and_then(|req| {
if let RequestKind::Single(req) = req {
Some(Rc::new(RefCell::new(req.clone())))
Expand All @@ -185,7 +186,7 @@ impl<'ae> ApiExplorer<'ae> {
colors,
config,

editor: ReqEditor::new(colors, selected_request.clone(), layout.req_editor, config),
editor: ReqEditor::new(colors, selected_request.as_mut(), layout.req_editor, config),
editor_tab: ReqEditorTabs::Request,

res_viewer: ResViewer::new(colors, None),
Expand All @@ -206,6 +207,7 @@ impl<'ae> ApiExplorer<'ae> {
curr_overlay: Overlays::None,
create_req_form_state: CreateReqFormState::default(),

sync_interval: std::time::Instant::now(),
response_rx,
request_tx,
layout,
Expand All @@ -230,7 +232,7 @@ impl<'ae> ApiExplorer<'ae> {
self.selected_request = Some(Rc::new(RefCell::new(req.clone())));
self.editor = ReqEditor::new(
self.colors,
self.selected_request.clone(),
self.selected_request.as_mut(),
self.layout.req_editor,
self.config,
);
Expand Down Expand Up @@ -746,6 +748,7 @@ impl<'ae> ApiExplorer<'ae> {
let form_state = &self.create_req_form_state;
let new_request = match form_state.req_kind {
CreateReqKind::Request => RequestKind::Single(Request {
id: uuid::Uuid::new_v4().to_string(),
name: form_state.req_name.clone(),
method: form_state.method.clone(),
uri: String::default(),
Expand All @@ -758,19 +761,58 @@ impl<'ae> ApiExplorer<'ae> {
}),
};

if let Some(requests) = self.schema.requests.as_mut() {
requests.push(new_request);
}
self.schema
.requests
.get_or_insert_with(Vec::new)
.push(new_request);

self.create_req_form_state = CreateReqFormState::default();
self.curr_overlay = Overlays::None;

self.sync_schema_changes();
}

fn sync_schema_changes(&mut self) {
let sender = self
.sender
.as_ref()
.expect("should have a sender at this point")
.clone();
let schema = self.schema.clone();
self.create_req_form_state = CreateReqFormState::default();
self.curr_overlay = Overlays::None;

let mut schema = self.schema.clone();
if let Some(request) = self.selected_request.as_ref() {
let mut request = request.borrow().clone();
let body = self.editor.body().to_string();
if !body.is_empty() {
request.body = Some(body);
request.body_type = Some("application/json".into());
}

schema
.requests
.as_mut()
.expect("no requests on schema, but we have a selected request")
.iter_mut()
.for_each(|other| match other {
RequestKind::Single(inner) => {
tracing::debug!("inner: {}, request: {}", inner.id, request.id);
request
.id
.eq(&inner.id)
.then(|| std::mem::swap(inner, &mut request));
}
RequestKind::Nested(dir) => dir.requests.iter_mut().for_each(|other| {
if let RequestKind::Single(inner) = other {
request
.id
.eq(&inner.id)
.then(|| std::mem::swap(inner, &mut request));
}
}),
});
}

self.sync_interval = std::time::Instant::now();
tokio::spawn(async move {
match reqtui::fs::sync_schema(schema).await {
Ok(_) => {}
Expand Down Expand Up @@ -838,6 +880,13 @@ impl Component for ApiExplorer<'_> {
Ok(())
}

fn handle_tick(&mut self) -> anyhow::Result<()> {
if self.sync_interval.elapsed().as_secs().ge(&5) {
self.sync_schema_changes();
}
Ok(())
}

fn register_command_handler(&mut self, sender: UnboundedSender<Command>) -> anyhow::Result<()> {
self.sender = Some(sender);
Ok(())
Expand Down Expand Up @@ -1035,6 +1084,7 @@ mod tests {

fn create_root_one() -> RequestKind {
RequestKind::Single(Request {
id: "any id".to_string(),
method: RequestMethod::Get,
name: "Root1".to_string(),
uri: "/root1".to_string(),
Expand All @@ -1045,6 +1095,7 @@ mod tests {

fn create_child_one() -> RequestKind {
RequestKind::Single(Request {
id: "any id".to_string(),
method: RequestMethod::Post,
name: "Child1".to_string(),
uri: "/nested1/child1".to_string(),
Expand All @@ -1055,6 +1106,7 @@ mod tests {

fn create_child_two() -> RequestKind {
RequestKind::Single(Request {
id: "any id".to_string(),
method: RequestMethod::Put,
name: "Child2".to_string(),
uri: "/nested1/child2".to_string(),
Expand All @@ -1065,6 +1117,7 @@ mod tests {

fn create_not_used() -> RequestKind {
RequestKind::Single(Request {
id: "any id".to_string(),
method: RequestMethod::Put,
name: "NotUsed".to_string(),
uri: "/not/used".to_string(),
Expand All @@ -1086,6 +1139,7 @@ mod tests {

fn create_root_two() -> RequestKind {
RequestKind::Single(Request {
id: "any id".to_string(),
method: RequestMethod::Delete,
name: "Root2".to_string(),
uri: "/root2".to_string(),
Expand Down
6 changes: 5 additions & 1 deletion tui/src/components/api_explorer/req_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub struct ReqEditor<'re> {
impl<'re> ReqEditor<'re> {
pub fn new(
colors: &'re colors::Colors,
request: Option<Rc<RefCell<Request>>>,
request: Option<&mut Rc<RefCell<Request>>>,
size: Rect,
config: &'re config::Config,
) -> Self {
Expand Down Expand Up @@ -140,6 +140,10 @@ impl<'re> ReqEditor<'re> {
}
}

pub fn body(&self) -> &TextObject<Write> {
&self.body
}

pub fn layout(&self) -> &ReqEditorLayout {
&self.layout
}
Expand Down
2 changes: 0 additions & 2 deletions tui/src/components/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ impl StatefulWidget for Input<'_> {

#[cfg(test)]
mod tests {
use ratatui::widgets::BorderType;

use super::*;

#[test]
Expand Down
Loading

0 comments on commit 86b02d7

Please sign in to comment.