Skip to main content

solid_pod_rs_git/
error.rs

1//! Typed error enum for the Git HTTP backend.
2//!
3//! Mirrors the failure shapes produced by JSS `src/handlers/git.js`:
4//! 400 on path traversal, 401 on missing/invalid auth, 404 on unknown
5//! repo, 500 on CGI spawn failure, and a dedicated variant for when
6//! the `git-http-backend` binary is not installed (so CI without git
7//! can still exercise unit tests).
8
9use thiserror::Error;
10
11/// All failure modes the Git HTTP service may surface.
12#[derive(Debug, Error)]
13pub enum GitError {
14    /// Path-traversal attempt or otherwise invalid URL. JSS returns
15    /// 400 for malformed requests and 403 for traversal; we fold both
16    /// into a single 400 per Rust idiom (the leakage surface is
17    /// identical — the client must not learn whether a repo exists
18    /// via the status code).
19    #[error("path traversal or invalid path: {0}")]
20    PathTraversal(String),
21
22    /// Authorisation required but missing / malformed / rejected.
23    #[error("unauthorised: {0}")]
24    Unauthorised(String),
25
26    /// The request targets a path that is not a git repository.
27    #[error("not a git repository: {0}")]
28    NotARepository(String),
29
30    /// `git-http-backend` (or `git`) binary not installed in PATH.
31    /// Distinguished from a generic I/O error so callers can gate
32    /// integration tests.
33    #[error("git-http-backend binary not available: {0}")]
34    BackendNotAvailable(String),
35
36    /// The CGI process exited non-zero before emitting headers.
37    #[error("git backend failed: exit={exit_code:?}, stderr={stderr}")]
38    BackendFailed {
39        /// Process exit code, if the child did terminate.
40        exit_code: Option<i32>,
41        /// Captured stderr content for diagnostics.
42        stderr: String,
43    },
44
45    /// The CGI process emitted malformed output.
46    #[error("malformed CGI output: {0}")]
47    MalformedCgi(String),
48
49    /// Generic underlying I/O failure.
50    #[error("i/o: {0}")]
51    Io(#[from] std::io::Error),
52
53    /// Underlying auth-layer error (NIP-98 decode, Schnorr mismatch, …).
54    #[error("auth: {0}")]
55    Auth(#[from] crate::auth::AuthError),
56}
57
58impl GitError {
59    /// Recommended HTTP status code to surface to the client.
60    #[must_use]
61    pub fn status_code(&self) -> u16 {
62        match self {
63            GitError::PathTraversal(_) => 400,
64            GitError::Unauthorised(_) | GitError::Auth(_) => 401,
65            GitError::NotARepository(_) => 404,
66            GitError::BackendNotAvailable(_)
67            | GitError::BackendFailed { .. }
68            | GitError::MalformedCgi(_)
69            | GitError::Io(_) => 500,
70        }
71    }
72}