use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::service::ServiceStatus;
pub const DAEMON_SERVICE: &str = "daemon";
pub const MAX_LINE_BYTES: usize = 1024 * 1024;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DaemonEnvelope {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub service: Option<String>,
pub op: String,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub payload: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub origin_invocation_id: Option<String>,
}
impl DaemonEnvelope {
pub fn service(name: impl Into<String>, op: impl Into<String>, payload: Value) -> Self {
Self {
service: Some(name.into()),
op: op.into(),
payload,
origin_invocation_id: None,
}
}
pub fn builtin(op: impl Into<String>) -> Self {
Self {
service: None,
op: op.into(),
payload: Value::Null,
origin_invocation_id: None,
}
}
#[must_use]
pub fn with_origin(mut self, invocation_id: impl Into<String>) -> Self {
self.origin_invocation_id = Some(invocation_id.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DaemonReply {
pub ok: bool,
#[serde(default, skip_serializing_if = "Value::is_null")]
pub payload: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
impl DaemonReply {
pub fn ok(payload: Value) -> Self {
Self {
ok: true,
payload,
error: None,
}
}
pub fn err(message: impl Into<String>) -> Self {
Self {
ok: false,
payload: Value::Null,
error: Some(message.into()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatusReport {
pub services: Vec<ServiceStatus>,
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn envelope_omits_origin_when_absent() {
let line = serde_json::to_string(&DaemonEnvelope::service(
"snowflake",
"query",
serde_json::json!({ "sql": "SELECT 1" }),
))
.unwrap();
assert!(!line.contains("origin_invocation_id"), "{line}");
}
#[test]
fn envelope_round_trips_origin() {
let env = DaemonEnvelope::service("snowflake", "query", Value::Null).with_origin("cli-42");
let line = serde_json::to_string(&env).unwrap();
assert!(line.contains("origin_invocation_id"), "{line}");
let back: DaemonEnvelope = serde_json::from_str(&line).unwrap();
assert_eq!(back.origin_invocation_id.as_deref(), Some("cli-42"));
}
#[test]
fn envelope_from_older_client_defaults_origin_to_none() {
let back: DaemonEnvelope =
serde_json::from_str(r#"{"service":"snowflake","op":"query"}"#).unwrap();
assert!(back.origin_invocation_id.is_none());
}
}