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!(