comparison src/conv.rs @ 78:002adfb98c5c

Rename files, reorder structs, remove annoying BorrowedBinaryData type. This is basically a cleanup change. Also it adds tests. - Renames the files with Questions and Answers to question and answer. - Reorders the structs in those files to put the important ones first. - Removes the BorrowedBinaryData type. It was a bad idea all along. Instead, we just use (&[u8], u8). - Adds some tests because I just can't help myself.
author Paul Fisher <paul@pfish.zone>
date Sun, 08 Jun 2025 03:48:40 -0400
parents 351bdc13005e
children 2128123b9406
comparison
equal deleted inserted replaced
77:351bdc13005e 78:002adfb98c5c
1 //! The PAM conversation and associated Stuff. 1 //! The PAM conversation and associated Stuff.
2 2
3 // Temporarily allowed until we get the actual conversation functions hooked up. 3 // Temporarily allowed until we get the actual conversation functions hooked up.
4 #![allow(dead_code)] 4 #![allow(dead_code)]
5 5
6 use crate::constants::Result; 6 use crate::constants::{ErrorCode, Result};
7 use crate::ErrorCode;
8 use secure_string::SecureString; 7 use secure_string::SecureString;
9 use std::cell::Cell; 8 use std::cell::Cell;
9 use std::fmt;
10 use std::result::Result as StdResult;
10 11
11 /// The types of message and request that can be sent to a user. 12 /// The types of message and request that can be sent to a user.
12 /// 13 ///
13 /// The data within each enum value is the prompt (or other information) 14 /// The data within each enum value is the prompt (or other information)
14 /// that will be presented to the user. 15 /// that will be presented to the user.
66 q: $qt, 67 q: $qt,
67 a: Cell<Result<$at>>, 68 a: Cell<Result<$at>>,
68 } 69 }
69 70
70 impl<'a> $name<'a> { 71 impl<'a> $name<'a> {
72 #[doc = concat!("Creates a `", stringify!($t), "` to be sent to the user.")]
73 pub fn new(question: $qt) -> Self {
74 Self {
75 q: question,
76 a: Cell::new(Err(ErrorCode::ConversationError)),
77 }
78 }
79
71 /// Converts this Q&A into a [`Message`] for the [`Conversation`]. 80 /// Converts this Q&A into a [`Message`] for the [`Conversation`].
72 pub fn message(&self) -> Message { 81 pub fn message(&self) -> Message<'_> {
73 $val(self) 82 $val(self)
74 } 83 }
75 84
76 /// The contents of the question being asked. 85 /// The contents of the question being asked.
77 /// 86 ///
94 /// Gets the answer to the question. 103 /// Gets the answer to the question.
95 pub fn answer(self) -> Result<$at> { 104 pub fn answer(self) -> Result<$at> {
96 self.a.into_inner() 105 self.a.into_inner()
97 } 106 }
98 } 107 }
99 }; 108
100 } 109 // shout out to stackoverflow user ballpointben for this lazy impl:
101 110 // https://stackoverflow.com/a/78871280/39808
102 macro_rules! ask { 111 impl fmt::Debug for $name<'_> {
103 ($t:ident) => { 112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> StdResult<(), fmt::Error> {
104 impl<'a> $t<'a> { 113 #[derive(Debug)]
105 #[doc = concat!("Creates a `", stringify!($t), "` to be sent to the user.")] 114 struct $name<'a> { q: $qt }
106 pub fn new(question: &'a str) -> Self { 115 fmt::Debug::fmt(&$name { q: self.q }, f)
107 Self {
108 q: question,
109 a: Cell::new(Err(ErrorCode::ConversationError)),
110 }
111 } 116 }
112 } 117 }
113 }; 118 };
114 } 119 }
115 120
118 Message::MaskedPrompt, 123 Message::MaskedPrompt,
119 "A Q&A that asks the user for text and does not show it while typing." 124 "A Q&A that asks the user for text and does not show it while typing."
120 "" 125 ""
121 "In other words, a password entry prompt." 126 "In other words, a password entry prompt."
122 ); 127 );
123 ask!(MaskedQAndA);
124 128
125 q_and_a!( 129 q_and_a!(
126 QAndA<'a, Q=&'a str, A=String>, 130 QAndA<'a, Q=&'a str, A=String>,
127 Message::Prompt, 131 Message::Prompt,
128 "A standard Q&A prompt that asks the user for text." 132 "A standard Q&A prompt that asks the user for text."
129 "" 133 ""
130 "This is the normal \"ask a person a question\" prompt." 134 "This is the normal \"ask a person a question\" prompt."
131 "When the user types, their input will be shown to them." 135 "When the user types, their input will be shown to them."
132 "It can be used for things like usernames." 136 "It can be used for things like usernames."
133 ); 137 );
134 ask!(QAndA);
135 138
136 q_and_a!( 139 q_and_a!(
137 RadioQAndA<'a, Q=&'a str, A=String>, 140 RadioQAndA<'a, Q=&'a str, A=String>,
138 Message::RadioPrompt, 141 Message::RadioPrompt,
139 "A Q&A for \"radio button\"–style data. (Linux-PAM extension)" 142 "A Q&A for \"radio button\"–style data. (Linux-PAM extension)"
140 "" 143 ""
141 "This message type is theoretically useful for \"yes/no/maybe\"" 144 "This message type is theoretically useful for \"yes/no/maybe\""
142 "questions, but nowhere in the documentation is it specified" 145 "questions, but nowhere in the documentation is it specified"
143 "what the format of the answer will be, or how this should be shown." 146 "what the format of the answer will be, or how this should be shown."
144 ); 147 );
145 ask!(RadioQAndA);
146 148
147 q_and_a!( 149 q_and_a!(
148 BinaryQAndA<'a, Q=BorrowedBinaryData<'a>, A=BinaryData>, 150 BinaryQAndA<'a, Q=(&'a [u8], u8), A=BinaryData>,
149 Message::BinaryPrompt, 151 Message::BinaryPrompt,
150 "Asks for binary data. (Linux-PAM extension)" 152 "Asks for binary data. (Linux-PAM extension)"
151 "" 153 ""
152 "This sends a binary message to the client application." 154 "This sends a binary message to the client application."
153 "It can be used to communicate with non-human logins," 155 "It can be used to communicate with non-human logins,"
154 "or to enable things like security keys." 156 "or to enable things like security keys."
155 "" 157 ""
156 "The `data_type` tag is a value that is simply passed through" 158 "The `data_type` tag is a value that is simply passed through"
157 "to the application. PAM does not define any meaning for it." 159 "to the application. PAM does not define any meaning for it."
158 ); 160 );
159 impl<'a> BinaryQAndA<'a> {
160 /// Creates a prompt for the given binary data.
161 ///
162 /// The `data_type` is a tag you can use for communication between
163 /// the module and the application. Its meaning is undefined by PAM.
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 {
171 Self {
172 q,
173 a: Cell::new(Err(ErrorCode::ConversationError)),
174 }
175 }
176 }
177
178 impl<'a> From<&'a BinaryData> for BinaryQAndA<'a> {
179 fn from(src: &'a BinaryData) -> Self {
180 BorrowedBinaryData::from(src).into()
181 }
182 }
183
184 /// A version of [`BinaryData`] where the `data` is borrowed.
185 #[derive(Copy, Clone, Debug, Default)]
186 pub struct BorrowedBinaryData<'a> {
187 data: &'a [u8],
188 data_type: u8,
189 }
190
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
197 /// Gets the data of this question.
198 pub fn data(&self) -> &[u8] {
199 self.data
200 }
201
202 /// Gets the "type" of this data.
203 pub fn data_type(&self) -> u8 {
204 self.data_type
205 }
206 }
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 161
214 /// Owned binary data. 162 /// Owned binary data.
215 /// 163 ///
216 /// For borrowed data, see [`BorrowedBinaryData`]. 164 /// You can take ownership of the stored data by destructuring it:
217 /// You can take ownership of the stored data with `.into::<Vec<u8>>()`. 165 ///
166 /// ```
167 /// # use nonstick::BinaryData;
168 /// # let binary_data = BinaryData::new(vec![99, 88, 77], 66);
169 /// let (data, data_type) = binary_data.into();
170 /// ```
218 #[derive(Debug, Default, PartialEq)] 171 #[derive(Debug, Default, PartialEq)]
219 pub struct BinaryData { 172 pub struct BinaryData {
220 data: Vec<u8>, 173 data: Vec<u8>,
221 data_type: u8, 174 data_type: u8,
222 } 175 }
223 176
224 impl BinaryData { 177 impl BinaryData {
225 /// Creates a `BinaryData` with the given contents and type. 178 /// Creates a `BinaryData` with the given contents and type.
226 pub fn new(data: Vec<u8>, data_type: u8) -> Self { 179 pub fn new(data: impl Into<Vec<u8>>, data_type: u8) -> Self {
227 Self { data, data_type } 180 Self { data: data.into(), data_type }
228 } 181 }
229 /// A borrowed view of the data here. 182 }
230 pub fn data(&self) -> &[u8] { 183
231 &self.data 184 impl From<BinaryData> for (Vec<u8>, u8) {
232 } 185 fn from(value: BinaryData) -> Self {
233 /// The type of the data stored in this. 186 (value.data, value.data_type)
234 pub fn data_type(&self) -> u8 { 187 }
235 self.data_type 188 }
236 } 189
237 } 190 impl From<(&'_[u8], u8)> for BinaryData {
238 191 fn from((data, data_type): (&'_[u8], u8)) -> Self {
239 impl<'a> From<BorrowedBinaryData<'a>> for BinaryData {
240 fn from(value: BorrowedBinaryData) -> Self {
241 Self { 192 Self {
242 data: value.data.to_vec(), 193 data: data.to_vec(),
243 data_type: value.data_type, 194 data_type,
244 } 195 }
196 }
197 }
198
199 impl<'a> From<&'a BinaryData> for (&'a[u8], u8) {
200 fn from(value: &'a BinaryData) -> Self {
201 (&value.data, value.data_type)
245 } 202 }
246 } 203 }
247 204
248 impl From<BinaryData> for Vec<u8> { 205 impl From<BinaryData> for Vec<u8> {
249 /// Takes ownership of the data stored herein. 206 /// Takes ownership of the data stored herein.
259 "" 216 ""
260 "While this does not have an answer, [`Conversation`] implementations" 217 "While this does not have an answer, [`Conversation`] implementations"
261 "should still call [`set_answer`][`QAndA::set_answer`] to verify that" 218 "should still call [`set_answer`][`QAndA::set_answer`] to verify that"
262 "the message has been displayed (or actively discarded)." 219 "the message has been displayed (or actively discarded)."
263 ); 220 );
264 impl<'a> InfoMsg<'a> {
265 /// Creates an informational message to send to the user.
266 pub fn new(message: &'a str) -> Self {
267 Self {
268 q: message,
269 a: Cell::new(Err(ErrorCode::ConversationError)),
270 }
271 }
272 }
273 221
274 q_and_a!( 222 q_and_a!(
275 ErrorMsg<'a, Q = &'a str, A = ()>, 223 ErrorMsg<'a, Q = &'a str, A = ()>,
276 Message::Error, 224 Message::Error,
277 "An error message to be passed to the user." 225 "An error message to be passed to the user."
278 "" 226 ""
279 "While this does not have an answer, [`Conversation`] implementations" 227 "While this does not have an answer, [`Conversation`] implementations"
280 "should still call [`set_answer`][`QAndA::set_answer`] to verify that" 228 "should still call [`set_answer`][`QAndA::set_answer`] to verify that"
281 "the message has been displayed (or actively discarded)." 229 "the message has been displayed (or actively discarded)."
282
283 ); 230 );
284 impl<'a> ErrorMsg<'a> {
285 /// Creates an error message to send to the user.
286 pub fn new(message: &'a str) -> Self {
287 Self {
288 q: message,
289 a: Cell::new(Err(ErrorCode::ConversationError)),
290 }
291 }
292 }
293 231
294 /// A channel for PAM modules to request information from the user. 232 /// A channel for PAM modules to request information from the user.
295 /// 233 ///
296 /// This trait is used by both applications and PAM modules: 234 /// This trait is used by both applications and PAM modules:
297 /// 235 ///
313 /// 251 ///
314 /// This can be used to wrap a free-floating function for use as a 252 /// This can be used to wrap a free-floating function for use as a
315 /// Conversation: 253 /// Conversation:
316 /// 254 ///
317 /// ``` 255 /// ```
318 /// use nonstick::conv::{Conversation, Message, conversation_func}; 256 /// use nonstick::conv::{conversation_func, Conversation, Message};
319 /// mod some_library { 257 /// mod some_library {
320 /// # use nonstick::Conversation; 258 /// # use nonstick::Conversation;
321 /// pub fn get_auth_data(conv: &mut impl Conversation) { /* ... */ } 259 /// pub fn get_auth_data(conv: &mut impl Conversation) {
260 /// /* ... */
261 /// }
322 /// } 262 /// }
323 /// 263 ///
324 /// fn my_terminal_prompt(messages: &[Message]) { 264 /// fn my_terminal_prompt(messages: &[Message]) {
325 /// // ... 265 /// // ...
326 /// # todo!() 266 /// # todo!()
362 /// ``` 302 /// ```
363 /// 303 ///
364 /// or to use a `SimpleConversation` as a `Conversation`: 304 /// or to use a `SimpleConversation` as a `Conversation`:
365 /// 305 ///
366 /// ``` 306 /// ```
307 /// use nonstick::{Conversation, SimpleConversation};
367 /// use secure_string::SecureString; 308 /// use secure_string::SecureString;
368 /// use nonstick::{Conversation, SimpleConversation};
369 /// # use nonstick::{BinaryData, Result}; 309 /// # use nonstick::{BinaryData, Result};
370 /// mod some_library { 310 /// mod some_library {
371 /// # use nonstick::Conversation; 311 /// # use nonstick::Conversation;
372 /// pub fn get_auth_data(conv: &mut impl Conversation) { /* ... */ } 312 /// pub fn get_auth_data(conv: &mut impl Conversation) { /* ... */
373 /// } 313 /// }
374 /// 314 /// }
375 /// struct MySimpleConvo { /* ... */ } 315 ///
316 /// struct MySimpleConvo {/* ... */}
376 /// # impl MySimpleConvo { fn new() -> Self { Self{} } } 317 /// # impl MySimpleConvo { fn new() -> Self { Self{} } }
377 /// 318 ///
378 /// impl SimpleConversation for MySimpleConvo { 319 /// impl SimpleConversation for MySimpleConvo {
379 /// // ... 320 /// // ...
380 /// # fn prompt(&mut self, request: &str) -> Result<String> { 321 /// # fn prompt(&mut self, request: &str) -> Result<String> {
395 /// # 336 /// #
396 /// # fn info_msg(&mut self, message: &str) { 337 /// # fn info_msg(&mut self, message: &str) {
397 /// # todo!() 338 /// # todo!()
398 /// # } 339 /// # }
399 /// # 340 /// #
400 /// # fn binary_prompt(&mut self, data: &[u8], data_type: u8) -> Result<BinaryData> { 341 /// # fn binary_prompt(&mut self, (data, data_type): (&[u8], u8)) -> Result<BinaryData> {
401 /// # todo!() 342 /// # todo!()
402 /// # } 343 /// # }
403 /// } 344 /// }
404 /// 345 ///
405 /// fn main() { 346 /// fn main() {
411 /// Lets you use this simple conversation as a full [Conversation]. 352 /// Lets you use this simple conversation as a full [Conversation].
412 /// 353 ///
413 /// The wrapper takes each message received in [`Conversation::communicate`] 354 /// The wrapper takes each message received in [`Conversation::communicate`]
414 /// and passes them one-by-one to the appropriate method, 355 /// and passes them one-by-one to the appropriate method,
415 /// then collects responses to return. 356 /// then collects responses to return.
416 fn as_conversation(&mut self) -> Demux<Self> 357 fn as_conversation(&mut self) -> Demux<'_, Self>
417 where 358 where
418 Self: Sized, 359 Self: Sized,
419 { 360 {
420 Demux(self) 361 Demux(self)
421 } 362 }
430 /// Alerts the user to an error. 371 /// Alerts the user to an error.
431 fn error_msg(&mut self, message: &str); 372 fn error_msg(&mut self, message: &str);
432 /// Sends an informational message to the user. 373 /// Sends an informational message to the user.
433 fn info_msg(&mut self, message: &str); 374 fn info_msg(&mut self, message: &str);
434 /// Requests binary data from the user (a Linux-PAM extension). 375 /// Requests binary data from the user (a Linux-PAM extension).
435 fn binary_prompt(&mut self, data: &[u8], data_type: u8) -> Result<BinaryData>; 376 fn binary_prompt(&mut self, data_and_type: (&[u8], u8)) -> Result<BinaryData>;
436 } 377 }
437 378
438 macro_rules! conv_fn { 379 macro_rules! conv_fn {
439 ($fn_name:ident($($param:ident: $pt:ty),+) -> $resp_type:ty { $msg:ty }) => { 380 ($fn_name:ident($($param:tt: $pt:ty),+) -> $resp_type:ty { $msg:ty }) => {
440 fn $fn_name(&mut self, $($param: $pt),*) -> Result<$resp_type> { 381 fn $fn_name(&mut self, $($param: $pt),*) -> Result<$resp_type> {
441 let prompt = <$msg>::new($($param),*); 382 let prompt = <$msg>::new($($param),*);
442 self.communicate(&[prompt.message()]); 383 self.communicate(&[prompt.message()]);
443 prompt.answer() 384 prompt.answer()
444 } 385 }
445 }; 386 };
446 ($fn_name:ident($($param:ident: $pt:ty),+) { $msg:ty }) => { 387 ($fn_name:ident($($param:tt: $pt:ty),+) { $msg:ty }) => {
447 fn $fn_name(&mut self, $($param: $pt),*) { 388 fn $fn_name(&mut self, $($param: $pt),*) {
448 self.communicate(&[<$msg>::new($($param),*).message()]); 389 self.communicate(&[<$msg>::new($($param),*).message()]);
449 } 390 }
450 }; 391 };
451 } 392 }
454 conv_fn!(prompt(message: &str) -> String { QAndA }); 395 conv_fn!(prompt(message: &str) -> String { QAndA });
455 conv_fn!(masked_prompt(message: &str) -> SecureString { MaskedQAndA } ); 396 conv_fn!(masked_prompt(message: &str) -> SecureString { MaskedQAndA } );
456 conv_fn!(radio_prompt(message: &str) -> String { RadioQAndA }); 397 conv_fn!(radio_prompt(message: &str) -> String { RadioQAndA });
457 conv_fn!(error_msg(message: &str) { ErrorMsg }); 398 conv_fn!(error_msg(message: &str) { ErrorMsg });
458 conv_fn!(info_msg(message: &str) { InfoMsg }); 399 conv_fn!(info_msg(message: &str) { InfoMsg });
459 conv_fn!(binary_prompt(data: &[u8], data_type: u8) -> BinaryData { BinaryQAndA }); 400 conv_fn!(binary_prompt((data, data_type): (&[u8], u8)) -> BinaryData { BinaryQAndA });
460 } 401 }
461 402
462 /// A [`Conversation`] which asks the questions one at a time. 403 /// A [`Conversation`] which asks the questions one at a time.
463 /// 404 ///
464 /// This is automatically created by [`SimpleConversation::as_conversation`]. 405 /// This is automatically created by [`SimpleConversation::as_conversation`].
483 self.0.error_msg(prompt.question()); 424 self.0.error_msg(prompt.question());
484 prompt.set_answer(Ok(())) 425 prompt.set_answer(Ok(()))
485 } 426 }
486 Message::BinaryPrompt(prompt) => { 427 Message::BinaryPrompt(prompt) => {
487 let q = prompt.question(); 428 let q = prompt.question();
488 prompt.set_answer(self.0.binary_prompt(q.data, q.data_type)) 429 prompt.set_answer(self.0.binary_prompt(q))
489 } 430 }
490 } 431 }
491 } 432 }
492 } 433 }
493 } 434 }
494 435
495 #[cfg(test)] 436 #[cfg(test)]
496 mod tests { 437 mod tests {
497 use super::{ 438 use super::{
498 BinaryQAndA, Conversation, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, 439 BinaryQAndA, Conversation, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, RadioQAndA,
499 RadioQAndA, Result, SecureString, SimpleConversation, 440 Result, SecureString, SimpleConversation,
500 }; 441 };
501 use crate::constants::ErrorCode; 442 use crate::constants::ErrorCode;
502 use crate::BinaryData; 443 use crate::BinaryData;
503 444
504 #[test] 445 #[test]
531 } 472 }
532 fn info_msg(&mut self, message: &str) { 473 fn info_msg(&mut self, message: &str) {
533 self.info_ran = true; 474 self.info_ran = true;
534 assert_eq!("did you know", message); 475 assert_eq!("did you know", message);
535 } 476 }
536 fn binary_prompt(&mut self, data: &[u8], data_type: u8) -> Result<BinaryData> { 477 fn binary_prompt(&mut self, data_and_type: (&[u8], u8)) -> Result<BinaryData> {
537 assert_eq!(&[10, 9, 8], data); 478 assert_eq!((&[10, 9, 8][..], 66), data_and_type);
538 assert_eq!(66, data_type);
539 Ok(BinaryData::new(vec![5, 5, 5], 5)) 479 Ok(BinaryData::new(vec![5, 5, 5], 5))
540 } 480 }
541 } 481 }
542 482
543 let mut tester = DemuxTester::default(); 483 let mut tester = DemuxTester::default();
571 // Test the Linux extensions separately. 511 // Test the Linux extensions separately.
572 512
573 let mut conv = tester.as_conversation(); 513 let mut conv = tester.as_conversation();
574 514
575 let radio = RadioQAndA::new("channel?"); 515 let radio = RadioQAndA::new("channel?");
576 let bin = BinaryQAndA::new(&[10, 9, 8], 66); 516 let bin = BinaryQAndA::new((&[10, 9, 8], 66));
577 conv.communicate(&[radio.message(), bin.message()]); 517 conv.communicate(&[radio.message(), bin.message()]);
578 518
579 assert_eq!("zero", radio.answer().unwrap()); 519 assert_eq!("zero", radio.answer().unwrap());
580 assert_eq!(BinaryData::new(vec![5, 5, 5], 5), bin.answer().unwrap()); 520 assert_eq!(BinaryData::new(vec![5, 5, 5], 5), bin.answer().unwrap());
581 } 521 }
603 Message::MaskedPrompt(ask) => { 543 Message::MaskedPrompt(ask) => {
604 assert_eq!("password!", ask.question()); 544 assert_eq!("password!", ask.question());
605 ask.set_answer(Ok("open sesame".into())) 545 ask.set_answer(Ok("open sesame".into()))
606 } 546 }
607 Message::BinaryPrompt(prompt) => { 547 Message::BinaryPrompt(prompt) => {
608 assert_eq!(&[1, 2, 3], prompt.question().data); 548 assert_eq!((&[1, 2, 3][..], 69), prompt.question());
609 assert_eq!(69, prompt.question().data_type);
610 prompt.set_answer(Ok(BinaryData::new(vec![3, 2, 1], 42))) 549 prompt.set_answer(Ok(BinaryData::new(vec![3, 2, 1], 42)))
611 } 550 }
612 Message::RadioPrompt(ask) => { 551 Message::RadioPrompt(ask) => {
613 assert_eq!("radio?", ask.question()); 552 assert_eq!("radio?", ask.question());
614 ask.set_answer(Ok("yes".to_owned())) 553 ask.set_answer(Ok("yes".to_owned()))
634 tester.info_msg("let me tell you"); 573 tester.info_msg("let me tell you");
635 { 574 {
636 assert_eq!("yes", tester.radio_prompt("radio?").unwrap()); 575 assert_eq!("yes", tester.radio_prompt("radio?").unwrap());
637 assert_eq!( 576 assert_eq!(
638 BinaryData::new(vec![3, 2, 1], 42), 577 BinaryData::new(vec![3, 2, 1], 42),
639 tester.binary_prompt(&[1, 2, 3], 69).unwrap(), 578 tester.binary_prompt((&[1, 2, 3], 69)).unwrap(),
640 ) 579 )
641 } 580 }
642 assert_eq!( 581 assert_eq!(
643 ErrorCode::BufferError, 582 ErrorCode::BufferError,
644 tester.prompt("should_error").unwrap_err(), 583 tester.prompt("should_error").unwrap_err(),