use serde::{Deserialize, Deserializer, Serialize};
#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum EmbedderState {
Bool(bool),
Str(String),
}
impl EmbedderState {
pub fn is_ready(&self) -> bool {
match self {
EmbedderState::Bool(b) => *b,
EmbedderState::Str(s) => s.eq_ignore_ascii_case("ready"),
}
}
}
impl<'de> Deserialize<'de> for EmbedderState {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
#[serde(untagged)]
enum Raw {
Bool(bool),
Str(String),
}
match Raw::deserialize(d)? {
Raw::Bool(b) => Ok(EmbedderState::Bool(b)),
Raw::Str(s) => Ok(EmbedderState::Str(s)),
}
}
}
impl Default for EmbedderState {
fn default() -> Self {
EmbedderState::Bool(false)
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct HealthResponse {
pub status: String,
#[serde(default)]
pub embedder: EmbedderState,
}
impl HealthResponse {
pub fn is_healthy(&self) -> bool {
self.status == "ok" && self.embedder.is_ready()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn embedder_state_bool_true_is_ready() {
let s = EmbedderState::Bool(true);
assert!(s.is_ready(), "Bool(true) must be ready");
}
#[test]
fn embedder_state_bool_false_is_not_ready() {
let s = EmbedderState::Bool(false);
assert!(!s.is_ready(), "Bool(false) must not be ready");
}
#[test]
fn embedder_state_string_ready_is_ready() {
let s = EmbedderState::Str("ready".to_string());
assert!(s.is_ready(), r#"Str("ready") must be ready"#);
}
#[test]
fn embedder_state_string_ready_case_insensitive() {
for variant in ["READY", "Ready", "rEaDy"] {
let s = EmbedderState::Str(variant.to_string());
assert!(
s.is_ready(),
"Str({variant:?}) must be ready (case-insensitive)"
);
}
}
#[test]
fn embedder_state_string_loading_is_not_ready() {
let s = EmbedderState::Str("loading".to_string());
assert!(!s.is_ready(), r#"Str("loading") must not be ready"#);
}
#[test]
fn embedder_state_string_empty_is_not_ready() {
let s = EmbedderState::Str(String::new());
assert!(!s.is_ready(), "Str(\"\") must not be ready");
}
#[test]
fn embedder_state_default_is_not_ready() {
let s = EmbedderState::default();
assert!(!s.is_ready(), "Default EmbedderState must not be ready");
}
#[test]
fn health_response_embedder_string_ready_is_healthy() {
let json = r#"{"status":"ok","version":"0.22.1","indexes":132,"uptime_secs":3600,"embedder":"ready"}"#;
let resp: HealthResponse =
serde_json::from_str(json).expect("must parse: this was the failing case in #628");
assert!(
resp.is_healthy(),
"embedder=string:\"ready\" + status=ok must be healthy"
);
}
#[test]
fn health_response_embedder_bool_true_is_healthy() {
let json = r#"{"status":"ok","embedder":true}"#;
let resp: HealthResponse = serde_json::from_str(json).expect("must parse: bool true form");
assert!(
resp.is_healthy(),
"embedder=bool:true + status=ok must be healthy"
);
}
#[test]
fn health_response_embedder_string_loading_parses_not_healthy() {
let json = r#"{"status":"ok","embedder":"loading"}"#;
let resp: HealthResponse = serde_json::from_str(json).expect("must parse without error");
assert!(
!resp.is_healthy(),
"embedder=string:\"loading\" must parse OK but report not healthy"
);
}
#[test]
fn health_response_embedder_bool_false_parses_not_healthy() {
let json = r#"{"status":"ok","embedder":false}"#;
let resp: HealthResponse = serde_json::from_str(json).expect("must parse without error");
assert!(
!resp.is_healthy(),
"embedder=bool:false must parse OK but report not healthy"
);
}
#[test]
fn health_response_extra_fields_ignored() {
let json = r#"{
"status": "ok",
"embedder": "ready",
"version": "0.22.1",
"indexes": 132,
"uptime_secs": 3600,
"unknown_future_field": {"nested": true}
}"#;
let resp: HealthResponse =
serde_json::from_str(json).expect("extra fields must be silently ignored");
assert!(resp.is_healthy());
}
#[test]
fn health_response_missing_embedder_defaults_to_not_ready() {
let json = r#"{"status":"ok"}"#;
let resp: HealthResponse =
serde_json::from_str(json).expect("missing embedder must default gracefully");
assert!(
!resp.is_healthy(),
"missing embedder field must default to not-ready"
);
}
#[test]
fn health_response_bad_status_is_unhealthy() {
let json = r#"{"status":"starting","embedder":"ready"}"#;
let resp: HealthResponse = serde_json::from_str(json).unwrap();
assert!(
!resp.is_healthy(),
"status != ok must be unhealthy even if embedder is ready"
);
}
}