use serde::{Deserialize, Serialize};
pub const EXT_PROVENANCE_KEY: &str = "ext-provenance";
pub const EXT_PROVENANCE_FIELDS: &[&str] = &[
"prompt",
"model",
"generation_duration_ms",
"biome",
"semantic_category",
];
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ExtProvenance {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prompt: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub generation_duration_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub biome: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub semantic_category: Option<String>,
}
#[cfg(feature = "schema")]
pub(crate) fn core_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!(true)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_under_the_extension_key() {
let provenance = ExtProvenance {
prompt: Some("a lighthouse over a foggy bay".into()),
model: Some("claude-fable-5-1".into()),
generation_duration_ms: Some(41_000),
biome: Some("coastal".into()),
semantic_category: Some("landmark".into()),
};
let json = serde_json::to_string(&provenance).unwrap();
let back: ExtProvenance = serde_json::from_str(&json).unwrap();
assert_eq!(back, provenance);
assert!(!json.contains("prompt\":null"));
}
#[test]
fn empty_serializes_to_an_empty_object() {
let json = serde_json::to_string(&ExtProvenance::default()).unwrap();
assert_eq!(json, "{}");
}
}