fips 1.0.1

A powerful mock server supported by a plugin system
Documentation
use crossterm::{
    event::{self, DisableMouseCapture, EnableMouseCapture, Event as CEvent},
    execute,
    terminal::{
        disable_raw_mode, enable_raw_mode, EnterAlternateScreen,
        LeaveAlternateScreen,
    },
};
use gradient_tui_fork::{
    backend::CrosstermBackend,
    style::{Color, Modifier, Style},
    text::{Span, Spans},
    widgets::List,
    Terminal,
};
use std::{
    io::stdout,
    sync::mpsc,
    thread,
    time::{Duration, Instant},
};

enum Event<I> {
    Input(I),
    Tick,
}

use crate::{
    configuration::configuration::Config,
    terminal_ui::{
        cli::{
            config_newtype::{AsyncFrom, ConfigurationNewtype},
            ui, App,
        },
        debug::{LoggableNT, PrintInfo},
        state::State,
        util,
    },
    utility::{
        log::{Loggable, LoggableType},
        options::CliOptions,
    },
    PaintLogsCallbacks,
};
use eyre::{ContextCompat, Result};
use std::panic;
use std::sync::{Arc, Mutex};
use thiserror::Error;
use tokio::runtime::Runtime;
use tokio::sync::Mutex as AsyncMutex;

#[derive(Error, Debug)]
pub enum FrontendError {
    #[error("Failed to start frontend due to io error")]
    GenericFrontend(#[from] std::io::Error),
    #[error("Failed to start frontend due to channel error")]
    Input(#[from] std::sync::mpsc::RecvError),
}

pub async fn spawn_frontend(
    _app: Option<App<'_>>,
    runtime: Runtime,
) -> Result<()> {
    enable_raw_mode()?;

    let mut unwrapped_app = _app.wrap_err("foo")?;

    let mut stdout = stdout();
    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;

    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend)?;

    let (tx, rx) = mpsc::channel();
    let tick_rate = Duration::from_millis(50);

    thread::spawn(move || -> Result<()> {
        let mut last_tick = Instant::now();
        loop {
            let timeout = tick_rate
                .checked_sub(last_tick.elapsed())
                .unwrap_or_else(|| Duration::from_secs(0));

            if event::poll(timeout).unwrap() {
                if let CEvent::Key(key) = event::read()? {
                    tx.send(Event::Input(key))?;
                }
            }

            if last_tick.elapsed() >= tick_rate {
                tx.send(Event::Tick)?;
                last_tick = Instant::now();
            }
        }
    });

    terminal.clear()?;

    panic::set_hook({
        let captured_state = unwrapped_app.state.clone();
        Box::new(move |panic_info| {
            log::error!("Panic: {}", panic_info);
            captured_state
                .add_message(PrintInfo::Plain(panic_info.to_string()));
        })
    });

    loop {
        let all_plugins: Vec<Spans> = unwrapped_app
            .state
            .configuration
            .lock()
            .await
            .rules
            .iter()
            .map(|x| x.into_inner())
            .filter_map(|rule| {
                rule.plugins.as_ref().map(|plugins| {
                    let functions = plugins.functions.lock().unwrap();
                    let function_names: Vec<String> = functions.keys().cloned().collect();
                    (&rule.name, function_names)
                })
            })
            .flat_map(|(rule_name, function_names)| {
                let rule_name = rule_name.to_string();
                function_names.into_iter().map(move |func_name| {
                    Spans::from(vec![
                        Span::styled(
                            format!("[{}] ", &rule_name),
                            Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)
                        ),
                        Span::raw(func_name)
                    ])
                })
            })
            .collect();

        let wrapper = ConfigurationNewtype(unwrapped_app.state.configuration.clone());
        let list = List::async_from(wrapper).await;

        terminal
            .draw(|f| ui::draw(f, &mut unwrapped_app, all_plugins, list))?;

        match rx.recv()? {
            Event::Input(event) => {
                util::match_keybinds(event, &mut unwrapped_app).await?
            }
            Event::Tick => unwrapped_app.on_tick()?,
        };

        if unwrapped_app.should_quit {
            disable_raw_mode()?;
            execute!(
                terminal.backend_mut(),
                LeaveAlternateScreen,
                DisableMouseCapture
            )?;
            runtime.shutdown_background();
            terminal.show_cursor()?;
            break;
        }
    }
    Ok(())
}

pub fn define_log_callbacks(state: Arc<State>) -> PaintLogsCallbacks {
    let inner_state = Arc::clone(&state);

    let log = Box::new(move |message: &Loggable| match &message.message_type {
        LoggableType::IncomingRequestAtFfips(i) => {
            inner_state
                .add_traffic_info(LoggableNT(
                    LoggableType::IncomingRequestAtFfips(i.clone()),
                ));
        }
        LoggableType::OutGoingResponseFromFips(i) => {
            inner_state
                .add_traffic_info(LoggableNT(
                    LoggableType::OutGoingResponseFromFips(i.clone()),
                ));
        }
        LoggableType::OutgoingRequestToServer(i) => {
            inner_state
                .add_traffic_info(LoggableNT(
                    LoggableType::OutgoingRequestToServer(i.clone()),
                ));
        }
        LoggableType::IncomingResponseFromServer(i) => {
            inner_state
                .add_traffic_info(LoggableNT(
                    LoggableType::IncomingResponseFromServer(i.clone()),
                ));
        }
        LoggableType::Plain => {
            inner_state
                .add_message(PrintInfo::Plain(message.message.clone()));
        }
    });
    PaintLogsCallbacks(log)
}

pub async fn setup(
    configuration: Arc<AsyncMutex<Config>>,
    options: CliOptions,
) -> (
    Option<Arc<State>>,
    Option<App<'static>>,
    Arc<PaintLogsCallbacks>,
) {
    let state = Arc::new(State {
        messages: Mutex::new(Vec::new()),
        configuration,
        traffic_info: Mutex::new(vec![]),
    });

    let app = App::new(true, Arc::clone(&state), options);

    let logging = Arc::new(define_log_callbacks(Arc::clone(&state)));
    (Some(state), Some(app), logging)
}