requestty/
answer.rs

1use std::{
2    collections::hash_map::{Entry, HashMap, IntoIter},
3    hash::Hash,
4    iter::FromIterator,
5    ops::{Deref, DerefMut},
6};
7
8use crate::question::OrderSelectItem;
9
10/// The different answer types that can be returned by the [`Question`]s
11///
12/// [`Question`]: crate::question::Question
13#[derive(Debug, Clone, PartialEq, PartialOrd)]
14pub enum Answer {
15    /// Strings will be returned by [`input`], [`password`] and [`editor`].
16    ///
17    /// [`input`]: crate::question::Question::input
18    /// [`password`]: crate::question::Question::password
19    /// [`editor`]: crate::question::Question::editor
20    String(String),
21    /// ListItems will be returned by [`select`] and [`raw_select`].
22    ///
23    /// [`select`]: crate::question::Question::select
24    /// [`raw_select`]: crate::question::Question::raw_select
25    ListItem(ListItem),
26    /// ExpandItems will be returned by [`expand`].
27    ///
28    /// [`expand`]: crate::question::Question::expand
29    ExpandItem(ExpandItem),
30    /// Ints will be returned by [`int`].
31    ///
32    /// [`int`]: crate::question::Question::int
33    Int(i64),
34    /// Floats will be returned by [`float`].
35    ///
36    /// [`float`]: crate::question::Question::float
37    Float(f64),
38    /// Bools will be returned by [`confirm`].
39    ///
40    /// [`confirm`]: crate::question::Question::confirm
41    Bool(bool),
42    /// ListItems will be returned by [`multi_select`] and [`order_select`].
43    ///
44    /// [`multi_select`]: crate::question::Question::multi_select
45    /// [`order_select`]: crate::question::Question::order_select
46    ListItems(Vec<ListItem>),
47}
48
49impl Answer {
50    /// Returns `true` if the answer is [`Answer::String`].
51    pub fn is_string(&self) -> bool {
52        matches!(self, Self::String(..))
53    }
54
55    /// Returns [`Some`] if it is a [`Answer::String`], otherwise returns [`None`].
56    pub fn as_string(&self) -> Option<&str> {
57        match self {
58            Self::String(v) => Some(v),
59            _ => None,
60        }
61    }
62
63    /// Returns the `Ok(String)` if it is one, otherwise returns itself as an [`Err`].
64    pub fn try_into_string(self) -> Result<String, Self> {
65        match self {
66            Self::String(v) => Ok(v),
67            _ => Err(self),
68        }
69    }
70
71    /// Returns `true` if the answer is [`Answer::ListItem`].
72    pub fn is_list_item(&self) -> bool {
73        matches!(self, Self::ListItem(..))
74    }
75
76    /// Returns [`Some`] if it is a [`Answer::ListItem`], otherwise returns [`None`].
77    pub fn as_list_item(&self) -> Option<&ListItem> {
78        match self {
79            Self::ListItem(v) => Some(v),
80            _ => None,
81        }
82    }
83
84    /// Returns the `Ok(ListItem)` if it is one, otherwise returns itself as an [`Err`].
85    pub fn try_into_list_item(self) -> Result<ListItem, Self> {
86        match self {
87            Self::ListItem(v) => Ok(v),
88            _ => Err(self),
89        }
90    }
91
92    /// Returns `true` if the answer is [`Answer::ExpandItem`].
93    pub fn is_expand_item(&self) -> bool {
94        matches!(self, Self::ExpandItem(..))
95    }
96
97    /// Returns [`Some`] if it is [`Answer::ExpandItem`], otherwise returns [`None`].
98    pub fn as_expand_item(&self) -> Option<&ExpandItem> {
99        match self {
100            Self::ExpandItem(v) => Some(v),
101            _ => None,
102        }
103    }
104
105    /// Returns the `Ok(ExpandItem)` if it is one, otherwise returns itself as an [`Err`].
106    pub fn try_into_expand_item(self) -> Result<ExpandItem, Self> {
107        match self {
108            Self::ExpandItem(v) => Ok(v),
109            _ => Err(self),
110        }
111    }
112
113    /// Returns `true` if the answer is [`Answer::Int`].
114    pub fn is_int(&self) -> bool {
115        matches!(self, Self::Int(..))
116    }
117
118    /// Returns [`Some`] if it is [`Answer::Int`], otherwise returns [`None`].
119    pub fn as_int(&self) -> Option<i64> {
120        match self {
121            Self::Int(v) => Some(*v),
122            _ => None,
123        }
124    }
125
126    /// Returns the `Ok(i64)` if it is one, otherwise returns itself as an [`Err`].
127    pub fn try_into_int(self) -> Result<i64, Self> {
128        match self {
129            Self::Int(v) => Ok(v),
130            _ => Err(self),
131        }
132    }
133
134    /// Returns `true` if the answer is [`Answer::Float`].
135    pub fn is_float(&self) -> bool {
136        matches!(self, Self::Float(..))
137    }
138
139    /// Returns [`Some`] if it is [`Answer::Float`], otherwise returns [`None`].
140    pub fn as_float(&self) -> Option<f64> {
141        match self {
142            Self::Float(v) => Some(*v),
143            _ => None,
144        }
145    }
146
147    /// Returns the `Ok(f64)` if it is one, otherwise returns itself as an [`Err`].
148    pub fn try_into_float(self) -> Result<f64, Self> {
149        match self {
150            Self::Float(v) => Ok(v),
151            _ => Err(self),
152        }
153    }
154
155    /// Returns `true` if the answer is [`Answer::Bool`].
156    pub fn is_bool(&self) -> bool {
157        matches!(self, Self::Bool(..))
158    }
159
160    /// Returns [`Some`] if it is [`Answer::Bool`], otherwise returns [`None`].
161    pub fn as_bool(&self) -> Option<bool> {
162        match self {
163            Self::Bool(v) => Some(*v),
164            _ => None,
165        }
166    }
167
168    /// Returns the `Ok(bool)` if it is one, otherwise returns itself as an [`Err`].
169    pub fn try_into_bool(self) -> Result<bool, Self> {
170        match self {
171            Self::Bool(v) => Ok(v),
172            _ => Err(self),
173        }
174    }
175
176    /// Returns `true` if the answer is [`Answer::ListItems`].
177    pub fn is_list_items(&self) -> bool {
178        matches!(self, Self::ListItems(..))
179    }
180
181    /// Returns [`Some`] if it is [`Answer::ListItems`], otherwise returns [`None`].
182    pub fn as_list_items(&self) -> Option<&[ListItem]> {
183        match self {
184            Self::ListItems(v) => Some(v),
185            _ => None,
186        }
187    }
188
189    /// Returns the `Ok(Vec<ListItem>)` if it is one, otherwise returns itself as an [`Err`].
190    pub fn try_into_list_items(self) -> Result<Vec<ListItem>, Self> {
191        match self {
192            Self::ListItems(v) => Ok(v),
193            _ => Err(self),
194        }
195    }
196}
197
198macro_rules! impl_from {
199    ($from:ty => $storage:ident) => {
200        impl From<$from> for Answer {
201            fn from(ans: $from) -> Self {
202                Self::$storage(ans)
203            }
204        }
205    };
206}
207
208impl_from!(String => String);
209impl_from!(i64 => Int);
210impl_from!(f64 => Float);
211impl_from!(bool => Bool);
212impl_from!(ExpandItem => ExpandItem);
213impl_from!(ListItem => ListItem);
214impl_from!(Vec<ListItem> => ListItems);
215
216impl From<Vec<OrderSelectItem>> for Answer {
217    fn from(v: Vec<OrderSelectItem>) -> Self {
218        Answer::ListItems(v.into_iter().map(|o| o.into()).collect())
219    }
220}
221
222/// A representation of a [`Choice`] at a particular index.
223///
224/// It will be returned by [`select`] and [`raw_select`].
225///
226/// [`Choice`]: crate::Choice
227/// [`select`]: crate::question::Question::select
228/// [`raw_select`]: crate::question::Question::raw_select
229#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
230pub struct ListItem {
231    /// The index of the choice
232    pub index: usize,
233    /// The content of the choice -- it is what was displayed to the user
234    pub text: String,
235}
236
237impl<I: Into<String>> From<(usize, I)> for ListItem {
238    fn from((index, text): (usize, I)) -> Self {
239        Self {
240            index,
241            text: text.into(),
242        }
243    }
244}
245
246impl From<OrderSelectItem> for ListItem {
247    fn from(o: OrderSelectItem) -> Self {
248        ListItem {
249            index: o.initial_index,
250            text: o.text.text,
251        }
252    }
253}
254
255/// A representation of a [`Choice`] for a particular key.
256///
257/// It will be returned by [`expand`].
258///
259/// [`Choice`]: crate::Choice
260/// [`expand`]: crate::question::Question::expand
261#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
262pub struct ExpandItem {
263    /// The key associated with the choice
264    pub key: char,
265    /// The content of the choice -- it is what was displayed to the user
266    pub text: String,
267}
268
269impl<I: Into<String>> From<(char, I)> for ExpandItem {
270    fn from((key, text): (char, I)) -> Self {
271        Self {
272            key,
273            text: text.into(),
274        }
275    }
276}
277
278/// A collections of answers of previously asked [`Question`]s.
279///
280/// [`Question`]: crate::question::Question
281#[derive(Default, Clone, PartialEq)]
282pub struct Answers {
283    answers: HashMap<String, Answer>,
284}
285
286impl std::fmt::Debug for Answers {
287    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288        self.answers.fmt(f)
289    }
290}
291
292impl Answers {
293    pub(crate) fn insert(&mut self, name: String, answer: Answer) -> &mut Answer {
294        match self.answers.entry(name) {
295            Entry::Occupied(entry) => {
296                let entry = entry.into_mut();
297                *entry = answer;
298                entry
299            }
300            Entry::Vacant(entry) => entry.insert(answer),
301        }
302    }
303}
304
305impl From<HashMap<String, Answer>> for Answers {
306    fn from(answers: HashMap<String, Answer>) -> Self {
307        Self { answers }
308    }
309}
310
311impl FromIterator<(String, Answer)> for Answers {
312    fn from_iter<T: IntoIterator<Item = (String, Answer)>>(iter: T) -> Self {
313        Self {
314            answers: iter.into_iter().collect(),
315        }
316    }
317}
318
319impl Extend<(String, Answer)> for Answers {
320    fn extend<T: IntoIterator<Item = (String, Answer)>>(&mut self, iter: T) {
321        self.answers.extend(iter)
322    }
323}
324
325impl Deref for Answers {
326    type Target = HashMap<String, Answer>;
327
328    fn deref(&self) -> &Self::Target {
329        &self.answers
330    }
331}
332
333impl DerefMut for Answers {
334    fn deref_mut(&mut self) -> &mut Self::Target {
335        &mut self.answers
336    }
337}
338
339impl IntoIterator for Answers {
340    type Item = (String, Answer);
341    type IntoIter = IntoIter<String, Answer>;
342
343    fn into_iter(self) -> Self::IntoIter {
344        self.answers.into_iter()
345    }
346}