Mercurial > crates > nonstick
diff 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 |
line wrap: on
line diff
--- a/src/conv.rs Sun Jun 08 01:03:46 2025 -0400 +++ b/src/conv.rs Sun Jun 08 03:48:40 2025 -0400 @@ -3,10 +3,11 @@ // Temporarily allowed until we get the actual conversation functions hooked up. #![allow(dead_code)] -use crate::constants::Result; -use crate::ErrorCode; +use crate::constants::{ErrorCode, Result}; use secure_string::SecureString; use std::cell::Cell; +use std::fmt; +use std::result::Result as StdResult; /// The types of message and request that can be sent to a user. /// @@ -68,8 +69,16 @@ } impl<'a> $name<'a> { + #[doc = concat!("Creates a `", stringify!($t), "` to be sent to the user.")] + pub fn new(question: $qt) -> Self { + Self { + q: question, + a: Cell::new(Err(ErrorCode::ConversationError)), + } + } + /// Converts this Q&A into a [`Message`] for the [`Conversation`]. - pub fn message(&self) -> Message { + pub fn message(&self) -> Message<'_> { $val(self) } @@ -96,18 +105,14 @@ self.a.into_inner() } } - }; -} -macro_rules! ask { - ($t:ident) => { - impl<'a> $t<'a> { - #[doc = concat!("Creates a `", stringify!($t), "` to be sent to the user.")] - pub fn new(question: &'a str) -> Self { - Self { - q: question, - a: Cell::new(Err(ErrorCode::ConversationError)), - } + // shout out to stackoverflow user ballpointben for this lazy impl: + // https://stackoverflow.com/a/78871280/39808 + impl fmt::Debug for $name<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> StdResult<(), fmt::Error> { + #[derive(Debug)] + struct $name<'a> { q: $qt } + fmt::Debug::fmt(&$name { q: self.q }, f) } } }; @@ -120,7 +125,6 @@ "" "In other words, a password entry prompt." ); -ask!(MaskedQAndA); q_and_a!( QAndA<'a, Q=&'a str, A=String>, @@ -131,7 +135,6 @@ "When the user types, their input will be shown to them." "It can be used for things like usernames." ); -ask!(QAndA); q_and_a!( RadioQAndA<'a, Q=&'a str, A=String>, @@ -142,10 +145,9 @@ "questions, but nowhere in the documentation is it specified" "what the format of the answer will be, or how this should be shown." ); -ask!(RadioQAndA); q_and_a!( - BinaryQAndA<'a, Q=BorrowedBinaryData<'a>, A=BinaryData>, + BinaryQAndA<'a, Q=(&'a [u8], u8), A=BinaryData>, Message::BinaryPrompt, "Asks for binary data. (Linux-PAM extension)" "" @@ -156,65 +158,16 @@ "The `data_type` tag is a value that is simply passed through" "to the application. PAM does not define any meaning for it." ); -impl<'a> BinaryQAndA<'a> { - /// Creates a prompt for the given binary data. - /// - /// The `data_type` is a tag you can use for communication between - /// the module and the application. Its meaning is undefined by PAM. - pub fn new(data: &'a [u8], data_type: u8) -> Self { - BorrowedBinaryData { data, data_type }.into() - } -} - -impl<'a> From<BorrowedBinaryData<'a>> for BinaryQAndA<'a> { - fn from(q: BorrowedBinaryData<'a>) -> Self { - Self { - q, - a: Cell::new(Err(ErrorCode::ConversationError)), - } - } -} - -impl<'a> From<&'a BinaryData> for BinaryQAndA<'a> { - fn from(src: &'a BinaryData) -> Self { - BorrowedBinaryData::from(src).into() - } -} - -/// A version of [`BinaryData`] where the `data` is borrowed. -#[derive(Copy, Clone, Debug, Default)] -pub struct BorrowedBinaryData<'a> { - data: &'a [u8], - data_type: u8, -} - -impl<'a> BorrowedBinaryData<'a> { - /// Creates a new BinaryQuestion as a view over the given data. - pub fn new(data: &'a [u8], data_type: u8) -> Self { - Self { data, data_type } - } - - /// Gets the data of this question. - pub fn data(&self) -> &[u8] { - self.data - } - - /// Gets the "type" of this data. - pub fn data_type(&self) -> u8 { - self.data_type - } -} - -impl<'a> From<&'a BinaryData> for BorrowedBinaryData<'a> { - fn from(value: &'a BinaryData) -> Self { - Self::new(&value.data, value.data_type) - } -} /// Owned binary data. /// -/// For borrowed data, see [`BorrowedBinaryData`]. -/// You can take ownership of the stored data with `.into::<Vec<u8>>()`. +/// You can take ownership of the stored data by destructuring it: +/// +/// ``` +/// # use nonstick::BinaryData; +/// # let binary_data = BinaryData::new(vec![99, 88, 77], 66); +/// let (data, data_type) = binary_data.into(); +/// ``` #[derive(Debug, Default, PartialEq)] pub struct BinaryData { data: Vec<u8>, @@ -223,28 +176,32 @@ impl BinaryData { /// Creates a `BinaryData` with the given contents and type. - pub fn new(data: Vec<u8>, data_type: u8) -> Self { - Self { data, data_type } + pub fn new(data: impl Into<Vec<u8>>, data_type: u8) -> Self { + Self { data: data.into(), data_type } } - /// A borrowed view of the data here. - pub fn data(&self) -> &[u8] { - &self.data - } - /// The type of the data stored in this. - pub fn data_type(&self) -> u8 { - self.data_type +} + +impl From<BinaryData> for (Vec<u8>, u8) { + fn from(value: BinaryData) -> Self { + (value.data, value.data_type) } } -impl<'a> From<BorrowedBinaryData<'a>> for BinaryData { - fn from(value: BorrowedBinaryData) -> Self { +impl From<(&'_[u8], u8)> for BinaryData { + fn from((data, data_type): (&'_[u8], u8)) -> Self { Self { - data: value.data.to_vec(), - data_type: value.data_type, + data: data.to_vec(), + data_type, } } } +impl<'a> From<&'a BinaryData> for (&'a[u8], u8) { + fn from(value: &'a BinaryData) -> Self { + (&value.data, value.data_type) + } +} + impl From<BinaryData> for Vec<u8> { /// Takes ownership of the data stored herein. fn from(value: BinaryData) -> Self { @@ -261,15 +218,6 @@ "should still call [`set_answer`][`QAndA::set_answer`] to verify that" "the message has been displayed (or actively discarded)." ); -impl<'a> InfoMsg<'a> { - /// Creates an informational message to send to the user. - pub fn new(message: &'a str) -> Self { - Self { - q: message, - a: Cell::new(Err(ErrorCode::ConversationError)), - } - } -} q_and_a!( ErrorMsg<'a, Q = &'a str, A = ()>, @@ -279,17 +227,7 @@ "While this does not have an answer, [`Conversation`] implementations" "should still call [`set_answer`][`QAndA::set_answer`] to verify that" "the message has been displayed (or actively discarded)." - ); -impl<'a> ErrorMsg<'a> { - /// Creates an error message to send to the user. - pub fn new(message: &'a str) -> Self { - Self { - q: message, - a: Cell::new(Err(ErrorCode::ConversationError)), - } - } -} /// A channel for PAM modules to request information from the user. /// @@ -315,10 +253,12 @@ /// Conversation: /// /// ``` -/// use nonstick::conv::{Conversation, Message, conversation_func}; +/// use nonstick::conv::{conversation_func, Conversation, Message}; /// mod some_library { /// # use nonstick::Conversation; -/// pub fn get_auth_data(conv: &mut impl Conversation) { /* ... */ } +/// pub fn get_auth_data(conv: &mut impl Conversation) { +/// /* ... */ +/// } /// } /// /// fn my_terminal_prompt(messages: &[Message]) { @@ -364,15 +304,16 @@ /// or to use a `SimpleConversation` as a `Conversation`: /// /// ``` +/// use nonstick::{Conversation, SimpleConversation}; /// use secure_string::SecureString; -/// use nonstick::{Conversation, SimpleConversation}; /// # use nonstick::{BinaryData, Result}; /// mod some_library { /// # use nonstick::Conversation; -/// pub fn get_auth_data(conv: &mut impl Conversation) { /* ... */ } +/// pub fn get_auth_data(conv: &mut impl Conversation) { /* ... */ +/// } /// } /// -/// struct MySimpleConvo { /* ... */ } +/// struct MySimpleConvo {/* ... */} /// # impl MySimpleConvo { fn new() -> Self { Self{} } } /// /// impl SimpleConversation for MySimpleConvo { @@ -397,7 +338,7 @@ /// # todo!() /// # } /// # -/// # fn binary_prompt(&mut self, data: &[u8], data_type: u8) -> Result<BinaryData> { +/// # fn binary_prompt(&mut self, (data, data_type): (&[u8], u8)) -> Result<BinaryData> { /// # todo!() /// # } /// } @@ -413,7 +354,7 @@ /// The wrapper takes each message received in [`Conversation::communicate`] /// and passes them one-by-one to the appropriate method, /// then collects responses to return. - fn as_conversation(&mut self) -> Demux<Self> + fn as_conversation(&mut self) -> Demux<'_, Self> where Self: Sized, { @@ -432,18 +373,18 @@ /// Sends an informational message to the user. fn info_msg(&mut self, message: &str); /// Requests binary data from the user (a Linux-PAM extension). - fn binary_prompt(&mut self, data: &[u8], data_type: u8) -> Result<BinaryData>; + fn binary_prompt(&mut self, data_and_type: (&[u8], u8)) -> Result<BinaryData>; } macro_rules! conv_fn { - ($fn_name:ident($($param:ident: $pt:ty),+) -> $resp_type:ty { $msg:ty }) => { + ($fn_name:ident($($param:tt: $pt:ty),+) -> $resp_type:ty { $msg:ty }) => { fn $fn_name(&mut self, $($param: $pt),*) -> Result<$resp_type> { let prompt = <$msg>::new($($param),*); self.communicate(&[prompt.message()]); prompt.answer() } }; - ($fn_name:ident($($param:ident: $pt:ty),+) { $msg:ty }) => { + ($fn_name:ident($($param:tt: $pt:ty),+) { $msg:ty }) => { fn $fn_name(&mut self, $($param: $pt),*) { self.communicate(&[<$msg>::new($($param),*).message()]); } @@ -456,7 +397,7 @@ conv_fn!(radio_prompt(message: &str) -> String { RadioQAndA }); conv_fn!(error_msg(message: &str) { ErrorMsg }); conv_fn!(info_msg(message: &str) { InfoMsg }); - conv_fn!(binary_prompt(data: &[u8], data_type: u8) -> BinaryData { BinaryQAndA }); + conv_fn!(binary_prompt((data, data_type): (&[u8], u8)) -> BinaryData { BinaryQAndA }); } /// A [`Conversation`] which asks the questions one at a time. @@ -485,7 +426,7 @@ } Message::BinaryPrompt(prompt) => { let q = prompt.question(); - prompt.set_answer(self.0.binary_prompt(q.data, q.data_type)) + prompt.set_answer(self.0.binary_prompt(q)) } } } @@ -495,8 +436,8 @@ #[cfg(test)] mod tests { use super::{ - BinaryQAndA, Conversation, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, - RadioQAndA, Result, SecureString, SimpleConversation, + BinaryQAndA, Conversation, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, RadioQAndA, + Result, SecureString, SimpleConversation, }; use crate::constants::ErrorCode; use crate::BinaryData; @@ -533,9 +474,8 @@ self.info_ran = true; assert_eq!("did you know", message); } - fn binary_prompt(&mut self, data: &[u8], data_type: u8) -> Result<BinaryData> { - assert_eq!(&[10, 9, 8], data); - assert_eq!(66, data_type); + fn binary_prompt(&mut self, data_and_type: (&[u8], u8)) -> Result<BinaryData> { + assert_eq!((&[10, 9, 8][..], 66), data_and_type); Ok(BinaryData::new(vec![5, 5, 5], 5)) } } @@ -573,7 +513,7 @@ let mut conv = tester.as_conversation(); let radio = RadioQAndA::new("channel?"); - let bin = BinaryQAndA::new(&[10, 9, 8], 66); + let bin = BinaryQAndA::new((&[10, 9, 8], 66)); conv.communicate(&[radio.message(), bin.message()]); assert_eq!("zero", radio.answer().unwrap()); @@ -605,8 +545,7 @@ ask.set_answer(Ok("open sesame".into())) } Message::BinaryPrompt(prompt) => { - assert_eq!(&[1, 2, 3], prompt.question().data); - assert_eq!(69, prompt.question().data_type); + assert_eq!((&[1, 2, 3][..], 69), prompt.question()); prompt.set_answer(Ok(BinaryData::new(vec![3, 2, 1], 42))) } Message::RadioPrompt(ask) => { @@ -636,7 +575,7 @@ assert_eq!("yes", tester.radio_prompt("radio?").unwrap()); assert_eq!( BinaryData::new(vec![3, 2, 1], 42), - tester.binary_prompt(&[1, 2, 3], 69).unwrap(), + tester.binary_prompt((&[1, 2, 3], 69)).unwrap(), ) } assert_eq!(