1use std::collections::BTreeMap;
2
3use serde::Serialize;
4use serde_json::{Map, Value};
5
6use crate::answer::SystemOneResponse;
7use crate::client::Client;
8use crate::error::{Error, Result};
9use crate::question::{NONE_OF_THE_ABOVE, Question};
10
11enum State {
12 Empty,
13 Whole(Value),
14 Fields(Map<String, Value>),
15}
16
17#[must_use = "a request does nothing until `.send().await`"]
24pub struct SystemOneRequest<'a> {
25 client: &'a Client,
26 model: Option<String>,
27 state: State,
28 questions: BTreeMap<String, Question>,
29 error: Option<Error>,
30}
31
32impl<'a> SystemOneRequest<'a> {
33 pub(crate) fn new(client: &'a Client) -> Self {
34 Self {
35 client,
36 model: None,
37 state: State::Empty,
38 questions: BTreeMap::new(),
39 error: None,
40 }
41 }
42
43 pub fn model(mut self, model: impl Into<String>) -> Self {
45 self.model = Some(model.into());
46 self
47 }
48
49 pub fn state(mut self, state: impl Serialize) -> Self {
52 match serde_json::to_value(state) {
53 Ok(v) => self.state = State::Whole(v),
54 Err(e) => self.fail(Error::RequestSerialization(e)),
55 }
56 self
57 }
58
59 pub fn field(mut self, name: impl Into<String>, value: impl Serialize) -> Self {
63 let value = match serde_json::to_value(value) {
64 Ok(v) => v,
65 Err(e) => {
66 self.fail(Error::RequestSerialization(e));
67 return self;
68 }
69 };
70 let name = name.into();
71 self.state = match std::mem::replace(&mut self.state, State::Empty) {
72 State::Empty => State::Fields(Map::from_iter([(name, value)])),
73 State::Fields(mut map) | State::Whole(Value::Object(mut map)) => {
74 map.insert(name, value);
75 State::Fields(map)
76 }
77 other @ State::Whole(_) => {
78 self.fail(Error::InvalidRequest(format!(
79 "cannot add field `{name}`: state is not an object"
80 )));
81 other
82 }
83 };
84 self
85 }
86
87 pub fn question(mut self, id: impl Into<String>, question: Question) -> Self {
89 let id = id.into();
90 if self.questions.insert(id.clone(), question).is_some() {
91 self.fail(Error::InvalidRequest(format!("duplicate question id `{id}`")));
92 }
93 self
94 }
95
96 pub fn noul(self, id: impl Into<String>, instructions: impl Into<Value>) -> Self {
98 self.question(id, Question::noul(instructions))
99 }
100
101 pub fn noul_with_criteria(
103 self,
104 id: impl Into<String>,
105 instructions: impl Into<Value>,
106 yes: impl Into<String>,
107 no: impl Into<String>,
108 ) -> Self {
109 self.question(id, Question::noul_with_criteria(instructions, yes, no))
110 }
111
112 pub fn choice(
115 mut self,
116 id: impl Into<String>,
117 instructions: impl Into<Value>,
118 options: impl FnOnce(ChoiceBuilder) -> ChoiceBuilder,
119 ) -> Self {
120 let id = id.into();
121 let built = options(ChoiceBuilder::default());
122 if let Some(dup) = built.duplicates.first() {
123 self.fail(Error::InvalidRequest(format!("choice `{id}` repeats option `{dup}`")));
124 return self;
125 }
126 self.question(id, Question::choice(instructions, built.options))
127 }
128
129 pub fn score<L: Into<String>>(
131 self,
132 id: impl Into<String>,
133 instructions: impl Into<Value>,
134 levels: impl IntoIterator<Item = L>,
135 ) -> Self {
136 self.question(id, Question::score(instructions, levels))
137 }
138
139 pub async fn send(self) -> Result<SystemOneResponse> {
149 if let Some(e) = self.error {
150 return Err(e);
151 }
152 let state = match self.state {
153 State::Empty => {
154 return Err(Error::InvalidRequest(
155 "no state: call `.state(..)` or `.field(..)`".into(),
156 ));
157 }
158 State::Whole(v) => v,
159 State::Fields(map) => Value::Object(map),
160 };
161 if self.questions.is_empty() {
162 return Err(Error::InvalidRequest("no questions".into()));
163 }
164 if let Some((id, problem)) = self
165 .questions
166 .iter()
167 .find_map(|(id, q)| q.structural_problem().map(|p| (id, p)))
168 {
169 return Err(Error::InvalidRequest(format!("question `{id}` {problem}")));
170 }
171 match self.model {
172 Some(model) => self.client.evaluate_with_model(&model, state, self.questions).await,
173 None => self.client.evaluate(state, self.questions).await,
174 }
175 }
176
177 fn fail(&mut self, e: Error) {
178 if self.error.is_none() {
179 self.error = Some(e);
180 }
181 }
182}
183
184#[derive(Default)]
186#[must_use = "return the builder from the `choice` closure"]
187pub struct ChoiceBuilder {
188 options: BTreeMap<String, Option<String>>,
189 duplicates: Vec<String>,
190}
191
192impl ChoiceBuilder {
193 fn insert(&mut self, name: String, description: Option<String>) {
194 if self.options.insert(name.clone(), description).is_some() {
195 self.duplicates.push(name);
196 }
197 }
198
199 pub fn option(mut self, name: impl Into<String>, description: impl Into<String>) -> Self {
201 self.insert(name.into(), Some(description.into()));
202 self
203 }
204
205 pub fn option_plain(mut self, name: impl Into<String>) -> Self {
207 self.insert(name.into(), None);
208 self
209 }
210
211 pub fn options_plain<K: Into<String>>(mut self, names: impl IntoIterator<Item = K>) -> Self {
213 for name in names {
214 self.insert(name.into(), None);
215 }
216 self
217 }
218
219 pub fn options<K, V>(mut self, pairs: impl IntoIterator<Item = (K, V)>) -> Self
221 where
222 K: Into<String>,
223 V: Into<String>,
224 {
225 for (name, description) in pairs {
226 self.insert(name.into(), Some(description.into()));
227 }
228 self
229 }
230
231 pub fn none_of_the_above(self, description: impl Into<String>) -> Self {
234 self.option(NONE_OF_THE_ABOVE, description)
235 }
236}