🏡 index : ~doyle/rgit.git

author Jordan Doyle <jordan@doyle.la> 2025-01-03 21:13:17.0 +07:00:00
committer Jordan Doyle <jordan@doyle.la> 2025-01-03 21:31:16.0 +07:00:00
commit
47ea4bb9862a0c8354ca205dc25ee6f0991b5d5c [patch]
tree
80d9156b4cb6f9ecc4f3c705b2e30be4c8d3f976
parent
72801bf80d1819ec146033f1dd021177923efcb7
download
47ea4bb9862a0c8354ca205dc25ee6f0991b5d5c.tar.gz

Split out URI parsing code to improve testability and remove need for boxing



Diff

 Cargo.lock              |   3 +--
 Cargo.toml              |   1 -
 src/methods/repo/mod.rs | 190 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
 3 files changed, 137 insertions(+), 57 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index fece92b..239bd46 100644
--- a/Cargo.lock
+++ a/Cargo.lock
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
version = 3

[[package]]
name = "addr2line"
@@ -2600,7 +2600,6 @@
 "tokio",
 "tokio-stream",
 "tokio-util",
 "tower",
 "tower-http",
 "tower-layer",
 "tower-service",
diff --git a/Cargo.toml b/Cargo.toml
index ae94462..6f93ff9 100644
--- a/Cargo.toml
+++ a/Cargo.toml
@@ -68,7 +68,6 @@
tokio = { version = "1.42", features = ["full", "tracing"] }
tokio-stream = "0.1"
tokio-util = { version = "0.7.10", features = ["io"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["cors", "timeout"] }
tower-layer = "0.3"
tower-service = "0.3"
diff --git a/src/methods/repo/mod.rs b/src/methods/repo/mod.rs
index 821836d..34d8c9e 100644
--- a/src/methods/repo/mod.rs
+++ a/src/methods/repo/mod.rs
@@ -18,12 +18,11 @@

use axum::{
    body::Body,
    handler::HandlerWithoutStateExt,
    handler::Handler,
    http::{Request, StatusCode},
    response::{IntoResponse, Response},
};
use path_clean::PathClean;
use tower::{util::BoxCloneService, Service};

use self::{
    about::handle as handle_about,
@@ -38,10 +37,7 @@
    tree::handle as handle_tree,
};
use crate::database::schema::tag::YokedString;
use crate::{
    database::schema::{commit::YokedCommit, tag::YokedTag},
    layers::UnwrapInfallible,
};
use crate::database::schema::{commit::YokedCommit, tag::YokedTag};

pub const DEFAULT_BRANCHES: [&str; 2] = ["refs/heads/master", "refs/heads/main"];

@@ -52,16 +48,53 @@
        .extensions()
        .get::<Arc<PathBuf>>()
        .expect("scan_path missing");

    let ParsedUri {
        uri,
        child_path,
        action,
    } = parse_uri(request.uri().path().trim_matches('/'));

    let uri = Path::new(uri).clean();
    let path = scan_path.join(&uri);

    let db = request
        .extensions()
        .get::<Arc<rocksdb::DB>>()
        .expect("db extension missing");
    if path.as_os_str().is_empty()
        || !crate::database::schema::repository::Repository::exists(db, &uri).unwrap_or_default()
    {
        return RepositoryNotFound.into_response();
    }

    let mut child_path = None;
    request.extensions_mut().insert(ChildPath(child_path));
    request.extensions_mut().insert(Repository(uri));
    request.extensions_mut().insert(RepositoryPath(path));

    macro_rules! h {
        ($handler:ident) => {
            BoxCloneService::new($handler.into_service())
        };
    match action {
        HandlerAction::About => handle_about.call(request, None::<()>).await,
        HandlerAction::SmartGit => handle_smart_git.call(request, None::<()>).await,
        HandlerAction::Refs => handle_refs.call(request, None::<()>).await,
        HandlerAction::Log => handle_log.call(request, None::<()>).await,
        HandlerAction::Tree => handle_tree.call(request, None::<()>).await,
        HandlerAction::Commit => handle_commit.call(request, None::<()>).await,
        HandlerAction::Diff => handle_diff.call(request, None::<()>).await,
        HandlerAction::Patch => handle_patch.call(request, None::<()>).await,
        HandlerAction::Tag => handle_tag.call(request, None::<()>).await,
        HandlerAction::Snapshot => handle_snapshot.call(request, None::<()>).await,
        HandlerAction::Summary => handle_summary.call(request, None::<()>).await,
    }
}

#[derive(Debug, PartialEq, Eq)]
struct ParsedUri<'a> {
    action: HandlerAction,
    uri: &'a str,
    child_path: Option<PathBuf>,
}

    let uri = request.uri().path().trim_matches('/');
