use axum::body::Bytes;
use axum::extract::{Path, State};
use axum::http::{header, HeaderMap, StatusCode};
use axum::response::{IntoResponse, Response};
use axum::Json;
use std::sync::Arc;
use crate::federation_transport as transport;
use crate::formats::{ModelFormat, ModelMetadata};
use super::error::ApiError;
use super::server::AppState;
fn require_peer_auth(headers: &HeaderMap, state: &AppState) -> Result<(), ApiError> {
let settings = &state.vault_config.federation;
let unauthorized = || ApiError::unauthorized("Invalid or missing federation key");
if !settings.enabled {
return Err(unauthorized());
}
let presented = headers
.get(transport::API_KEY_HEADER)
.and_then(|v| v.to_str().ok())
.ok_or_else(unauthorized)?;
if presented.is_empty() {
return Err(unauthorized());
}
let accepted = transport::accepted_keys(settings).map_err(|e| {
eprintln!("federation: failed to resolve peer keys: {e}");
ApiError::internal("Federation key configuration error")
})?;
if accepted.is_empty() || !transport::key_is_accepted(presented, &accepted) {
return Err(unauthorized());
}
Ok(())
}
fn validate_name(name: &str) -> Result<(), ApiError> {
if name.is_empty() || name.len() > 128 {
return Err(ApiError::bad_request("Name must be 1-128 characters"));
}
if !name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.')
{
return Err(ApiError::bad_request(
"Name must contain only ASCII alphanumerics, hyphens, underscores, or dots",
));
}
if name.contains("..") {
return Err(ApiError::bad_request("Name must not contain '..'"));
}
Ok(())
}
pub async fn manifest(
State(state): State<Arc<AppState>>,
headers: HeaderMap,
) -> Result<Json<serde_json::Value>, ApiError> {
require_peer_auth(&headers, &state)?;
let manager = state
.federation
.as_ref()
.ok_or_else(|| ApiError::internal("Federation manager unavailable"))?;
let vault = state.vault.read().await;
let models: Vec<(String, Vec<crate::version::ModelVersion>)> = vault
.list_models()
.into_iter()
.map(|name| {
let versions = vault.list_versions(&name).into_iter().cloned().collect();
(name, versions)
})
.collect();
drop(vault);
let manifest = manager.generate_manifest(models).await;
serde_json::to_value(&manifest)
.map(Json)
.map_err(|e| ApiError::internal(format!("Failed to serialize manifest: {e}")))
}
pub async fn get_version(
State(state): State<Arc<AppState>>,
Path((name, checkpoint_id)): Path<(String, String)>,
headers: HeaderMap,
) -> Result<Response, ApiError> {
require_peer_auth(&headers, &state)?;
validate_name(&name)?;
validate_name(&checkpoint_id)?;
let vault = state.vault.read().await;
let version = vault
.list_versions(&name)
.into_iter()
.find(|v| transport::federation_checkpoint_id(v) == checkpoint_id)
.map(|v| v.version)
.ok_or_else(|| ApiError::not_found("Version not found"))?;
let data = vault.get_model(&name, Some(version)).map_err(|e| {
eprintln!("federation: failed to read {name}/{checkpoint_id}: {e}");
ApiError::internal("Failed to read model")
})?;
drop(vault);
let body = transport::seal_for_transit(&state.vault_config.federation, &data).map_err(|e| {
eprintln!("federation: failed to seal {name}/{checkpoint_id}: {e}");
ApiError::internal("Failed to seal model for transit")
})?;
Ok((
StatusCode::OK,
[(header::CONTENT_TYPE, "application/octet-stream")],
body,
)
.into_response())
}
pub async fn put_version(
State(state): State<Arc<AppState>>,
Path((name, checkpoint_id)): Path<(String, String)>,
headers: HeaderMap,
body: Bytes,
) -> Result<(StatusCode, Json<serde_json::Value>), ApiError> {
require_peer_auth(&headers, &state)?;
validate_name(&name)?;
validate_name(&checkpoint_id)?;
if body.is_empty() {
return Err(ApiError::bad_request("Empty body"));
}
let data = transport::open_from_transit(&state.vault_config.federation, body.to_vec())
.map_err(|e| ApiError::bad_request(format!("Rejected transfer: {e}")))?;
let mut vault = state.vault.write().await;
if vault
.list_versions(&name)
.iter()
.any(|v| transport::federation_checkpoint_id(v) == checkpoint_id)
{
return Ok((
StatusCode::OK,
Json(serde_json::json!({
"status": "already_present",
"checkpoint_id": checkpoint_id,
})),
));
}
let declared_format = headers
.get("X-Model-Format")
.and_then(|v| v.to_str().ok())
.filter(|s| !s.is_empty())
.unwrap_or("unknown");
let mut metadata = ModelMetadata::new(name.clone(), ModelFormat::from_stored(declared_format))
.with_description(format!("Received from federation peer ({checkpoint_id})"));
metadata.custom_fields = transport::origin_metadata(&checkpoint_id);
let version = vault
.store_model(&name, data, metadata, None)
.map_err(|e| {
eprintln!("federation: failed to store {name}/{checkpoint_id}: {e}");
ApiError::internal("Failed to store model")
})?;
Ok((
StatusCode::CREATED,
Json(serde_json::json!({
"status": "stored",
"version": version.version,
"checkpoint_id": version.checkpoint_id,
})),
))
}