forge_primitives/
question.rs1use serde::{Deserialize, Serialize};
6
7use crate::session_update::ToolCall;
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct QuestionOption {
11 pub option_id: String,
12 pub label: String,
13 pub description: Option<String>,
14 pub preview: Option<String>,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub struct QuestionPrompt {
19 pub question: String,
20 pub header: String,
21 pub multi_select: bool,
22 pub options: Vec<QuestionOption>,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26pub struct QuestionRequest {
27 pub tool_call: ToolCall,
28 pub prompt: QuestionPrompt,
29 pub question_index: u64,
30 pub total_questions: u64,
31}
32
33#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
34pub struct QuestionAnnotation {
35 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub preview: Option<String>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub notes: Option<String>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(tag = "outcome", rename_all = "snake_case")]
43pub enum QuestionOutcome {
44 Answered { selected_option_ids: Vec<String>, annotation: Option<QuestionAnnotation> },
45 Cancelled,
46}
47
48#[cfg(test)]
49mod tests {
50 use super::QuestionAnnotation;
51
52 #[test]
53 fn annotation_omits_none_fields_rather_than_serializing_null() {
54 let annotation = QuestionAnnotation { preview: Some("shown".to_owned()), notes: None };
60 let value = serde_json::to_value(&annotation).expect("serialize");
61 let obj = value.as_object().expect("annotation serializes to an object");
62 assert_eq!(obj.get("preview").and_then(serde_json::Value::as_str), Some("shown"));
63 assert!(
64 !obj.contains_key("notes"),
65 "None fields must be omitted, not serialized as null; got {value}",
66 );
67 }
68}