1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
use super::{Json, Path, RegistryHeader};
use axum::{
    debug_handler, extract::State, http::StatusCode, response::IntoResponse, routing::get, Router,
};
use indexmap::IndexMap;
use std::path::PathBuf;
use url::Url;
use warg_api::v1::content::{ContentError, ContentSource, ContentSourcesResponse};
use warg_crypto::hash::AnyHash;

#[derive(Clone)]
pub struct Config {
    content_base_url: Url,
    files_dir: PathBuf,
}

impl Config {
    pub fn new(content_base_url: Url, files_dir: PathBuf) -> Self {
        Self {
            content_base_url,
            files_dir,
        }
    }

    pub fn into_router(self) -> Router {
        Router::new()
            .route("/:digest", get(get_content))
            .with_state(self)
    }

    fn content_present(&self, digest: &AnyHash) -> bool {
        self.content_path(digest).is_file()
    }

    fn content_file_name(&self, digest: &AnyHash) -> String {
        digest.to_string().replace(':', "-")
    }

    fn content_path(&self, digest: &AnyHash) -> PathBuf {
        self.files_dir.join(self.content_file_name(digest))
    }

    fn content_url(&self, digest: &AnyHash) -> String {
        self.content_base_url
            .join("content/")
            .unwrap()
            .join(&self.content_file_name(digest))
            .unwrap()
            .to_string()
    }
}

struct ContentApiError(ContentError);

impl IntoResponse for ContentApiError {
    fn into_response(self) -> axum::response::Response {
        (StatusCode::from_u16(self.0.status()).unwrap(), Json(self.0)).into_response()
    }
}

#[debug_handler]
async fn get_content(
    State(config): State<Config>,
    Path(digest): Path<AnyHash>,
    RegistryHeader(_registry_header): RegistryHeader,
) -> Result<Json<ContentSourcesResponse>, ContentApiError> {
    if !config.content_present(&digest) {
        return Err(ContentApiError(ContentError::ContentDigestNotFound(digest)));
    }

    let mut content_sources = IndexMap::with_capacity(1);
    let url = config.content_url(&digest);
    content_sources.insert(
        digest,
        vec![ContentSource::HttpGet {
            url,
            accept_ranges: false,
            size: None,
        }],
    );

    Ok(Json(ContentSourcesResponse { content_sources }))
}