warg_server/api/v1/
content.rs

1use super::{Json, Path, RegistryHeader};
2use axum::{
3    debug_handler, extract::State, http::StatusCode, response::IntoResponse, routing::get, Router,
4};
5use indexmap::IndexMap;
6use std::path::PathBuf;
7use url::Url;
8use warg_api::v1::content::{ContentError, ContentSource, ContentSourcesResponse};
9use warg_crypto::hash::AnyHash;
10
11#[derive(Clone)]
12pub struct Config {
13    content_base_url: Url,
14    files_dir: PathBuf,
15}
16
17impl Config {
18    pub fn new(content_base_url: Url, files_dir: PathBuf) -> Self {
19        Self {
20            content_base_url,
21            files_dir,
22        }
23    }
24
25    pub fn into_router(self) -> Router {
26        Router::new()
27            .route("/:digest", get(get_content))
28            .with_state(self)
29    }
30
31    fn content_present(&self, digest: &AnyHash) -> bool {
32        self.content_path(digest).is_file()
33    }
34
35    fn content_file_name(&self, digest: &AnyHash) -> String {
36        digest.to_string().replace(':', "-")
37    }
38
39    fn content_path(&self, digest: &AnyHash) -> PathBuf {
40        self.files_dir.join(self.content_file_name(digest))
41    }
42
43    fn content_url(&self, digest: &AnyHash) -> String {
44        self.content_base_url
45            .join("content/")
46            .unwrap()
47            .join(&self.content_file_name(digest))
48            .unwrap()
49            .to_string()
50    }
51}
52
53struct ContentApiError(ContentError);
54
55impl IntoResponse for ContentApiError {
56    fn into_response(self) -> axum::response::Response {
57        (StatusCode::from_u16(self.0.status()).unwrap(), Json(self.0)).into_response()
58    }
59}
60
61#[debug_handler]
62async fn get_content(
63    State(config): State<Config>,
64    Path(digest): Path<AnyHash>,
65    RegistryHeader(_registry_header): RegistryHeader,
66) -> Result<Json<ContentSourcesResponse>, ContentApiError> {
67    if !config.content_present(&digest) {
68        return Err(ContentApiError(ContentError::ContentDigestNotFound(digest)));
69    }
70
71    let mut content_sources = IndexMap::with_capacity(1);
72    let url = config.content_url(&digest);
73    content_sources.insert(
74        digest,
75        vec![ContentSource::HttpGet {
76            url,
77            accept_ranges: false,
78            size: None,
79        }],
80    );
81
82    Ok(Json(ContentSourcesResponse { content_sources }))
83}