replay 0.1.2

A simple HTTP request replay tool
use crate::proxy::PROXY_LOG_FILE;
use crate::store::{load_logs_from_file, LogStore};
use actix_web::{web, App, HttpServer, HttpRequest, HttpResponse, Responder};
use actix_web::http::header::{HeaderName, HeaderValue};
use owo_colors::OwoColorize;

pub async fn start_replay_server(logs: LogStore, bind_address: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
  if let Ok(file_logs) = load_logs_from_file(PROXY_LOG_FILE) {
      let mut logs_guard = logs.lock().await;
      for log in file_logs {
          logs_guard.push(log);
      }
  }

  fn build_request_key(method: &str, path: &str, query: &Option<String>) -> String {
      if let Some(q) = query {
          format!("{} {}?{}", method, path, q)
      } else {
          format!("{} {}", method, path)
      }
  }

  async fn replay_handler(
      req: HttpRequest,
      _body: web::Bytes,
      logs: web::Data<LogStore>,
  ) -> impl Responder {
      let method = req.method().to_string();
      let path = req.path().to_string();
      let query = req.query_string();
      let query_opt = if query.is_empty() { None } else { Some(query.to_string()) };

      let key = build_request_key(&method, &path, &query_opt);
      println!("Replay server received request: {}", key.magenta());

      let response = {
          let logs_guard = logs.lock().await;
          logs_guard.iter()
              .find(|log| {
                  let log_key = build_request_key(
                      &log.request.method,
                      &log.request.path,
                      &log.request.query_params
                  );
                  log_key == key
              })
              .cloned()
      };

      if let Some(log) = response {
          println!("Found matching response for: {}", key.magenta());

          let mut response_builder = HttpResponse::build(
              actix_web::http::StatusCode::from_u16(log.response.status).unwrap_or(actix_web::http::StatusCode::OK)
          );

          for (name, value) in log.response.headers {
              if let (Ok(header_name), Ok(header_value)) = (
                  HeaderName::try_from(name.as_str()),
                  HeaderValue::try_from(value.as_str())
              ) {
                  response_builder.append_header((header_name, header_value));
              }
          }

          if let Some(body) = log.response.body {
              response_builder.body(body)
          } else {
              response_builder.finish()
          }
      } else {
          println!("No matching response found for: {}", key.magenta());
          HttpResponse::NotFound().body("No matching request found in logs")
      }
  }

  async fn list_requests(logs: web::Data<LogStore>) -> impl Responder {
      let logs_guard = logs.lock().await;
      let requests: Vec<_> = logs_guard.iter().map(|log| {
          let key = build_request_key(
              &log.request.method,
              &log.request.path,
              &log.request.query_params
          );
          (key, log.response.status)
      }).collect();

      web::Json(requests)
  }

  HttpServer::new(move || {
      App::new()
          .app_data(web::Data::new(logs.clone()))
          .route("/admin/requests", web::get().to(list_requests))
          .default_service(web::route().to(replay_handler))
  })
  .bind(bind_address)?
  .run()
  .await?;

  Ok(())
}