use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct CallError {
pub name: String,
pub message: String,
}
#[derive(Debug, Clone)]
pub enum CallOutcome {
Ok {
value: Option<serde_json::Value>,
text: Option<String>,
},
Err(CallError),
}
impl CallOutcome {
pub fn is_ok(&self) -> bool {
matches!(self, CallOutcome::Ok { .. })
}
}
#[derive(Debug, Clone)]
pub struct CallAnswer {
pub origin: String,
pub outcome: CallOutcome,
pub attachment: Option<serde_json::Value>,
pub attachment_bytes: Option<usize>,
}
impl Serialize for CallAnswer {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeMap;
let mut m = serializer.serialize_map(None)?;
m.serialize_entry("origin", &self.origin)?;
m.serialize_entry("ok", &self.outcome.is_ok())?;
if let CallOutcome::Ok { value, text } = &self.outcome {
if let Some(v) = value {
m.serialize_entry("value", v)?;
}
if let Some(t) = text {
m.serialize_entry("text", t)?;
}
}
if let Some(a) = &self.attachment {
m.serialize_entry("attachment", a)?;
}
if let Some(n) = self.attachment_bytes {
m.serialize_entry("attachment_bytes", &n)?;
}
if let CallOutcome::Err(e) = &self.outcome {
m.serialize_entry("error", e)?;
}
m.end()
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CallReport {
pub key: String,
pub timeout_s: f64,
pub answers: Vec<CallAnswer>,
}
impl CallReport {
pub fn exit_code(&self) -> i32 {
if self.answers.is_empty() {
2
} else if self
.answers
.iter()
.any(|a| matches!(a.outcome, CallOutcome::Err(_)))
{
1
} else {
0
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ProbeReport {
pub input: String,
pub origin: String,
pub via: String,
pub call: CallReport,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ValueSource {
Storage,
Cache,
Window,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn call_exit_codes() {
let mut r = CallReport {
key: "k".into(),
timeout_s: 5.0,
answers: vec![],
};
assert_eq!(r.exit_code(), 2, "silence is its own exit code");
r.answers.push(CallAnswer {
origin: "h-1".into(),
outcome: CallOutcome::Ok {
value: None,
text: Some("x".into()),
},
attachment: None,
attachment_bytes: None,
});
assert_eq!(r.exit_code(), 0);
r.answers.push(CallAnswer {
origin: "h-2".into(),
outcome: CallOutcome::Err(CallError {
name: "error/busy".into(),
message: "later".into(),
}),
attachment: None,
attachment_bytes: None,
});
assert_eq!(r.exit_code(), 1, "any refusal fails the invocation");
}
}