#![allow(clippy::result_large_err)]
pub mod artifacts;
pub mod auth;
pub mod datasources;
pub mod health;
pub mod nodes;
pub mod nodes_admin;
pub mod pipelines;
use axum::http::HeaderMap;
use console_auth::{ApiError, AuthContext};
use console_store::{AuditEntry, Store};
use utoipa::ToSchema;
#[derive(Debug, serde::Serialize, ToSchema)]
pub struct ErrorResponse {
pub error: ErrorBody,
}
#[derive(Debug, serde::Serialize, ToSchema)]
pub struct ErrorBody {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_key: Option<String>,
}
pub fn iso(ts: Option<&str>) -> Option<String> {
ts.map(|s| format!("{}Z", s.replace(' ', "T")))
}
pub fn operator_of(ctx: &AuthContext, headers: &HeaderMap) -> String {
headers
.get("x-operator")
.and_then(|v| v.to_str().ok())
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string)
.unwrap_or_else(|| ctx.user.username.clone())
}
pub fn audit(
store: &Store,
actor: &str,
action: &str,
entity_type: &str,
entity_id: Option<String>,
detail_json: Option<String>,
) {
if let Err(e) = store.insert_audit(&AuditEntry {
actor: actor.to_string(),
action: action.to_string(),
entity_type: entity_type.to_string(),
entity_id,
detail_json,
}) {
tracing::warn!(action, error = %e, "audit write failed");
}
}
pub fn not_implemented(what: &str, doc_ref: &str) -> ApiError {
ApiError::new(
axum::http::StatusCode::NOT_IMPLEMENTED,
"NOT_IMPLEMENTED",
format!("{what} 尚未实现({doc_ref});本端点按计划推迟到后续阶段交付"),
)
}
#[cfg(test)]
mod tests {
use super::{ErrorBody, ErrorResponse};
#[test]
fn error_body_default_has_no_message_key() {
let body = ErrorResponse {
error: ErrorBody {
code: "UNAUTHENTICATED".to_string(),
message: "未登录或会话已过期".to_string(),
message_key: None,
},
};
let json = serde_json::to_string(&body).expect("serialize ErrorResponse");
assert!(
!json.contains("message_key"),
"message_key leaked into default wire shape: {json}"
);
assert_eq!(
json,
r#"{"error":{"code":"UNAUTHENTICATED","message":"未登录或会话已过期"}}"#
);
}
#[test]
fn error_body_message_key_serializes_when_set() {
let body = ErrorBody {
code: "NOT_FOUND".to_string(),
message: "资源不存在".to_string(),
message_key: Some("err.not_found".to_string()),
};
assert_eq!(
serde_json::to_string(&body).expect("serialize ErrorBody"),
r#"{"code":"NOT_FOUND","message":"资源不存在","message_key":"err.not_found"}"#
);
}
}