use super::{Monitor, MonitorError, MonitoringWitness, ValidationError};
use crate::witness::AddCheckpointRequest;
use axum::{
body::Bytes,
extract::State,
http::{header, StatusCode},
response::{IntoResponse, Response},
Json,
};
use serde::Serialize;
use std::sync::Arc;
const CONTENT_TYPE_TLOG_SIZE: &str = "text/x.tlog.size";
pub async fn add_checkpoint<M: Monitor + 'static>(
State(witness): State<Arc<MonitoringWitness<M>>>,
body: Bytes,
) -> Response {
let body_str = match std::str::from_utf8(&body) {
Ok(s) => s,
Err(e) => {
return (StatusCode::BAD_REQUEST, format!("invalid UTF-8: {}", e)).into_response();
}
};
let request = match AddCheckpointRequest::from_ascii(body_str) {
Ok(r) => r,
Err(e) => {
return (StatusCode::BAD_REQUEST, format!("parse error: {}", e)).into_response();
}
};
match witness.add_checkpoint(request).await {
Ok(cosig) => {
let sig_line = cosig.to_line();
(StatusCode::OK, sig_line).into_response()
}
Err(e) => monitor_error_to_response(e),
}
}
fn monitor_error_to_response(err: MonitorError) -> Response {
match err {
MonitorError::Witness(witness_err) => {
use crate::witness::WitnessError;
match witness_err {
WitnessError::Conflict(size) => (
StatusCode::CONFLICT,
[(header::CONTENT_TYPE, CONTENT_TYPE_TLOG_SIZE)],
format!("{}\n", size),
)
.into_response(),
WitnessError::UnknownLog(origin) => {
(StatusCode::NOT_FOUND, format!("unknown log: {}", origin)).into_response()
}
WitnessError::InvalidSignature(msg) => {
(StatusCode::FORBIDDEN, format!("invalid signature: {}", msg)).into_response()
}
WitnessError::InvalidProof(msg) => (
StatusCode::UNPROCESSABLE_ENTITY,
format!("invalid proof: {}", msg),
)
.into_response(),
WitnessError::BadRequest(msg) => {
(StatusCode::BAD_REQUEST, format!("bad request: {}", msg)).into_response()
}
WitnessError::Internal(msg) => {
tracing::error!("Internal witness error: {}", msg);
(StatusCode::INTERNAL_SERVER_ERROR, "internal error").into_response()
}
}
}
MonitorError::Validation(validation_err) => {
let response = ValidationErrorResponse::from(validation_err);
(StatusCode::UNPROCESSABLE_ENTITY, Json(response)).into_response()
}
}
}
#[derive(Debug, Serialize)]
pub struct ValidationErrorResponse {
pub error: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<ValidationErrorDetails>,
}
#[derive(Debug, Serialize)]
pub struct ValidationErrorDetails {
#[serde(skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub first_index: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub current_index: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub first_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub current_hash: Option<String>,
}
impl From<ValidationError> for ValidationErrorResponse {
fn from(err: ValidationError) -> Self {
match err {
ValidationError::DuplicateSha256 {
hash,
first_index,
current_index,
} => Self {
error: "duplicate_sha256".to_string(),
message: format!("SHA256 {} already exists at index {}", hash, first_index),
details: Some(ValidationErrorDetails {
key: Some(hash),
first_index: Some(first_index),
current_index: Some(current_index),
first_hash: None,
current_hash: None,
}),
},
ValidationError::DuplicateFilename {
filename,
first_hash,
current_hash,
first_index,
current_index,
} => Self {
error: "duplicate_filename".to_string(),
message: format!("Filename {} already exists with different hash", filename),
details: Some(ValidationErrorDetails {
key: Some(filename),
first_index: Some(first_index),
current_index: Some(current_index),
first_hash: Some(first_hash),
current_hash: Some(current_hash),
}),
},
ValidationError::ParseError(msg) => Self {
error: "parse_error".to_string(),
message: msg,
details: None,
},
ValidationError::Other(msg) => Self {
error: "validation_error".to_string(),
message: msg,
details: None,
},
}
}
}
pub async fn health() -> &'static str {
"ok"
}
#[derive(Debug, Serialize)]
pub struct MonitorStatsResponse {
pub monitor: String,
pub witness: String,
pub sha256_count: usize,
pub filename_count: usize,
}
pub async fn stats<M: Monitor + 'static>(
State(witness): State<Arc<MonitoringWitness<M>>>,
) -> Response {
let response = MonitorStatsResponse {
monitor: witness.monitor_name().to_string(),
witness: witness.name().to_string(),
sha256_count: 0, filename_count: 0,
};
(StatusCode::OK, Json(response)).into_response()
}