#![cfg_attr(
not(feature = "extract"),
doc = "[crate::extract::ExtractError::Refused]: crate#cargo-features"
)]
use std::collections::BTreeMap;
use std::hash::{Hash, Hasher};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::{Error, ExtraMap, Result};
pub const RAW_RESPONSE_KEY: &str = "raw_response";
pub const DECLINE_EXPLANATION_KEY: &str = "decline_explanation";
pub const ALLAMA_NAMESPACE: &str = "allama";
#[derive(Debug, Clone, Default)]
pub struct ProviderData(BTreeMap<String, ExtraMap>);
pub trait ProviderDataEntry: Serialize + DeserializeOwned {
const NAMESPACE: &'static str;
const KEY: &'static str;
}
impl PartialEq for ProviderData {
fn eq(&self, other: &Self) -> bool {
self.namespaces().eq(other.namespaces())
}
}
impl Eq for ProviderData {}
impl Hash for ProviderData {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_usize(self.namespaces().count());
for namespace in self.namespaces() {
namespace.hash(state);
}
}
}
impl Serialize for ProviderData {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
serializer.collect_map(self.namespaces())
}
}
impl<'de> Deserialize<'de> for ProviderData {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
let mut namespaces = BTreeMap::<String, ExtraMap>::deserialize(deserializer)?;
namespaces.retain(|_, entries| !entries.is_empty());
Ok(Self(namespaces))
}
}
impl ProviderData {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.values().all(ExtraMap::is_empty)
}
#[must_use]
pub fn namespace(&self, namespace: &str) -> Option<&ExtraMap> {
self.0.get(namespace).filter(|entries| !entries.is_empty())
}
pub fn namespace_mut(&mut self, namespace: impl Into<String>) -> &mut ExtraMap {
self.0.entry(namespace.into()).or_default()
}
pub fn namespaces(&self) -> impl Iterator<Item = (&str, &ExtraMap)> {
self.0
.iter()
.filter(|(_, entries)| !entries.is_empty())
.map(|(namespace, entries)| (namespace.as_str(), entries))
}
pub fn foreign_namespaces<'a>(&'a self, namespace: &'a str) -> impl Iterator<Item = &'a str> {
self.namespaces()
.map(|(name, _)| name)
.filter(move |name| *name != namespace && *name != ALLAMA_NAMESPACE)
}
#[must_use]
pub fn get_raw(&self, namespace: &str, key: &str) -> Option<&serde_json::Value> {
self.0.get(namespace)?.get(key)
}
pub fn insert_raw(
&mut self,
namespace: impl Into<String>,
key: impl Into<String>,
value: serde_json::Value,
) {
self.namespace_mut(namespace).insert(key.into(), value);
}
#[must_use]
pub fn with_raw(
mut self,
namespace: impl Into<String>,
key: impl Into<String>,
value: serde_json::Value,
) -> Self {
self.insert_raw(namespace, key, value);
self
}
pub fn get<T: ProviderDataEntry>(&self) -> Result<Option<T>> {
self.get_raw(T::NAMESPACE, T::KEY)
.map(|value| T::deserialize(value).map_err(Error::from))
.transpose()
}
pub fn insert<T: ProviderDataEntry>(&mut self, value: &T) -> Result<()> {
let value = serde_json::to_value(value)?;
self.insert_raw(T::NAMESPACE, T::KEY, value);
Ok(())
}
pub fn remove_raw(&mut self, namespace: &str, key: &str) -> Option<serde_json::Value> {
let entries = self.0.get_mut(namespace)?;
let removed = entries.remove(key);
if entries.is_empty() {
self.0.remove(namespace);
}
removed
}
pub fn remove_namespace(&mut self, namespace: &str) -> Option<ExtraMap> {
self.0
.remove(namespace)
.filter(|entries| !entries.is_empty())
}
pub fn merge(&mut self, other: ProviderData) {
for (namespace, entries) in other.0 {
self.namespace_mut(namespace).extend(entries);
}
}
pub fn retain_namespaces(&mut self, mut keep: impl FnMut(&str) -> bool) {
self.0.retain(|namespace, _| keep(namespace));
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Signature {
value: String,
}
impl ProviderDataEntry for Signature {
const NAMESPACE: &'static str = "anthropic";
const KEY: &'static str = "signature";
}
#[test]
fn typed_round_trip() {
let mut data = ProviderData::new();
data.insert(&Signature {
value: "abc".into(),
})
.unwrap();
assert_eq!(
data.get::<Signature>().unwrap(),
Some(Signature {
value: "abc".into()
})
);
assert_eq!(
serde_json::to_value(&data).unwrap(),
json!({"anthropic": {"signature": {"value": "abc"}}})
);
}
#[test]
fn foreign_namespaces_skip_the_own_and_core_namespaces() {
let mut data = ProviderData::new()
.with_raw("google-ai", "thought_signature", json!("s"))
.with_raw(ALLAMA_NAMESPACE, "note", json!(1))
.with_raw("anthropic", "signature", json!("a"));
data.namespace_mut("openai");
assert_eq!(
data.foreign_namespaces("google-ai").collect::<Vec<_>>(),
["anthropic"]
);
}
#[test]
fn empty_namespaces_are_ignored() {
let mut data = ProviderData::new();
data.namespace_mut("google-ai");
assert!(data.is_empty());
assert!(data.namespace("google-ai").is_none());
assert_eq!(data.namespaces().count(), 0);
assert_eq!(data, ProviderData::new());
assert_eq!(hash(&data), hash(&ProviderData::new()));
assert_eq!(serde_json::to_value(&data).unwrap(), json!({}));
assert_eq!(data.remove_namespace("google-ai"), None);
}
#[test]
fn empty_namespace_left_by_namespace_mut_is_absent() {
let mut data = ProviderData::new().with_raw("openai", "a", json!(1));
data.namespace_mut("google-ai").insert("k".into(), json!(1));
data.namespace_mut("google-ai").remove("k");
let expected = ProviderData::new().with_raw("openai", "a", json!(1));
assert_eq!(data, expected);
assert_eq!(hash(&data), hash(&expected));
assert_eq!(
serde_json::to_value(&data).unwrap(),
json!({"openai": {"a": 1}})
);
}
#[test]
fn deserialize_drops_empty_namespaces() {
let decoded: ProviderData =
serde_json::from_value(json!({"openai": {"a": 1}, "anthropic": {}})).unwrap();
assert_eq!(
decoded,
ProviderData::new().with_raw("openai", "a", json!(1))
);
assert_eq!(decoded.0.len(), 1);
}
#[test]
fn hash_ignores_insertion_order() {
let mut a = serde_json::Map::new();
a.insert("x".into(), json!(1));
a.insert("y".into(), json!(2));
let mut b = serde_json::Map::new();
b.insert("y".into(), json!(2));
b.insert("x".into(), json!(1));
let a = ProviderData::new().with_raw("ns", "k", serde_json::Value::Object(a));
let b = ProviderData::new().with_raw("ns", "k", serde_json::Value::Object(b));
assert_eq!(a, b);
assert_eq!(hash(&a), hash(&b));
}
fn hash<T: Hash>(value: &T) -> u64 {
let mut hasher = std::hash::DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}
#[test]
fn merge_overwrites_per_key() {
let mut a = ProviderData::new()
.with_raw("openai", "a", json!(1))
.with_raw("openai", "b", json!(2));
a.merge(ProviderData::new().with_raw("openai", "b", json!(3)));
assert_eq!(a.get_raw("openai", "a"), Some(&json!(1)));
assert_eq!(a.get_raw("openai", "b"), Some(&json!(3)));
}
#[test]
fn remove_raw_drops_empty_namespace() {
let mut data = ProviderData::new().with_raw("openai", "a", json!(1));
assert_eq!(data.remove_raw("openai", "a"), Some(json!(1)));
assert!(data.is_empty());
}
#[test]
fn typed_decode_errors_surface() {
let data = ProviderData::new().with_raw("anthropic", "signature", json!(42));
assert!(data.get::<Signature>().is_err());
}
#[test]
fn absent_typed_entry_is_ok_none() {
let data = ProviderData::new().with_raw("anthropic", "other", json!(42));
assert_eq!(data.get::<Signature>().unwrap(), None);
}
}