diff src/conv.rs @ 130:80c07e5ab22f

Transfer over (almost) completely to using libpam-sys. This reimplements everything in nonstick on top of the new -sys crate. We don't yet use libpam-sys's helpers for binary message payloads. Soon.
author Paul Fisher <paul@pfish.zone>
date Tue, 01 Jul 2025 06:11:43 -0400
parents f3e260f9ddcb
children
line wrap: on
line diff
--- a/src/conv.rs	Mon Jun 30 23:49:54 2025 -0400
+++ b/src/conv.rs	Tue Jul 01 06:11:43 2025 -0400
@@ -9,12 +9,10 @@
 use std::fmt::Debug;
 use std::result::Result as StdResult;
 
-/// The types of message and request that can be sent to a user.
-///
-/// The data within each enum value is the prompt (or other information)
-/// that will be presented to the user.
+/// An individual pair of request/response to be sent to the user.
+#[derive(Debug)]
 #[non_exhaustive]
-pub enum Message<'a> {
+pub enum Exchange<'a> {
     Prompt(&'a QAndA<'a>),
     MaskedPrompt(&'a MaskedQAndA<'a>),
     Error(&'a ErrorMsg<'a>),
@@ -23,22 +21,22 @@
     BinaryPrompt(&'a BinaryQAndA<'a>),
 }
 
-impl Message<'_> {
+impl Exchange<'_> {
     /// Sets an error answer on this question, without having to inspect it.
     ///
     /// Use this as a default match case:
     ///
     /// ```
-    /// use nonstick::conv::{Message, QAndA};
+    /// use nonstick::conv::{Exchange, QAndA};
     /// use nonstick::ErrorCode;
     ///
-    /// fn cant_respond(message: Message) {
+    /// fn cant_respond(message: Exchange) {
     ///     match message {
-    ///         Message::Info(i) => {
+    ///         Exchange::Info(i) => {
     ///             eprintln!("fyi, {}", i.question());
     ///             i.set_answer(Ok(()))
     ///         }
-    ///         Message::Error(e) => {
+    ///         Exchange::Error(e) => {
     ///             eprintln!("ERROR: {}", e.question());
     ///             e.set_answer(Ok(()))
     ///         }
@@ -48,12 +46,12 @@
     /// }
     pub fn set_error(&self, err: ErrorCode) {
         match *self {
-            Message::Prompt(m) => m.set_answer(Err(err)),
-            Message::MaskedPrompt(m) => m.set_answer(Err(err)),
-            Message::Error(m) => m.set_answer(Err(err)),
-            Message::Info(m) => m.set_answer(Err(err)),
-            Message::RadioPrompt(m) => m.set_answer(Err(err)),
-            Message::BinaryPrompt(m) => m.set_answer(Err(err)),
+            Exchange::Prompt(m) => m.set_answer(Err(err)),
+            Exchange::MaskedPrompt(m) => m.set_answer(Err(err)),
+            Exchange::Error(m) => m.set_answer(Err(err)),
+            Exchange::Info(m) => m.set_answer(Err(err)),
+            Exchange::RadioPrompt(m) => m.set_answer(Err(err)),
+            Exchange::BinaryPrompt(m) => m.set_answer(Err(err)),
         }
     }
 }
@@ -76,8 +74,8 @@
                 }
             }
 
-            /// Converts this Q&A into a [`Message`] for the [`Conversation`].
-            pub fn message(&self) -> Message<'_> {
+            /// Converts this Q&A into a [`Exchange`] for the [`Conversation`].
+            pub fn exchange(&self) -> Exchange<'_> {
                 $val(self)
             }
 
@@ -110,9 +108,7 @@
         $(#[$m])*
         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)
+                f.debug_struct(stringify!($name)).field("q", &self.q).finish_non_exhaustive()
             }
         }
     };
@@ -123,7 +119,7 @@
     ///
     /// In other words, a password entry prompt.
     MaskedQAndA<'a, Q=&'a str, A=String>,
