openkind_client/
question.rs1use std::collections::HashMap;
27
28use openkind_core::{ChoiceQuestion, NoulCriteria, NoulQuestion, Question, ScoreQuestion, State};
29
30pub type Instructions = serde_json::Value;
32
33pub fn noul(instructions: impl Into<Instructions>) -> Question {
35 Question::Noul(NoulQuestion {
36 instructions: instructions.into(),
37 criteria: None,
38 })
39}
40
41pub fn noul_with(
43 instructions: impl Into<Instructions>,
44 is_true: impl Into<String>,
45 is_false: impl Into<String>,
46) -> Question {
47 Question::Noul(NoulQuestion {
48 instructions: instructions.into(),
49 criteria: Some(NoulCriteria {
50 r#true: is_true.into(),
51 r#false: is_false.into(),
52 }),
53 })
54}
55
56pub fn choice<K: Into<String>>(
59 instructions: impl Into<Instructions>,
60 criteria: impl IntoIterator<Item = (K, Option<String>)>,
61) -> Question {
62 Question::Choice(ChoiceQuestion {
63 instructions: instructions.into(),
64 criteria: criteria
65 .into_iter()
66 .map(|(k, v)| (k.into(), v))
67 .collect::<HashMap<_, _, _>>(),
68 })
69}
70
71pub fn score(
73 instructions: impl Into<Instructions>,
74 levels: impl IntoIterator<Item: Into<String>>,
75) -> Question {
76 Question::Score(ScoreQuestion {
77 instructions: instructions.into(),
78 criteria: levels.into_iter().map(Into::into).collect(),
79 })
80}
81
82pub fn text(s: impl Into<String>) -> State {
84 State::Text(s.into())
85}
86
87pub fn structured_object(value: serde_json::Map<String, serde_json::Value>) -> State {
89 State::Object(value)
90}
91
92pub fn structured_array(values: Vec<serde_json::Value>) -> State {
94 State::Array(values)
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use openkind_core::{validate_request, SystemRequest};
101
102 #[test]
103 fn helpers_produce_valid_wire_requests() {
104 let request = SystemRequest {
105 state: text("I was charged twice. Please help."),
106 model: "mock".into(),
107 questions: HashMap::from_iter([
108 ("billing".to_string(), noul("Is this about billing?")),
109 (
110 "tone".to_string(),
111 choice("What is the tone?", [("calm", None), ("angry", None)]),
112 ),
113 (
114 "severity".to_string(),
115 score("How severe?", ["low", "medium", "high"]),
116 ),
117 (
118 "truthy".to_string(),
119 noul_with("Did the user dispute?", "user disputes", "user accepts"),
120 ),
121 ]),
122 };
123 assert!(validate_request(&request).is_ok(), "must pass validation");
124 }
125
126 #[test]
127 fn noul_serializes_without_confidence() {
128 let q = noul("yes or no?");
129 let wire = serde_json::to_value(q).unwrap();
130 assert_eq!(wire["type"], "noul");
131 assert_eq!(wire["instructions"], "yes or no?");
132 }
133
134 #[test]
135 fn noul_with_uses_reserved_true_false_keys() {
136 let q = noul_with("disputed?", "disputed", "accepted");
137 let wire = serde_json::to_value(q).unwrap();
138 assert_eq!(wire["criteria"]["true"], "disputed");
139 assert_eq!(wire["criteria"]["false"], "accepted");
140 }
141
142 #[test]
143 fn structured_states_serialize() {
144 let mut map = serde_json::Map::new();
145 map.insert("user".into(), serde_json::json!({"id": 7}));
146 assert!(matches!(structured_object(map), State::Object(_)));
147 assert!(matches!(
148 structured_array(vec![serde_json::json!("hello")]),
149 State::Array(_)
150 ));
151 }
152}