comparison src/conv.rs @ 77:351bdc13005e

Update the libpam module to work with the new structure.
author Paul Fisher <paul@pfish.zone>
date Sun, 08 Jun 2025 01:03:46 -0400
parents e58d24849e82
children 002adfb98c5c
comparison
equal deleted inserted replaced
76:e58d24849e82 77:351bdc13005e
12 /// 12 ///
13 /// The data within each enum value is the prompt (or other information) 13 /// The data within each enum value is the prompt (or other information)
14 /// that will be presented to the user. 14 /// that will be presented to the user.
15 #[non_exhaustive] 15 #[non_exhaustive]
16 pub enum Message<'a> { 16 pub enum Message<'a> {
17 MaskedPrompt(&'a MaskedPrompt<'a>), 17 Prompt(&'a QAndA<'a>),
18 Prompt(&'a Prompt<'a>), 18 MaskedPrompt(&'a MaskedQAndA<'a>),
19 RadioPrompt(&'a RadioPrompt<'a>), 19 RadioPrompt(&'a RadioQAndA<'a>),
20 BinaryPrompt(&'a BinaryPrompt<'a>), 20 BinaryPrompt(&'a BinaryQAndA<'a>),
21 InfoMsg(&'a InfoMsg<'a>), 21 Error(&'a ErrorMsg<'a>),
22 ErrorMsg(&'a ErrorMsg<'a>), 22 Info(&'a InfoMsg<'a>),
23 } 23 }
24 24
25 impl Message<'_> { 25 impl Message<'_> {
26 /// Sets an error answer on this question, without having to inspect it. 26 /// Sets an error answer on this question, without having to inspect it.
27 /// 27 ///
31 /// use nonstick::conv::{Message, QAndA}; 31 /// use nonstick::conv::{Message, QAndA};
32 /// use nonstick::ErrorCode; 32 /// use nonstick::ErrorCode;
33 /// 33 ///
34 /// fn cant_respond(message: Message) { 34 /// fn cant_respond(message: Message) {
35 /// match message { 35 /// match message {
36 /// Message::InfoMsg(i) => { 36 /// Message::Info(i) => {
37 /// eprintln!("fyi, {}", i.question()); 37 /// eprintln!("fyi, {}", i.question());
38 /// i.set_answer(Ok(())) 38 /// i.set_answer(Ok(()))
39 /// } 39 /// }
40 /// Message::ErrorMsg(e) => { 40 /// Message::Error(e) => {
41 /// eprintln!("ERROR: {}", e.question()); 41 /// eprintln!("ERROR: {}", e.question());
42 /// e.set_answer(Ok(())) 42 /// e.set_answer(Ok(()))
43 /// } 43 /// }
44 /// // We can't answer any questions. 44 /// // We can't answer any questions.
45 /// other => other.set_error(ErrorCode::ConversationError), 45 /// other => other.set_error(ErrorCode::ConversationError),
46 /// } 46 /// }
47 /// } 47 /// }
48 pub fn set_error(&self, err: ErrorCode) { 48 pub fn set_error(&self, err: ErrorCode) {
49 match self { 49 match *self {
50 Message::Prompt(m) => m.set_answer(Err(err)),
50 Message::MaskedPrompt(m) => m.set_answer(Err(err)), 51 Message::MaskedPrompt(m) => m.set_answer(Err(err)),
51 Message::Prompt(m) => m.set_answer(Err(err)),
52 Message::RadioPrompt(m) => m.set_answer(Err(err)), 52 Message::RadioPrompt(m) => m.set_answer(Err(err)),
53 Message::BinaryPrompt(m) => m.set_answer(Err(err)), 53 Message::BinaryPrompt(m) => m.set_answer(Err(err)),
54 Message::InfoMsg(m) => m.set_answer(Err(err)), 54 Message::Error(m) => m.set_answer(Err(err)),
55 Message::ErrorMsg(m) => m.set_answer(Err(err)), 55 Message::Info(m) => m.set_answer(Err(err)),
56 } 56 }
57 } 57 }
58 }
59
60 /// A question-and-answer pair that can be communicated in a [`Conversation`].
61 ///
62 /// The asking side creates a `QAndA`, then converts it to a [`Message`]
63 /// and sends it via a [`Conversation`]. The Conversation then retrieves
64 /// the answer to the question (if needed) and sets the response.
65 /// Once control returns to the asker, the asker gets the answer from this
66 /// `QAndA` and uses it however it wants.
67 ///
68 /// For a more detailed explanation of how this works,
69 /// see [`Conversation::communicate`].
70 pub trait QAndA<'a> {
71 /// The type of the content of the question.
72 type Question: Copy;
73 /// The type of the answer to the question.
74 type Answer;
75
76 /// Converts this Q-and-A pair into a [`Message`] for the [`Conversation`].
77 fn message(&self) -> Message;
78
79 /// The contents of the question being asked.
80 ///
81 /// For instance, this might say `"Username:"` to prompt the user
82 /// for their name.
83 fn question(&self) -> Self::Question;
84
85 /// Sets the answer to the question.
86 ///
87 /// The [`Conversation`] implementation calls this to set the answer.
88 /// The conversation should *always call this function*, even for messages
89 /// that don't have "an answer" (like error or info messages).
90 fn set_answer(&self, answer: Result<Self::Answer>);
91
92 /// Gets the answer to the question.
93 fn answer(self) -> Result<Self::Answer>;
94 } 58 }
95 59
96 macro_rules! q_and_a { 60 macro_rules! q_and_a {
97 ($name:ident<'a, Q=$qt:ty, A=$at:ty>, $($doc:literal)*) => { 61 ($name:ident<'a, Q=$qt:ty, A=$at:ty>, $val:path, $($doc:literal)*) => {
98 $( 62 $(
99 #[doc = $doc] 63 #[doc = $doc]
100 )* 64 )*
101 pub struct $name<'a> { 65 pub struct $name<'a> {
102 q: $qt, 66 q: $qt,
103 a: Cell<Result<$at>>, 67 a: Cell<Result<$at>>,
104 } 68 }
105 69
106 impl<'a> QAndA<'a> for $name<'a> { 70 impl<'a> $name<'a> {
107 type Question = $qt; 71 /// Converts this Q&A into a [`Message`] for the [`Conversation`].
108 type Answer = $at; 72 pub fn message(&self) -> Message {
109 73 $val(self)
110 fn question(&self) -> Self::Question { 74 }
75
76 /// The contents of the question being asked.
77 ///
78 /// For instance, this might say `"Username:"` to prompt the user
79 /// for their name, or the text of an error message.
80 pub fn question(&self) -> $qt {
111 self.q 81 self.q
112 } 82 }
113 83
114 fn set_answer(&self, answer: Result<Self::Answer>) { 84 /// Sets the answer to the question.
85 ///
86 /// The [`Conversation`] implementation calls this to set the answer.
87 /// The conversation should *always call this function*,
88 /// even for Q&A messages that don't have "an answer"
89 /// (like error or info messages).
90 pub fn set_answer(&self, answer: Result<$at>) {
115 self.a.set(answer) 91 self.a.set(answer)
116 } 92 }
117 93
118 fn answer(self) -> Result<Self::Answer> { 94 /// Gets the answer to the question.
95 pub fn answer(self) -> Result<$at> {
119 self.a.into_inner() 96 self.a.into_inner()
120 }
121
122 fn message(&self) -> Message {
123 Message::$name(self)
124 } 97 }
125 } 98 }
126 }; 99 };
127 } 100 }
128 101
129 macro_rules! ask { 102 macro_rules! ask {
130 ($t:ident) => { 103 ($t:ident) => {
131 impl<'a> $t<'a> { 104 impl<'a> $t<'a> {
132 #[doc = concat!("Creates a `", stringify!($t), "` to be sent to the user.")] 105 #[doc = concat!("Creates a `", stringify!($t), "` to be sent to the user.")]
133 fn ask(question: &'a str) -> Self { 106 pub fn new(question: &'a str) -> Self {
134 Self { 107 Self {
135 q: question, 108 q: question,
136 a: Cell::new(Err(ErrorCode::ConversationError)), 109 a: Cell::new(Err(ErrorCode::ConversationError)),
137 } 110 }
138 } 111 }
139 } 112 }
140 }; 113 };
141 } 114 }
142 115
143 q_and_a!( 116 q_and_a!(
144 MaskedPrompt<'a, Q=&'a str, A=SecureString>, 117 MaskedQAndA<'a, Q=&'a str, A=SecureString>,
145 "Asks the user for data and does not echo it back while being entered." 118 Message::MaskedPrompt,
119 "A Q&A that asks the user for text and does not show it while typing."
146 "" 120 ""
147 "In other words, a password entry prompt." 121 "In other words, a password entry prompt."
148 ); 122 );
149 ask!(MaskedPrompt); 123 ask!(MaskedQAndA);
150 124
151 q_and_a!( 125 q_and_a!(
152 Prompt<'a, Q=&'a str, A=String>, 126 QAndA<'a, Q=&'a str, A=String>,
153 "Asks the user for data." 127 Message::Prompt,
128 "A standard Q&A prompt that asks the user for text."
154 "" 129 ""
155 "This is the normal \"ask a person a question\" prompt." 130 "This is the normal \"ask a person a question\" prompt."
156 "When the user types, their input will be shown to them." 131 "When the user types, their input will be shown to them."
157 "It can be used for things like usernames." 132 "It can be used for things like usernames."
158 ); 133 );
159 ask!(Prompt); 134 ask!(QAndA);
160 135
161 q_and_a!( 136 q_and_a!(
162 RadioPrompt<'a, Q=&'a str, A=String>, 137 RadioQAndA<'a, Q=&'a str, A=String>,
163 "Asks the user for \"radio button\"–style data. (Linux-PAM extension)" 138 Message::RadioPrompt,
139 "A Q&A for \"radio button\"–style data. (Linux-PAM extension)"
164 "" 140 ""
165 "This message type is theoretically useful for \"yes/no/maybe\"" 141 "This message type is theoretically useful for \"yes/no/maybe\""
166 "questions, but nowhere in the documentation is it specified" 142 "questions, but nowhere in the documentation is it specified"
167 "what the format of the answer will be, or how this should be shown." 143 "what the format of the answer will be, or how this should be shown."
168 ); 144 );
169 ask!(RadioPrompt); 145 ask!(RadioQAndA);
170 146
171 q_and_a!( 147 q_and_a!(
172 BinaryPrompt<'a, Q=BinaryQuestion<'a>, A=BinaryData>, 148 BinaryQAndA<'a, Q=BorrowedBinaryData<'a>, A=BinaryData>,
149 Message::BinaryPrompt,
173 "Asks for binary data. (Linux-PAM extension)" 150 "Asks for binary data. (Linux-PAM extension)"
174 "" 151 ""
175 "This sends a binary message to the client application." 152 "This sends a binary message to the client application."
176 "It can be used to communicate with non-human logins," 153 "It can be used to communicate with non-human logins,"
177 "or to enable things like security keys." 154 "or to enable things like security keys."
178 "" 155 ""
179 "The `data_type` tag is a value that is simply passed through" 156 "The `data_type` tag is a value that is simply passed through"
180 "to the application. PAM does not define any meaning for it." 157 "to the application. PAM does not define any meaning for it."
181 ); 158 );
182 impl<'a> BinaryPrompt<'a> { 159 impl<'a> BinaryQAndA<'a> {
183 /// Creates a prompt for the given binary data. 160 /// Creates a prompt for the given binary data.
184 /// 161 ///
185 /// The `data_type` is a tag you can use for communication between 162 /// The `data_type` is a tag you can use for communication between
186 /// the module and the application. Its meaning is undefined by PAM. 163 /// the module and the application. Its meaning is undefined by PAM.
187 fn ask(data: &'a [u8], data_type: u8) -> Self { 164 pub fn new(data: &'a [u8], data_type: u8) -> Self {
165 BorrowedBinaryData { data, data_type }.into()
166 }
167 }
168
169 impl<'a> From<BorrowedBinaryData<'a>> for BinaryQAndA<'a> {
170 fn from(q: BorrowedBinaryData<'a>) -> Self {
188 Self { 171 Self {
189 q: BinaryQuestion { data, data_type }, 172 q,
190 a: Cell::new(Err(ErrorCode::ConversationError)), 173 a: Cell::new(Err(ErrorCode::ConversationError)),
191 } 174 }
192 } 175 }
193 } 176 }
194 177
195 /// The contents of a question requesting binary data. 178 impl<'a> From<&'a BinaryData> for BinaryQAndA<'a> {
196 /// 179 fn from(src: &'a BinaryData) -> Self {
197 /// A borrowed version of [`BinaryData`]. 180 BorrowedBinaryData::from(src).into()
198 #[derive(Copy, Clone, Debug)] 181 }
199 pub struct BinaryQuestion<'a> { 182 }
183
184 /// A version of [`BinaryData`] where the `data` is borrowed.
185 #[derive(Copy, Clone, Debug, Default)]
186 pub struct BorrowedBinaryData<'a> {
200 data: &'a [u8], 187 data: &'a [u8],
201 data_type: u8, 188 data_type: u8,
202 } 189 }
203 190
204 impl BinaryQuestion<'_> { 191 impl<'a> BorrowedBinaryData<'a> {
192 /// Creates a new BinaryQuestion as a view over the given data.
193 pub fn new(data: &'a [u8], data_type: u8) -> Self {
194 Self { data, data_type }
195 }
196
205 /// Gets the data of this question. 197 /// Gets the data of this question.
206 pub fn data(&self) -> &[u8] { 198 pub fn data(&self) -> &[u8] {
207 self.data 199 self.data
208 } 200 }
209 201
211 pub fn data_type(&self) -> u8 { 203 pub fn data_type(&self) -> u8 {
212 self.data_type 204 self.data_type
213 } 205 }
214 } 206 }
215 207
208 impl<'a> From<&'a BinaryData> for BorrowedBinaryData<'a> {
209 fn from(value: &'a BinaryData) -> Self {
210 Self::new(&value.data, value.data_type)
211 }
212 }
213
216 /// Owned binary data. 214 /// Owned binary data.
217 /// 215 ///
218 /// For borrowed data, see [`BinaryQuestion`]. 216 /// For borrowed data, see [`BorrowedBinaryData`].
219 /// You can take ownership of the stored data with `.into::<Vec<u8>>()`. 217 /// You can take ownership of the stored data with `.into::<Vec<u8>>()`.
220 #[derive(Debug, PartialEq)] 218 #[derive(Debug, Default, PartialEq)]
221 pub struct BinaryData { 219 pub struct BinaryData {
222 data: Vec<u8>, 220 data: Vec<u8>,
223 data_type: u8, 221 data_type: u8,
224 } 222 }
225 223
236 pub fn data_type(&self) -> u8 { 234 pub fn data_type(&self) -> u8 {
237 self.data_type 235 self.data_type
238 } 236 }
239 } 237 }
240 238
239 impl<'a> From<BorrowedBinaryData<'a>> for BinaryData {
240 fn from(value: BorrowedBinaryData) -> Self {
241 Self {
242 data: value.data.to_vec(),
243 data_type: value.data_type,
244 }
245 }
246 }
247
241 impl From<BinaryData> for Vec<u8> { 248 impl From<BinaryData> for Vec<u8> {
242 /// Takes ownership of the data stored herein. 249 /// Takes ownership of the data stored herein.
243 fn from(value: BinaryData) -> Self { 250 fn from(value: BinaryData) -> Self {
244 value.data 251 value.data
245 } 252 }
246 } 253 }
247 254
248 q_and_a!( 255 q_and_a!(
249 InfoMsg<'a, Q = &'a str, A = ()>, 256 InfoMsg<'a, Q = &'a str, A = ()>,
257 Message::Info,
250 "A message containing information to be passed to the user." 258 "A message containing information to be passed to the user."
251 "" 259 ""
252 "While this does not have an answer, [`Conversation`] implementations" 260 "While this does not have an answer, [`Conversation`] implementations"
253 "should still call [`set_answer`][`QAndA::set_answer`] to verify that" 261 "should still call [`set_answer`][`QAndA::set_answer`] to verify that"
254 "the message has been displayed (or actively discarded)." 262 "the message has been displayed (or actively discarded)."
255 ); 263 );
256 impl<'a> InfoMsg<'a> { 264 impl<'a> InfoMsg<'a> {
257 /// Creates an informational message to send to the user. 265 /// Creates an informational message to send to the user.
258 fn new(message: &'a str) -> Self { 266 pub fn new(message: &'a str) -> Self {
259 Self { 267 Self {
260 q: message, 268 q: message,
261 a: Cell::new(Err(ErrorCode::ConversationError)), 269 a: Cell::new(Err(ErrorCode::ConversationError)),
262 } 270 }
263 } 271 }
264 } 272 }
265 273
266 q_and_a!( 274 q_and_a!(
267 ErrorMsg<'a, Q = &'a str, A = ()>, 275 ErrorMsg<'a, Q = &'a str, A = ()>,
276 Message::Error,
268 "An error message to be passed to the user." 277 "An error message to be passed to the user."
269 "" 278 ""
270 "While this does not have an answer, [`Conversation`] implementations" 279 "While this does not have an answer, [`Conversation`] implementations"
271 "should still call [`set_answer`][`QAndA::set_answer`] to verify that" 280 "should still call [`set_answer`][`QAndA::set_answer`] to verify that"
272 "the message has been displayed (or actively discarded)." 281 "the message has been displayed (or actively discarded)."
273 282
274 ); 283 );
275 impl<'a> ErrorMsg<'a> { 284 impl<'a> ErrorMsg<'a> {
276 /// Creates an error message to send to the user. 285 /// Creates an error message to send to the user.
277 fn new(message: &'a str) -> Self { 286 pub fn new(message: &'a str) -> Self {
278 Self { 287 Self {
279 q: message, 288 q: message,
280 a: Cell::new(Err(ErrorCode::ConversationError)), 289 a: Cell::new(Err(ErrorCode::ConversationError)),
281 } 290 }
282 } 291 }
425 /// Requests binary data from the user (a Linux-PAM extension). 434 /// Requests binary data from the user (a Linux-PAM extension).
426 fn binary_prompt(&mut self, data: &[u8], data_type: u8) -> Result<BinaryData>; 435 fn binary_prompt(&mut self, data: &[u8], data_type: u8) -> Result<BinaryData>;
427 } 436 }
428 437
429 macro_rules! conv_fn { 438 macro_rules! conv_fn {
430 ($fn_name:ident($($param:ident: $pt:ty),+) -> $resp_type:ty { $ask:path }) => { 439 ($fn_name:ident($($param:ident: $pt:ty),+) -> $resp_type:ty { $msg:ty }) => {
431 fn $fn_name(&mut self, $($param: $pt),*) -> Result<$resp_type> { 440 fn $fn_name(&mut self, $($param: $pt),*) -> Result<$resp_type> {
432 let prompt = $ask($($param),*); 441 let prompt = <$msg>::new($($param),*);
433 self.communicate(&[prompt.message()]); 442 self.communicate(&[prompt.message()]);
434 prompt.answer() 443 prompt.answer()
435 } 444 }
436 }; 445 };
437 ($fn_name:ident($($param:ident: $pt:ty),+) { $ask:path }) => { 446 ($fn_name:ident($($param:ident: $pt:ty),+) { $msg:ty }) => {
438 fn $fn_name(&mut self, $($param: $pt),*) { 447 fn $fn_name(&mut self, $($param: $pt),*) {
439 self.communicate(&[$ask($($param),*).message()]); 448 self.communicate(&[<$msg>::new($($param),*).message()]);
440 } 449 }
441 }; 450 };
442 } 451 }
443 452
444 impl<C: Conversation> SimpleConversation for C { 453 impl<C: Conversation> SimpleConversation for C {
445 conv_fn!(prompt(message: &str) -> String { Prompt::ask }); 454 conv_fn!(prompt(message: &str) -> String { QAndA });
446 conv_fn!(masked_prompt(message: &str) -> SecureString { MaskedPrompt::ask }); 455 conv_fn!(masked_prompt(message: &str) -> SecureString { MaskedQAndA } );
447 conv_fn!(radio_prompt(message: &str) -> String { RadioPrompt::ask }); 456 conv_fn!(radio_prompt(message: &str) -> String { RadioQAndA });
448 conv_fn!(error_msg(message: &str) { ErrorMsg::new }); 457 conv_fn!(error_msg(message: &str) { ErrorMsg });
449 conv_fn!(info_msg(message: &str) { InfoMsg::new }); 458 conv_fn!(info_msg(message: &str) { InfoMsg });
450 conv_fn!(binary_prompt(data: &[u8], data_type: u8) -> BinaryData { BinaryPrompt::ask }); 459 conv_fn!(binary_prompt(data: &[u8], data_type: u8) -> BinaryData { BinaryQAndA });
451 } 460 }
452 461
453 /// A [`Conversation`] which asks the questions one at a time. 462 /// A [`Conversation`] which asks the questions one at a time.
454 /// 463 ///
455 /// This is automatically created by [`SimpleConversation::as_conversation`]. 464 /// This is automatically created by [`SimpleConversation::as_conversation`].
464 prompt.set_answer(self.0.masked_prompt(prompt.question())) 473 prompt.set_answer(self.0.masked_prompt(prompt.question()))
465 } 474 }
466 Message::RadioPrompt(prompt) => { 475 Message::RadioPrompt(prompt) => {
467 prompt.set_answer(self.0.radio_prompt(prompt.question())) 476 prompt.set_answer(self.0.radio_prompt(prompt.question()))
468 } 477 }
469 Message::InfoMsg(prompt) => { 478 Message::Info(prompt) => {
470 self.0.info_msg(prompt.question()); 479 self.0.info_msg(prompt.question());
471 prompt.set_answer(Ok(())) 480 prompt.set_answer(Ok(()))
472 } 481 }
473 Message::ErrorMsg(prompt) => { 482 Message::Error(prompt) => {
474 self.0.error_msg(prompt.question()); 483 self.0.error_msg(prompt.question());
475 prompt.set_answer(Ok(())) 484 prompt.set_answer(Ok(()))
476 } 485 }
477 Message::BinaryPrompt(prompt) => { 486 Message::BinaryPrompt(prompt) => {
478 let q = prompt.question(); 487 let q = prompt.question();
484 } 493 }
485 494
486 #[cfg(test)] 495 #[cfg(test)]
487 mod tests { 496 mod tests {
488 use super::{ 497 use super::{
489 BinaryPrompt, Conversation, ErrorMsg, InfoMsg, MaskedPrompt, Message, Prompt, QAndA, 498 BinaryQAndA, Conversation, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA,
490 RadioPrompt, Result, SecureString, SimpleConversation, 499 RadioQAndA, Result, SecureString, SimpleConversation,
491 }; 500 };
492 use crate::constants::ErrorCode; 501 use crate::constants::ErrorCode;
493 use crate::BinaryData; 502 use crate::BinaryData;
494 503
495 #[test] 504 #[test]
531 } 540 }
532 } 541 }
533 542
534 let mut tester = DemuxTester::default(); 543 let mut tester = DemuxTester::default();
535 544
536 let what = Prompt::ask("what"); 545 let what = QAndA::new("what");
537 let pass = MaskedPrompt::ask("reveal"); 546 let pass = MaskedQAndA::new("reveal");
538 let err = ErrorMsg::new("whoopsie"); 547 let err = ErrorMsg::new("whoopsie");
539 let info = InfoMsg::new("did you know"); 548 let info = InfoMsg::new("did you know");
540 let has_err = Prompt::ask("give_err"); 549 let has_err = QAndA::new("give_err");
541 550
542 let mut conv = tester.as_conversation(); 551 let mut conv = tester.as_conversation();
543 552
544 // Basic tests. 553 // Basic tests.
545 554
561 570
562 // Test the Linux extensions separately. 571 // Test the Linux extensions separately.
563 572
564 let mut conv = tester.as_conversation(); 573 let mut conv = tester.as_conversation();
565 574
566 let radio = RadioPrompt::ask("channel?"); 575 let radio = RadioQAndA::new("channel?");
567 let bin = BinaryPrompt::ask(&[10, 9, 8], 66); 576 let bin = BinaryQAndA::new(&[10, 9, 8], 66);
568 conv.communicate(&[radio.message(), bin.message()]); 577 conv.communicate(&[radio.message(), bin.message()]);
569 578
570 assert_eq!("zero", radio.answer().unwrap()); 579 assert_eq!("zero", radio.answer().unwrap());
571 assert_eq!(BinaryData::new(vec![5, 5, 5], 5), bin.answer().unwrap()); 580 assert_eq!(BinaryData::new(vec![5, 5, 5], 5), bin.answer().unwrap());
572 } 581 }
576 585
577 impl Conversation for MuxTester { 586 impl Conversation for MuxTester {
578 fn communicate(&mut self, messages: &[Message]) { 587 fn communicate(&mut self, messages: &[Message]) {
579 if let [msg] = messages { 588 if let [msg] = messages {
580 match *msg { 589 match *msg {
581 Message::InfoMsg(info) => { 590 Message::Info(info) => {
582 assert_eq!("let me tell you", info.question()); 591 assert_eq!("let me tell you", info.question());
583 info.set_answer(Ok(())) 592 info.set_answer(Ok(()))
584 } 593 }
585 Message::ErrorMsg(error) => { 594 Message::Error(error) => {
586 assert_eq!("oh no", error.question()); 595 assert_eq!("oh no", error.question());
587 error.set_answer(Ok(())) 596 error.set_answer(Ok(()))
588 } 597 }
589 Message::Prompt(prompt) => prompt.set_answer(match prompt.question() { 598 Message::Prompt(prompt) => prompt.set_answer(match prompt.question() {
590 "should_err" => Err(ErrorCode::PermissionDenied), 599 "should_err" => Err(ErrorCode::PermissionDenied),