use std::borrow::Cow;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ProviderEchoSnapshot {
pub provider: Cow<'static, str>,
pub payload: serde_json::Value,
}
impl ProviderEchoSnapshot {
#[must_use]
pub fn new(provider: impl Into<Cow<'static, str>>, payload: serde_json::Value) -> Self {
Self {
provider: provider.into(),
payload,
}
}
#[must_use]
pub fn for_provider(
provider: impl Into<Cow<'static, str>>,
field: &str,
value: impl Into<serde_json::Value>,
) -> Self {
let mut map = serde_json::Map::with_capacity(1);
map.insert(field.to_owned(), value.into());
Self {
provider: provider.into(),
payload: serde_json::Value::Object(map),
}
}
#[must_use]
pub fn payload_field(&self, field: &str) -> Option<&serde_json::Value> {
self.payload.as_object().and_then(|obj| obj.get(field))
}
#[must_use]
pub fn payload_str(&self, field: &str) -> Option<&str> {
self.payload_field(field)
.and_then(serde_json::Value::as_str)
}
#[must_use]
pub fn find_in<'a>(echoes: &'a [Self], name: &str) -> Option<&'a Self> {
echoes.iter().find(|e| e.provider.as_ref() == name)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn round_trip_serde() {
let snap =
ProviderEchoSnapshot::for_provider("anthropic-messages", "signature", "WaUjzkypQ2…");
let s = serde_json::to_string(&snap).unwrap();
let back: ProviderEchoSnapshot = serde_json::from_str(&s).unwrap();
assert_eq!(snap, back);
}
#[test]
fn payload_field_lookup() {
let snap = ProviderEchoSnapshot::for_provider("gemini", "thought_signature", "EhsM…");
assert_eq!(snap.payload_str("thought_signature"), Some("EhsM…"));
assert_eq!(snap.payload_str("missing"), None);
}
#[test]
fn payload_field_handles_non_object() {
let snap = ProviderEchoSnapshot::new("x", json!("scalar"));
assert_eq!(snap.payload_field("anything"), None);
}
#[test]
fn find_picks_first_match() {
let echoes = vec![
ProviderEchoSnapshot::for_provider("anthropic-messages", "signature", "a"),
ProviderEchoSnapshot::for_provider("gemini", "thought_signature", "g"),
];
assert_eq!(
ProviderEchoSnapshot::find_in(&echoes, "anthropic-messages")
.and_then(|s| s.payload_str("signature")),
Some("a"),
);
assert_eq!(
ProviderEchoSnapshot::find_in(&echoes, "gemini")
.and_then(|s| s.payload_str("thought_signature")),
Some("g"),
);
assert!(ProviderEchoSnapshot::find_in(&echoes, "openai-responses").is_none());
}
}