1use crate::{Client, Error, response::Answers};
4use cerno_types::{
5 Calibration, ChoiceSpec, LevelSpec, Question, QuestionKind, ScoreSpec, SystemOneRequest,
6};
7
8pub struct Levels(pub(crate) LevelSpec);
13
14impl From<u8> for Levels {
15 fn from(count: u8) -> Self {
16 Self::count(count.into())
17 }
18}
19
20impl Levels {
21 pub fn count(count: u32) -> Self {
25 Self(LevelSpec::Count(count))
26 }
27
28 pub fn labels<S: AsRef<str>>(labels: impl IntoIterator<Item = S>) -> Self {
30 Self(LevelSpec::Labels(collect(labels)))
31 }
32}
33
34macro_rules! levels_from_labels {
38 ($($t:ty),* $(,)?) => {
39 $(impl From<$t> for Levels {
40 fn from(labels: $t) -> Self {
41 Self::labels(labels)
42 }
43 })*
44 };
45}
46
47levels_from_labels!(Vec<String>, Vec<&str>, &[&str], &[String]);
48
49impl<const N: usize> From<[&str; N]> for Levels {
50 fn from(labels: [&str; N]) -> Self {
51 Self::labels(labels)
52 }
53}
54
55impl<const N: usize> From<[String; N]> for Levels {
56 fn from(labels: [String; N]) -> Self {
57 Self::labels(labels)
58 }
59}
60
61#[must_use = "a builder does nothing until send() is called"]
63pub struct SystemOne<'a> {
64 client: &'a Client,
65 request: SystemOneRequest,
66}
67
68impl<'a> SystemOne<'a> {
69 pub(crate) fn new(client: &'a Client, state: String) -> Self {
70 Self {
71 client,
72 request: SystemOneRequest {
73 state,
74 model: None,
75 calibration: None,
76 questions: Vec::new(),
77 },
78 }
79 }
80
81 pub fn model(mut self, model: impl Into<String>) -> Self {
83 self.request.model = Some(model.into());
84 self
85 }
86
87 pub fn calibration(mut self, temperature: f64) -> Self {
89 self.request.calibration = Some(Calibration { temperature });
90 self
91 }
92
93 pub fn noul(self, id: impl Into<String>, question: impl Into<String>) -> Self {
95 self.push(id, QuestionKind::Noul(question.into()))
96 }
97
98 pub fn choice<S: AsRef<str>>(
100 self,
101 id: impl Into<String>,
102 question: impl Into<String>,
103 options: impl IntoIterator<Item = S>,
104 ) -> Self {
105 let spec = ChoiceSpec {
106 question: Some(question.into()),
107 options: collect(options),
108 };
109 self.push(id, QuestionKind::Choice(spec))
110 }
111
112 pub fn choice_of<S: AsRef<str>>(
114 self,
115 id: impl Into<String>,
116 options: impl IntoIterator<Item = S>,
117 ) -> Self {
118 let spec = ChoiceSpec {
119 question: None,
120 options: collect(options),
121 };
122 self.push(id, QuestionKind::Choice(spec))
123 }
124
125 pub fn score(
127 self,
128 id: impl Into<String>,
129 question: impl Into<String>,
130 levels: impl Into<Levels>,
131 ) -> Self {
132 let spec = ScoreSpec {
133 question: Some(question.into()),
134 levels: levels.into().0,
135 };
136 self.push(id, QuestionKind::Score(spec))
137 }
138
139 pub fn body(&self) -> &SystemOneRequest {
142 &self.request
143 }
144
145 pub async fn send(self) -> Result<Answers, Error> {
146 let response = self.client.post_systemone(&self.request).await?;
147 Ok(Answers::new(response))
148 }
149
150 fn push(mut self, id: impl Into<String>, kind: QuestionKind) -> Self {
151 self.request.questions.push(Question {
152 id: id.into(),
153 kind,
154 });
155 self
156 }
157}
158
159fn collect<S: AsRef<str>>(items: impl IntoIterator<Item = S>) -> Vec<String> {
160 items.into_iter().map(|s| s.as_ref().to_string()).collect()
161}