1use crate::{AnswerType, Error, ErrorKind};
3use indexmap::IndexMap;
4use serde::{Deserialize, Serialize};
5fn invalid(message: &str) -> Error {
6 Error::new(ErrorKind::InvalidQuestion, message)
7}
8fn instructions(value: String) -> Result<String, Error> {
9 if is_blank(&value) {
10 Err(invalid("instructions must be a non-empty string"))
11 } else {
12 Ok(value)
13 }
14}
15fn confidence(value: f64) -> Result<f64, Error> {
16 if value.is_finite() && (0.0..=1.0).contains(&value) {
17 Ok(value)
18 } else {
19 Err(invalid(
20 "min_confidence must be a finite number from 0 to 1",
21 ))
22 }
23}
24fn labels<'a>(values: impl IntoIterator<Item = &'a str>) -> Result<(), Error> {
25 let values: Vec<_> = values.into_iter().collect();
26 if values.len() < 2 {
27 return Err(invalid("need at least 2 outcomes"));
28 }
29 let mut seen = std::collections::HashSet::new();
30 if values.iter().any(|v| v.is_empty() || !seen.insert(*v)) {
31 return Err(invalid("outcomes must be unique non-empty strings"));
32 }
33 Ok(())
34}
35#[derive(Clone, PartialEq, Serialize, Deserialize)]
37#[serde(try_from = "BooleanFields")]
38pub struct Boolean {
39 instructions: String,
40 min_confidence: f64,
41 true_description: Option<String>,
42 false_description: Option<String>,
43}
44impl Boolean {
45 pub fn new(text: impl Into<String>) -> Result<Self, Error> {
51 Ok(Self {
52 instructions: instructions(text.into())?,
53 min_confidence: 0.0,
54 true_description: None,
55 false_description: None,
56 })
57 }
58 pub fn with_descriptions(mut self, yes: Option<String>, no: Option<String>) -> Self {
60 self.true_description = yes;
61 self.false_description = no;
62 self
63 }
64 pub fn true_description(&self) -> Option<&str> {
66 self.true_description.as_deref()
67 }
68 pub fn false_description(&self) -> Option<&str> {
70 self.false_description.as_deref()
71 }
72}
73#[derive(Clone, PartialEq, Serialize, Deserialize)]
75#[serde(try_from = "ChoiceFields")]
76pub struct Choice {
77 instructions: String,
78 min_confidence: f64,
79 options: IndexMap<String, Option<String>>,
80}
81impl Choice {
82 pub fn new(
88 text: impl Into<String>,
89 options: IndexMap<String, Option<String>>,
90 ) -> Result<Self, Error> {
91 labels(options.keys().map(String::as_str))?;
92 Ok(Self {
93 instructions: instructions(text.into())?,
94 min_confidence: 0.0,
95 options,
96 })
97 }
98 pub fn options(&self) -> &IndexMap<String, Option<String>> {
100 &self.options
101 }
102}
103#[derive(Clone, PartialEq, Serialize, Deserialize)]
105#[serde(try_from = "ScoreFields")]
106pub struct Score {
107 instructions: String,
108 min_confidence: f64,
109 levels: Vec<String>,
110}
111impl Score {
112 pub fn new(text: impl Into<String>, levels: Vec<String>) -> Result<Self, Error> {
118 labels(levels.iter().map(String::as_str))?;
119 Ok(Self {
120 instructions: instructions(text.into())?,
121 min_confidence: 0.0,
122 levels,
123 })
124 }
125 pub fn levels(&self) -> &[String] {
127 &self.levels
128 }
129}
130#[derive(Clone, PartialEq, Serialize, Deserialize)]
132#[serde(try_from = "BoxQuestionFields")]
133pub struct BoxQuestion {
134 instructions: String,
135 min_confidence: f64,
136}
137impl BoxQuestion {
138 pub fn new(text: impl Into<String>) -> Result<Self, Error> {
144 Ok(Self {
145 instructions: instructions(text.into())?,
146 min_confidence: 0.0,
147 })
148 }
149}
150macro_rules! common { ($($ty:ty),*) => {$(
151 impl $ty {
152 pub fn instructions(&self)->&str { &self.instructions }
154 pub fn min_confidence(&self)->f64 { self.min_confidence }
156 pub fn with_min_confidence(mut self, value:f64)->Result<Self,Error> { self.min_confidence=confidence(value)?; Ok(self) }
161 }
162)*}; }
163common!(Boolean, Choice, Score, BoxQuestion);
164#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
166#[serde(tag = "type", rename_all = "lowercase")]
167pub enum Question {
168 Boolean(Boolean),
170 Choice(Choice),
172 Score(Score),
174 Box(BoxQuestion),
176}
177impl Question {
178 pub fn instructions(&self) -> &str {
180 match self {
181 Self::Boolean(q) => q.instructions(),
182 Self::Choice(q) => q.instructions(),
183 Self::Score(q) => q.instructions(),
184 Self::Box(q) => q.instructions(),
185 }
186 }
187 pub fn min_confidence(&self) -> f64 {
189 match self {
190 Self::Boolean(q) => q.min_confidence(),
191 Self::Choice(q) => q.min_confidence(),
192 Self::Score(q) => q.min_confidence(),
193 Self::Box(q) => q.min_confidence(),
194 }
195 }
196 pub fn kind(&self) -> &'static str {
198 match self {
199 Self::Boolean(_) => "boolean",
200 Self::Choice(_) => "choice",
201 Self::Score(_) => "score",
202 Self::Box(_) => "box",
203 }
204 }
205}
206pub trait TypedQuestion: Clone + Into<Question> {
208 type Output: AnswerType;
210}
211macro_rules! typed {
212 ($ty:ty,$variant:ident,$output:ty) => {
213 impl From<$ty> for Question {
214 fn from(q: $ty) -> Self {
215 Self::$variant(q)
216 }
217 }
218 impl TypedQuestion for $ty {
219 type Output = $output;
220 }
221 };
222}
223typed!(Boolean, Boolean, bool);
224typed!(Choice, Choice, String);
225typed!(Score, Score, f64);
226typed!(BoxQuestion, Box, Region);
227#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
229#[serde(try_from = "RegionFields")]
230pub struct Region {
231 top: f64,
232 bottom: f64,
233 left: f64,
234 right: f64,
235}
236impl Region {
237 pub fn new(top: f64, bottom: f64, left: f64, right: f64) -> Result<Self, Error> {
243 if [top, bottom, left, right]
244 .iter()
245 .any(|v| !v.is_finite() || !(0.0..=1.0).contains(v))
246 || top > bottom
247 || left > right
248 {
249 return Err(Error::new(
250 ErrorKind::InvalidArgument,
251 "region edges must be ordered finite numbers from 0 to 1",
252 ));
253 }
254 Ok(Self {
255 top,
256 bottom,
257 left,
258 right,
259 })
260 }
261 pub fn top(&self) -> f64 {
263 self.top
264 }
265 pub fn bottom(&self) -> f64 {
267 self.bottom
268 }
269 pub fn left(&self) -> f64 {
271 self.left
272 }
273 pub fn right(&self) -> f64 {
275 self.right
276 }
277 pub fn width(&self) -> f64 {
279 self.right - self.left
280 }
281 pub fn height(&self) -> f64 {
283 self.bottom - self.top
284 }
285 pub fn center(&self) -> (f64, f64) {
287 (
288 (self.left + self.right) / 2.0,
289 (self.top + self.bottom) / 2.0,
290 )
291 }
292 pub fn padded(&self, fraction: f64) -> Result<Self, Error> {
298 if !fraction.is_finite() {
299 return Err(Error::new(
300 ErrorKind::InvalidArgument,
301 "padding must be finite",
302 ));
303 }
304 let dx = self.width() * fraction;
305 let dy = self.height() * fraction;
306 Self::new(
307 (self.top - dy).max(0.0),
308 (self.bottom + dy).min(1.0),
309 (self.left - dx).max(0.0),
310 (self.right + dx).min(1.0),
311 )
312 }
313 pub fn pixels(&self, width: u32, height: u32) -> Result<(u32, u32, u32, u32), Error> {
327 if width == 0 || height == 0 {
328 return Err(Error::new(
329 ErrorKind::InvalidArgument,
330 "pixel dimensions must be positive",
331 ));
332 }
333 Ok((
334 (self.left * f64::from(width)).round_ties_even() as u32,
335 (self.top * f64::from(height)).round_ties_even() as u32,
336 (self.right * f64::from(width)).round_ties_even() as u32,
337 (self.bottom * f64::from(height)).round_ties_even() as u32,
338 ))
339 }
340}
341
342#[derive(Deserialize)]
343#[serde(deny_unknown_fields)]
344struct BooleanFields {
345 instructions: String,
346 #[serde(default)]
347 min_confidence: f64,
348 #[serde(default)]
349 true_description: Option<String>,
350 #[serde(default)]
351 false_description: Option<String>,
352}
353impl TryFrom<BooleanFields> for Boolean {
354 type Error = Error;
355 fn try_from(v: BooleanFields) -> Result<Self, Error> {
356 Self::new(v.instructions)?
357 .with_descriptions(v.true_description, v.false_description)
358 .with_min_confidence(v.min_confidence)
359 }
360}
361#[derive(Deserialize)]
362#[serde(deny_unknown_fields)]
363struct ChoiceFields {
364 instructions: String,
365 #[serde(default)]
366 min_confidence: f64,
367 options: IndexMap<String, Option<String>>,
368}
369impl TryFrom<ChoiceFields> for Choice {
370 type Error = Error;
371 fn try_from(v: ChoiceFields) -> Result<Self, Error> {
372 Self::new(v.instructions, v.options)?.with_min_confidence(v.min_confidence)
373 }
374}
375#[derive(Deserialize)]
376#[serde(deny_unknown_fields)]
377struct ScoreFields {
378 instructions: String,
379 #[serde(default)]
380 min_confidence: f64,
381 levels: Vec<String>,
382}
383impl TryFrom<ScoreFields> for Score {
384 type Error = Error;
385 fn try_from(v: ScoreFields) -> Result<Self, Error> {
386 Self::new(v.instructions, v.levels)?.with_min_confidence(v.min_confidence)
387 }
388}
389#[derive(Deserialize)]
390#[serde(deny_unknown_fields)]
391struct BoxQuestionFields {
392 instructions: String,
393 #[serde(default)]
394 min_confidence: f64,
395}
396impl TryFrom<BoxQuestionFields> for BoxQuestion {
397 type Error = Error;
398 fn try_from(v: BoxQuestionFields) -> Result<Self, Error> {
399 Self::new(v.instructions)?.with_min_confidence(v.min_confidence)
400 }
401}
402#[derive(Deserialize)]
403#[serde(deny_unknown_fields)]
404struct RegionFields {
405 top: f64,
406 bottom: f64,
407 left: f64,
408 right: f64,
409}
410impl TryFrom<RegionFields> for Region {
411 type Error = Error;
412 fn try_from(v: RegionFields) -> Result<Self, Error> {
413 Self::new(v.top, v.bottom, v.left, v.right)
414 }
415}
416
417macro_rules! private_debug {
418 ($($ty:ty),*) => {$(
419 impl std::fmt::Debug for $ty {
420 fn fmt(&self, f:&mut std::fmt::Formatter<'_>)->std::fmt::Result {
421 f.debug_struct(stringify!($ty)).field("min_confidence",&self.min_confidence).finish_non_exhaustive()
422 }
423 }
424 )*};
425}
426private_debug!(Boolean, Choice, Score, BoxQuestion);
427
428pub(crate) fn is_blank(value: &str) -> bool {
431 value
432 .chars()
433 .all(|c| c.is_whitespace() || ('\u{1c}'..='\u{1f}').contains(&c))
434}