use serde_json::Value;
#[derive(Debug, Clone)]
pub struct StructuredResult {
pub data: Value,
pub raw_json: String,
}
pub fn create_structured_result(data: Value, raw_json: String) -> StructuredResult {
StructuredResult { data, raw_json }
}
pub fn is_structured_result(value: &Value) -> bool {
value
.as_object()
.map(|o| o.contains_key("__prompty_structured"))
.unwrap_or(false)
}
pub fn to_structured_value(result: &StructuredResult) -> Value {
serde_json::json!({
"__prompty_structured": true,
"data": result.data,
"raw_json": result.raw_json,
})
}
pub fn from_structured_value(value: &Value) -> Option<StructuredResult> {
if !is_structured_result(value) {
return None;
}
let obj = value.as_object()?;
let data = obj.get("data")?.clone();
let raw_json = obj.get("raw_json")?.as_str()?.to_string();
Some(StructuredResult { data, raw_json })
}
pub fn unwrap_structured(value: &Value) -> Value {
if let Some(sr) = from_structured_value(value) {
sr.data
} else {
value.clone()
}
}
#[allow(clippy::type_complexity)]
pub fn cast<T>(
value: &Value,
validator: Option<&dyn Fn(&T) -> Result<(), String>>,
) -> Result<T, CastError>
where
T: serde::de::DeserializeOwned,
{
let json_str = if let Some(sr) = from_structured_value(value) {
sr.raw_json
} else if let Some(s) = value.as_str() {
s.to_string()
} else {
serde_json::to_string(value).map_err(|e| CastError::Serialization(e.to_string()))?
};
let parsed: T = serde_json::from_str(&json_str).map_err(|e| CastError::Parse(e.to_string()))?;
if let Some(v) = validator {
v(&parsed).map_err(CastError::Validation)?;
}
Ok(parsed)
}
#[derive(Debug, thiserror::Error)]
pub enum CastError {
#[error("Failed to serialize value: {0}")]
Serialization(String),
#[error("Failed to parse JSON: {0}")]
Parse(String),
#[error("Validation failed: {0}")]
Validation(String),
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[derive(Debug, Deserialize, PartialEq)]
struct Weather {
city: String,
temp: f64,
}
#[test]
fn test_create_structured_result() {
let data = serde_json::json!({"city": "NY", "temp": 72.0});
let raw = r#"{"city":"NY","temp":72.0}"#.to_string();
let sr = create_structured_result(data.clone(), raw.clone());
assert_eq!(sr.data, data);
assert_eq!(sr.raw_json, raw);
}
#[test]
fn test_is_structured_result() {
let sr = serde_json::json!({
"__prompty_structured": true,
"data": {"city": "NY"},
"raw_json": "{\"city\":\"NY\"}"
});
assert!(is_structured_result(&sr));
assert!(!is_structured_result(&serde_json::json!({"city": "NY"})));
assert!(!is_structured_result(&serde_json::json!("hello")));
}
#[test]
fn test_to_and_from_structured_value() {
let data = serde_json::json!({"city": "NY", "temp": 72.0});
let raw = r#"{"city":"NY","temp":72.0}"#.to_string();
let sr = create_structured_result(data.clone(), raw.clone());
let val = to_structured_value(&sr);
assert!(is_structured_result(&val));
let recovered = from_structured_value(&val).unwrap();
assert_eq!(recovered.data, data);
assert_eq!(recovered.raw_json, raw);
}
#[test]
fn test_cast_from_structured_result() {
let raw = r#"{"city":"NY","temp":72.0}"#;
let data: Value = serde_json::from_str(raw).unwrap();
let sr = create_structured_result(data, raw.to_string());
let val = to_structured_value(&sr);
let weather: Weather = cast(&val, None).unwrap();
assert_eq!(weather.city, "NY");
assert_eq!(weather.temp, 72.0);
}
#[test]
fn test_cast_from_string() {
let val = serde_json::json!(r#"{"city":"LA","temp":85.0}"#);
let weather: Weather = cast(&val, None).unwrap();
assert_eq!(weather.city, "LA");
assert_eq!(weather.temp, 85.0);
}
#[test]
fn test_cast_from_object() {
let val = serde_json::json!({"city": "SF", "temp": 65.0});
let weather: Weather = cast(&val, None).unwrap();
assert_eq!(weather.city, "SF");
assert_eq!(weather.temp, 65.0);
}
#[test]
fn test_cast_with_validator() {
let val = serde_json::json!({"city": "NY", "temp": 72.0});
let validator = |w: &Weather| {
if w.temp > 100.0 {
Err("Temperature too high".into())
} else {
Ok(())
}
};
let weather: Weather = cast(&val, Some(&validator)).unwrap();
assert_eq!(weather.city, "NY");
}
#[test]
fn test_cast_with_validator_failure() {
let val = serde_json::json!({"city": "Death Valley", "temp": 130.0});
let validator = |w: &Weather| {
if w.temp > 100.0 {
Err("Temperature too high".into())
} else {
Ok(())
}
};
let result: Result<Weather, _> = cast(&val, Some(&validator));
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), CastError::Validation(_)));
}
#[test]
fn test_cast_invalid_json() {
let val = serde_json::json!("not valid json for Weather");
let result: Result<Weather, _> = cast(&val, None);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), CastError::Parse(_)));
}
#[test]
fn test_from_structured_value_non_structured() {
let val = serde_json::json!({"city": "NY"});
assert!(from_structured_value(&val).is_none());
}
#[test]
fn test_unwrap_structured_with_structured() {
let data = serde_json::json!({"city": "NY", "temp": 72.0});
let raw = r#"{"city":"NY","temp":72.0}"#.to_string();
let sr = create_structured_result(data.clone(), raw);
let val = to_structured_value(&sr);
let unwrapped = unwrap_structured(&val);
assert_eq!(unwrapped, data);
}
#[test]
fn test_unwrap_structured_without_structured() {
let val = serde_json::json!("Hello world");
let unwrapped = unwrap_structured(&val);
assert_eq!(unwrapped, val);
}
}