use serde::{Deserialize, Serialize};
use super::Id;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Subtopic {
pub id: Id,
pub topic: String,
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Question {
pub id: Id,
pub topic: String,
pub subtopic_id: Id,
pub subtopic: String,
pub text: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
System,
User,
Assistant,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Message {
pub role: Role,
pub content: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning_content: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ReasoningKind {
Raw,
Summary,
Redacted,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FinishReason {
Stop,
Length,
ContentFilter,
Refusal,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Exclusion {
Truncated,
Empty,
Refused,
NoRawReasoning,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Meta {
pub model: String,
pub input_tokens: u64,
pub output_tokens: u64,
pub finish_reason: FinishReason,
pub reasoning_kind: ReasoningKind,
pub excluded: Option<Exclusion>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Example {
pub id: Id,
pub topic: String,
pub subtopic: String,
pub messages: Vec<Message>,
pub meta: Meta,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn example_serializes_in_the_documented_shape() -> Result<(), serde_json::Error> {
let example = Example {
id: Id::of(&["x"]),
topic: "ownership".into(),
subtopic: "borrowing".into(),
messages: vec![
Message {
role: Role::User,
content: "Why?".into(),
reasoning_content: None,
},
Message {
role: Role::Assistant,
content: "Because.".into(),
reasoning_content: Some("Let me think.".into()),
},
],
meta: Meta {
model: "m".into(),
input_tokens: 3,
output_tokens: 5,
finish_reason: FinishReason::Stop,
reasoning_kind: ReasoningKind::Raw,
excluded: None,
},
};
let value = serde_json::to_value(&example)?;
assert_eq!(
value["messages"][0],
serde_json::json!({"role": "user", "content": "Why?"})
);
assert_eq!(
value["messages"][1]["reasoning_content"],
serde_json::json!("Let me think.")
);
assert_eq!(
value["meta"],
serde_json::json!({
"model": "m", "input_tokens": 3, "output_tokens": 5,
"finish_reason": "stop", "reasoning_kind": "raw", "excluded": null
})
);
let back: Example = serde_json::from_value(value)?;
assert_eq!(back, example);
Ok(())
}
#[test]
fn exclusions_use_snake_case() -> Result<(), serde_json::Error> {
assert_eq!(
serde_json::to_string(&Exclusion::NoRawReasoning)?,
"\"no_raw_reasoning\""
);
Ok(())
}
}