use serde::de::{self, Deserializer};
use serde::ser::SerializeMap;
use serde::{Deserialize, Serialize, Serializer};
use crate::types::string_enum;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct OutputConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub effort: Option<Effort>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub format: Option<OutputFormat>,
}
string_enum! {
pub enum Effort {
Low = "low",
Medium = "medium",
High = "high",
Xhigh = "xhigh",
Max = "max",
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum OutputFormat {
JsonSchema {
schema: serde_json::Value,
},
Unknown(serde_json::Value),
}
impl Serialize for OutputFormat {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
OutputFormat::JsonSchema { schema } => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "json_schema")?;
map.serialize_entry("schema", schema)?;
map.end()
}
OutputFormat::Unknown(value) => value.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for OutputFormat {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
let tag = value.get("type").and_then(|t| t.as_str());
match tag {
Some("json_schema") => {
#[derive(Deserialize)]
struct Fields {
schema: serde_json::Value,
}
let r: Fields = serde_json::from_value(value).map_err(de::Error::custom)?;
Ok(OutputFormat::JsonSchema { schema: r.schema })
}
_ => Ok(OutputFormat::Unknown(value)),
}
}
}
impl OutputFormat {
pub fn json_schema(schema: serde_json::Value) -> Self {
OutputFormat::JsonSchema { schema }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rt<T>(json: serde_json::Value) -> serde_json::Value
where
T: serde::Serialize + serde::de::DeserializeOwned + PartialEq + std::fmt::Debug,
{
let parsed: T = serde_json::from_value(json).expect("deserialize");
let out = serde_json::to_value(&parsed).expect("serialize");
let reparsed: T = serde_json::from_value(out.clone()).expect("re-deserialize");
assert_eq!(parsed, reparsed, "struct round-trip mismatch");
out
}
#[test]
fn output_config_round_trips() {
let j = serde_json::json!({
"effort": "high",
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {"answer": {"type": "string"}},
"additionalProperties": false
}
}
});
assert_eq!(rt::<OutputConfig>(j.clone()), j);
}
#[test]
fn effort_levels_round_trip() {
for level in ["low", "medium", "high", "xhigh", "max"] {
let j = serde_json::json!(level);
assert_eq!(rt::<Effort>(j.clone()), j);
}
}
#[test]
fn unknown_output_format_is_preserved() {
let j = serde_json::json!({"type": "future_format", "detail": 1});
let parsed: OutputFormat = serde_json::from_value(j.clone()).unwrap();
assert!(matches!(parsed, OutputFormat::Unknown(_)));
assert_eq!(serde_json::to_value(&parsed).unwrap(), j);
}
}