1use std::sync::Arc;
4
5use axum::{
6 extract::{Path, State},
7 http::StatusCode,
8 response::{IntoResponse, Response},
9 routing::{get, head, put},
10 Router,
11};
12use tower_http::trace::TraceLayer;
13
14use crate::storage::Storage;
15use crate::verification::Verifier;
16
17#[derive(Clone)]
19pub struct AppState {
20 storage: Arc<Storage>,
21 verifier: Arc<Verifier>,
22}
23
24impl AppState {
25 pub fn new(storage: Storage, verifier: Verifier) -> Self {
27 Self {
28 storage: Arc::new(storage),
29 verifier: Arc::new(verifier),
30 }
31 }
32}
33
34pub fn create_router(state: AppState) -> Router {
36 Router::new()
37 .route("/v1/artifacts/:cache_key", put(upload_artifact))
38 .route("/v1/artifacts/:cache_key", get(download_artifact))
39 .route("/v1/artifacts/:cache_key", head(check_artifact))
40 .layer(TraceLayer::new_for_http())
41 .with_state(state)
42}
43
44async fn upload_artifact(
48 State(state): State<AppState>,
49 Path(cache_key): Path<String>,
50 body: axum::body::Body,
51) -> Result<Response, ServerError> {
52 if !cache_key.chars().all(|c| c.is_ascii_hexdigit()) || cache_key.len() < 32 {
54 return Err(ServerError::BadRequest(format!(
55 "Invalid cache key format: {}",
56 cache_key
57 )));
58 }
59
60 let max_size = state.storage.max_artifact_size() as usize;
62 let bytes = axum::body::to_bytes(body, max_size)
63 .await
64 .map_err(|e| {
65 if e.to_string().contains("too large") {
66 ServerError::PayloadTooLarge(format!(
67 "Artifact size exceeds maximum {}",
68 max_size
69 ))
70 } else {
71 ServerError::Internal(format!("Failed to read request body: {}", e))
72 }
73 })?;
74
75 let (artifact, hash) = state
77 .verifier
78 .verify_upload(&bytes, &cache_key)
79 .map_err(|e| ServerError::UnprocessableEntity(e.to_string()))?;
80
81 state
83 .storage
84 .store_artifact(&cache_key, bytes.to_vec(), hash, &artifact)
85 .await
86 .map_err(|e| {
87 if e.to_string().contains("already exists") {
88 ServerError::Conflict(format!("Artifact {} already exists", cache_key))
89 } else {
90 ServerError::Internal(format!("Failed to store artifact: {}", e))
91 }
92 })?;
93
94 Ok(StatusCode::CREATED.into_response())
95}
96
97async fn download_artifact(
101 State(state): State<AppState>,
102 Path(cache_key): Path<String>,
103) -> Result<Response, ServerError> {
104 if !cache_key.chars().all(|c| c.is_ascii_hexdigit()) || cache_key.len() < 32 {
106 return Err(ServerError::BadRequest(format!(
107 "Invalid cache key format: {}",
108 cache_key
109 )));
110 }
111
112 if !state.storage.has_artifact(&cache_key) {
114 return Err(ServerError::NotFound);
115 }
116
117 let data = state
119 .storage
120 .read_artifact(&cache_key)
121 .await
122 .map_err(|e| ServerError::Internal(format!("Failed to read artifact: {}", e)))?;
123
124 let metadata = state
126 .storage
127 .read_metadata(&cache_key)
128 .await
129 .map_err(|e| ServerError::Internal(format!("Failed to read metadata: {}", e)))?;
130
131 let response = Response::builder()
133 .status(StatusCode::OK)
134 .header("Content-Type", "application/zstd")
135 .header("Content-Length", data.len())
136 .header("X-Artifact-Hash", &metadata.hash)
137 .body(axum::body::Body::from(data))
138 .map_err(|e| ServerError::Internal(format!("Failed to create response: {}", e)))?;
139
140 Ok(response)
141}
142
143async fn check_artifact(
147 State(state): State<AppState>,
148 Path(cache_key): Path<String>,
149) -> Result<Response, ServerError> {
150 if !cache_key.chars().all(|c| c.is_ascii_hexdigit()) || cache_key.len() < 32 {
152 return Err(ServerError::BadRequest(format!(
153 "Invalid cache key format: {}",
154 cache_key
155 )));
156 }
157
158 if state.storage.has_artifact(&cache_key) {
159 if let Ok(metadata) = state.storage.read_metadata(&cache_key).await {
161 let response = Response::builder()
162 .status(StatusCode::OK)
163 .header("Content-Type", "application/zstd")
164 .header("Content-Length", metadata.size)
165 .header("X-Artifact-Hash", &metadata.hash)
166 .body(axum::body::Body::empty())
167 .map_err(|e| ServerError::Internal(format!("Failed to create response: {}", e)))?;
168
169 return Ok(response);
170 }
171 }
172
173 Err(ServerError::NotFound)
174}
175
176#[derive(Debug)]
178pub enum ServerError {
179 BadRequest(String),
180 Conflict(String),
181 PayloadTooLarge(String),
182 UnprocessableEntity(String),
183 NotFound,
184 Internal(String),
185}
186
187impl IntoResponse for ServerError {
188 fn into_response(self) -> Response {
189 let (status, message) = match self {
190 ServerError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
191 ServerError::Conflict(msg) => (StatusCode::CONFLICT, msg),
192 ServerError::PayloadTooLarge(msg) => (StatusCode::PAYLOAD_TOO_LARGE, msg),
193 ServerError::UnprocessableEntity(msg) => (StatusCode::UNPROCESSABLE_ENTITY, msg),
194 ServerError::NotFound => (StatusCode::NOT_FOUND, "Artifact not found".to_string()),
195 ServerError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
196 };
197
198 let body = axum::Json(serde_json::json!({ "error": message }));
199 (status, body).into_response()
200 }
201}