use openquiver_acp::SessionHandle;
use openquiver_graph::Value;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum RunValue {
Text(String),
Number(#[serde(serialize_with = "finite")] f64),
Bool(bool),
Array(Vec<RunValue>),
Agent(String),
Session(SessionHandle),
Secret(String),
}
fn finite<S: serde::Serializer>(n: &f64, serializer: S) -> Result<S::Ok, S::Error> {
if !n.is_finite() {
return Err(serde::ser::Error::custom(format!(
"{n} is not a number a journal can hold"
)));
}
serializer.serialize_f64(*n)
}
impl From<&Value> for RunValue {
fn from(value: &Value) -> Self {
match value {
Value::Text(text) => RunValue::Text(text.clone()),
Value::Number(n) => RunValue::Number(*n),
Value::Bool(b) => RunValue::Bool(*b),
Value::Array(items) => RunValue::Array(items.iter().map(RunValue::from).collect()),
Value::Secret(name) => RunValue::Secret(name.clone()),
}
}
}
impl RunValue {
pub fn as_text(&self) -> Option<String> {
match self {
RunValue::Text(text) => Some(text.clone()),
RunValue::Number(n) => Some(n.to_string()),
RunValue::Bool(b) => Some(b.to_string()),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
RunValue::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_agent(&self) -> Option<&str> {
match self {
RunValue::Agent(name) => Some(name),
_ => None,
}
}
pub fn as_session(&self) -> Option<&SessionHandle> {
match self {
RunValue::Session(handle) => Some(handle),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::RunValue;
#[test]
fn a_number_json_cannot_hold_is_refused_rather_than_written_as_null() {
for bad in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
let written = serde_json::to_value(RunValue::Number(bad));
assert!(
written.is_err(),
"{bad} was written as {written:?}, which reads back as nothing"
);
}
}
#[test]
fn an_ordinary_number_still_round_trips() {
let written = serde_json::to_value(RunValue::Number(3.5)).expect("write");
let read: RunValue = serde_json::from_value(written).expect("read");
assert_eq!(read, RunValue::Number(3.5));
}
}