warg_server/api/v1/
content.rs1use 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}