-    Message::MaskedPrompt
+    Exchange::MaskedPrompt
 );
 
 q_and_a!(
@@ -133,7 +129,7 @@
     /// When the user types, their input will be shown to them.
     /// It can be used for things like usernames.
     QAndA<'a, Q=&'a str, A=String>,
-    Message::Prompt
+    Exchange::Prompt
 );
 
 q_and_a!(
@@ -143,7 +139,7 @@
     /// questions, but nowhere in the documentation is it specified
     /// what the format of the answer will be, or how this should be shown.
     RadioQAndA<'a, Q=&'a str, A=String>,
-    Message::RadioPrompt
+    Exchange::RadioPrompt
 );
 
 q_and_a!(
@@ -156,7 +152,7 @@
     /// The `data_type` tag is a value that is simply passed through
     /// to the application. PAM does not define any meaning for it.
     BinaryQAndA<'a, Q=(&'a [u8], u8), A=BinaryData>,
-    Message::BinaryPrompt
+    Exchange::BinaryPrompt
 );
 
 /// Owned binary data.
@@ -208,7 +204,7 @@
     /// should still call [`set_answer`][`QAndA::set_answer`] to verify that
     /// the message has been displayed (or actively discarded).
     InfoMsg<'a, Q = &'a str, A = ()>,
-    Message::Info
+    Exchange::Info
 );
 
 q_and_a!(
@@ -218,7 +214,7 @@
     /// should still call [`set_answer`][`QAndA::set_answer`] to verify that
     /// the message has been displayed (or actively discarded).
     ErrorMsg<'a, Q = &'a str, A = ()>,
-    Message::Error
+    Exchange::Error
 );
 
 /// A channel for PAM modules to request information from the user.
@@ -236,7 +232,7 @@
     /// as there were messages in the request; one corresponding to each.
     ///
     /// TODO: write detailed documentation about how to use this.
-    fn communicate(&self, messages: &[Message]);
+    fn communicate(&self, messages: &[Exchange]);
 }
 
 /// Turns a simple function into a [`Conversation`].
@@ -245,7 +241,7 @@
 /// Conversation:
 ///
 /// ```
-/// use nonstick::conv::{conversation_func, Conversation, Message};
+/// use nonstick::conv::{conversation_func, Conversation, Exchange};
 /// mod some_library {
 /// #    use nonstick::Conversation;
 ///     pub fn get_auth_data(conv: &mut impl Conversation) {
@@ -253,7 +249,7 @@
 ///     }
 /// }
 ///
-/// fn my_terminal_prompt(messages: &[Message]) {
+/// fn my_terminal_prompt(messages: &[Exchange]) {
 ///     // ...
 /// #    unimplemented!()
 /// }
@@ -262,14 +258,14 @@
 ///     some_library::get_auth_data(&mut conversation_func(my_terminal_prompt));
 /// }
 /// ```
