use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct Statistics {
pub documents_total: u32,
pub documents_inbox: u32,
pub inbox_tag: u32,
pub inbox_tags: Vec<u32>,
pub document_file_type_counts: Vec<DocumentFileTypeCount>,
pub character_count: u64,
pub tag_count: u32,
pub correspondent_count: u32,
pub document_type_count: u32,
pub storage_path_count: u32,
pub current_asn: u32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct DocumentFileTypeCount {
pub mime_type: String,
#[serde(rename = "mime_type_count")]
pub count: u32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ServerStatus {
#[serde(rename = "pngx_version")]
pub version: String,
pub server_os: String,
pub install_type: String,
pub storage: Storage,
pub database: Database,
pub tasks: StatusTask,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Storage {
pub total: u64,
pub available: u64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(from = "RawDatabase")]
pub struct Database {
pub db_type: String,
pub url: String,
pub status: Health,
pub migration_status: MigrationStatus,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MigrationStatus {
pub latest_migration: String,
pub unapplied_migrations: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub enum Health {
#[serde(rename = "OK")]
Ok,
#[serde(untagged)]
Error(String),
}
#[derive(Debug, Clone, Deserialize)]
pub struct StatusTask {
#[serde(flatten)]
pub redis: RedisStatus,
#[serde(flatten)]
pub celery: CeleryStatus,
#[serde(flatten)]
pub index: IndexStatus,
#[serde(flatten)]
pub sanity_check: SanityCheckStatus,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(from = "RawRedisStatus")]
pub struct RedisStatus {
pub url: String,
pub status: Health,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(from = "RawCeleryStatus")]
pub struct CeleryStatus {
pub status: Health,
pub url: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(from = "RawIndexStatus")]
pub struct IndexStatus {
pub status: Health,
pub last_modified: Option<chrono::DateTime<chrono::Utc>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(from = "RawClassifierStatus")]
pub struct ClassifierStatus {
pub status: Health,
pub last_trained: Option<chrono::DateTime<chrono::Utc>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(from = "RawSanityCheckStatus")]
pub struct SanityCheckStatus {
pub status: Health,
pub last_run: Option<chrono::DateTime<chrono::Utc>>,
}
#[derive(Deserialize)]
pub struct RawDatabase {
#[serde(rename = "type")]
pub db_type: String,
pub url: String,
pub status: String,
pub error: Option<String>,
pub migration_status: MigrationStatus,
}
#[derive(Deserialize)]
#[allow(clippy::struct_field_names)]
struct RawRedisStatus {
redis_url: String,
redis_status: String,
redis_error: Option<String>,
}
#[derive(Deserialize)]
#[allow(clippy::struct_field_names)]
struct RawCeleryStatus {
celery_status: String,
celery_url: String,
celery_error: Option<String>,
}
#[derive(Deserialize)]
#[allow(clippy::struct_field_names)]
struct RawIndexStatus {
index_status: String,
index_last_modified: Option<chrono::DateTime<chrono::Utc>>,
index_error: Option<String>,
}
#[derive(Deserialize)]
#[allow(clippy::struct_field_names)]
struct RawClassifierStatus {
classifier_status: String,
classifier_last_trained: Option<chrono::DateTime<chrono::Utc>>,
classifier_error: Option<String>,
}
#[derive(Deserialize)]
#[allow(clippy::struct_field_names)]
struct RawSanityCheckStatus {
sanity_check_status: String,
sanity_check_last_run: Option<chrono::DateTime<chrono::Utc>>,
sanity_check_error: Option<String>,
}
impl From<RawDatabase> for Database {
fn from(raw: RawDatabase) -> Self {
Self {
db_type: raw.db_type,
url: raw.url,
status: merge_status_with_error(raw.status, raw.error),
migration_status: raw.migration_status,
}
}
}
impl From<RawRedisStatus> for RedisStatus {
fn from(raw: RawRedisStatus) -> Self {
Self {
url: raw.redis_url,
status: merge_status_with_error(raw.redis_status, raw.redis_error),
}
}
}
impl From<RawCeleryStatus> for CeleryStatus {
fn from(raw: RawCeleryStatus) -> Self {
Self {
status: merge_status_with_error(raw.celery_status, raw.celery_error),
url: raw.celery_url,
}
}
}
impl From<RawIndexStatus> for IndexStatus {
fn from(raw: RawIndexStatus) -> Self {
Self {
status: merge_status_with_error(raw.index_status, raw.index_error),
last_modified: raw.index_last_modified,
}
}
}
impl From<RawClassifierStatus> for ClassifierStatus {
fn from(raw: RawClassifierStatus) -> Self {
Self {
status: merge_status_with_error(raw.classifier_status, raw.classifier_error),
last_trained: raw.classifier_last_trained,
}
}
}
impl From<RawSanityCheckStatus> for SanityCheckStatus {
fn from(raw: RawSanityCheckStatus) -> Self {
Self {
status: merge_status_with_error(raw.sanity_check_status, raw.sanity_check_error),
last_run: raw.sanity_check_last_run,
}
}
}
impl std::fmt::Display for Health {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Health::Ok => write!(f, "OK"),
Health::Error(err) => write!(f, "Error: {err}"),
}
}
}
fn merge_status_with_error(status: String, error: Option<String>) -> Health {
if let Some(error) = error {
Health::Error(error)
} else if status.to_lowercase() != "ok" {
Health::Error(status)
} else {
Health::Ok
}
}
impl ServerStatus {
#[must_use]
pub fn overall(&self) -> Health {
let mut errors = Vec::new();
if let Health::Error(ref err) = self.database.status {
errors.push(format!("database: {err}"));
}
if let Health::Error(ref err) = self.tasks.redis.status {
errors.push(format!("task redis: {err}"));
}
if let Health::Error(ref err) = self.tasks.celery.status {
errors.push(format!("task celery: {err}"));
}
if let Health::Error(ref err) = self.tasks.index.status {
errors.push(format!("task index: {err}"));
}
if let Health::Error(ref err) = self.tasks.sanity_check.status {
errors.push(format!("task sanity_check: {err}"));
}
if errors.is_empty() {
Health::Ok
} else {
Health::Error(errors.join(", "))
}
}
}