#![allow(
// Unused asyncs are the norm in Actix route definition files
clippy::unused_async,
clippy::unreachable,
clippy::let_with_type_underscore,
// Clippy wrongly detects the `infinite_loop` lint on functions with tracing::instrument!
clippy::infinite_loop
)]
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use git2::{self, ErrorCode};
use std::path::PathBuf;
use tracing_actix_web::TracingLogger;
use super::errors::{CliError, HTTPError, StelaeError};
use crate::utils::git::{Repo, GIT_REQUEST_NOT_FOUND};
use crate::utils::http::get_contenttype;
use crate::{server::tracing::StelaeRootSpanBuilder, utils::paths::clean_path};
struct AppState {
archive_path: PathBuf,
}
#[get("/")]
async fn index() -> &'static str {
"Welcome to Stelae"
}
#[get("{path}")]
async fn misc(path: web::Path<String>) -> actix_web::Result<&'static str, StelaeError> {
match path.as_str() {
"error" => Err(StelaeError::GitError),
_ => Ok("\u{2728}"),
}
}
#[get("/{namespace}/{name}/{commitish}{remainder:/+([^{}]*?)?/*}")]
#[tracing::instrument(name = "Retrieving a Git blob", skip(path, data))]
async fn get_blob(
path: web::Path<(String, String, String, String)>,
data: web::Data<AppState>,
) -> impl Responder {
let (namespace, name, commitish, remainder) = path.into_inner();
let archive_path = &data.archive_path;
let blob = Repo::find_blob(archive_path, &namespace, &name, &remainder, &commitish);
let blob_path = clean_path(&remainder);
let contenttype = get_contenttype(&blob_path);
match blob {
Ok(content) => HttpResponse::Ok().insert_header(contenttype).body(content),
Err(error) => blob_error_response(&error, &namespace, &name),
}
}
#[allow(clippy::wildcard_enum_match_arm)]
#[tracing::instrument(name = "Error with Git blob request", skip(error, namespace, name))]
fn blob_error_response(error: &anyhow::Error, namespace: &str, name: &str) -> HttpResponse {
tracing::error!("{error}",);
if let Some(git_error) = error.downcast_ref::<git2::Error>() {
return match git_error.code() {
ErrorCode::NotFound => {
HttpResponse::NotFound().body(format!("repo {namespace}/{name} does not exist"))
}
_ => HttpResponse::InternalServerError().body("Unexpected Git error"),
};
}
match error {
_ if error.to_string() == GIT_REQUEST_NOT_FOUND => {
HttpResponse::NotFound().body(HTTPError::NotFound.to_string())
}
_ => HttpResponse::InternalServerError().body(HTTPError::InternalServerError.to_string()),
}
}
#[actix_web::main] pub async fn serve_git(
raw_archive_path: &str,
archive_path: PathBuf,
port: u16,
) -> Result<(), CliError> {
let bind = "127.0.0.1";
let message = "Serving content from the Stelae archive at";
tracing::info!("{message} '{raw_archive_path}' on http://{bind}:{port}.",);
HttpServer::new(move || {
App::new()
.wrap(TracingLogger::<StelaeRootSpanBuilder>::new())
.service(index)
.service(misc)
.service(get_blob)
.app_data(web::Data::new(AppState {
archive_path: archive_path.clone(),
}))
})
.bind((bind, port))?
.run()
.await
.map_err(|err| {
tracing::error!("Error running Git server: {err:?}");
CliError::GenericError
})
}