fn parse_uri(uri: &str) -> ParsedUri<'_> {
    let mut uri_parts = memchr::memchr_iter(b'/', uri.as_bytes());

    let original_uri = uri;
@@ -71,28 +104,75 @@
        (None, uri)
    };

    let mut service = match action {
        Some("about") => h!(handle_about),
        Some("git-upload-pack") => h!(handle_smart_git),
    match action {
        Some("about") => ParsedUri {
            action: HandlerAction::About,
            uri,
            child_path: None,
        },
        Some("git-upload-pack") => ParsedUri {
            action: HandlerAction::SmartGit,
            uri,
            child_path: None,
        },
        Some("refs") => {
            if let Some(idx) = uri_parts.next_back() {
                if uri.get(idx + 1..) == Some("info") {
                    uri = &uri[..idx];
                    h!(handle_smart_git)
                    ParsedUri {
                        action: HandlerAction::SmartGit,
                        uri: &uri[..idx],
                        child_path: None,
                    }
                } else {
                    h!(handle_refs)
                    ParsedUri {
                        action: HandlerAction::Refs,
                        uri,
                        child_path: None,
                    }
                }
            } else {
                h!(handle_refs)
                ParsedUri {
                    action: HandlerAction::Refs,
                    uri,
                    child_path: None,
                }
            }
        }
        Some("log") => h!(handle_log),
        Some("tree") => h!(handle_tree),
        Some("commit") => h!(handle_commit),
        Some("diff") => h!(handle_diff),
        Some("patch") => h!(handle_patch),
        Some("tag") => h!(handle_tag),
        Some("snapshot") => h!(handle_snapshot),
        Some("log") => ParsedUri {
            action: HandlerAction::Log,
            uri,
            child_path: None,
        },
        Some("tree") => ParsedUri {
            action: HandlerAction::Tree,
            uri,
            child_path: None,
        },
        Some("commit") => ParsedUri {
            action: HandlerAction::Commit,
            uri,
            child_path: None,
        },
        Some("diff") => ParsedUri {
            action: HandlerAction::Diff,
            uri,
            child_path: None,
        },
        Some("patch") => ParsedUri {
            action: HandlerAction::Patch,
            uri,
            child_path: None,
        },
        Some("tag") => ParsedUri {
            action: HandlerAction::Tag,
            uri,
            child_path: None,
        },
        Some("snapshot") => ParsedUri {
            action: HandlerAction::Snapshot,
            uri,
            child_path: None,
        },
        Some(_) => {
            static TREE_FINDER: LazyLock<memchr::memmem::Finder> =
                LazyLock::new(|| memchr::memmem::Finder::new(b"/tree/"));
@@ -101,39 +181,41 @@

            // match tree children
            if let Some(idx) = TREE_FINDER.find(uri.as_bytes()) {
                // 6 is the length of /tree/
                child_path = Some(Path::new(&uri[idx + 6..]).clean());
                uri = &uri[..idx];
                h!(handle_tree)
                ParsedUri {
                    action: HandlerAction::Tree,
                    uri: &uri[..idx],
                    // 6 is the length of /tree/
                    child_path: Some(Path::new(&uri[idx + 6..]).clean()),
                }
            } else {
                h!(handle_summary)
                ParsedUri {
                    action: HandlerAction::Summary,
                    uri,
                    child_path: None,
                }
            }
        }
        None => h!(handle_summary),
    };

    let uri = Path::new(uri).clean();
    let path = scan_path.join(&uri);

    let db = request
        .extensions()
        .get::<Arc<rocksdb::DB>>()
        .expect("db extension missing");
    if path.as_os_str().is_empty()
        || !crate::database::schema::repository::Repository::exists(db, &uri).unwrap_or_default()
    {
        return RepositoryNotFound.into_response();
        None => ParsedUri {
            action: HandlerAction::Summary,
            uri,
            child_path: None,
        },
    }

    request.extensions_mut().insert(ChildPath(child_path));
    request.extensions_mut().insert(Repository(uri));
    request.extensions_mut().insert(RepositoryPath(path));
}

    service
        .call(request)
        .await
        .unwrap_infallible()
        .into_response()
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum HandlerAction {
    About,
    SmartGit,
    Refs,
    Log,
    Tree,
    Commit,
    Diff,
    Patch,
    Tag,
    Snapshot,
    Summary,
}

#[derive(Clone)]