Skip to content

Commit

Permalink
fix(api): do not error if serving a partial stele
Browse files Browse the repository at this point in the history
- Moved `find_blob` to `git/utils/mod.rs` module as `Repo` associated
  function.
- Refactor `RepoState` and `FallbackState` to not keep open handles to
  git repositories. This change makes it so that stelae can serve
  partial stele, and not fail if any data repository is missing from
  `repositories.json`. Handles to git repos are now open at request time instead.
  • Loading branch information
n-dusan committed Jul 23, 2024
1 parent 121443b commit 5c8fe99
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 46 deletions.
18 changes: 14 additions & 4 deletions src/server/api/serve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub async fn serve(
let mut path = format!("{prefix}/{tail}");
path = clean_path(&path);
let contenttype = get_contenttype(&path);
let blob = find_current_blob(&data.repo, &shared, &path);
let blob = find_current_blob(&data, &shared, &path);
match blob {
Ok(content) => HttpResponse::Ok().insert_header(contenttype).body(content),
Err(error) => {
Expand All @@ -38,13 +38,23 @@ pub async fn serve(
/// Latest blob is found by looking at the HEAD commit
#[allow(clippy::panic_in_result_fn, clippy::unreachable)]
#[tracing::instrument(name = "Finding document", skip(repo, shared))]
fn find_current_blob(repo: &Repo, shared: &SharedState, path: &str) -> anyhow::Result<Vec<u8>> {
let blob = repo.get_bytes_at_path(HEAD_COMMIT, path);
fn find_current_blob(
repo: &RepoState,
shared: &SharedState,
path: &str,
) -> anyhow::Result<Vec<u8>> {
let blob = Repo::find_blob(&repo.archive_path, &repo.org, &repo.name, path, HEAD_COMMIT);
match blob {
Ok(content) => Ok(content),
Err(error) => {
if let Some(fallback) = shared.fallback.as_ref() {
let fallback_blob = fallback.repo.get_bytes_at_path(HEAD_COMMIT, path);
let fallback_blob = Repo::find_blob(
&fallback.archive_path,
&fallback.org,
&fallback.name,
path,
HEAD_COMMIT,
);
return fallback_blob.map_or_else(
|err| anyhow::bail!("No fallback blob found - {}", err.to_string()),
Ok,
Expand Down
72 changes: 47 additions & 25 deletions src/server/api/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ use std::{fmt, path::PathBuf};
use crate::{
db,
stelae::{archive::Archive, stele::Stele, types::repositories::Repository},
utils::{archive::get_name_parts, git},
utils::archive::get_name_parts,
};
use git2::Repository as GitRepository;

/// Global, read-only state
pub trait Global {
Expand Down Expand Up @@ -37,12 +36,36 @@ impl Global for App {

/// Repository to serve
pub struct RepoData {
/// git2 wrapper repository pointing to the repo in the archive.
pub repo: git::Repo,
/// Path to the archive
pub archive_path: PathBuf,
/// Path to the Stele
pub path: PathBuf,
/// Repo organization
pub org: String,
/// Repo name
pub name: String,
// /// path to the git repository
// pub repo_path: PathBuf;
///Latest or historical
pub serve: String,
}

impl RepoData {
/// Create a new Repo state object
#[must_use]
pub fn new(archive_path: &str, org: &str, name: &str, serve: &str) -> Self {
let mut repo_path = archive_path.to_owned();
repo_path = format!("{repo_path}/{org}/{name}");
Self {
archive_path: PathBuf::from(archive_path),
path: PathBuf::from(&repo_path),
org: org.to_owned(),
name: name.to_owned(),
serve: serve.to_owned(),
}
}
}

/// Shared, read-only app state
pub struct Shared {
/// Repository to fall back to if the current one is not found
Expand All @@ -54,8 +77,8 @@ impl fmt::Debug for RepoData {
write!(
formatter,
"Repo for {} in the archive at {}",
self.repo.name,
self.repo.path.display()
self.name,
self.path.display()
)
}
}
Expand All @@ -67,8 +90,8 @@ impl fmt::Debug for Shared {
Some(fallback) => write!(
formatter,
"Repo for {} in the archive at {}",
fallback.repo.name,
fallback.repo.path.display()
fallback.name,
fallback.path.display()
),
None => write!(formatter, "No fallback repo"),
}
Expand All @@ -79,7 +102,10 @@ impl fmt::Debug for Shared {
impl Clone for RepoData {
fn clone(&self) -> Self {
Self {
repo: self.repo.clone(),
archive_path: self.archive_path.clone(),
path: self.path.clone(),
org: self.org.clone(),
name: self.name.clone(),
serve: self.serve.clone(),
}
}
Expand All @@ -102,18 +128,12 @@ impl Clone for Shared {
pub fn init_repo(repo: &Repository, stele: &Stele) -> anyhow::Result<RepoData> {
let custom = &repo.custom;
let (org, name) = get_name_parts(&repo.name)?;
let mut repo_path = stele.archive_path.to_string_lossy().into_owned();
repo_path = format!("{repo_path}/{org}/{name}");
Ok(RepoData {
repo: git::Repo {
archive_path: stele.archive_path.to_string_lossy().to_string(),
path: PathBuf::from(&repo_path),
org,
name,
repo: GitRepository::open(&repo_path)?,
},
serve: custom.serve.clone(),
})
Ok(RepoData::new(
&stele.archive_path.to_string_lossy(),
&org,
&name,
&custom.serve,
))
}

/// Initialize the shared application state
Expand All @@ -128,10 +148,12 @@ pub fn init_shared(stele: &Stele) -> anyhow::Result<Shared> {
.get_fallback_repo()
.map(|repo| {
let (org, name) = get_name_parts(&repo.name)?;
Ok::<RepoData, anyhow::Error>(RepoData {
repo: git::Repo::new(&stele.archive_path, &org, &name)?,
serve: repo.custom.serve.clone(),
})
Ok::<RepoData, anyhow::Error>(RepoData::new(
&stele.archive_path.to_string_lossy(),
&org,
&name,
&repo.custom.serve,
))
})
.transpose()?;
Ok(Shared { fallback })
Expand Down
19 changes: 2 additions & 17 deletions src/server/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use git2::{self, ErrorCode};
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use tracing_actix_web::TracingLogger;

use super::errors::{CliError, StelaeError};
Expand Down Expand Up @@ -51,7 +51,7 @@ async fn get_blob(
) -> impl Responder {
let (namespace, name, commitish, remainder) = path.into_inner();
let archive_path = &data.archive_path;
let blob = find_blob(archive_path, &namespace, &name, &remainder, &commitish);
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 {
Expand All @@ -60,21 +60,6 @@ async fn get_blob(
}
}

/// Do the work of looking for the requested Git object.
// TODO: This looks like it could live in `utils::git::Repo`
fn find_blob(
archive_path: &Path,
namespace: &str,
name: &str,
remainder: &str,
commitish: &str,
) -> anyhow::Result<Vec<u8>> {
let repo = Repo::new(archive_path, namespace, name)?;
let blob_path = clean_path(remainder);
let blob = repo.get_bytes_at_path(commitish, &blob_path)?;
Ok(blob)
}

/// A centralised place to match potentially unsafe internal errors to safe user-facing error responses
#[allow(clippy::wildcard_enum_match_arm)]
#[tracing::instrument(name = "Error with Git blob request", skip(error, namespace, name))]
Expand Down
19 changes: 19 additions & 0 deletions src/utils/git.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! The git module contains structs for interacting with git repositories
//! in the Stelae Archive.
use crate::utils::paths::clean_path;
use anyhow::Context;
use git2::Repository;
use std::{
Expand Down Expand Up @@ -70,6 +71,24 @@ impl Repo {
})
}

/// Do the work of looking for the requested Git object.
///
///
/// # Errors
/// Will error if the Repo couldn't be found, or if there was a problem with the Git object.
pub fn find_blob(
archive_path: &Path,
namespace: &str,
name: &str,
remainder: &str,
commitish: &str,
) -> anyhow::Result<Vec<u8>> {
let repo = Self::new(archive_path, namespace, name)?;
let blob_path = clean_path(remainder);
let blob = repo.get_bytes_at_path(commitish, &blob_path)?;
Ok(blob)
}

/// Returns bytes of blob found in the commit `commitish` at path `path`
/// if a blob is not found at path, it will try adding ".html", "index.html,
/// and "/index.html".
Expand Down

0 comments on commit 5c8fe99

Please sign in to comment.