openapi-tui 0.10.2

This TUI allows you to list and browse APIs described by the openapi specification.
use std::collections::VecDeque;

use color_eyre::eyre::Result;
use ratatui::{
  prelude::*,
  widgets::{block::*, *},
};

use crate::{
  action::Action,
  pages::phone::{RequestBuilder, RequestPane},
  panes::Pane,
  state::{OperationItemType, State},
  tui::Frame,
};

#[derive(Default)]
pub struct AddressPane {
  focused: bool,
  focused_border_style: Style,
  base_urls: VecDeque<String>,
}

impl AddressPane {
  pub fn new(focused: bool, focused_border_style: Style) -> Self {
    Self { focused, focused_border_style, base_urls: VecDeque::new() }
  }

  fn border_style(&self) -> Style {
    match self.focused {
      true => self.focused_border_style,
      false => Style::default(),
    }
  }

  fn border_type(&self) -> BorderType {
    match self.focused {
      true => BorderType::Thick,
      false => BorderType::Plain,
    }
  }

  fn method_color(method: &str) -> Color {
    match method {
      "GET" => Color::LightCyan,
      "POST" => Color::LightBlue,
      "PUT" => Color::LightYellow,
      "DELETE" => Color::LightRed,
      _ => Color::Gray,
    }
  }
}

impl RequestPane for AddressPane {}

impl RequestBuilder for AddressPane {
  fn path(&self, url: String) -> String {
    format!("{}{}", self.base_urls.front().cloned().unwrap_or_default(), url)
  }
}

impl Pane for AddressPane {
  fn height_constraint(&self) -> Constraint {
    Constraint::Max(3)
  }

  fn init(&mut self, state: &State) -> Result<()> {
    self.base_urls = state.default_server_urls(&None).into();
    Ok(())
  }

  fn update(&mut self, action: Action, _state: &mut State) -> Result<Option<Action>> {
    match action {
      Action::Focus => {
        self.focused = true;
        static STATUS_LINE: &str = "[ENTER → request]";
        return Ok(Some(Action::TimedStatusLine(STATUS_LINE.into(), 3)));
      },
      Action::UnFocus => {
        self.focused = false;
      },
      Action::Up => {
        if let Some(front) = self.base_urls.pop_front() {
          self.base_urls.push_back(front.to_string());
        }
      },
      Action::Down => {
        if let Some(back) = self.base_urls.pop_back() {
          self.base_urls.push_front(back.to_string());
        }
      },
      Action::Update => {},
      Action::Submit => {},

      _ => {},
    }
    Ok(None)
  }

  fn draw(&mut self, frame: &mut Frame<'_>, area: Rect, state: &State) -> Result<()> {
    if let Some(operation_item) = state.active_operation() {
      let base_url = self.base_urls.front().cloned().unwrap_or(String::new());
      let title = operation_item.operation.summary.clone().unwrap_or_default();

      let inner = area.inner(Margin { horizontal: 1, vertical: 1 });
      frame.render_widget(
        match operation_item.r#type {
          OperationItemType::Path => Paragraph::new(Line::from(vec![
            Span::styled(
              format!("{:7}", operation_item.method.as_str()),
              Style::default().fg(Self::method_color(operation_item.method.as_str())),
            ),
            Span::styled(base_url, Style::default().fg(Color::DarkGray)),
            Span::styled(&operation_item.path, Style::default().fg(Color::White)),
          ])),
          OperationItemType::Webhook => Paragraph::new(Line::from(vec![
            Span::styled("EVENT ", Style::default().fg(Color::LightMagenta)),
            Span::styled(
              format!("{} ", operation_item.method.as_str()),
              Style::default().fg(Self::method_color(operation_item.method.as_str())),
            ),
            Span::styled(&operation_item.path, Style::default().fg(Color::White)),
          ])),
        },
        inner,
      );

      frame.render_widget(
        Block::default()
          .title(title)
          .borders(Borders::ALL)
          .border_style(self.border_style())
          .border_type(self.border_type()),
        area,
      );
    } else {
      frame.render_widget(
        Block::default()
          .title("[No Active API]")
          .borders(Borders::ALL)
          .border_style(self.border_style())
          .border_type(self.border_type()),
        area,
      );
    }

    Ok(())
  }
}