-pub fn conversation_func(func: impl Fn(&[Message])) -> impl Conversation {
+pub fn conversation_func(func: impl Fn(&[Exchange])) -> impl Conversation {
     FunctionConvo(func)
 }
 
-struct FunctionConvo<C: Fn(&[Message])>(C);
+struct FunctionConvo<C: Fn(&[Exchange])>(C);
 
-impl<C: Fn(&[Message])> Conversation for FunctionConvo<C> {
-    fn communicate(&self, messages: &[Message]) {
+impl<C: Fn(&[Exchange])> Conversation for FunctionConvo<C> {
+    fn communicate(&self, messages: &[Exchange]) {
         self.0(messages)
     }
 }
@@ -399,14 +395,14 @@
         $(#[$m])*
         fn $fn_name(&self, $($param: $pt),*) -> Result<$resp_type> {
             let prompt = <$msg>::new($($param),*);
-            self.communicate(&[prompt.message()]);
+            self.communicate(&[prompt.exchange()]);
             prompt.answer()
         }
     };
     ($(#[$m:meta])*$fn_name:ident($($param:tt: $pt:ty),+) { $msg:ty }) => {
         $(#[$m])*
         fn $fn_name(&self, $($param: $pt),*) {
-            self.communicate(&[<$msg>::new($($param),*).message()]);
+            self.communicate(&[<$msg>::new($($param),*).exchange()]);
         }
     };
 }
@@ -433,25 +429,25 @@
 }
 
 impl<CA: ConversationAdapter> Conversation for Demux<CA> {
-    fn communicate(&self, messages: &[Message]) {
+    fn communicate(&self, messages: &[Exchange]) {
         for msg in messages {
             match msg {
-                Message::Prompt(prompt) => prompt.set_answer(self.0.prompt(prompt.question())),
-                Message::MaskedPrompt(prompt) => {
+                Exchange::Prompt(prompt) => prompt.set_answer(self.0.prompt(prompt.question())),
+                Exchange::MaskedPrompt(prompt) => {
                     prompt.set_answer(self.0.masked_prompt(prompt.question()))
                 }
-                Message::RadioPrompt(prompt) => {
+                Exchange::RadioPrompt(prompt) => {
                     prompt.set_answer(self.0.radio_prompt(prompt.question()))
                 }
-                Message::Info(prompt) => {
+                Exchange::Info(prompt) => {
                     self.0.info_msg(prompt.question());
                     prompt.set_answer(Ok(()))
                 }
-                Message::Error(prompt) => {
+                Exchange::Error(prompt) => {
                     self.0.error_msg(prompt.question());
                     prompt.set_answer(Ok(()))
                 }
-                Message::BinaryPrompt(prompt) => {
+                Exchange::BinaryPrompt(prompt) => {
                     let q = prompt.question();
                     prompt.set_answer(self.0.binary_prompt(q))
                 }
@@ -516,11 +512,11 @@
         // Basic tests.
 
         conv.communicate(&[
-            what.message(),
-            pass.message(),
-            err.message(),
-            info.message(),
-            has_err.message(),
+            what.exchange(),
+            pass.exchange(),
+            err.exchange(),
+            info.exchange(),
+            has_err.exchange(),
         ]);
 
         assert_eq!("whatwhat", what.answer().unwrap());
@@ -538,7 +534,7 @@
 
             let radio = RadioQAndA::new("channel?");
             let bin = BinaryQAndA::new((&[10, 9, 8], 66));
-            conv.communicate(&[radio.message(), bin.message()]);
+            conv.communicate(&[radio.exchange(), bin.exchange()]);
 
             assert_eq!("zero", radio.answer().unwrap());
             assert_eq!(BinaryData::from(([5, 5, 5], 5)), bin.answer().unwrap());
@@ -549,31 +545,31 @@
         struct MuxTester;
 
         impl Conversation for MuxTester {
-            fn communicate(&self, messages: &[Message]) {
+            fn communicate(&self, messages: &[Exchange]) {
                 if let [msg] = messages {
                     match *msg {
-                        Message::Info(info) => {
+                        Exchange::Info(info) => {
                             assert_eq!("let me tell you", info.question());
                             info.set_answer(Ok(()))
                         }
-                        Message::Error(error) => {
+                        Exchange::Error(error) => {
                             assert_eq!("oh no", error.question());
                             error.set_answer(Ok(()))
                         }
-                        Message::Prompt(prompt) => prompt.set_answer(match prompt.question() {
+                        Exchange::Prompt(prompt) => prompt.set_answer(match prompt.question() {
                             "should_err" => Err(ErrorCode::PermissionDenied),
                             "question" => Ok("answer".to_owned()),
                             other => panic!("unexpected question {other:?}"),
                         }),
-                        Message::MaskedPrompt(ask) => {
+                        Exchange::MaskedPrompt(ask) => {
                             assert_eq!("password!", ask.question());
                             ask.set_answer(Ok("open sesame".into()))
                         }
-                        Message::BinaryPrompt(prompt) => {
+                        Exchange::BinaryPrompt(prompt) => {
                             assert_eq!((&[1, 2, 3][..], 69), prompt.question());
                             prompt.set_answer(Ok(BinaryData::from((&[3, 2, 1], 42))))
                         }
-                        Message::RadioPrompt(ask) => {
+                        Exchange::RadioPrompt(ask) => {
                             assert_eq!("radio?", ask.question());
                             ask.set_answer(Ok("yes".to_owned()))
                         }