From d2aa4bdd2b25091f2c51f427c7d36ccced8199ac Mon Sep 17 00:00:00 2001 From: wiru Date: Mon, 10 Jun 2024 23:45:17 -0300 Subject: [PATCH] feat: redesign of create request form --- .../collection_viewer/collection_viewer.rs | 126 +++------ .../pages/collection_viewer/request_editor.rs | 3 +- .../headers_editor_edit_form.rs | 13 +- .../src/pages/collection_viewer/sidebar.rs | 75 ++++-- .../sidebar/create_request_form.rs | 255 ++++++++++++++++++ hac-client/src/pages/input.rs | 3 +- hac-client/src/utils.rs | 27 ++ hac-core/src/collection/types.rs | 44 ++- hac-core/src/fs/fs.rs | 10 +- 9 files changed, 427 insertions(+), 129 deletions(-) create mode 100644 hac-client/src/pages/collection_viewer/sidebar/create_request_form.rs diff --git a/hac-client/src/pages/collection_viewer/collection_viewer.rs b/hac-client/src/pages/collection_viewer/collection_viewer.rs index 3b420a1..40f2393 100755 --- a/hac-client/src/pages/collection_viewer/collection_viewer.rs +++ b/hac-client/src/pages/collection_viewer/collection_viewer.rs @@ -3,10 +3,10 @@ use hac_core::command::Command; use hac_core::net::request_manager::Response; use crate::pages::collection_viewer::collection_store::{CollectionStore, CollectionStoreAction}; -use crate::pages::collection_viewer::request_editor::RequestEditor; -use crate::pages::collection_viewer::request_uri::RequestUri; -use crate::pages::collection_viewer::response_viewer::ResponseViewer; -use crate::pages::collection_viewer::sidebar::Sidebar; +use crate::pages::collection_viewer::request_editor::{RequestEditor, RequestEditorEvent}; +use crate::pages::collection_viewer::request_uri::{RequestUri, RequestUriEvent}; +use crate::pages::collection_viewer::response_viewer::{ResponseViewer, ResponseViewerEvent}; +use crate::pages::collection_viewer::sidebar::{self, Sidebar, SidebarEvent}; use crate::pages::input::Input; use crate::pages::overlay::draw_overlay; use crate::pages::{Eventful, Renderable}; @@ -24,11 +24,6 @@ use ratatui::widgets::{Block, Borders, Clear, Padding, Paragraph}; use ratatui::Frame; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; -use super::request_editor::RequestEditorEvent; -use super::request_uri::RequestUriEvent; -use super::response_viewer::ResponseViewerEvent; -use super::sidebar::{self, SidebarEvent}; - #[derive(Debug, PartialEq)] pub struct ExplorerLayout { pub hint_pane: Rect, @@ -43,6 +38,7 @@ pub struct ExplorerLayout { pub enum CollectionViewerOverlay { None, CreateRequest, + CreateDirectory, RequestMethod, HeadersHelp, HeadersDelete, @@ -168,33 +164,33 @@ impl<'cv> CollectionViewer<'cv> { let layout = build_layout(size); let (request_tx, response_rx) = unbounded_channel::(); - CollectionViewer { - request_editor: RequestEditor::new( - colors, - config, - collection_store.clone(), - layout.req_editor, - ), - response_viewer: ResponseViewer::new( - colors, - collection_store.clone(), - None, - layout.response_preview, - ), - sidebar: sidebar::Sidebar::new(colors, collection_store.clone()), - request_uri: RequestUri::new(colors, collection_store.clone(), layout.req_uri), + let sidebar = sidebar::Sidebar::new(colors, collection_store.clone()); + + let request_editor = + RequestEditor::new(colors, config, collection_store.clone(), layout.req_editor); + let response_viewer = ResponseViewer::new( + colors, + collection_store.clone(), + None, + layout.response_preview, + ); + + let request_uri = RequestUri::new(colors, collection_store.clone(), layout.req_uri); + + CollectionViewer { + request_editor, + response_viewer, + sidebar, + request_uri, colors, layout, global_command_sender: None, collection_sync_timer: std::time::Instant::now(), - create_req_form_state: CreateReqFormState::default(), - responses_map: HashMap::default(), response_rx, request_tx, - dry_run, collection_store, } @@ -223,62 +219,6 @@ impl<'cv> CollectionViewer<'cv> { } } - fn draw_req_uri_hint(&self, frame: &mut Frame) { - let hint = "[type anything -> edit] [enter -> execute request] [ -> quit] [tab -> switch pane]" - .fg(self.colors.normal.magenta) - .into_centered_line(); - - frame.render_widget(hint, self.layout.hint_pane); - } - - fn draw_sidebar_hint(&self, frame: &mut Frame) { - let hint = - "[j/k -> navigate] [enter -> select item] [n -> create request] [? -> help] [ -> quit] [tab -> switch pane]" - .fg(self.colors.normal.magenta) - .into_centered_line(); - - frame.render_widget(hint, self.layout.hint_pane); - } - - fn draw_preview_hint(&self, frame: &mut Frame) { - let hint = match self - .collection_store.borrow() - .get_selected_pane() - .as_ref() - .is_some_and(|selected| selected.eq(&PaneFocus::Preview)) - { - false => "[j/k -> scroll] [enter -> interact] [? -> help] [ -> quit] [tab -> switch pane]" - .fg(self.colors.normal.magenta) - .into_centered_line(), - true => { - "[j/k -> scroll] [esc -> deselect] [tab -> switch tab] [? -> help] [ -> quit] [tab -> switch tab]" - .fg(self.colors.normal.magenta) - .into_centered_line() - } - }; - - frame.render_widget(hint, self.layout.hint_pane); - } - - fn draw_editor_hint(&self, frame: &mut Frame) { - let hint = match self - .collection_store - .borrow() - .get_selected_pane() - .as_ref() - .is_some_and(|selected| selected.eq(&PaneFocus::Editor)) - { - false => "[enter -> interact] [? -> help] [ -> quit] [tab -> switch pane]" - .fg(self.colors.normal.magenta) - .into_centered_line(), - true => "[esc -> deselect] [tab -> switch tab] [? -> help] [ -> quit] [tab -> switch tab]" - .fg(self.colors.normal.magenta) - .into_centered_line(), - }; - - frame.render_widget(hint, self.layout.hint_pane); - } - fn draw_create_request_form(&mut self, frame: &mut Frame) { let size = self.layout.create_req_form; let item_height = 3; @@ -675,7 +615,12 @@ impl Renderable for CollectionViewer<'_> { let overlay = self.collection_store.borrow().peek_overlay(); match overlay { - CollectionViewerOverlay::CreateRequest => self.draw_create_request_form(frame), + CollectionViewerOverlay::CreateRequest => { + self.sidebar.draw_overlay(frame, overlay)?; + } + CollectionViewerOverlay::CreateDirectory => { + self.sidebar.draw_overlay(frame, overlay)?; + } CollectionViewerOverlay::RequestMethod => self.draw_request_method_form(frame), CollectionViewerOverlay::HeadersHelp => { self.request_editor.draw_overlay(frame, overlay)? @@ -803,11 +748,14 @@ impl Eventful for CollectionViewer<'_> { if let Some(curr_pane) = selected_pane { match curr_pane { PaneFocus::Sidebar => match self.sidebar.handle_key_event(key_event)? { - Some(SidebarEvent::CreateRequest) => { - self.collection_store - .borrow_mut() - .push_overlay(CollectionViewerOverlay::CreateRequest); - } + Some(SidebarEvent::CreateRequest) => self + .collection_store + .borrow_mut() + .push_overlay(CollectionViewerOverlay::CreateRequest), + Some(SidebarEvent::CreateDirectory) => self + .collection_store + .borrow_mut() + .push_overlay(CollectionViewerOverlay::CreateDirectory), Some(SidebarEvent::RemoveSelection) => self.update_selection(None), Some(SidebarEvent::Quit) => return Ok(Some(Command::Quit)), // when theres no event we do nothing diff --git a/hac-client/src/pages/collection_viewer/request_editor.rs b/hac-client/src/pages/collection_viewer/request_editor.rs index 33c3c24..7cec024 100755 --- a/hac-client/src/pages/collection_viewer/request_editor.rs +++ b/hac-client/src/pages/collection_viewer/request_editor.rs @@ -12,6 +12,7 @@ use hac_core::text_object::{TextObject, Write}; use headers_editor::{HeadersEditor, HeadersEditorEvent}; use crate::pages::collection_viewer::collection_store::CollectionStore; +use crate::pages::collection_viewer::collection_viewer::{CollectionViewerOverlay, PaneFocus}; use crate::pages::under_construction::UnderConstruction; use crate::pages::Eventful; use crate::pages::Renderable; @@ -28,8 +29,6 @@ use ratatui::style::{Style, Stylize}; use ratatui::widgets::{Block, Borders, Tabs}; use ratatui::Frame; -use super::collection_viewer::{CollectionViewerOverlay, PaneFocus}; - /// set of possible events the edtior can send to the parent #[derive(Debug)] pub enum RequestEditorEvent { diff --git a/hac-client/src/pages/collection_viewer/request_editor/headers_editor_edit_form.rs b/hac-client/src/pages/collection_viewer/request_editor/headers_editor_edit_form.rs index d38e74c..ce8392a 100644 --- a/hac-client/src/pages/collection_viewer/request_editor/headers_editor_edit_form.rs +++ b/hac-client/src/pages/collection_viewer/request_editor/headers_editor_edit_form.rs @@ -6,7 +6,7 @@ use crate::pages::overlay::make_overlay; use crate::pages::{Eventful, Renderable}; use std::cell::RefCell; -use std::ops::{Add, Div}; +use std::ops::{Add, Div, Sub}; use std::rc::Rc; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; @@ -135,7 +135,7 @@ impl Renderable for HeadersEditorForm<'_> { let total_size = logo_size.add(11).add(2); let mut size = Rect::new( - size.width.div(2).saturating_sub(25), + size.width.div(2).sub(25), size.height .div(2) .saturating_sub(logo_size.div(2)) @@ -151,13 +151,18 @@ impl Renderable for HeadersEditorForm<'_> { size.y = size.height.div(2).saturating_sub(5); } - let name_input = Input::new(self.colors, "Name".into()); - let value_input = Input::new(self.colors, "Value".into()); + let mut name_input = Input::new(self.colors, "Name".into()); + let mut value_input = Input::new(self.colors, "Value".into()); let hint = Paragraph::new( "Press enter to confirm, press esc to cancel".fg(self.colors.bright.black), ) .centered(); + match self.focused_input { + HeadersEditorFormInput::Name => name_input.focus(), + HeadersEditorFormInput::Value => value_input.focus(), + } + if !logo.is_empty() { let logo = logo .iter() diff --git a/hac-client/src/pages/collection_viewer/sidebar.rs b/hac-client/src/pages/collection_viewer/sidebar.rs index a3f4dfa..1611a40 100755 --- a/hac-client/src/pages/collection_viewer/sidebar.rs +++ b/hac-client/src/pages/collection_viewer/sidebar.rs @@ -1,6 +1,11 @@ +mod create_request_form; + use hac_core::collection::types::{Request, RequestKind, RequestMethod}; use crate::pages::collection_viewer::collection_store::{CollectionStore, CollectionStoreAction}; +use crate::pages::collection_viewer::collection_viewer::{CollectionViewerOverlay, PaneFocus}; +use crate::pages::collection_viewer::sidebar::create_request_form::CreateRequestForm; +use crate::pages::collection_viewer::sidebar::create_request_form::CreateRequestFormEvent; use crate::pages::{Eventful, Renderable}; use std::cell::RefCell; @@ -15,14 +20,15 @@ use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders, Paragraph}; use ratatui::Frame; -use super::collection_viewer::PaneFocus; - /// set of events Sidebar can emit to the caller when handling events. #[derive(Debug)] pub enum SidebarEvent { - /// user pressed CreateRequest hotkey, which should notify the caller to open + /// user pressed `CreateRequest (n)` hotkey, which should notify the caller to open /// the create_request_form and properly handle the creation of a new request CreateRequest, + /// user pressed `CreateDirectory (d)` hotkey, which should notify the caller to open + /// the `create_directory_form` overlay to create a new directory on the collection + CreateDirectory, /// user pressed `Esc` so we notify the caller to remove the selection from /// this pane, essentially bubbling the key handling scope to the caller RemoveSelection, @@ -36,6 +42,7 @@ pub struct Sidebar<'sbar> { colors: &'sbar hac_colors::Colors, lines: Vec>, collection_store: Rc>, + create_request_form: CreateRequestForm<'sbar>, } impl<'sbar> Sidebar<'sbar> { @@ -45,6 +52,7 @@ impl<'sbar> Sidebar<'sbar> { ) -> Self { let mut sidebar = Self { colors, + create_request_form: CreateRequestForm::new(colors, collection_store.clone()), lines: vec![], collection_store, }; @@ -65,6 +73,22 @@ impl<'sbar> Sidebar<'sbar> { self.colors, ); } + + pub fn draw_overlay( + &mut self, + frame: &mut Frame, + overlay: CollectionViewerOverlay, + ) -> anyhow::Result<()> { + match overlay { + CollectionViewerOverlay::CreateRequest => { + self.create_request_form.draw(frame, frame.size())?; + } + CollectionViewerOverlay::CreateDirectory => {} + _ => {} + }; + + Ok(()) + } } impl<'sbar> Renderable for Sidebar<'sbar> { @@ -123,33 +147,44 @@ impl<'a> Eventful for Sidebar<'a> { "handled an event to the sidebar while it was not selected" ); + let mut store = self.collection_store.borrow_mut(); + + match store.peek_overlay() { + CollectionViewerOverlay::CreateRequest => { + match self.create_request_form.handle_key_event(key_event)? { + Some(CreateRequestFormEvent::Confirm) => return Ok(None), + Some(CreateRequestFormEvent::Cancel) => _ = store.pop_overlay(), + None => return Ok(None), + } + } + CollectionViewerOverlay::CreateDirectory => todo!(), + _ => {} + }; + if let (KeyCode::Char('c'), KeyModifiers::CONTROL) = (key_event.code, key_event.modifiers) { return Ok(Some(SidebarEvent::Quit)); } - let mut store = self.collection_store.borrow_mut(); match key_event.code { KeyCode::Enter => { - if store.get_hovered_request().is_some() && store.get_requests().is_some() { - let request = store.find_hovered_request(); - match request { - RequestKind::Nested(_) => { - store - .dispatch(CollectionStoreAction::ToggleDirectory(request.get_id())); - } - RequestKind::Single(req) => { - store.dispatch(CollectionStoreAction::SetSelectedRequest(Some(req))); - } + if store.get_requests().is_none() || store.get_hovered_request().is_none() { + return Ok(None); + } + + let request = store.find_hovered_request(); + match request { + RequestKind::Nested(_) => { + store.dispatch(CollectionStoreAction::ToggleDirectory(request.get_id())); + } + RequestKind::Single(req) => { + store.dispatch(CollectionStoreAction::SetSelectedRequest(Some(req))); } } } - KeyCode::Char('j') | KeyCode::Down => { - store.dispatch(CollectionStoreAction::HoverNext); - } - KeyCode::Char('k') | KeyCode::Up => { - store.dispatch(CollectionStoreAction::HoverPrev); - } + KeyCode::Char('j') | KeyCode::Down => store.dispatch(CollectionStoreAction::HoverNext), + KeyCode::Char('k') | KeyCode::Up => store.dispatch(CollectionStoreAction::HoverPrev), KeyCode::Char('n') => return Ok(Some(SidebarEvent::CreateRequest)), + KeyCode::Char('d') => return Ok(Some(SidebarEvent::CreateDirectory)), KeyCode::Esc => return Ok(Some(SidebarEvent::RemoveSelection)), _ => {} } diff --git a/hac-client/src/pages/collection_viewer/sidebar/create_request_form.rs b/hac-client/src/pages/collection_viewer/sidebar/create_request_form.rs new file mode 100644 index 0000000..4848cfc --- /dev/null +++ b/hac-client/src/pages/collection_viewer/sidebar/create_request_form.rs @@ -0,0 +1,255 @@ +use hac_core::collection::types::RequestMethod; + +use crate::ascii::LOGO_ASCII; +use crate::pages::collection_viewer::collection_store::CollectionStore; +use crate::pages::input::Input; +use crate::pages::overlay::make_overlay; +use crate::pages::{Eventful, Renderable}; + +use std::cell::RefCell; +use std::ops::{Add, Div, Sub}; +use std::rc::Rc; + +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use rand::Rng; +use ratatui::layout::{Constraint, Direction, Layout, Rect}; +use ratatui::style::Stylize; +use ratatui::text::Line; +use ratatui::widgets::{Block, Borders, Paragraph}; +use ratatui::Frame; + +#[derive(Debug, PartialEq)] +enum FormField { + Name, + Method, + Parent, +} + +impl FormField { + fn next(&self) -> Self { + match self { + FormField::Name => FormField::Method, + FormField::Method => FormField::Parent, + FormField::Parent => FormField::Name, + } + } + + fn prev(&self) -> Self { + match self { + FormField::Name => FormField::Parent, + FormField::Method => FormField::Name, + FormField::Parent => FormField::Method, + } + } +} + +#[derive(Debug)] +pub struct CreateRequestForm<'crf> { + colors: &'crf hac_colors::Colors, + collection_store: Rc>, + logo_idx: usize, + request_name: String, + request_method: RequestMethod, + /// we store the parent dir uuid so its easier to find it. + parent_dir: Option, + focused_field: FormField, +} + +impl<'crf> CreateRequestForm<'crf> { + pub fn new( + colors: &'crf hac_colors::Colors, + collection_store: Rc>, + ) -> Self { + let logo_idx = rand::thread_rng().gen_range(0..LOGO_ASCII.len()); + + CreateRequestForm { + colors, + collection_store, + logo_idx, + request_name: String::default(), + request_method: RequestMethod::Get, + parent_dir: None, + focused_field: FormField::Name, + } + } + + fn reset(&mut self) { + self.request_name = String::default(); + self.request_method = RequestMethod::Get; + self.parent_dir = None; + } +} + +impl Renderable for CreateRequestForm<'_> { + fn draw(&mut self, frame: &mut Frame, _: Rect) -> anyhow::Result<()> { + make_overlay(self.colors, self.colors.normal.black, 0.1, frame); + + let mut logo = LOGO_ASCII[self.logo_idx]; + let mut logo_size = logo.len() as u16; + // adding size of the form + spacing + hint + let total_size = logo_size.add(11).add(2); + + let size = frame.size(); + let mut size = Rect::new( + size.width.div(2).sub(32), + size.height + .div(2) + .saturating_sub(logo_size.div(2)) + .saturating_sub(6), + 65, + logo_size.add(12), + ); + + if total_size.ge(&frame.size().height) { + logo = &[]; + logo_size = 0; + size.height = 12; + size.y = size.height.div(2).saturating_sub(5); + } + + if !logo.is_empty() { + let logo = logo + .iter() + .map(|line| Line::from(line.fg(self.colors.normal.red)).centered()) + .collect::>(); + + let logo_size = Rect::new(size.x, size.y, size.width, logo_size); + frame.render_widget(Paragraph::new(logo), logo_size); + } + + let mut name_input = Input::new(self.colors, "Name".into()); + let method_title = Paragraph::new("Method"); + let hint = Paragraph::new( + "[Confirm: Enter] [Cancel: Esc] [Switch field: Tab]".fg(self.colors.bright.black), + ) + .centered(); + + if self.focused_field.eq(&FormField::Name) { + name_input.focus(); + } + + let name_size = Rect::new(size.x, size.y.add(logo_size).add(1), size.width, 3); + let method_title_size = Rect::new(size.x, name_size.y.add(3), size.width, 1); + let methods_size = Rect::new(size.x, method_title_size.y.add(1), size.width, 3); + let parent_size = Rect::new(size.x, methods_size.y.add(3), size.width, 3); + let hint_size = Rect::new(size.x, parent_size.y.add(4), size.width, 1); + + let methods_items = Layout::default() + .direction(Direction::Horizontal) + .constraints((0..5).map(|_| Constraint::Length(13))) + .split(methods_size); + + let parent_name = format!( + "{}None{}", + " ".repeat(parent_size.width.div(2).sub(2).into()), + " ".repeat(parent_size.width.div(2).sub(2).into()) + ); + let parent = Paragraph::new(parent_name) + .centered() + .block( + Block::default() + .title("Parent".fg(self.colors.normal.white)) + .borders(Borders::ALL) + .fg(if self.focused_field.eq(&FormField::Parent) { + self.colors.normal.red + } else { + self.colors.bright.black + }), + ) + .fg(self.colors.bright.black); + + for (idx, method) in RequestMethod::iter().enumerate() { + let border_color = match (&self.request_method, &self.focused_field) { + (m, FormField::Method) if m.eq(method) => self.colors.normal.red, + (m, _) if m.eq(method) => self.colors.bright.blue, + _ => self.colors.bright.black, + }; + let method = Paragraph::new(Line::from(vec![ + format!(" {}", idx.add(1)).fg(self.colors.bright.black), + format!( + " {}{}", + method, + " ".repeat(10.sub(method.to_string().len())) + ) + .fg(self.colors.normal.white), + ])) + .block(Block::default().borders(Borders::ALL).fg(border_color)); + frame.render_widget(method, methods_items[idx]); + } + + frame.render_stateful_widget(name_input, name_size, &mut self.request_name); + frame.render_widget(method_title, method_title_size); + frame.render_widget(parent, parent_size); + frame.render_widget(hint, hint_size); + + Ok(()) + } +} + +pub enum CreateRequestFormEvent { + Confirm, + Cancel, +} + +impl Eventful for CreateRequestForm<'_> { + type Result = CreateRequestFormEvent; + + #[tracing::instrument(skip_all, err)] + fn handle_key_event(&mut self, key_event: KeyEvent) -> anyhow::Result> { + tracing::debug!("{key_event:#?}"); + + if let KeyCode::Tab = key_event.code { + self.focused_field = self.focused_field.next(); + return Ok(None); + } + + if let KeyCode::BackTab = key_event.code { + self.focused_field = self.focused_field.prev(); + return Ok(None); + } + + if let KeyCode::Enter = key_event.code { + self.reset(); + return Ok(Some(CreateRequestFormEvent::Confirm)); + } + + if let KeyCode::Esc = key_event.code { + self.reset(); + return Ok(Some(CreateRequestFormEvent::Cancel)); + } + + if let (KeyCode::Char('c'), KeyModifiers::CONTROL) = (key_event.code, key_event.modifiers) { + self.reset(); + return Ok(Some(CreateRequestFormEvent::Cancel)); + } + + match self.focused_field { + FormField::Name => match key_event.code { + KeyCode::Char(c) => { + self.request_name.push(c); + } + KeyCode::Backspace => { + self.request_name.pop(); + } + _ => {} + }, + FormField::Method => match key_event.code { + KeyCode::Char(c @ '1'..='5') => { + self.request_method = (c.to_digit(10).unwrap() as usize).sub(1).try_into()?; + } + KeyCode::Left => self.request_method = self.request_method.prev(), + KeyCode::Down => self.request_method = 4.try_into()?, + KeyCode::Up => self.request_method = 0.try_into()?, + KeyCode::Right => self.request_method = self.request_method.next(), + KeyCode::Char('h') => self.request_method = self.request_method.prev(), + KeyCode::Char('j') => self.request_method = 4.try_into()?, + KeyCode::Char('k') => self.request_method = 0.try_into()?, + KeyCode::Char('l') => self.request_method = self.request_method.next(), + _ => {} + }, + FormField::Parent => {} + } + + Ok(None) + } +} diff --git a/hac-client/src/pages/input.rs b/hac-client/src/pages/input.rs index ff4794c..4292940 100755 --- a/hac-client/src/pages/input.rs +++ b/hac-client/src/pages/input.rs @@ -3,7 +3,6 @@ use ratatui::layout::Rect; use ratatui::style::{Style, Styled}; use ratatui::widgets::{Block, Borders, Paragraph, StatefulWidget, Widget}; - /// input component used in forms and everywhere else that the user can /// input text to a single, named field pub struct Input<'a> { @@ -38,7 +37,7 @@ impl<'a> Input<'a> { fn build_input(&self, value: String, size: Rect) -> Paragraph<'_> { let border_color = if self.focused { - Style::default().fg(self.colors.bright.magenta) + Style::default().fg(self.colors.normal.red) } else { Style::default().fg(self.colors.primary.hover) }; diff --git a/hac-client/src/utils.rs b/hac-client/src/utils.rs index 7cba109..1caf873 100755 --- a/hac-client/src/utils.rs +++ b/hac-client/src/utils.rs @@ -217,3 +217,30 @@ fn ansi_to_rgb(val: u8) -> Option<(u8, u8, u8)> { None } } + +pub trait EnumIter { + fn iter() -> &'static [Self] + where + Self: Sized; + + fn len() -> usize + where + Self: Sized; +} + +#[macro_export] +macro_rules! impl_enum_iter { + ($name:ident { $($variant:ident),* $(,)? }) => { + impl $name { + pub const fn iter() -> &'static [$name] { + &[ + $( $name::$variant ),* + ] + } + + pub const fn len() -> usize { + $name::iter().len() + } + } + }; +} diff --git a/hac-core/src/collection/types.rs b/hac-core/src/collection/types.rs index 819f10a..fd4abf5 100755 --- a/hac-core/src/collection/types.rs +++ b/hac-core/src/collection/types.rs @@ -57,6 +57,21 @@ pub enum RequestMethod { Delete, } +impl TryFrom for RequestMethod { + type Error = anyhow::Error; + + fn try_from(value: usize) -> anyhow::Result { + match value { + 0 => Ok(RequestMethod::Get), + 1 => Ok(RequestMethod::Post), + 2 => Ok(RequestMethod::Put), + 3 => Ok(RequestMethod::Patch), + 4 => Ok(RequestMethod::Delete), + _ => anyhow::bail!("invalid request method index"), + } + } +} + impl RequestMethod { pub fn next(&self) -> Self { match self { @@ -67,13 +82,23 @@ impl RequestMethod { RequestMethod::Delete => RequestMethod::Get, } } + + pub fn prev(&self) -> Self { + match self { + RequestMethod::Get => RequestMethod::Delete, + RequestMethod::Post => RequestMethod::Get, + RequestMethod::Put => RequestMethod::Post, + RequestMethod::Patch => RequestMethod::Put, + RequestMethod::Delete => RequestMethod::Patch, + } + } } impl std::fmt::Display for RequestMethod { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Post => f.write_str("POST"), Self::Get => f.write_str("GET"), + Self::Post => f.write_str("POST"), Self::Put => f.write_str("PUT"), Self::Patch => f.write_str("PATCH"), Self::Delete => f.write_str("DELETE"), @@ -82,14 +107,15 @@ impl std::fmt::Display for RequestMethod { } impl RequestMethod { - pub fn len(&self) -> Option { - match self { - RequestMethod::Get => Some(RequestMethod::Post), - RequestMethod::Post => Some(RequestMethod::Put), - RequestMethod::Put => Some(RequestMethod::Patch), - RequestMethod::Patch => Some(RequestMethod::Delete), - RequestMethod::Delete => None, - } + pub fn iter() -> std::slice::Iter<'static, RequestMethod> { + [ + RequestMethod::Get, + RequestMethod::Post, + RequestMethod::Put, + RequestMethod::Patch, + RequestMethod::Delete, + ] + .iter() } } diff --git a/hac-core/src/fs/fs.rs b/hac-core/src/fs/fs.rs index 20be763..4f1f9db 100755 --- a/hac-core/src/fs/fs.rs +++ b/hac-core/src/fs/fs.rs @@ -1,10 +1,14 @@ use crate::collection::{collection::create_from_form, Collection}; use crate::fs::error::FsError; -use std::path::PathBuf; +use std::path::Path; -#[tracing::instrument(err)] -pub async fn delete_collection(path: &PathBuf) -> anyhow::Result<(), FsError> { +#[tracing::instrument(err, skip_all)] +pub async fn delete_collection

(path: P) -> anyhow::Result<(), FsError> +where + P: AsRef, +{ + let path = path.as_ref(); tokio::fs::remove_file(path) .await .map_err(|_| FsError::IOError(format!("failed to delete collection: {:?}", path)))?;