1use serde::{Deserialize, Serialize};
2use utils::{ReasoningEffort, is_false};
3
4use crate::meta::{from_meta, to_meta};
5
6type Meta = serde_json::Map<String, serde_json::Value>;
7
8#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
10pub struct ConfigOptionMeta {
11 #[serde(default, skip_serializing_if = "is_false")]
12 pub multi_select: bool,
13}
14
15#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
17pub struct SelectOptionMeta {
18 #[serde(default, skip_serializing_if = "Vec::is_empty")]
19 pub reasoning_levels: Vec<ReasoningEffort>,
20 #[serde(default, skip_serializing_if = "is_false")]
21 pub supports_image: bool,
22 #[serde(default, skip_serializing_if = "is_false")]
23 pub supports_audio: bool,
24}
25
26impl SelectOptionMeta {
27 pub fn supports_reasoning(&self) -> bool {
28 self.reasoning_levels.iter().any(|effort| effort.is_enabled())
29 }
30}
31
32impl ConfigOptionMeta {
33 pub fn into_meta(self) -> Option<Meta> {
34 to_meta(&self, None)
35 }
36
37 pub fn from_meta(meta: Option<&Meta>) -> Self {
38 from_meta(meta, None)
39 }
40}
41
42impl SelectOptionMeta {
43 pub fn into_meta(self) -> Option<Meta> {
44 to_meta(&self, None)
45 }
46
47 pub fn from_meta(meta: Option<&Meta>) -> Self {
48 from_meta(meta, None)
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn config_option_meta_roundtrip() {
58 let original = ConfigOptionMeta { multi_select: true };
59 let meta = original.clone().into_meta();
60 assert!(meta.is_some());
61 let restored = ConfigOptionMeta::from_meta(meta.as_ref());
62 assert_eq!(restored, original);
63 }
64
65 #[test]
66 fn select_option_meta_roundtrip() {
67 let original = SelectOptionMeta {
68 reasoning_levels: vec![ReasoningEffort::Low, ReasoningEffort::Medium, ReasoningEffort::High],
69 supports_image: true,
70 supports_audio: false,
71 };
72 let meta = original.clone().into_meta();
73 assert!(meta.is_some());
74 let restored = SelectOptionMeta::from_meta(meta.as_ref());
75 assert_eq!(restored, original);
76 }
77
78 #[test]
79 fn default_produces_none() {
80 assert!(ConfigOptionMeta::default().into_meta().is_none());
81 assert!(SelectOptionMeta::default().into_meta().is_none());
82 }
83
84 #[test]
85 fn from_meta_none_returns_default() {
86 assert_eq!(ConfigOptionMeta::from_meta(None), ConfigOptionMeta::default());
87 assert_eq!(SelectOptionMeta::from_meta(None), SelectOptionMeta::default());
88 }
89
90 #[test]
91 fn unknown_keys_are_ignored() {
92 let mut map = serde_json::Map::new();
93 map.insert("multi_select".to_string(), serde_json::Value::Bool(true));
94 map.insert("unknown_field".to_string(), serde_json::Value::String("hello".to_string()));
95 let parsed = ConfigOptionMeta::from_meta(Some(&map));
96 assert_eq!(parsed, ConfigOptionMeta { multi_select: true });
97 }
98
99 #[test]
100 fn false_fields_omitted_from_serialized_output() {
101 let meta = ConfigOptionMeta { multi_select: false };
102 let value = serde_json::to_value(&meta).unwrap();
103 let obj = value.as_object().unwrap();
104 assert!(!obj.contains_key("multi_select"));
105
106 let meta = SelectOptionMeta::default();
107 let value = serde_json::to_value(&meta).unwrap();
108 let obj = value.as_object().unwrap();
109 assert!(!obj.contains_key("reasoning_levels"));
110 assert!(!obj.contains_key("supports_image"));
111 assert!(!obj.contains_key("supports_audio"));
112 }
113
114 #[test]
115 fn supports_reasoning_convenience() {
116 assert!(!SelectOptionMeta::default().supports_reasoning());
117 let meta = SelectOptionMeta {
118 reasoning_levels: vec![ReasoningEffort::Low, ReasoningEffort::High],
119 supports_image: false,
120 supports_audio: false,
121 };
122 assert!(meta.supports_reasoning());
123 }
124}