raw
1use std::path;
2
3use axum::{
4 extract::{Path, State},
5 http::{StatusCode, header},
6 response::{IntoResponse as _, Response},
7};
8
9use crate::{
10 BileState,
11 error::Context as _,
12 git::Repository,
13 http::{
14 extractor::{ObjectName, Ref, RepoName},
15 response::{ErrorPage, Result},
16 },
17 utils::blob_mime,
18};
19
20#[tracing::instrument(skip_all)]
21pub(crate) async fn get(
22 state: State<BileState>,
23 Path((repo_name, r#ref, object_name)): Path<(RepoName, Ref, ObjectName)>,
24) -> Response {
25 state
26 .spawn(move |state| inner(&state, &repo_name, &r#ref, &object_name))
27 .await
28}
29
30#[tracing::instrument(skip_all)]
31fn inner(
32 state: &BileState,
33 repo_name: &RepoName,
34 r#ref: &Ref,
35 object_name: &ObjectName,
36) -> Result<Response> {
37 let repo = match Repository::open(&state.config, repo_name).context("opening repository") {
38 Ok(Some(repo)) => repo,
39 Ok(None) => {
40 return Ok(ErrorPage::new(&state.config)
41 .with_status(StatusCode::NOT_FOUND)
42 .into_response());
43 }
44 Err(err) => {
45 tracing::error!(err=?err, "failed to open repository");
46
47 return Ok(ErrorPage::new(&state.config)
48 .with_status(StatusCode::NOT_FOUND)
49 .into_response());
50 }
51 };
52
53 let path = path::Path::new(&object_name.0);
54
55 let Some((_, tree)) = repo
56 .commit_tree(&r#ref.0)
57 .context("failed to get commit tree")?
58 else {
59 return Ok(ErrorPage::new(&state.config)
60 .with_status(StatusCode::NOT_FOUND)
61 .into_response());
62 };
63
64 let Some(blob) = repo.tree_blob(&tree, path)? else {
65 return Ok(ErrorPage::new(&state.config)
66 .with_status(StatusCode::NOT_FOUND)
67 .into_response());
68 };
69
70 let extension = path
71 .extension()
72 .and_then(std::ffi::OsStr::to_str)
73 .unwrap_or_default();
74
75 let mime = blob_mime(&blob, extension);
76
77 Ok((
78 StatusCode::OK,
79 [(header::CONTENT_TYPE, mime.as_ref())],
80 blob.content().to_vec(),
81 )
82 .into_response())
83}
84